0 00:00:00,000 --> 00:00:07,160 1 00:00:07,200 --> 00:00:09,040 下面我们来讨论 2 00:00:09,080 --> 00:00:11,680 进程通讯机制的具体实现 3 00:00:11,720 --> 00:00:17,360 首先我们讨论的是信号和管道 4 00:00:17,400 --> 00:00:20,200 信号和管道是操作系统 5 00:00:20,240 --> 00:00:25,200 提供的两种简单的通讯机制 6 00:00:25,240 --> 00:00:27,480 首先是信号 7 00:00:27,520 --> 00:00:33,400 信号是进程间软件中断通知和处理机制 8 00:00:33,440 --> 00:00:36,120 我们在前边讲过中断 9 00:00:36,160 --> 00:00:37,880 它实际上是我们CPU 10 00:00:37,920 --> 00:00:39,720 在执行指令的时候的 11 00:00:39,760 --> 00:00:41,880 一种异常处理机制 12 00:00:41,920 --> 00:00:46,120 这种机制借鉴到进程当中来 13 00:00:46,160 --> 00:00:47,760 就是我们这里信号 14 00:00:47,800 --> 00:00:49,440 进程在执行的过程当中 15 00:00:49,480 --> 00:00:52,440 它有正常的执行流程 16 00:00:52,480 --> 00:00:54,360 如果说这时候我有意外的事件 17 00:00:54,400 --> 00:00:56,080 我怎么来处理呢 18 00:00:56,120 --> 00:00:58,720 并且是每一个进程 19 00:00:58,760 --> 00:01:00,200 有不同的处理的时候 20 00:01:00,240 --> 00:01:01,440 这时候我怎么办 21 00:01:01,480 --> 00:01:03,120 这就是我们这里的信号 22 00:01:03,160 --> 00:01:05,440 我们在实际系统当中见到的 23 00:01:05,480 --> 00:01:07,480 一个例子就是Ctrl-C 24 00:01:07,520 --> 00:01:10,440 我们在一个进程执行过程当中 25 00:01:10,480 --> 00:01:14,160 我们按Ctrl-C可以把这个进程停下来 26 00:01:14,200 --> 00:01:16,600 但是在你的实际代码当中 27 00:01:16,640 --> 00:01:20,680 并没有一段代码是完成对Ctrl-C的处理 28 00:01:20,720 --> 00:01:23,280 这个处理是在哪实现的呢 29 00:01:23,320 --> 00:01:25,840 这就是我们这里的信号 30 00:01:25,880 --> 00:01:28,600 操作系统在编译你的应用程序的时候 31 00:01:28,640 --> 00:01:30,200 它会缺省的加上 32 00:01:30,240 --> 00:01:33,040 对这些信号的处理例程 33 00:01:33,080 --> 00:01:34,480 如果说用户想 34 00:01:34,520 --> 00:01:36,760 指定自己的信号处理例程 35 00:01:36,800 --> 00:01:39,240 就需要在你的应用程序当中 36 00:01:39,280 --> 00:01:40,840 给出相应的实现 37 00:01:40,880 --> 00:01:42,280 比如说我们在这里看到的 38 00:01:42,320 --> 00:01:45,000 kill、stop、continue 39 00:01:45,040 --> 00:01:47,960 就是我们这里的几个常见的信号 40 00:01:48,000 --> 00:01:51,120 信号的处理机制有这样几种情况 41 00:01:51,160 --> 00:01:52,480 一种是捕获 42 00:01:52,520 --> 00:01:56,040 也就相当于进程执行的时候 43 00:01:56,080 --> 00:02:00,400 信号的处理例程 是由用户指定的 44 00:02:00,440 --> 00:02:05,120 每一个进程有自己不同的处理方法 45 00:02:05,160 --> 00:02:08,480 忽视或者叫忽略 那这时候呢 46 00:02:08,520 --> 00:02:11,960 是由操作系统的缺省处理例程 47 00:02:12,000 --> 00:02:14,160 来处理进程的信号 48 00:02:14,200 --> 00:02:19,120 比如说我们进程终止和进程挂起 49 00:02:19,160 --> 00:02:21,400 还有一种是屏蔽 50 00:02:21,440 --> 00:02:25,200 禁止进程接收和处理信号 51 00:02:25,240 --> 00:02:29,000 这种情况的一个实例是我们的登录程序 52 00:02:29,040 --> 00:02:31,320 你按Ctrl-C它是不能停下来的 53 00:02:31,360 --> 00:02:32,520 原因在于这时候 54 00:02:32,560 --> 00:02:34,760 把相应的处理给屏蔽掉了 55 00:02:34,800 --> 00:02:36,080 这种通讯机制呢 56 00:02:36,120 --> 00:02:41,560 它能够传送的信息仅仅是信号的类型 57 00:02:41,600 --> 00:02:43,560 它不能再传送其他的内容 58 00:02:43,600 --> 00:02:46,920 所以它传送的信息量是很小的 59 00:02:46,960 --> 00:02:48,640 这种做法它仅仅是用来 60 00:02:48,680 --> 00:02:51,920 做一种快速的响应机制 61 00:02:51,960 --> 00:02:56,960 它比别的通讯机制要快 62 00:02:57,000 --> 00:02:59,680 信号它的实现是如何进行的呢 63 00:02:59,720 --> 00:03:02,240 我们在这给出一个简单的图示 64 00:03:02,280 --> 00:03:04,000 在我们的ucore plus当中 65 00:03:04,040 --> 00:03:05,440 也有相应的实现 66 00:03:05,480 --> 00:03:06,480 有兴趣同学可以 67 00:03:06,520 --> 00:03:09,720 进一步去看相应的实现机制 68 00:03:09,760 --> 00:03:13,920 首先进程在启动的时候 69 00:03:13,960 --> 00:03:18,560 它需要注册相应的信号处理例程 70 00:03:18,600 --> 00:03:19,840 给操作系统内核 71 00:03:19,880 --> 00:03:22,960 以便于操作系统内核 72 00:03:23,000 --> 00:03:26,040 在有相应的信号送过来的时候 73 00:03:26,080 --> 00:03:30,520 它能够知道去执行哪一个处理函数 74 00:03:30,560 --> 00:03:35,600 这是注册 然后是有其他进程 75 00:03:35,640 --> 00:03:39,720 或者说其他设备发出信号的时候 76 00:03:39,760 --> 00:03:41,360 那么操作系统内核 77 00:03:41,400 --> 00:03:44,680 负责把这个信号送给指定的进程 78 00:03:44,720 --> 00:03:49,880 并且启动其中的信号处理函数 79 00:03:49,920 --> 00:03:52,080 然后执行信号处理函数的时候 80 00:03:52,120 --> 00:03:53,440 完成相应的处理 81 00:03:53,480 --> 00:03:56,960 比如说我把当前正在执行的进程掐掉 82 00:03:57,000 --> 00:03:58,480 或者说把它暂停 83 00:03:58,520 --> 00:03:59,840 或者说忽视 84 00:03:59,880 --> 00:04:01,360 都是由这来完成的 85 00:04:01,400 --> 00:04:04,960 接下来我们给出一个实际的例子 86 00:04:05,000 --> 00:04:07,800 在这个例子当中我们可以 87 00:04:07,840 --> 00:04:11,800 在linux里面来执行这一段代码 88 00:04:11,840 --> 00:04:12,800 它的主程序里头 89 00:04:12,840 --> 00:04:14,600 就是signal 实际上是 90 00:04:14,640 --> 00:04:19,280 我们的注册信号处理例程的系统调用 91 00:04:19,320 --> 00:04:21,880 你注册了两个信号 92 00:04:21,920 --> 00:04:24,600 它的处理函数到底是什么 93 00:04:24,640 --> 00:04:26,080 然后我这个进程 94 00:04:26,120 --> 00:04:27,640 就进到死循环里头了 95 00:04:27,680 --> 00:04:32,960 前边是它的实现 signal_int 96 00:04:33,000 --> 00:04:36,080 它完成的功能是打印字符串 97 00:04:36,120 --> 00:04:38,320 告诉你按了Ctrl-C 98 00:04:38,360 --> 00:04:39,800 然后就忽略过去了 99 00:04:39,840 --> 00:04:42,040 在前边加上这个重新注册 100 00:04:42,080 --> 00:04:44,760 是为了兼容性的缘故 101 00:04:44,800 --> 00:04:46,960 另外一个是退出 102 00:04:47,000 --> 00:04:49,480 如果你按Ctrl/这时候 103 00:04:49,520 --> 00:04:51,280 你的程序会结束下来 104 00:04:51,320 --> 00:04:53,200 因为如果没有这个的话 105 00:04:53,240 --> 00:04:55,800 你这个程序没有正常退出的时候 106 00:04:55,840 --> 00:04:57,200 因为我们在这是个死循环 107 00:04:57,240 --> 00:04:58,560 它就永远不会退出了 108 00:04:58,600 --> 00:05:03,400 我们按Ctrl/来实现原来Ctrl-C的功能 109 00:05:03,440 --> 00:05:05,840 这地方给出来你实现的退出 110 00:05:05,880 --> 00:05:08,440 这个程序执行起来之后的效果 111 00:05:08,480 --> 00:05:12,600 就是按Ctrl-C它给出一个提示 112 00:05:12,640 --> 00:05:14,640 然后程序继续执行 113 00:05:14,680 --> 00:05:18,800 按Ctrl/这时候程序退出 114 00:05:18,840 --> 00:05:24,320 这是信号使用的一个示例 115 00:05:24,360 --> 00:05:26,560 接下来我们说管道 116 00:05:26,600 --> 00:05:28,560 管道是进程间 117 00:05:28,600 --> 00:05:31,520 基于内存文件的通讯机制 118 00:05:31,560 --> 00:05:34,000 也就是说两个进程想通讯 119 00:05:34,040 --> 00:05:35,680 它中间的数据放在哪呢 120 00:05:35,720 --> 00:05:37,520 在内存里建一个临时文件 121 00:05:37,560 --> 00:05:38,720 把这个数据放这里头 122 00:05:38,760 --> 00:05:40,280 这就是我们这里的管道 123 00:05:40,320 --> 00:05:44,120 它利用父进程创建子进程的过程当中 124 00:05:44,160 --> 00:05:46,240 继承文件描述符 125 00:05:46,280 --> 00:05:48,000 这几个缺省的文件描述符 126 00:05:48,040 --> 00:05:51,280 在子进程里都是有继承的 127 00:05:51,320 --> 00:05:54,600 标准输入 标准输出和标准的错误输出 128 00:05:54,640 --> 00:05:56,120 那我们在这里头呢 129 00:05:56,160 --> 00:05:58,240 创建一个管道的时候 130 00:05:58,280 --> 00:06:01,640 我只关心我通讯的管道是谁 131 00:06:01,680 --> 00:06:04,320 并不关心另一头是谁往里放的数据 132 00:06:04,360 --> 00:06:05,400 这实际上是我们这里头说到的 133 00:06:05,440 --> 00:06:08,280 一种间接通讯机制 134 00:06:08,320 --> 00:06:11,480 这个数据可能来自于键盘 135 00:06:11,520 --> 00:06:13,840 也可能来自于其他程序 136 00:06:13,880 --> 00:06:15,800 或者说文件 137 00:06:15,840 --> 00:06:18,400 写出去的时候我可能会写到终端 138 00:06:18,440 --> 00:06:19,720 也可能会写到文件 139 00:06:19,760 --> 00:06:23,520 或者说给其他的进程 140 00:06:23,560 --> 00:06:24,920 那我们在这 141 00:06:24,960 --> 00:06:28,040 跟它相关的系统调用有这样几个 142 00:06:28,080 --> 00:06:30,680 一个是读管道 143 00:06:30,720 --> 00:06:32,040 因为我们创建的管道 144 00:06:32,080 --> 00:06:35,320 在这里有一个FD文件描述符 145 00:06:35,360 --> 00:06:36,400 创建完之后 146 00:06:36,440 --> 00:06:38,480 我就可以利用它来进行读了 147 00:06:38,520 --> 00:06:42,840 我们在实际的一个库函数scanf() 148 00:06:42,880 --> 00:06:46,840 就是基于管道的读来实现的 149 00:06:46,880 --> 00:06:48,800 另一个是写write 150 00:06:48,840 --> 00:06:51,000 这和我们通常的文件读写的 151 00:06:51,040 --> 00:06:53,360 系统调用是完全一样的 152 00:06:53,400 --> 00:06:55,240 我们上边的printf() 153 00:06:55,280 --> 00:06:58,040 也是基于对管道的写来实现的 154 00:06:58,080 --> 00:07:01,880 它把它直接送到屏幕上去 155 00:07:01,920 --> 00:07:03,880 再有一个是管道的创建 156 00:07:03,920 --> 00:07:05,520 这是一个单独的pipe 157 00:07:05,560 --> 00:07:06,960 创建的结果会生成 158 00:07:07,000 --> 00:07:09,040 一个文件描述符数组 159 00:07:09,080 --> 00:07:10,640 这个数组有两个成员 160 00:07:10,680 --> 00:07:12,280 一个是读文件描述符 161 00:07:12,320 --> 00:07:14,080 一个是写文件描述符 162 00:07:14,120 --> 00:07:15,960 我们就是利用继承的关系 163 00:07:16,000 --> 00:07:17,560 在两个不同的进程当中 164 00:07:17,600 --> 00:07:19,600 使用不同的文件描述符 165 00:07:19,640 --> 00:07:21,120 一头读 一头写 166 00:07:21,160 --> 00:07:24,400 那就实现了两者之间的通讯了 167 00:07:24,440 --> 00:07:26,600 我们也是给一个例子 168 00:07:26,640 --> 00:07:28,040 这是ls和more 169 00:07:28,080 --> 00:07:30,400 这是两个系统命令 170 00:07:30,440 --> 00:07:32,960 在这两个命令里头呢 中间加一个竖线 171 00:07:33,000 --> 00:07:34,960 实际上你就由shell给它们俩之间 172 00:07:35,000 --> 00:07:36,800 建立一条管道 173 00:07:36,840 --> 00:07:38,400 我们可以用下面的图示 174 00:07:38,440 --> 00:07:40,400 来说明这个过程 175 00:07:40,440 --> 00:07:42,640 首先在执行这个命令的时候 176 00:07:42,680 --> 00:07:44,760 shell程序解释这条命令 177 00:07:44,800 --> 00:07:47,360 它首先会去解释这个管道 178 00:07:47,400 --> 00:07:49,320 然后建立相应的管道 179 00:07:49,360 --> 00:07:51,560 然后前边有一个ls 180 00:07:51,600 --> 00:07:55,440 它创建一个进程 来执行ls 181 00:07:55,480 --> 00:07:57,880 并且把它的标准输出 182 00:07:57,920 --> 00:07:59,360 接到这个管道上 183 00:07:59,400 --> 00:08:01,960 作为输入的这一端 184 00:08:02,000 --> 00:08:05,320 然后它会创建另外一个进程 185 00:08:05,360 --> 00:08:07,320 执行这个more这个命令 186 00:08:07,360 --> 00:08:10,480 然后它的输入来源于这个管道 187 00:08:10,520 --> 00:08:12,680 这样的话就把两个命令的一个的输入 188 00:08:12,720 --> 00:08:15,560 和另一个的输出接到了一起 189 00:08:15,600 --> 00:08:18,720 这个在我们的Linux和Unix系统里头 190 00:08:18,760 --> 00:08:20,760 是比较常见的一种做法 191 00:08:20,800 --> 00:08:22,640 这是管道 192 00:08:22,720 --> 00:08:24,560 193 00:08:24,600 --> 00:08:24,640