51.3I-O 控制方式
# 5.1.3_I-O 控制方式
在这个小节中我们会学习本章的一个重要的考点 IO 控制方式,上一小节中我们学习了 IO 控制器,但是随着计算机的发展,IO 控制器也是在不停的发展的,相应的 IO 控制器对设备的控制方式也出现了不同的变化或者说进化。我们需要掌握这样的 4 种 IO 控制方式,程序直接控制方式、中断驱动方式、DMA 方式和通道控制方式。
在学习的过程中我们需要注意这样的几个问题,第一在各种 IO 控制方式当中完成一次读写操作的流程分别是怎么样的。第二,我们需要注意的是 CPU 对 IO 操作的一个干预的频率。第三我们需要注意在不同的控制方式当中进行一次 IO 所传送的数据单位到底是多少。第四我们还需要注意数据的流向。第五我们需要注意的是各个各种控制方式的一个主要缺点和主要优点,什么是干预频率,什么是传送单位,什么是数据流向,这些可能现在听起来会比较模糊,咱们一会直接看具体的例子就可以理解了。
# 程序直接控制方式
我们首先要了解的是程序直接控制方式,这也是最早期的一种 IO 控制方式,我们来分析一下,如果采用程序直接控制方式的话,那么那么完成一次读写操作的流程到底是什么样的?
我们以读操作为例,经过上个小节的学习,我们知道一个 IO 控制器它有这样的一些部分组成,如果要完成一个读操作的话,CPU 首先会通过控制线向 IO 控制器发出一个读指令,于是 IO 控制器会根据 CPU 的要求启动相应的设备,并且把这个设备对应的状态设置为未就绪或者说忙碌的一个状态。那我们假设状态寄存器为一表示的是设备忙碌,
如果要进行一个读操作的话,CPU 首先会通过控制线向 IO 控制器发出一个读指令,然后 IO 控制器会根据 CPU 的要求来启动相应的设备,并且会把这个设备对应的状态设置为未就绪或者说忙碌的一个状态。
我们假设状态寄存器为一表示的是这个设备正在忙碌,接下来这个设备就会开始准备计算机想要读入的数据,但是由于设备的速度要比 CPU 的速度慢很多,所以在设备还没有完成 IO 之前,CPU 会一直不断的轮巡检查设备的状态,也就是检查状态寄存器当中的数据。如果这个数字一直为一的话,那就表明此时这个设备正在忙碌,他还没有准备好想要读入的数据。 CPU 轮巡检查状态,寄存器的过程,其实在背后它就是在不断的执行一个程序的循环。
接下来如果这个设备已经准备好了输入的数据的话,那么这个设备就会给 IO 控制器传送这一次要输入的数据,并且报告自己的状态已经变成了已就绪的状态。接下来 IO 逻辑会把设备传送过来的数据放到数据寄存器当中,并且会把状态寄存器改为 0,也就是已就绪的状态。在这个过程中 CPU 其实也是在不停的轮巡循环的检查的,当他发现状态寄存器变为了 0,也就是这个设备已经变成了已就绪的状态的时候, CPU 就可以从数据寄存器当中读取出此次要输入的这个数据了。 CPU 首先是会把这个数据读入到 CPU 自己的寄存器当中,然后再把 CPU 寄存器当中的内容再放到内存当中。
所以其实这个数据输入的过程本来应该是从设备输入到内存的,但是这个过程中必须先经过 CPU 的寄存器,然后再由寄存器转存到内存当中,这样的话就完成了一次读操作
如果说之后还需要继续读入别的数据,CPU 又会再发送下一条读指令,然后再继续循环刚才我们所分析的这一系列流程
所以其实如果采用程序直接控制方式的话,那么我们需要掌握的一个重点的核心词叫做轮巡,采用这种方式完成一次读写操作的流程,就像这个图表示的这样,这也是咱们课本当中给出的图。
经过刚才的分析,相信大家应该已经可以看得懂这个图的含义了,大家可以暂停一下,自己来捋一遍。
我们来解释一下这个流程图当中不太容易理解的一些点。
这儿为什么会有一个 CPU 到 IO 的标注呢?
因为发出读命令是 CPU 向 IO 控制器发出的,所以 CPU 指向 IO 是这个意思。
第二个步骤读 IO 模块的状态,通过刚才的分析,我们知道其实读 IO 模块的状态是从 IO 控制器的状态寄存器当中读出数据,然后放到 CPU 的寄存器当中进行分析,所以这儿的标注是 IO 向 CPU,
那在读出 IO 模块的状态之后,还需要对这个状态进行判断,需要判断此时想要读注的数据是否已经准备好,如果这个数据没有准备好的话,它还会继续不断的轮循,不断的读取 IO 模块的状态。这个地方大家会发现这有一个叫做错误条件的东西,因为 IO 设备有可能会出现一些故障,如果 IO 设备出错的话,也会在 IO 控制器的状态寄存器当中写入相应的那些错误代码, CPU 就可以根据这些代码来判断此时 IO 设备是否已经产生错误了,所以这有可能会产生一个错误条件。
CPU 一直不断的轮巡,直到发现这个 IO 已经准备好了,此时就可以从 IO 模块当中读取一个字的内容,把它放到 CPU 的寄存器当中,也就是从 IO 控制器的数据寄存器当中读入一个字的内容放到 CPU 的寄存器当中,所以这个地方的标注是 IO 指指向 CPU
接下来当这个数据被读入 CPU 的寄存器之后,其实读数据的过程还没有完成,还需要把 CPU 寄存器当中的内容转存到存储器当中,这样的话就完成了一个读操作。
接下来如果还要读入更多的数据的话,那就会回到刚开始的步骤,然后再重复一遍咱们刚才分析的这一系列流程
这个地方可能会有一个大家不太容易理解的点,为什么数据读入 CPU 寄存其中之后,还需要往存储器当中写入这个数据,其实我们看一段我们很熟悉的 c 语言带代码就可以理解为什么要这么做了。只要是学过 c 语言同学,肯定写过这样的一个函数叫做 scanf,从键盘这种 IO 设备里读入一个输入的字符,并且赋值给其中的某一个变量,那么我们知道我们定义的这些变量 ABCD 其实它们是存放在存储器也就是内存当中的,所以其实这些数据从键盘读入之后,最终肯定是要被放到存储器,也就是内存当中,因此当 CPU 获得我们从键盘输入的数据之后,其实还没有结束,还需要把这些数据把它写入到相应的存储器的相应单元里。
同理,当我们使用 printf 这个输出数据的函数的时候,其实我们做的事情是要把内存当中存储的这些变量的数据拿出来,然后最后经过 CPU 再输出设备上。讲到这个地方,我们就对程序直接控制方式的流程有了一个很清晰的认识了。
接下来我们按照开篇提到的那种思路来继续分析另外的一些问题。我们来看一下 CPU 干预的频率如何。在使用程序直接控制这种方式的时候,CPU 需要不断的轮巡检查 IO 操作是否已经完成,所以 CPU 干预的频率是很频繁的,不仅在 IO 操作开始之前,还要完成之后需要 CPU 的介入,在等待 IO 完成的过程当中, CPU 也需要不断的进行轮巡检查,这也是程序直接控制方式的最大的缺点。
第三个我们需要关注的问题是数据的传送单位,从刚才这个分析我们也会发现每一次读入或者写出的数据量是一个字。
第四个我们需要注意的是数据流向,所谓的数据流向就是我们刚才分析的,在读入数据的时候,其实是从 IO 设备到 CPU 的寄存器再到内存,如果说是数据输出的话,就是从内存到 CPU 再到 IO 设备,所以每一个字的读和写都需要 CPU 的介入帮助,也就是说 CPU 需要花费大量的时间来辅助完成 IO 的过程。
第五个问题我们再来看一下它的主要缺点和主要优点,优点的话实现简单可以用软件的方式就可以实现,由于它轮巡的过程其实就是在执行一系列循环检查的指令,所以这种方式才叫程序直接控制方式,通过执行一系列特定的程序代码循环检查的这样的程序代码,就可以实现这种控制方式。
这种方式的缺点也很明显,CPU 和 IO 设备他们只能串行的工作,因为在 CPU 发出一个 IO 指令之后,CPU 并不能去做别的事情,他需要一直不断的循环检查这个 IO 是否已经完成了,所以 CPU 会长期处于一个盲等的状态,导致 CPU 的利用率低,那相应的当 CPU 在进行别的一些计算工作的时候,IO 设备肯定也是空闲的,所以 IO 设备的利用率其实也是低的。怎么解决这个问题?为此人们提出了中断驱动方式
# 中断驱动方式
中断这个概念咱们在之前已经接触过很多了,相信大家对中断并不陌生,那与之前的那种 IO 控制方式相比,中断驱动方式主要是引入了中断机构,可以让 CPU 在发出 IO 指令之后,转头可以做别的事情,也就是可以切换到别的进程。
由于我们的 IO 设备是速度很慢的,而 CPU 又是一种速度很快的一个硬件机构,所以当 CPU 发出 IO 指令之后,可以把此时需要等待 IO 的进程先阻塞起来,然后 CPU 去先去做其他事情,也就是可以先切换到别的进程执行。
然后当 IO 完成之后,IO 控制器会向 CPU 发出一个中断信号, CPU 检测到中断信号之后,就会根据中断信号的类型来执行相应的中断处理程序。
在 CPU 处理中断的过程当中,它会从 IO 控制其中读出一个字的数据传送到 CPU 寄存器,并且在写入主存当中,接下来 CPU 就可以恢复之前被阻塞的进程继续往下执行了,当然也可以选择不恢复它,让它继续在就绪队列里等待,然后先执行别的进程,这些点咱们在之前的章节当中也已经聊过很多次了,这就不再赘述。
我们需要注意这样的几个点,
首先 CPU 是会在每个指令周期的末尾来检查中断,也就是说每条指令执行结束之后,CPU 都会例行的检查一下此时有没有中断信号的到达。
第二,在中断处理的过程当中, CPU 需要保存和恢复进程的运行环境,但是这个过程咱们之前也分析过,它是需要一定的时间开销的,所以如果中断发生的太频繁,频率太高的话,显然也是会降低系统的性能的地方,需大家需要注意,
在中断驱动方式当中,每次发生中断只能读入一个字的数据,所以如果要读入大量的数据的话,那显然会发生大量的中断,这样的话就会导致系统的性能降低。
除了引入了中断技术之外,别的这些流程和咱们之前的那种控制方式其实大同小异的,这就不再赘述。
相比于之前的程序直接控制方式来说,采用中断驱动方式,CPU 的干预频率就变得低了很多,CPU 只需要在每一次 IO 操作开始之前发出一个 IO 指令,然后转头做别的事情,然后当 IO 完成之后,CPU 也需要介入来处理相应的中断,由于等待 IO 完成的过程中,CPU 可以切换到别的进程执行,所以在引入了中断之后,才实现了 CPU 和 IO 设备并行工作的这样的一个特点。
那么第三我们需要知道的是每发出一个读或者写指令,只会读入或者写出一个字大小的数据。
第四,数据的流向和之前的程序直接控制方式是相同的,在读入数据的时候,同样是从 IO 设备读入到 CPU 的寄存器当中,再从 CPU 寄存器转存到内存当中,然后写出的时候一个相反的过程。
中断驱动方式的优点其实就是解决了程序直接控制方式的最大的缺点。引入了中断技术之后,可以让 CPU和 IO 设备并行的工作,然后 CPU 不再需要不停的轮巡来检查 IO 是否完成,这样的话 CPU 利用率 IOIO 设备的利用率也得到了明显的提升
但是这种方式也存在一个很明显的缺点,就是由于它每次只能传送一个字,所以当我们需要传送大量的数据的时候,那显然会发生很多次的中断,而每一次中断的处理又需要付出一定的时间代价,所以如果中断发生的太频繁,那么中断处理会消耗很多的 CPU 时间。
另外采用这种方式的时候,在读入数据或者写出数据的时候都必须先经过 CPU,但是通过之前的分析我们也知道,其实读入数据无非就是把 IO 设备准备好的数据放到内存里,而写出数据无非就是把内存中的数据写出到 IO 设备,所以能不能把中间必须经过 CPU 的中转步骤给砍掉。为此人们又提出了一种新的 IO 控制方式叫做 DMA 方式,
# DMA 方式
DMA 方式主要就是为了解决中断驱动方式留下的那几个问题, DMA 方式的中文名又叫直接存储器存取。所以在遇到这个名词的时候,大家也需要知道它指的就是 DMA,这种方式主要用于对于块设备的这种 IO 控制,
- 相比于中断驱动方式,DMA 方式的数据传送单位由字变为了块,每次会读入或者写出一个块。
- 另外数据的流向不再需要经过 CPU,而是可以在 DNA 控制器的控制下,直接从设备放入到内存,或者直接从内存写出到设备。
- 第三,CPU 对于 IO 操作的干预频率有进一步的降低,仅仅在传送一个或者多个数据块的开始和结束的时候,才需要 CPU 进行干预。
所以 DMA 方式的一个流程大致就是这个样子。首先 CPU 会给 IO 模块发出一个读或者写一个块的指令之后, CPU 就可以转头做其他事情。接下来 DMA 控制器会根据 CPU 发出的这些命令参数,然后完成 CPU 指定的这一系列的读写工作。当 CPU 指定的这些块读完或者写完之后,又会由 DMA 控制器向 CPU 发出一个中断信号,然后 CPU 又介入处理中断
在 CPU 发出读或者写指令的时候,它需要说明此次要读入或者写出的到底有多少数据,让这些数据应该存放在内存的什么位置,数据在外部设备上又应该存放在什么位置,这些是 CPU 需要告诉 DMA 控制器的一些信息,咱们一会还会细聊。
CPU 给出了这些参数之后,DMA 控制器就会根据 CPU 的这些要求来完成对应的读写工作。整块数据传输完成之后才向 CPU 发出中断信号,要求 CPU 介入来处理中断。
咱们刚才一直在说的 DMA 控制器其实也是一种 IO 控制器,只不过它和咱们上一小节介绍的 IO 控制器有那么一些小小的区别,不过 DMA 控制器依然是由三个部分组成,第一个部分是主机或者说 CPU 和控制器的接口,第二个部分是 IO 控制逻辑,第三部分是块设备和控制器的接口。这和 IO 控制器的三个部分都是一一对应的,并不难理解。
为了实现控制器和 CPU 之间的通信,它会在这个地方设置一系列的寄存器,然后 CPU 可以通过系统总线来读或者写其中的某一些寄存器当中的内容,用这种方式达到控制 IO 设备的一个目的。
- 像这个地方 DR 它其实是一个英文缩写,它是一个数据寄存器,就是用于暂存从设备到内存或者从内存到设备的数据,相当于一个中转站,这个和咱们之前介绍的数据寄存器没有太大的区别。
- Mar 这个也是一个英文的缩写,它是用于存放内存地址的,比如说要把数据从设备输入到计算机的时候, mar 当中会存放这些读入的数据应该放到内存中的什么位置这样的一个信息。那相应的,当数据需要从内存输出到设备的时候,mar 寄存器中又会指明我们要输出的数据是存放在内存的什么位置,
- Dc 寄存器又叫数据计数器,就是用来记录此时还要读或者要写的字节数还有多少
- CR 同样也是一个缩写,它是命令状态寄存器用于存放 CPU发来的 IO 命令相关的一些参数,还有设备的状态等等一系列的信息。
那和之前咱们介绍的 IO 控制器一样,这些寄存器也有可能会有多个,在这个地方并没有列全,这些寄存器是最主要的主机和控制器之间的接口,
而在控制器和块设备之间也有一个相应的接口,通过这个接口可以实现控制器对于这些块设备的一个通信,控制的过程。除此之外,系统总线还会把 DMA 控制器和内存连接在一起,所以 DMA 控制器和内存之间可以直接进行数据的读写,不再需要经过 CPU。比如说 CPU 可以在刚开始指明这次要读入的数据是存放在磁盘的什么位置,这些读入的数据应该存放在内存的什么位置,这些信息是存放在 mar 里的,并且还会说明此次要读入的数据的数据量到底是多少,这个数据量又是存放在 DC 寄存器当中。接下来 DMA 控制器就会根据 CPU 提供的这一系列的参数,从磁盘的相应位置读入数据,然后写到内存里,而这个过程就不再需要 CPU 的干预,只有 DMA 控制器完成了整个 CPU指定的这一系列的任务之后,他才会向 CPU 发出一个中断信号,然后 CPU 再介入进行后续的处理。
这个地方需要注意的是 dma 控制器并不是每次直接读入一整块的数据,然后直接把一整块放到内存当中。其实 dma 控制器在读入数据的过程当中,也是一个字一个字读入的,然后每次读入的一个字都是先先会存放在 DR,也就是数据寄存器当中,再从 DR 写入到内存当中,用这样一个字一个字的方式,最终就可以完成一整块的数据的读入工作。
采用 dma 方式完成一次读写操作的流程,就是咱们刚才说到的过程,并不复杂,在采用这种方式之后,CPU 的干预频率就进一步的降低了,仅在传送了一个或者多个数据块的开始和结束的时候才需要 CPU 干预。在开始之前 CPU 需要发出相应的 IO 指令,并且指明那些相应的参数,然后在结束之后 CPU 又需要处理中断,然后进行后续的一系列处理,而数据的传送单位也从一个字变成了一个块,CPU 每发出一个读指令或者写指令之后,DMA 控制器就可以完成对一个块或者多个块的读和写的操作。
但是需要注意的是这个地方指的多个块,只能是读写那些连续的多个块,并且这些块在读入内存之后也必须是连续存放的。也就是说如果我们是想要读入多个离散的块或者这些读入的块,需要离散的存放在内存的不同位置的话,那么采用 DMA 方式,同样是需要 CPU 发出多条 IO 指令,
在采用了 DMA 方式之后,数据的流向就不再需要经过 CPU,它可以直接从 IO 设备读入,然后在 dma 控制器的控制下,直接把数据放入到内存;在输出的时候就刚好相反,同样是不需要经过 CPU 的
所以这种方式的优点就是进一步提升了数据传输的效率。数据传输以块为单位,然后 CPU 介入的频率可以进一步的降低,这样的话 CPU 就可以有更多的时间去进行别的处理。另外数据传输的过程也不需要再经过 CPU,所以数据传输的效率也得到了进一步的提升。所有的这些其实带来的结果都是可以让 CPU 从这些繁杂的 IO 工作当中抽离出来,让 CPU 有更多的时间去处理别的那些计算任务。所以采用这种方式之后,CPU 和 IO 设备的并行性得到了进一步的提升,资源利用率也得到了进一步的提升。
但是它的缺点就是咱们刚才所说的, CPU 每发出一条 IO 指令,只能读或者写一个或者多个连续的数据块,如果我们要读取离散的数据块或者读入的数据,块要离散的存放在不同的内存区域当中的时候,就需要发出多条 IO 指令,因此这是 DMA 方式还可以继续改进的一个特点。为了解决这个问题,人们又提出了通道控制方式,
# 通道控制方式
所谓的通道其实它是一种硬件,大家可以把通道理解为是一种弱机版的 CPU。通道可以识别并且执行一系列的通道指令,就类似于咱们 CPU 识别的那些指令一样,为什么叫弱鸡版的 CPU 咱们一会再解释。
我们首先来看一下通道的工作原理,CPU、内存,通道通过系统总线连接到一起,首先 CPU 会向通道这个硬件发出 IO 指令,并且指明此次要执行的通道程序,或者说通道指令的序列,它是存放在内存的什么位置的。同时 CPU 还需要指明此次要执行操作的设备到底是哪一个,再把这些信息告诉了通道之后,CPU 就可以切换到其他进程执行了,那么之后通道会根据 CPU 的指示去找到此次要执行的通道程序存放在内存当中的什么位置。
这个通道程序其实大家可以把它理解成是一种任务清单,其实这个任务清单就是一系列通道指令的集合,本质上它和我们熟悉的普通的那些程序其实都是一样的,在这个任务清单的通道指令当中,也会像通道指明此次要读入和写出的数据到底是多少,然后读写的数据应该放在内存当中的什么位置,它是放在外存中的什么位置的等等这一系列的信息,这些都是通道在执行这个程序的过程当中就可以知道的事情。
所以如果采用这种方式的话,就相当于 CPU 只是告诉通道,你现在去执行这样的一个任务,这个任务的清单我已经放在内存里了,但具体这个任务需要做什么,并不是由 CPU 直接告诉通道的,而是由通道直接去读取内存当中的程序,然后一步一步执行。
当这个通道执行完了这一系列的任务之后,他就会向 CPU 发出一个中断信号, CPU 接收到中断信号之后对中断进行处理,然后再继续执行接下来的那一系列的程序。所以这就是通道控制方式当中完成一次 IO 所需要经历的一系列步骤。
为什么说通道是一种弱极版的 CPU 呢?因为通道它可以识别一系列的通道指令,但是它所能识别的这些指令与 CPU 能识别的那些指令相比,它的指令是很单一的,并且通道也并没有自己的内存,它需要和 CPU 共享主机的内存,所以由于通道硬件只能识别一些很简单的很单一的通道指令,因此我们可以把它理解为是一个弱极版的 CPU
通过刚才分析我们也知道了,在通道控制方式当中完成一次读写操作的流程,就是这个样子,而引入了通道之后,CPU 的干预频率就进一步的降低了。CPU 可以一次扔给通道一堆事情,这些事情会写在通道程序里,所以通道可以根据通道程序的指示,一步一步完成 IO 操作。然后当他完成了一系列的数据块的读写之后,才需要对 CPU 发出中断信号,因此 CPU 的干预频率是极低的。
DMA 控制方式当中,每一次读写是读写一个数据款或者多个连续的数据款,但是在通道控制方式当中,每一次读写可以完成对一组数据块的读写操作,与 DMA 控制方式类似,采用了通道控制方式之后,可以在通道的控制下,让数据直接从 IO 设备、读入、内存或者直接把内存中的数据输出到 IO 设备当中,那通道方式的主要缺点就是实现复杂,需要专门的通道硬件的支持,但是它的优点也很明显,就是 CPU、通道 IO 设备可以并行的工作资源利用率特别高。
# 小结
那么这个小节我们介绍了 4 种,我们需要掌握的 IO 控制方式,分别是程序直接控制方式、中断驱动方式、DMA 方式和通道控制方式。我们需要注意这样几个维度的问题,首先我们需要理清楚在每一种控制方式下,完成一次读写操作的大致过程,大致流程是什么样的,经常在选择题当中进行考察。另外我们还需要理解每种控制方式的 CPU 干预频率到底是高的还是低的,然后每一次 IO 的数据传输单位是字还是块还是多少,
通过之前的分析大家会发现,其实每一个阶段的优点都是解决了上一个阶段留下的最大的缺点,总体上来说整个的 IO 控制方式的发展过程就是要尽量的减少 CPU 对 IO 过程的干预,把 CPU 从繁杂的 IO 控制事务当中解脱出来,以便更多的去完成数据处理的任务。那也正是因为如此,大家会发现 CPU 的干预频率从极高慢慢变成了极低,而每次 IO 的数据传输单位又从字这么一个很小的数据单位又慢慢变成块,而最后的通道方式甚至可以支持每次传输一组块,所以整个发展过程都是在追求这样的一个事情。
第一次学习这一块内容的同学可能并不太容易理解通道,所以我们出了一个比喻,通道可以把它理解为是一种弱极版的 CPU 然后通道程序可以把它理解为就是一个任务清单,通道需要根据这个任务清单来执行这一系列的任务,而这个通道程序又是由一系列的通道指令组成的。另外大家还需要注意,一个通道可以控制多个 IO 控制器,而一个 IO 控制器又可以控制多个 IO 设备。小节的内容十分重要,大家还需要经过课后习题进行进一步的巩固。