0 00:00:00,000 --> 00:00:15,760 1 00:00:15,800 --> 00:00:19,800 今天我们来介绍I/O子系统 2 00:00:19,840 --> 00:00:23,560 I/O子系统是我们计算机操作系统当中 3 00:00:23,600 --> 00:00:26,480 负责与外设打交道的部分 4 00:00:26,520 --> 00:00:27,680 那么在这里呢 5 00:00:27,720 --> 00:00:30,160 我们首先会来介绍 6 00:00:30,200 --> 00:00:33,680 I/O子系统里头它的I/O特征 7 00:00:33,720 --> 00:00:36,640 这里主要是说我们在系统里头 8 00:00:36,680 --> 00:00:39,480 可以连接的设备是有很多种的 9 00:00:39,520 --> 00:00:41,160 速度有很大的差异 10 00:00:41,200 --> 00:00:44,240 那么这些各种设备的访问特征 11 00:00:44,280 --> 00:00:45,240 直接影响到我们 12 00:00:45,280 --> 00:00:47,800 用什么样的方式来跟设备进行交互 13 00:00:47,840 --> 00:00:49,480 在了解它的特征之后 14 00:00:49,520 --> 00:00:50,640 我们会来介绍 15 00:00:50,680 --> 00:00:53,640 I/O的结构和它的数据传输 16 00:00:53,680 --> 00:00:55,200 最后两个部分是 17 00:00:55,240 --> 00:00:58,800 我们在这里举了一个实际的例子 磁盘 18 00:00:58,840 --> 00:01:00,280 对于磁盘的I/O来说 19 00:01:00,320 --> 00:01:02,160 我们会又涉及到两个问题 20 00:01:02,200 --> 00:01:05,000 一个是磁盘的调度 21 00:01:05,040 --> 00:01:08,080 这里指的是说我们在磁盘访问的时候 22 00:01:08,120 --> 00:01:10,120 由于磁头有机械的移动 23 00:01:10,160 --> 00:01:12,600 所以这个时间是会很长 24 00:01:12,640 --> 00:01:14,280 所以在这里头呢我们需要 25 00:01:14,320 --> 00:01:18,360 有相应的一些算法来提高它的性能 26 00:01:18,400 --> 00:01:20,360 这就是我们这里的磁盘调度 27 00:01:20,400 --> 00:01:22,480 另外一个呢是磁盘缓存 28 00:01:22,520 --> 00:01:24,800 也就是说我们把磁盘上的数据 29 00:01:24,840 --> 00:01:26,680 读到内存当中来使用 30 00:01:26,720 --> 00:01:29,280 写回去的数据还需要写回到磁盘里头 31 00:01:29,320 --> 00:01:30,480 这读和写呢 32 00:01:30,520 --> 00:01:32,520 我们可以在内存里头加缓存 33 00:01:32,560 --> 00:01:34,960 从而提高它的访问效率 34 00:01:35,000 --> 00:01:37,720 如何来控制这个磁盘缓存 35 00:01:37,760 --> 00:01:39,880 也是我们这里要讨论的问题 36 00:01:39,920 --> 00:01:42,720 37 00:01:42,760 --> 00:01:45,400 首先我们对I/O设备的接口 38 00:01:45,440 --> 00:01:47,040 进行一个分类 39 00:01:47,080 --> 00:01:50,600 在这里头我们大致的分类有这样三类 40 00:01:50,640 --> 00:01:53,240 字符设备 块设备和网络设备 41 00:01:53,280 --> 00:01:54,520 这三类设备呢 42 00:01:54,560 --> 00:01:56,800 各有一些各自的特点 43 00:01:56,840 --> 00:01:59,200 字符设备通常情况下它的速度很慢 44 00:01:59,240 --> 00:02:00,680 比如说在这里举的例子是 45 00:02:00,720 --> 00:02:03,600 串口 键盘和鼠标 46 00:02:03,640 --> 00:02:06,680 而块设备呢通常是我们的存储设备 47 00:02:06,720 --> 00:02:10,280 比如说磁盘 磁带和光驱 48 00:02:10,320 --> 00:02:11,680 而网络设备呢 49 00:02:11,720 --> 00:02:14,640 是计算机系统与外界打交道的 50 00:02:14,680 --> 00:02:15,760 最重要的手段 51 00:02:15,800 --> 00:02:16,560 你比如说在这里头 52 00:02:16,600 --> 00:02:20,440 我们的以太网 802.11无线网 蓝牙等等 53 00:02:20,480 --> 00:02:22,440 这些都算是网络设备 54 00:02:22,480 --> 00:02:24,840 那么这三种不同的设备呢 55 00:02:24,880 --> 00:02:27,680 它的访问是具有不同的特征的 56 00:02:27,720 --> 00:02:30,160 首先第一个字符设备 57 00:02:30,200 --> 00:02:31,240 所谓字符设备呢 58 00:02:31,280 --> 00:02:34,640 它的访问是以字节为基本的访问单位 59 00:02:34,680 --> 00:02:36,720 然后是顺序访问的 60 00:02:36,760 --> 00:02:40,080 比如说像我们的键盘你敲一个键之后 61 00:02:40,120 --> 00:02:41,360 每次你只能敲一个 62 00:02:41,400 --> 00:02:43,320 如果两个在一块的话它是组合的 63 00:02:43,360 --> 00:02:45,760 对于计算机系统来说它也是一个 64 00:02:45,800 --> 00:02:47,040 那这个输入是 65 00:02:47,080 --> 00:02:49,560 一个字节一个字节来进行输出的 66 00:02:49,600 --> 00:02:52,080 像串口它也是两个方向 67 00:02:52,120 --> 00:02:52,880 每个方向也是 68 00:02:52,920 --> 00:02:55,720 一个字节一个字节为单位往外输出的 69 00:02:55,760 --> 00:02:56,800 这时候呢 70 00:02:56,840 --> 00:02:59,880 它的访问通常由get put 71 00:02:59,920 --> 00:03:02,000 这样两个I/O的命令 72 00:03:02,040 --> 00:03:05,600 在这里我们通常把它封装成一个文件 73 00:03:05,640 --> 00:03:08,400 用文件访问的接口和语义 74 00:03:08,440 --> 00:03:12,680 来对这些字符设备进行访问 75 00:03:12,720 --> 00:03:14,320 这是第一类 76 00:03:14,360 --> 00:03:16,920 第二类呢是块设备 77 00:03:16,960 --> 00:03:19,400 块设备它最主要的特征是 78 00:03:19,440 --> 00:03:21,760 底下的访问是以基本的数据块 79 00:03:21,800 --> 00:03:24,040 为最小访问单位的 80 00:03:24,080 --> 00:03:26,920 也就是说读写是以一个数据块为单位 81 00:03:26,960 --> 00:03:30,000 这种访问呢是比较均匀的 82 00:03:30,040 --> 00:03:32,040 它的数据量也是比较大的 83 00:03:32,080 --> 00:03:33,680 那我们在访问的时候呢 84 00:03:33,720 --> 00:03:36,520 通常情况下可以使用文件的接口 85 00:03:36,560 --> 00:03:38,400 我们实际上在前面的文件系统里 86 00:03:38,440 --> 00:03:39,840 就是这样在讨论的 87 00:03:39,880 --> 00:03:42,960 为了提高性能也可以使用原始的I/O接口 88 00:03:43,000 --> 00:03:46,200 也就是说我直接对磁盘上的扇区 89 00:03:46,240 --> 00:03:48,440 进行读写控制 90 00:03:48,480 --> 00:03:50,400 我们也说到第三种方式 91 00:03:50,440 --> 00:03:54,240 我可以把磁盘映射到内存当中 92 00:03:54,280 --> 00:03:55,920 用内存映射文件 93 00:03:55,960 --> 00:03:59,160 来对磁盘上的数据进行访问 94 00:03:59,200 --> 00:04:01,880 第三种呢是网络设备 95 00:04:01,920 --> 00:04:03,760 网络设备它的最主要特征呢 96 00:04:03,800 --> 00:04:05,880 是它的交互是比较复杂的 97 00:04:05,920 --> 00:04:08,520 我们有一门专门的网络原理课程 98 00:04:08,560 --> 00:04:10,280 来讨论这一协议 99 00:04:10,320 --> 00:04:13,360 这个地方就是以格式化的报文交换 100 00:04:13,400 --> 00:04:14,800 是它最主要的特征 101 00:04:14,840 --> 00:04:16,440 为了应对这种特征呢 102 00:04:16,480 --> 00:04:21,640 它的I/O命令是专门的网络报文收发接口 103 00:04:21,680 --> 00:04:23,520 send/receive 104 00:04:23,560 --> 00:04:26,520 然后我们多种不同的网络协议呢 105 00:04:26,560 --> 00:04:28,760 是封装在这个网络接口下面的 106 00:04:28,800 --> 00:04:30,080 以使用不同的协议 107 00:04:30,120 --> 00:04:33,000 跟不同的对象进行交互 108 00:04:33,040 --> 00:04:34,240 这是我们在这儿呢 109 00:04:34,280 --> 00:04:39,040 对设备访问的特征有一个大致的描述 110 00:04:39,080 --> 00:04:40,560 有了这些描述之后 111 00:04:40,600 --> 00:04:43,880 我们更主要的问题是会来讨论 112 00:04:43,920 --> 00:04:46,680 CPU与设备之间的交互 113 00:04:46,720 --> 00:04:48,600 那这种交互关系呢 114 00:04:48,640 --> 00:04:52,640 也就是我们这里说的同步和异步I/O 115 00:04:52,680 --> 00:04:56,840 这张图给出了我们用户的进程 116 00:04:56,880 --> 00:04:59,440 与设备进行I/O操作的时候 117 00:04:59,480 --> 00:05:01,680 它的一个大致的结构 118 00:05:01,720 --> 00:05:04,040 用户发出I/O请求 119 00:05:04,080 --> 00:05:06,800 然后这个请求会送到 120 00:05:06,840 --> 00:05:09,720 操作系统内核当中的设备驱动 121 00:05:09,760 --> 00:05:13,880 设备驱动呢会把它转换成硬件的控制 122 00:05:13,920 --> 00:05:16,200 控制你的硬件进行相应的操作 123 00:05:16,240 --> 00:05:18,160 硬件操作完成之后呢 124 00:05:18,200 --> 00:05:21,080 它会产生中断 125 00:05:21,120 --> 00:05:24,480 由内核当中的中断处理例程进行响应 126 00:05:24,520 --> 00:05:27,400 最后送到设备驱动 然后回到用户态 127 00:05:27,440 --> 00:05:30,720 这是进行I/O的一个过程 128 00:05:30,760 --> 00:05:32,080 这个过程实际上 129 00:05:32,120 --> 00:05:36,280 我们说到的第一种方式是阻塞I/O 130 00:05:36,320 --> 00:05:39,080 也就是说我发出请求到数据回来 131 00:05:39,120 --> 00:05:42,200 中间我进程是要处于等待状态的 132 00:05:42,240 --> 00:05:43,840 一直到有数据回来 133 00:05:43,880 --> 00:05:45,560 这种特征对应过来的 134 00:05:45,600 --> 00:05:47,520 收和发分别是这样的 135 00:05:47,560 --> 00:05:49,440 你在读数据的时候 136 00:05:49,480 --> 00:05:52,160 我把命令发出去之后 137 00:05:52,200 --> 00:05:53,760 进程进入等待状态 138 00:05:53,800 --> 00:05:56,960 一直到完成数据的读出 139 00:05:57,000 --> 00:06:00,600 而如果说是写数据 140 00:06:00,640 --> 00:06:04,120 那么是我发出写请求之后 141 00:06:04,160 --> 00:06:05,560 进程进入等待状态 142 00:06:05,600 --> 00:06:09,120 一直到设备完成数据的写入处理 143 00:06:09,160 --> 00:06:10,440 也就是说如果是磁盘的话 144 00:06:10,480 --> 00:06:12,960 它真实写到磁盘扇区了 145 00:06:13,000 --> 00:06:15,040 那这时候我才结束 146 00:06:15,080 --> 00:06:17,000 这个过程到我们这张图里 147 00:06:17,040 --> 00:06:18,760 就变成是用户的请求 148 00:06:18,800 --> 00:06:22,240 首先通过系统调用到设备驱动 149 00:06:22,280 --> 00:06:24,800 设备驱动会把用户请求转换成 150 00:06:24,840 --> 00:06:26,960 实际的硬件控制命令 151 00:06:27,000 --> 00:06:30,920 下来的时候是绕过中间的中断处理的 152 00:06:30,960 --> 00:06:32,800 中断只是在返回的时候有处理 153 00:06:32,840 --> 00:06:35,080 直接控制硬件进行相应的操作 154 00:06:35,120 --> 00:06:36,800 操作结束之后呢 155 00:06:36,840 --> 00:06:38,440 它会产生中断请求 156 00:06:38,480 --> 00:06:39,640 然后转到设备驱动 157 00:06:39,680 --> 00:06:42,760 最后通过系统调用的返回 到用户态 158 00:06:42,800 --> 00:06:45,360 用户得到相应的结果 159 00:06:45,400 --> 00:06:46,960 这是第一种方式 160 00:06:47,000 --> 00:06:48,480 在这种方式里头呢 161 00:06:48,520 --> 00:06:51,120 进程是需要等待的 162 00:06:51,160 --> 00:06:52,960 等待一定会读到数据 163 00:06:53,000 --> 00:06:55,920 或者一定会把数据完成写 164 00:06:55,960 --> 00:06:58,960 第二种方式是非阻塞I/O 165 00:06:59,000 --> 00:07:02,040 也就是说进程在执行的过程当中 166 00:07:02,080 --> 00:07:04,760 我把命令发出去之后我就不等待 167 00:07:04,800 --> 00:07:06,120 这种不等待呢 168 00:07:06,160 --> 00:07:09,000 转换过来是对于读写操作 169 00:07:09,040 --> 00:07:12,280 把系统调用发出命令之后立即返回 170 00:07:12,320 --> 00:07:16,080 返回的值是你进行成功传送的字节数 171 00:07:16,120 --> 00:07:19,920 读或者写的字节数 172 00:07:19,960 --> 00:07:21,440 这时候你在读写的过程当中呢 173 00:07:21,480 --> 00:07:24,720 有可能什么也没读到 什么也没写进去 174 00:07:24,760 --> 00:07:27,760 返回的值是零 这种情况呢 175 00:07:27,800 --> 00:07:29,960 我们还是在这张图里表示出来 176 00:07:30,000 --> 00:07:31,520 那就是写 177 00:07:31,560 --> 00:07:34,520 然后它就直接返回了 178 00:07:34,560 --> 00:07:35,760 这是第二种方式 179 00:07:35,800 --> 00:07:39,840 第二种方式 它可能读写不成功 180 00:07:39,880 --> 00:07:41,760 或者说读写的数据量 181 00:07:41,800 --> 00:07:45,080 跟我想写出去的数据量不一致 182 00:07:45,120 --> 00:07:49,200 于是我们就有了第三种方式 183 00:07:49,240 --> 00:07:50,880 异步I/O 184 00:07:50,920 --> 00:07:55,640 异步I/O是把阻塞和非阻塞两种方式结合起来 185 00:07:55,720 --> 00:07:57,800 它在读数据的时候是 186 00:07:57,840 --> 00:08:00,440 我把我要做的事情标记好 187 00:08:00,480 --> 00:08:02,320 把缓冲区设好 188 00:08:02,360 --> 00:08:05,040 那这样的话告诉内核然后它就返回了 189 00:08:05,080 --> 00:08:08,480 操作系统内核在完成相应的数据处理 190 00:08:08,520 --> 00:08:10,720 并且把读到的数据放到缓存区之后 191 00:08:10,760 --> 00:08:12,360 它会通知用户 192 00:08:12,400 --> 00:08:13,960 而写呢是反过来 193 00:08:14,000 --> 00:08:16,320 我标记好我的数据在什么地方 194 00:08:16,360 --> 00:08:19,840 操作系统完成写到实际的设备上之后 195 00:08:19,880 --> 00:08:20,960 它会通知用户 196 00:08:21,000 --> 00:08:23,960 这个过程在这张图里表现出来是这样的 197 00:08:24,000 --> 00:08:26,480 我先通过系统调用 198 00:08:26,520 --> 00:08:29,240 把我要写的数据告诉设备驱动 199 00:08:29,280 --> 00:08:33,080 然后设备驱动控制硬件设备进行操作 200 00:08:33,120 --> 00:08:34,840 控制完成之后 201 00:08:34,880 --> 00:08:37,600 我不会去等它结果我直接就返回了 202 00:08:37,640 --> 00:08:40,800 而设备操作完成之后它会通过中断 203 00:08:40,840 --> 00:08:43,000 这时候返回出相应的结果 204 00:08:43,040 --> 00:08:44,640 在这个阶段 205 00:08:44,680 --> 00:08:46,560 驱动里头是会需要等待的 206 00:08:46,600 --> 00:08:48,480 而我的应用程序这一段 207 00:08:48,520 --> 00:08:50,400 是可以干别的事情的 208 00:08:50,440 --> 00:08:52,640 209 00:08:52,720 --> 00:08:57,440 这是我们说到的三种I/O操作的方式 210 00:08:57,480 --> 00:08:57,840 211 00:08:57,880 --> 00:08:58,560 212 00:08:58,600 --> 00:08:58,920 213 00:08:58,960 --> 00:08:59,000