0 00:00:00,000 --> 00:00:07,360 1 00:00:07,400 --> 00:00:12,200 下面我们来讨论I/O数据传输 2 00:00:12,240 --> 00:00:15,040 在I/O子系统当中 3 00:00:15,080 --> 00:00:18,080 设备和CPU之间的数据传输性能 4 00:00:18,120 --> 00:00:20,800 也是我们这里关注的一个重要的问题 5 00:00:20,840 --> 00:00:23,080 CPU和设备之间的数据传输 6 00:00:23,120 --> 00:00:24,880 有这样两种方式 7 00:00:24,920 --> 00:00:27,800 第一种是程序控制I/O 8 00:00:27,840 --> 00:00:31,560 也就是说CPU通过in/out指令 9 00:00:31,600 --> 00:00:34,320 或者说load/ store指令 10 00:00:34,360 --> 00:00:35,560 如果说后一种情况 11 00:00:35,600 --> 00:00:37,720 你是做了内存映射的话 12 00:00:37,760 --> 00:00:40,640 那它是通过load和store指令 13 00:00:40,680 --> 00:00:45,240 来完成设备和CPU之间的数据交换 14 00:00:45,280 --> 00:00:49,000 那这种方式由于需要CPU的参与 15 00:00:49,040 --> 00:00:52,120 它的硬件实现相对简单 16 00:00:52,160 --> 00:00:54,200 编程也是比较容易的 17 00:00:54,240 --> 00:00:57,720 它的问题主要是在于消耗的CPU时间 18 00:00:57,760 --> 00:01:01,240 和你要传输的数据量是成正比的 19 00:01:01,280 --> 00:01:02,400 数据量越大 20 00:01:02,440 --> 00:01:05,040 它的传输时间开销也就越大 21 00:01:05,080 --> 00:01:09,600 这时候它适用于简单的小型的设备I/O 22 00:01:09,640 --> 00:01:11,400 如果说你传送数据量大的话 23 00:01:11,440 --> 00:01:15,000 这种方式对CPU的占用就是比较多的 24 00:01:15,040 --> 00:01:19,360 第二种方式是DMA直接存储访问 25 00:01:19,400 --> 00:01:20,960 它是让设备控制器 26 00:01:21,000 --> 00:01:24,440 直接可以访问系统总线 27 00:01:24,480 --> 00:01:27,400 然后设备控制器和内存之间 28 00:01:27,440 --> 00:01:29,360 直接进行数据交换 29 00:01:29,400 --> 00:01:32,320 这种方式呢 它的特点是 30 00:01:32,360 --> 00:01:35,360 CPU可以在数据传输的过程当中干别的 31 00:01:35,400 --> 00:01:36,720 不受影响 32 00:01:36,760 --> 00:01:41,880 当然它的开始和结束也需要CPU的参与 33 00:01:41,920 --> 00:01:46,160 这时候它适用于高吞吐量的设备I/O 34 00:01:46,200 --> 00:01:49,200 这是我们CPU和系统之间的 35 00:01:49,240 --> 00:01:51,360 两种数据传输方式 36 00:01:51,400 --> 00:01:52,320 我们在这儿 37 00:01:52,360 --> 00:01:55,320 再用磁盘数据读取这个过程 38 00:01:55,360 --> 00:01:56,800 来细化一下 39 00:01:56,840 --> 00:01:58,760 看它的交流过程是什么样子的 40 00:01:58,800 --> 00:02:00,000 首先我们看到 41 00:02:00,040 --> 00:02:04,240 这是我们系统里的连线图 42 00:02:04,280 --> 00:02:06,040 CPU通过系统总线 43 00:02:06,080 --> 00:02:09,040 连到内存和DMA控制器 44 00:02:09,080 --> 00:02:11,320 然后DMA控制器 45 00:02:11,360 --> 00:02:14,560 通过PCI总线连到我的磁盘设备 46 00:02:14,600 --> 00:02:15,440 我们在这里 47 00:02:15,480 --> 00:02:18,240 一次磁盘读取过程是什么样的 48 00:02:18,280 --> 00:02:20,960 CPU在执行用户代码的过程当中 49 00:02:21,000 --> 00:02:23,520 会产生磁盘读取请求 50 00:02:23,560 --> 00:02:25,560 这个请求转到设备驱动 51 00:02:25,600 --> 00:02:27,680 在内核里执行 52 00:02:27,720 --> 00:02:31,080 设备驱动需要我把磁盘上的 53 00:02:31,120 --> 00:02:32,080 什么地方的数据 54 00:02:32,120 --> 00:02:33,880 读到内存的什么地方去 55 00:02:33,920 --> 00:02:36,720 这个设备请求就会转换成 56 00:02:36,760 --> 00:02:41,680 对磁盘控制器的操作 57 00:02:41,720 --> 00:02:42,560 这个操作导致 58 00:02:42,600 --> 00:02:46,000 磁盘控制器开始读取数据 59 00:02:46,040 --> 00:02:49,320 读取数据之后初始化DMA传送 60 00:02:49,360 --> 00:02:51,960 然后把数据通过PCI总线 61 00:02:52,000 --> 00:02:53,880 传到DMA控制器 62 00:02:53,920 --> 00:02:55,440 然后DMA控制器 63 00:02:55,480 --> 00:02:58,840 再把数据传到内存的指定区域 64 00:02:58,880 --> 00:03:01,000 也就是我们这里这个X 65 00:03:01,040 --> 00:03:02,520 完成传送之后 66 00:03:02,560 --> 00:03:05,000 它产生一个中断请求 67 00:03:05,040 --> 00:03:07,040 CPU响应这个中断请求 68 00:03:07,080 --> 00:03:10,480 来判断我最后这个操作是否结束 69 00:03:10,520 --> 00:03:13,560 最后呢回到应用程序里 70 00:03:13,600 --> 00:03:18,480 这是通过DMA和中断的方式 71 00:03:18,520 --> 00:03:21,880 来完成磁盘数据读取的这个过程 72 00:03:21,920 --> 00:03:23,720 73 00:03:23,760 --> 00:03:26,440 另外还有一个方向是说 74 00:03:26,480 --> 00:03:28,880 设备如何通知CPU 75 00:03:28,920 --> 00:03:30,720 如何通知操作系统 76 00:03:30,760 --> 00:03:34,120 在这里操作系统需要知道设备的状态 77 00:03:34,160 --> 00:03:35,160 完成时间 78 00:03:35,200 --> 00:03:36,960 比如说遇到的错误之类的 79 00:03:37,000 --> 00:03:39,000 然后在这儿有两种方式 80 00:03:39,040 --> 00:03:42,760 一种是轮询 一种是设备中断 81 00:03:42,800 --> 00:03:46,320 我们在这里一个一个来讨论 82 00:03:46,360 --> 00:03:49,880 轮询是指I/O设备上定义了 83 00:03:49,920 --> 00:03:53,040 一组状态和控制寄存器 84 00:03:53,080 --> 00:03:56,240 操作系统定期的检查这些状态寄存器 85 00:03:56,280 --> 00:03:58,000 从而知道设备的状态 86 00:03:58,040 --> 00:04:00,560 比如说我的数据是否发送完了 87 00:04:00,600 --> 00:04:02,640 我的缓冲区是否还有空地儿 88 00:04:02,680 --> 00:04:05,440 或者说我的缓冲区里是否有数据 89 00:04:05,480 --> 00:04:08,320 这种做法相对来说它是简单的 90 00:04:08,360 --> 00:04:11,160 但是如果你在这里操作频繁 91 00:04:11,200 --> 00:04:13,600 多长时间会产生一次 不可预测的话 92 00:04:13,640 --> 00:04:16,600 那么这时候开销和延时都比较大的 93 00:04:16,640 --> 00:04:18,520 在这里开销大是说 94 00:04:18,560 --> 00:04:20,040 我需要频繁进行操作 95 00:04:20,080 --> 00:04:21,960 延时大是因为我不知道 96 00:04:22,000 --> 00:04:25,320 我什么时候能知道设备状态的改变 97 00:04:25,360 --> 00:04:27,600 我只能在我查的时候才能知道 98 00:04:27,640 --> 00:04:29,560 如果说设备状态改变之后 99 00:04:29,600 --> 00:04:31,240 很长一段时间没有去查 100 00:04:31,280 --> 00:04:32,840 这个延时就大了 101 00:04:32,880 --> 00:04:34,560 所以这是第一种方式 102 00:04:34,600 --> 00:04:37,160 第二种方式是设备中断 103 00:04:37,200 --> 00:04:38,520 在这种方式里头 104 00:04:38,560 --> 00:04:41,200 CPU布置好I/O操作之后 105 00:04:41,240 --> 00:04:43,360 它就可以去忙活别的了 106 00:04:43,400 --> 00:04:45,240 然后这时候I/O设备 107 00:04:45,280 --> 00:04:47,760 进行相应的I/O请求处理 108 00:04:47,800 --> 00:04:50,600 处理完之后 产生中断 109 00:04:50,640 --> 00:04:54,120 触发CPU 让CPU响应 110 00:04:54,160 --> 00:04:55,680 CPU接收中断 111 00:04:55,720 --> 00:04:58,880 然后执行相应的中断处理例程 112 00:04:58,920 --> 00:05:01,040 这是它的一个交互过程 113 00:05:01,080 --> 00:05:03,160 那么在这个交互过程当中 114 00:05:03,200 --> 00:05:06,240 它是处理不可预知事件效果比较好 115 00:05:06,280 --> 00:05:07,840 只要你一产生中断 116 00:05:07,880 --> 00:05:09,520 CPU马上就能给出响应 117 00:05:09,560 --> 00:05:13,560 原因在于CPU是在每两条指令执行期间 118 00:05:13,600 --> 00:05:16,680 会去检查一次是否有中断请求 119 00:05:16,720 --> 00:05:18,120 如果中断比较多 120 00:05:18,160 --> 00:05:21,600 那这时候CPU被中断的频率就会比较高 121 00:05:21,640 --> 00:05:23,800 这时候它的开销相对来说 122 00:05:23,840 --> 00:05:26,320 和DMA方式传送数据的时候 123 00:05:26,360 --> 00:05:29,560 它的开销也是大的 124 00:05:29,600 --> 00:05:31,040 实际的做法是什么呢 125 00:05:31,080 --> 00:05:32,440 会是在一些高速设备 126 00:05:32,480 --> 00:05:35,240 会把两种办法结合起来 127 00:05:35,280 --> 00:05:37,840 比如说在我们这里的高速网络设备 128 00:05:37,880 --> 00:05:38,440 第一次的时候 129 00:05:38,480 --> 00:05:41,280 它是采用中断方式来进行响应的 130 00:05:41,320 --> 00:05:43,840 如果有数据包的接收 131 00:05:43,880 --> 00:05:45,400 那这时候呢 132 00:05:45,440 --> 00:05:48,360 CPU会响应中断来处理这条数据 133 00:05:48,400 --> 00:05:52,480 由于输入输出数据比较多 量比较大 134 00:05:52,520 --> 00:05:54,720 那这时候第一次中断处理完 135 00:05:54,760 --> 00:05:57,000 相应的数据之后它会再进行轮询 136 00:05:57,040 --> 00:05:58,600 如果有数据马上就要处理 137 00:05:58,640 --> 00:06:00,760 这样的话后面就变成轮询方式了 138 00:06:00,800 --> 00:06:02,960 一直到没有的时候 它才结束 139 00:06:03,000 --> 00:06:05,000 下一次中间隔的时间比较长的时候 140 00:06:05,040 --> 00:06:07,680 它再会转到中断方式 141 00:06:07,720 --> 00:06:09,960 这是设备中断 142 00:06:10,000 --> 00:06:11,600 下面我们具体来看一下 143 00:06:11,640 --> 00:06:13,920 中断I/O的处理流程 144 00:06:13,960 --> 00:06:18,400 首先是CPU在执行指令的过程当中 145 00:06:18,440 --> 00:06:20,400 产生了I/O请求 146 00:06:20,440 --> 00:06:24,280 这时候设备驱动会初始化这个I/O请求 147 00:06:24,320 --> 00:06:28,200 当中的I/O控制器会初始化I/O操作 148 00:06:28,240 --> 00:06:31,080 然后由设备进行相应的操作 149 00:06:31,120 --> 00:06:33,080 操作完成或者出错 150 00:06:33,120 --> 00:06:35,920 这时候它产生中断 151 00:06:35,960 --> 00:06:39,520 CPU在完成I/O请求初始化之后 152 00:06:39,560 --> 00:06:40,960 它就可以去干别的了 153 00:06:41,000 --> 00:06:43,400 这时候它在每执行一条指令之后 154 00:06:43,440 --> 00:06:45,080 它会检查中断请求 155 00:06:45,120 --> 00:06:46,720 如果不做这个检查的话 156 00:06:46,760 --> 00:06:47,880 那你这边有请求之后 157 00:06:47,920 --> 00:06:50,480 它是不能做到及时响应的 158 00:06:50,520 --> 00:06:53,320 正是由于CPU在设计上就已经做到了 159 00:06:53,360 --> 00:06:56,640 用硬件来检查每一条指令执行期间 160 00:06:56,680 --> 00:06:59,200 是否有中断请求 如果有 161 00:06:59,240 --> 00:07:01,280 它就不去进行下一条指令的执行 162 00:07:01,320 --> 00:07:03,280 而转到中断的处理 163 00:07:03,320 --> 00:07:05,920 那么这时候CPU接收到中断请求之后 164 00:07:05,960 --> 00:07:07,920 在当前指令结束之后 165 00:07:07,960 --> 00:07:09,680 它就可以来进行这个响应 166 00:07:09,720 --> 00:07:11,680 所以你这个延时通常是 167 00:07:11,720 --> 00:07:14,800 最长是一条指令的执行时间 168 00:07:14,840 --> 00:07:16,400 那么收到这个请求之后 169 00:07:16,440 --> 00:07:19,520 它分发给相应的中断服务例程 170 00:07:19,560 --> 00:07:23,920 由相应的中断服务例程进行处理 171 00:07:23,960 --> 00:07:25,080 这是它的处理 172 00:07:25,120 --> 00:07:26,680 处理完毕之后 173 00:07:26,720 --> 00:07:30,960 CPU恢复被中断的进程的执行 174 00:07:31,000 --> 00:07:32,280 在这个执行的过程当中 175 00:07:32,320 --> 00:07:34,920 又可能执行到某一个地方去 176 00:07:34,960 --> 00:07:38,120 再产生新的I/O请求 177 00:07:38,160 --> 00:07:40,160 这是基于设备中断的 178 00:07:40,200 --> 00:07:42,720 I/O请求的处理过程 179 00:07:42,760 --> 00:07:44,200 有了这些描述之后 180 00:07:44,240 --> 00:07:48,360 I/O的数据传输的方式和流程 181 00:07:48,400 --> 00:07:50,120 我们就说清楚了 182 00:07:50,160 --> 00:07:51,320 183 00:07:51,360 --> 00:07:51,400