0 00:00:00,000 --> 00:00:07,000 1 00:00:07,080 --> 00:00:08,400 下面我们来介绍 2 00:00:08,560 --> 00:00:11,240 进程的等待和退出 3 00:00:11,360 --> 00:00:13,200 等待和退出呢实际上 4 00:00:13,360 --> 00:00:16,000 是父子进程之间的一种交互 5 00:00:16,040 --> 00:00:19,760 完成子进程的资源回收 6 00:00:19,800 --> 00:00:22,440 那首先第一个是等待 7 00:00:22,480 --> 00:00:24,760 那个wait()这个系统调用呢 8 00:00:24,800 --> 00:00:28,200 可以用于父进程等待子进程的结束 9 00:00:28,240 --> 00:00:30,480 那具体说起来呢 10 00:00:30,520 --> 00:00:32,760 子进程通过exic() 11 00:00:32,800 --> 00:00:35,360 向父进程返回一个值 12 00:00:35,400 --> 00:00:38,560 父进程呢通过wait() 13 00:00:38,600 --> 00:00:41,320 接受并处理这个返回值 14 00:00:41,360 --> 00:00:43,320 那这时候就有一个问题说 15 00:00:43,360 --> 00:00:46,480 这两个到底谁先谁后 16 00:00:46,520 --> 00:00:48,560 wait父进程先等待 17 00:00:48,600 --> 00:00:50,680 还是子进程先做exit() 18 00:00:50,720 --> 00:00:52,240 这两种情况的不同呢 19 00:00:52,280 --> 00:00:53,960 会导致它下面的处理呢 20 00:00:54,000 --> 00:00:55,480 会有一些区别 21 00:00:55,520 --> 00:00:57,120 wait执行的调用呢 22 00:00:57,160 --> 00:00:58,480 在我们这里就是 23 00:00:58,520 --> 00:01:00,960 如果有子进程存活 24 00:01:01,000 --> 00:01:03,680 也就是说父进程创建的子进程 25 00:01:03,720 --> 00:01:04,800 还有子进程 26 00:01:04,840 --> 00:01:07,880 那这时候呢父进程进入等待状态 27 00:01:07,920 --> 00:01:11,720 等待子进程的返回结果 28 00:01:11,760 --> 00:01:13,360 好那等待的时候呢 29 00:01:13,400 --> 00:01:15,040 这就相当于是wait 30 00:01:15,080 --> 00:01:16,880 父进程先执行wait 31 00:01:16,920 --> 00:01:18,720 等到子进程执行的时候呢 32 00:01:18,760 --> 00:01:20,120 它执行exit () 33 00:01:20,160 --> 00:01:24,200 这是exit ()是在wait之后执行的 34 00:01:24,240 --> 00:01:25,280 好那么这时候呢 35 00:01:25,320 --> 00:01:29,440 子进程的exit ()退出唤醒父进程 36 00:01:29,480 --> 00:01:31,120 父进程呢就由等待状态 37 00:01:31,160 --> 00:01:32,600 回到就绪状态 38 00:01:32,640 --> 00:01:34,920 等它得到CPU的使用权 39 00:01:34,960 --> 00:01:36,120 可以执行的时候 40 00:01:36,160 --> 00:01:38,480 那么父进程就处理 41 00:01:38,520 --> 00:01:41,120 子进程的返回的这个返回值 42 00:01:41,200 --> 00:01:46,000 这是wait在前exit ()在后的情况 43 00:01:46,040 --> 00:01:48,520 如果不是这样那就有一种情况 44 00:01:48,560 --> 00:01:51,520 就是有僵尸子进程等待 45 00:01:51,560 --> 00:01:52,520 这指什么意思呢 46 00:01:52,560 --> 00:01:56,400 就是子进程先执行exit () 47 00:01:56,440 --> 00:01:58,200 那这时候说它返回一个值 48 00:01:58,240 --> 00:02:00,600 等待父进程的处理 49 00:02:00,640 --> 00:02:02,480 那这时候呢exit ()在前 50 00:02:02,520 --> 00:02:05,120 好在这个时候如果子进程 51 00:02:05,160 --> 00:02:07,360 还一直处在这个等待的状态 52 00:02:07,400 --> 00:02:09,320 在这里等待父进程的处理 53 00:02:09,360 --> 00:02:10,680 那么这时候呢 54 00:02:10,720 --> 00:02:13,040 父进程的wait就直接返回 55 00:02:13,080 --> 00:02:14,400 如果有多个的话 56 00:02:14,440 --> 00:02:18,760 就从其中一个返回它的值 57 00:02:18,800 --> 00:02:20,920 如果没有子进程在 58 00:02:20,960 --> 00:02:22,840 有可能我一个进程在执行的时候 59 00:02:22,880 --> 00:02:23,920 它执行了wait 60 00:02:23,960 --> 00:02:25,280 实际上它干脆就没有子进程 61 00:02:25,320 --> 00:02:28,120 或者说子进程已经结束了 62 00:02:28,160 --> 00:02:30,960 好那这时候呢它就直接返回 63 00:02:31,000 --> 00:02:33,520 这是呢wait的功能 64 00:02:33,560 --> 00:02:37,320 那接下来说exit 65 00:02:37,360 --> 00:02:39,680 在这里头呢它叫有序退出 66 00:02:39,720 --> 00:02:40,680 它指的是什么意思呢 67 00:02:40,720 --> 00:02:42,640 我执行这个exit 68 00:02:42,680 --> 00:02:43,840 这时候呢 69 00:02:43,880 --> 00:02:46,000 这个进程呢就会在这个 70 00:02:46,040 --> 00:02:46,960 系统调用里头 71 00:02:47,000 --> 00:02:49,000 就会进行资源的回收 72 00:02:49,040 --> 00:02:51,320 那具体的功能是这样的 73 00:02:51,360 --> 00:02:53,760 首先第一个是我们刚才说到的 74 00:02:53,800 --> 00:02:59,240 调用参数返回给父进程结果那exit 75 00:02:59,280 --> 00:03:01,920 然后父进程呢可以拿到它这个结果 76 00:03:01,960 --> 00:03:04,000 比如说我们在做一个编译 77 00:03:04,040 --> 00:03:05,520 你的编译是否成功 78 00:03:05,560 --> 00:03:09,320 这个编译成功之后呢我回复是0 79 00:03:09,360 --> 00:03:10,680 如果不成功我回复错误码 80 00:03:10,720 --> 00:03:12,360 这样的话我下面的 81 00:03:12,400 --> 00:03:15,640 调用编译的这个进程 82 00:03:15,680 --> 00:03:17,080 它依据你编译的结果 83 00:03:17,120 --> 00:03:18,960 来决定后续怎么做 84 00:03:19,000 --> 00:03:20,240 这是它第一项功能 85 00:03:20,280 --> 00:03:23,000 然后第二部分功能是资源回收 86 00:03:23,040 --> 00:03:24,280 你比如说我们占用的 87 00:03:24,320 --> 00:03:26,280 各种资源的回收 88 00:03:26,320 --> 00:03:28,640 占用的打开的文件 89 00:03:28,680 --> 00:03:31,320 分配的内存和创建的 90 00:03:31,360 --> 00:03:34,280 内核相关数据结构 91 00:03:34,320 --> 00:03:38,000 然后检查父进程是否还存活 92 00:03:38,040 --> 00:03:40,240 刚才我们说父进程会检查子进程 93 00:03:40,280 --> 00:03:41,800 子进程也会检查父进程 94 00:03:41,840 --> 00:03:44,080 子进程是执行exit的时候 95 00:03:44,120 --> 00:03:45,200 检查父进程 96 00:03:45,240 --> 00:03:47,200 父进程是在执行wait的时候 97 00:03:47,240 --> 00:03:48,800 检查子进程 98 00:03:48,840 --> 00:03:52,000 好如果这个子进程在exit的时候 99 00:03:52,040 --> 00:03:55,920 检查父进程如果父进程是存活的 100 00:03:55,960 --> 00:03:57,560 那么也就是这时候呢 101 00:03:57,600 --> 00:03:59,440 子进程就保留一个状态 102 00:03:59,480 --> 00:04:01,440 它自己进入僵尸状态 103 00:04:01,480 --> 00:04:04,280 也就是我们退出状态 104 00:04:04,320 --> 00:04:08,080 然后等待父进程对它结果做处理 105 00:04:08,120 --> 00:04:10,160 好如果说没有 106 00:04:10,200 --> 00:04:12,400 也就相当于创建完子进程之后 107 00:04:12,440 --> 00:04:14,640 父进程已经结束了 108 00:04:14,680 --> 00:04:16,640 那么这时候呢它就直接 109 00:04:16,680 --> 00:04:21,640 释放相应的数据结构并且进程结束 110 00:04:21,680 --> 00:04:22,920 好那在这儿呢 111 00:04:22,960 --> 00:04:26,200 同时这个检查完之后会清理 112 00:04:26,240 --> 00:04:30,800 所有的处于等待状态的僵尸进程 113 00:04:30,840 --> 00:04:32,040 好那么这样的话 114 00:04:32,080 --> 00:04:34,560 这个exit退出实际上这是 115 00:04:34,600 --> 00:04:36,480 把进程占用的所有的资源 116 00:04:36,520 --> 00:04:39,320 包括这里头申请了 117 00:04:39,360 --> 00:04:40,480 并没有显式释放的 118 00:04:40,520 --> 00:04:42,960 它都会给它释放掉 119 00:04:43,000 --> 00:04:45,640 这是退出 120 00:04:45,680 --> 00:04:47,880 我们先来看wait 121 00:04:47,920 --> 00:04:51,280 这是wait的实现 122 00:04:51,320 --> 00:04:59,760 那么我们先看有哪些函数会来调 123 00:04:59,800 --> 00:05:03,960 那各种平台上的sys_wait()会到这儿来 124 00:05:04,000 --> 00:05:05,680 然后它在这里头呢 125 00:05:05,720 --> 00:05:11,520 做的工作是在这里头do_exit() 126 00:05:11,560 --> 00:05:14,920 实际上是子进程的收尾工作 127 00:05:14,960 --> 00:05:17,600 然后把它放到队列里头去 128 00:05:17,640 --> 00:05:18,960 schedule()在这个地方 129 00:05:19,000 --> 00:05:20,480 是把它放到队列里头去 130 00:05:20,520 --> 00:05:26,640 好我们也可以通过它的流程图来看 131 00:05:26,680 --> 00:05:28,960 那在这个地方呢实际上就是说 132 00:05:29,000 --> 00:05:33,080 我们把当前进程改成了sleeping的状态 133 00:05:33,120 --> 00:05:36,440 这样的话它就变成阻塞状态了 134 00:05:36,480 --> 00:05:38,760 那也就是我们找到了wait()里头 135 00:05:38,800 --> 00:05:40,680 它是如何进到阻塞状态的 136 00:05:40,720 --> 00:05:42,800 在这儿呢你就可以看到相关的代码 137 00:05:42,840 --> 00:05:44,840 这是最明显的标志 138 00:05:44,880 --> 00:05:48,320 好接下来一个是do_exit() 139 00:05:48,360 --> 00:05:51,520 这是它的实现 140 00:05:51,560 --> 00:05:52,600 它实际上在这里呢 141 00:05:52,640 --> 00:05:56,040 就是保存在这里头呢 142 00:05:56,080 --> 00:05:59,680 是把它把这个进程给杀掉 143 00:05:59,720 --> 00:06:05,880 do_exit()把一个进程或者一个线程关掉 144 00:06:05,920 --> 00:06:08,360 那我们也可以通过 145 00:06:08,400 --> 00:06:12,080 它的调用关系图来看它的执行情况 146 00:06:12,120 --> 00:06:14,960 那也是各种平台上的exit() 147 00:06:15,000 --> 00:06:16,960 最后到这个地方来 148 00:06:17,000 --> 00:06:18,440 它在这里呢 149 00:06:18,480 --> 00:06:20,720 最后都是到这个地方 150 00:06:20,760 --> 00:06:25,400 来释放相关的这些数据结构 151 00:06:25,440 --> 00:06:28,160 这是它的相关的工作 152 00:06:28,200 --> 00:06:32,040 好我们仍然可以来看一下 153 00:06:32,080 --> 00:06:35,320 在这里它做的主要工作都是在干啥 154 00:06:35,360 --> 00:06:39,520 那在这儿呢前面的注释已经说了 155 00:06:39,560 --> 00:06:42,240 它把它地址空间页表 156 00:06:42,280 --> 00:06:46,200 存储的这一部分空间给释放掉 157 00:06:46,240 --> 00:06:50,640 然后把自己的状态改成叫僵尸状态 158 00:06:50,680 --> 00:06:53,160 同时唤醒它的父亲 159 00:06:53,200 --> 00:06:55,400 也就是说如果是wait的话 160 00:06:55,440 --> 00:06:57,040 在那边等着的那个 161 00:06:57,080 --> 00:07:00,680 我们也可以来看看它的流程图 162 00:07:00,720 --> 00:07:03,200 这是它最主要的工作 163 00:07:03,240 --> 00:07:06,200 是关于存储的那一部分的处理 164 00:07:06,240 --> 00:07:14,840 165 00:07:14,880 --> 00:07:16,680 好在这个地方呢 166 00:07:16,720 --> 00:07:18,840 最后这一步是最主要的 167 00:07:18,880 --> 00:07:22,440 我们需要去wakeup_proc()唤醒 168 00:07:22,480 --> 00:07:25,280 处于wait状态的父进程 169 00:07:25,320 --> 00:07:28,560 170 00:07:28,600 --> 00:07:31,320 好除此之外呢我们刚才讲到的 171 00:07:31,360 --> 00:07:36,160 这几个创建加载等待退出 172 00:07:36,200 --> 00:07:38,560 这四个系统调用之外 173 00:07:38,600 --> 00:07:39,960 我们对进程控制 174 00:07:40,000 --> 00:07:41,880 还有一些其它系统调用 175 00:07:41,920 --> 00:07:44,120 比如说优先级控制 176 00:07:44,160 --> 00:07:47,280 那在unix系统呢有一个nice系统调用 177 00:07:47,320 --> 00:07:51,240 它完成对进程的初始优先级的设置 178 00:07:51,280 --> 00:07:54,800 好在unix系统里头进程的优先级 179 00:07:54,840 --> 00:07:58,760 会随着执行时间的延续而衰减 180 00:07:58,800 --> 00:08:00,960 也就是说它的优先级是在不断变化的 181 00:08:01,000 --> 00:08:03,040 这是对优先级的控制 182 00:08:03,080 --> 00:08:04,600 然后还有一个呢 183 00:08:04,640 --> 00:08:06,840 是对调试的支持 184 00:08:06,880 --> 00:08:08,400 在我们目前ucore里头呢 185 00:08:08,440 --> 00:08:10,160 你没有办法在ucore内部 186 00:08:10,200 --> 00:08:12,320 调试你写的应用程序 187 00:08:12,360 --> 00:08:13,520 原因在于在我们这里 188 00:08:13,560 --> 00:08:15,360 这一部分支持还不好 189 00:08:15,400 --> 00:08:17,760 实际上在标准的操作系统里头呢 190 00:08:17,800 --> 00:08:20,200 unix里头它有叫ptrace 191 00:08:20,240 --> 00:08:21,400 这个系统调用呢 192 00:08:21,440 --> 00:08:24,240 完成一个进程对另一个进程的 193 00:08:24,280 --> 00:08:25,960 执行过程的控制 194 00:08:26,000 --> 00:08:27,920 这就有点像我们说的调试 195 00:08:27,960 --> 00:08:32,520 我让一个被调试进程继续执行 196 00:08:32,560 --> 00:08:34,240 在某处设置断点 197 00:08:34,280 --> 00:08:37,280 检查它的内存和寄存器的状态 198 00:08:37,320 --> 00:08:41,360 这是操作系统必须提供的一项功能 199 00:08:41,400 --> 00:08:43,160 然后还有就是定时 200 00:08:43,200 --> 00:08:45,480 我们前面说过sleep 201 00:08:45,520 --> 00:08:50,240 它可以让进程在指定的时间 202 00:08:50,280 --> 00:08:51,520 进入等待状态 203 00:08:51,560 --> 00:08:53,560 然后时间到了它进入就绪状态 204 00:08:53,600 --> 00:08:57,600 然后继续执行这是定时的功能 205 00:08:57,640 --> 00:08:59,480 好到这个地方为止呢 206 00:08:59,520 --> 00:09:00,760 我们就说清楚了 207 00:09:00,800 --> 00:09:04,680 进程控制的几个相关系统调用 208 00:09:04,720 --> 00:09:08,320 它们的实现和它们的功能 209 00:09:08,360 --> 00:09:11,000 那把这些控制功能和我们前面 210 00:09:11,040 --> 00:09:15,000 讲到的进程状态图进行一个联系 211 00:09:15,040 --> 00:09:16,840 我们会看到这样一种情况 212 00:09:16,880 --> 00:09:19,320 这是我们前面讲到过的 213 00:09:19,360 --> 00:09:22,840 三状态进程模型里头 214 00:09:22,880 --> 00:09:25,720 那核心的就绪运行等待 215 00:09:25,760 --> 00:09:28,800 再加上创建和退出这五个状态 216 00:09:28,840 --> 00:09:30,320 我们刚才执行的 217 00:09:30,360 --> 00:09:33,920 说到那几个进程控制的系统调用呢 218 00:09:33,960 --> 00:09:35,880 对这个状态呢都是会有影响的 219 00:09:35,920 --> 00:09:38,760 我们看看它的影响在什么地方 220 00:09:38,800 --> 00:09:41,920 首先是fork fork会有啥影响 221 00:09:41,960 --> 00:09:45,000 fork会导致一个新的进程的创建 222 00:09:45,040 --> 00:09:47,760 好创建在那里准备工作做完 223 00:09:47,800 --> 00:09:49,800 fork准备好了放在就绪队列里头 224 00:09:49,840 --> 00:09:52,080 的时候它就变成就绪了 225 00:09:52,120 --> 00:09:54,840 好然后再有一个是wait 226 00:09:54,880 --> 00:09:57,320 wait会什么 父进程执行wait 227 00:09:57,360 --> 00:09:59,680 会导致父进程由运行状态 228 00:09:59,720 --> 00:10:02,200 进入等待状态 229 00:10:02,240 --> 00:10:07,160 好然后再有一个是exit 退出 230 00:10:07,200 --> 00:10:09,360 退出会导致当前进程 231 00:10:09,400 --> 00:10:12,000 由运行进入退出状态 232 00:10:12,040 --> 00:10:15,160 同时子进程的exit 233 00:10:15,200 --> 00:10:19,520 会导致由于wait进入等待状态的 234 00:10:19,560 --> 00:10:23,280 父进程变成事件发生触发 235 00:10:23,320 --> 00:10:25,040 导致它进入就绪状态 236 00:10:25,080 --> 00:10:27,600 以便于它能再进行执行的时候 237 00:10:27,640 --> 00:10:33,400 对子进程的返回的结果进行处理 238 00:10:33,440 --> 00:10:38,080 那这时候我们还有一个啥没说加载 239 00:10:38,120 --> 00:10:40,600 这个系统调用它在什么地方 240 00:10:40,640 --> 00:10:43,400 实际上加载是你在执行过程当中的 241 00:10:43,440 --> 00:10:44,840 一种状态 242 00:10:44,880 --> 00:10:46,080 准确的来说 243 00:10:46,120 --> 00:10:47,240 那大家可以通过代码 244 00:10:47,280 --> 00:10:48,240 去阅读看看这里头 245 00:10:48,280 --> 00:10:53,360 我在加载可执行文件的过程当中 246 00:10:53,400 --> 00:10:57,000 它还有相应的状态变化 247 00:10:57,040 --> 00:10:58,640 好那到这个地方呢 248 00:10:58,680 --> 00:11:00,120 我们就说清楚了 249 00:11:00,160 --> 00:11:02,680 操作系统当中的进程控制 250 00:11:02,720 --> 00:11:05,000 那从进程的切换 251 00:11:05,040 --> 00:11:07,240 再到跟进程控制相关的 252 00:11:07,280 --> 00:11:10,680 创建加载等待退出 253 00:11:10,840 --> 00:11:12,920 这样主要的这几个系统调用 254 00:11:12,960 --> 00:11:15,560 那这是呢操作系统提供给 255 00:11:15,600 --> 00:11:17,920 应用程序用的最频繁的 256 00:11:17,960 --> 00:11:19,840 最主要的几个功能 257 00:11:19,880 --> 00:11:20,920 258 00:11:20,960 --> 00:11:23,360 好今天的课就上到这里下课 259 00:11:23,440 --> 00:11:23,960 260 00:11:24,000 --> 00:11:24,040 261 00:11:24,080 --> 00:11:24,120