在软考的计算机系统基础知识模块里,有一个看似朴素却几乎年年必考的知识点,那就是CPU与外部设备之间交换数据的三种控制方式:程序查询方式、程序中断方式与直接存储器访问方式,后者就是本文的主角DMA。这个考点贯穿了计算机组成原理、操作系统和接口技术三条线索。许多考生只记住了三个名词,却没理解三种方式在硬件结构上的本质差异,一到真题里考"在谁与谁之间建立数据通路""每传送一个数据占用什么周期"这类问题时,就立刻露怯。本文要把这个问题从根上讲透。
要理解三种数据传送方式,先要回到计算机系统的结构原点。按照冯·诺依曼体系结构,一台计算机由运算器、控制器、存储器、输入设备和输出设备五大部件构成,其中运算器与控制器合称中央处理器,也就是CPU。外设这一端,无论是键盘、鼠标、打印机、磁盘还是网卡,都是通过I/O接口挂接在系统总线上,与主存和CPU交换信息。所谓输入输出,本质上是数据在"主存"与"外设"两个端点之间的搬移。问题在于,这两个端点的速度差异极为悬殊。CPU以纳秒级的速度工作,主存的存取速度虽然慢于CPU但仍在同一数量级,而外设尤其是机械式的输入输出设备,其速度往往要比CPU慢上好几个数量级。一个字符的键盘输入、一次磁盘的磁头定位,在CPU看来都是极其漫长的等待。于是,如何协调这两个速度悬殊的端点,让CPU的宝贵时间不被慢速外设白白拖垮,就构成了I/O控制方式要解决的核心矛盾。
最朴素的想法,是让CPU直接参与每一次数据传送:外设要传数据,CPU就停下手中的活,专门为它服务一次,这就是程序查询方式的雏形。它硬件简单、控制直接,缺点是CPU与外设完全串行,外设准备数据的漫长过程中CPU只能空转等待。在现代计算机里,CPU要同时服务大量设备,这种"盯着等"的方式是对算力的巨大浪费。于是工程师沿着两条思路改进:第一条是让外设在准备好数据后主动"打断"CPU,CPU平时照常干活,只有外设真正需要服务时才停下来处理,这就是程序中断方式;第二条更彻底——既然数据传送只是把数据从一个地方搬到另一个地方,并不需要CPU的"智能",那就增设一个专门的硬件部件来干这个搬运的活,让CPU彻底抽身,这就是直接存储器访问方式,即DMA。
程序查询方式又称程序直接控制方式,是三种方式中最早出现、硬件结构最简单的一种。它的核心思想是:CPU是数据传送的唯一主角,每一步都由CPU主动发起并全程监控。这种方式下,CPU与外设之间通过I/O接口中的状态寄存器、数据寄存器和控制寄存器进行交互,整个过程由CPU执行一段专门的查询程序来完成。
程序查询方式的工作过程是一个不断重复的三步循环。第一步是初始化,CPU执行输出指令,向外设的控制寄存器写入命令,告诉外设"我现在要读取你的数据"或者"准备向你写数据",同时把状态标志清零。第二步是轮询,这是查询方式最显著的特征。CPU反复读取接口中状态寄存器的内容,检查"就绪"位或"忙"位,判断外设是否准备好进行数据传送;若没准备好,CPU就进入循环,一遍遍读取状态位,直到就绪标志被置位。第三步是传送,一旦检测到外设就绪,CPU就执行一次传送指令,把数据从数据寄存器读入CPU寄存器,或从CPU寄存器写入数据寄存器,完成一个数据单元的交换。传送完这一个数据后,CPU还要判断是否还有数据要传,若有就回到第二步继续轮询,直到整批数据传送完毕。
程序查询方式最大的问题在于CPU利用率极低。在第二步轮询等待的过程中,CPU并没有做任何有意义的计算工作,它只是在机械地重复读取状态位,这段时间完全被浪费掉了。更关键的是,查询方式下CPU与外设是严格的串行工作关系,CPU的全部注意力都被外设牵制,无法同时处理其他任务。举一个实际场景来说明:当CPU以查询方式从键盘读入一个字符时,用户敲键的时间间隔可能长达几百毫秒,在这几百毫秒里,高速的CPU只能不停地查询状态位,相当于让一位顶尖的数学家站在门卫岗亭里反复询问"人来了吗"。此外,查询方式还无法及时响应紧急的随机事件。假如系统中某个设备发生了需要立即处理的异常,而CPU此刻正忙于查询另一个慢速设备,那么紧急事件就只能被搁置。正是这些缺陷,促使人们发明了程序中断方式。
程序中断方式是对程序查询方式的一次根本性改进。它的核心思想是把主动权从CPU手里交还给外设:CPU平时照常执行自己的程序,外设则在数据准备就绪之后,主动向CPU发出一个中断请求信号,请求CPU"抽空"为它服务。CPU收到请求后,暂停当前程序的执行,转入对应的中断服务程序完成数据传送,处理完毕再返回被打断的地方继续原程序。这样一来,CPU就不再需要空转等待外设,CPU与外设从串行关系转变为并行关系。
一次完整的中断处理包含请求、响应、服务、返回四个阶段。请求阶段,外设通过接口向CPU发出中断请求信号,该信号通常经过中断控制器进行优先级排队和屏蔽判断,只有优先级足够高且未被屏蔽的请求才会被送达CPU。响应阶段,CPU在当前指令执行完毕时检测到有效的中断请求,进入中断响应周期,向外设回送中断响应信号,并获取中断类型号。服务阶段,CPU先保护现场,把程序计数器PC、程序状态字PSW及相关寄存器的内容压入堆栈,然后根据中断类型号查找中断向量表,取得中断服务程序入口地址,转而执行服务程序完成数据传送或事件处理。返回阶段,CPU恢复现场,把压栈保存的现场信息弹回各寄存器,执行中断返回指令,回到主程序被打断的位置继续执行。
这里有两个软考高频细节。第一个是中断向量的概念。中断向量并不是数据,而是中断服务程序的入口地址。系统把各中断源对应的服务程序入口地址集中存放在主存的一块区域里,形成中断向量表;CPU响应中断时,用中断类型号作为索引查这张表,就能得到入口地址跳转执行。2021年软件设计师真题曾直接考查"中断向量提供的是什么",答案是中断服务程序的入口地址。第二个是现场保护的必要性。中断发生在主程序执行的任意时刻,若服务程序执行中修改了CPU寄存器,返回后主程序状态就会遭到破坏,导致计算错误甚至崩溃,因此必须在进入服务程序前保存现场,在返回前恢复现场。这两个细节共同说明:中断方式虽让CPU从空转中解放,但每次中断都要付出额外开销。
然而,程序中断方式也有它的局限。首先,中断方式下数据仍然要经过CPU。每一次数据传送,都是由CPU在中断服务程序中执行传送指令完成的,数据要先从外设读入CPU的寄存器,再写入主存,CPU依然是数据传送的中转站。其次,中断一次只能传送一个数据单元,如果要传送一整块数据,就需要反复中断、反复保护现场和恢复现场,开销随数据量线性增长。对于字符设备这类一次只传少量数据的外设来说,中断方式恰到好处;但对于磁盘、高速网络这类需要成块、高速传送大量数据的设备来说,中断方式的CPU负担依然沉重。这就引出了第三种、也是效率最高的方式——DMA。
DMA的英文全称是Direct Memory Access,中文译为直接存储器访问,也常被译作直接内存存取。它的核心思想一句话就能概括:让数据在主存与外设之间直接成块传送,不再经过CPU。为了实现这一点,系统需要增设一个专门的硬件部件,即DMA控制器,通常记作DMAC。DMA控制器在CPU的授权下,暂时接管系统总线的控制权,像一个独立的搬运工,直接把数据从外设搬到主存,或者从主存搬到外设,整个过程CPU不参与数据搬移本身。
DMA控制器内部通常包含几个关键寄存器:内存地址寄存器,存放当前要传送数据在主存中的地址;字节计数寄存器,记录还剩多少数据单元没有传送;状态寄存器与控制寄存器,保存DMA控制器的运行状态和控制命令;数据缓冲寄存器,作为数据传送中的暂存。一次典型的DMA数据传送分为准备、传送、结束三个阶段。准备阶段,CPU对DMA控制器初始化,写入传送方向、主存起始地址和数据块长度,这一步CPU必须参与。传送阶段,外设准备好数据后向DMA控制器发出DMA请求;DMA控制器随即向CPU发出总线请求信号,请求占用总线;CPU在合适时机响应,让出总线控制权,把数据线、地址线和控制线置为高阻状态;DMA控制器接管总线后,直接在主存与外设之间完成数据传送,每传完一个数据,内存地址寄存器自动加一、字节计数寄存器自动减一,只要计数不为零就继续传送。结束阶段,当字节计数寄存器减到零,DMA控制器撤销总线请求,把总线控制权交还CPU,同时向CPU发出中断请求,通知CPU"数据已全部传完",CPU响应后进行善后处理,如检查传送是否正确、启动对该数据块的后续加工。
理解了DMA控制器的结构之后,一个更深层的问题浮现出来:DMA控制器接管总线的那段时间里,CPU在做什么?这就引出了DMA方式最核心的机制——周期挪用,也叫周期窃取,英文是cycle stealing,非常形象。CPU并不总是在使用总线:只有访存和取指操作才占用总线,而在CPU内部进行运算、译码时总线是空闲的。周期挪用正是利用这些空闲间隙——DMA控制器只在CPU不使用总线的时候,"挪用"一个
本篇完!