0 00:00:00,000 --> 00:00:15,480 1 00:00:15,520 --> 00:00:18,520 下面我们来介绍进程控制 2 00:00:18,560 --> 00:00:19,800 在进程控制当中呢 3 00:00:19,840 --> 00:00:21,760 我们会涉及这样几个问题 4 00:00:21,920 --> 00:00:24,720 一个是在内核当中的进程切换 5 00:00:24,760 --> 00:00:26,120 也就是说我一个进程 6 00:00:26,160 --> 00:00:27,440 在运行过程当中 7 00:00:27,480 --> 00:00:30,920 内核如何来实现它从一个进程 8 00:00:30,960 --> 00:00:34,040 到另一个进程的切换 9 00:00:34,080 --> 00:00:35,080 后面的三个呢 10 00:00:35,120 --> 00:00:38,520 实际上为用户提供的系统调用服务 11 00:00:38,560 --> 00:00:43,000 那是用户在执行它的应用的过程当中 12 00:00:43,040 --> 00:00:45,640 它有需求要创建一个新的进程 13 00:00:45,680 --> 00:00:46,760 好在这里头呢 14 00:00:46,800 --> 00:00:49,400 我如何来创建一个新的进程 15 00:00:49,440 --> 00:00:52,160 在这里头运行一个新的程序 16 00:00:52,200 --> 00:00:53,720 那这个是进程加载 17 00:00:53,760 --> 00:00:55,400 父进程创建子进程之后 18 00:00:55,440 --> 00:00:56,160 它们俩之间呢 19 00:00:56,200 --> 00:00:57,480 需要有一些协调的关系 20 00:00:57,520 --> 00:00:59,600 你比如说子进程结束之后 21 00:00:59,640 --> 00:01:02,600 父进程负责回收它所占用的资源 22 00:01:02,640 --> 00:01:03,400 那这个时候 23 00:01:03,440 --> 00:01:05,320 它们之间有一个通讯关系 24 00:01:05,360 --> 00:01:08,360 这就是进程的等待和退出 25 00:01:08,400 --> 00:01:11,560 下面我们一个一个的来 26 00:01:11,600 --> 00:01:13,640 首先是进程切换 27 00:01:13,680 --> 00:01:15,080 进程切换呢 28 00:01:15,120 --> 00:01:17,240 有时候也称之为叫上下文切换 29 00:01:17,280 --> 00:01:20,200 它是暂停当前运行进程 30 00:01:20,240 --> 00:01:21,160 把这个进程呢 31 00:01:21,200 --> 00:01:24,040 从运行状态变成其它状态 32 00:01:24,080 --> 00:01:25,640 这里所谓的其它状态呢 33 00:01:25,680 --> 00:01:28,760 可能会是由于进行IO操作 34 00:01:28,800 --> 00:01:31,160 或者等待事件而进入等待状态 35 00:01:31,200 --> 00:01:33,200 也可能是由于被抢先 36 00:01:33,240 --> 00:01:36,360 又或者说时间片运行完 37 00:01:36,400 --> 00:01:38,320 那转回到就绪状态 38 00:01:38,360 --> 00:01:39,640 另一个是说 39 00:01:39,680 --> 00:01:41,240 我把当前的进程停下来之后 40 00:01:41,280 --> 00:01:44,840 那我会调度另一个进程 41 00:01:44,880 --> 00:01:48,680 让它从就绪状态变成运行状态 42 00:01:48,720 --> 00:01:50,080 也就是说我从就绪队列里 43 00:01:50,120 --> 00:01:53,560 找一个新的进程然后恢复 44 00:01:53,600 --> 00:01:55,400 并且让它继续运行 45 00:01:55,440 --> 00:01:58,800 这是进程切换所要做的事情 46 00:01:58,840 --> 00:02:00,320 好那具体说起来 47 00:02:00,360 --> 00:02:01,600 有一些切换的时候 48 00:02:01,640 --> 00:02:02,840 我们有一些什么要求呢 49 00:02:02,880 --> 00:02:04,200 那在切换之前 50 00:02:04,240 --> 00:02:06,960 需要去保存进程的上下文 51 00:02:07,000 --> 00:02:07,760 也就是说 52 00:02:07,800 --> 00:02:09,360 你下一个进程在执行过程当中 53 00:02:09,400 --> 00:02:12,440 需要用到这些CPU的寄存器 54 00:02:12,480 --> 00:02:13,520 状态寄存器 55 00:02:13,560 --> 00:02:16,680 好那这些呢你需要先做保存 56 00:02:16,720 --> 00:02:19,440 好切换到下一个进程运行的时候 57 00:02:19,480 --> 00:02:21,080 在切换过来之后 58 00:02:21,120 --> 00:02:23,000 下一个进程开始执行之前 59 00:02:23,040 --> 00:02:25,120 我还要继续把它上一次 60 00:02:25,160 --> 00:02:26,760 暂停时候的当时的 61 00:02:26,800 --> 00:02:29,040 寄存器的状态恢复回来 62 00:02:29,080 --> 00:02:30,120 好恢复回来之后 63 00:02:30,160 --> 00:02:31,240 那它才能够从 64 00:02:31,280 --> 00:02:34,520 上一次暂停的位置继续往下执行 65 00:02:34,560 --> 00:02:35,840 还有一个要求是说 66 00:02:35,880 --> 00:02:38,960 由于我们现在计算机系统里 67 00:02:39,000 --> 00:02:41,360 进程的切换是非常频繁的 68 00:02:41,400 --> 00:02:44,040 通常情况下是10毫秒左右 69 00:02:44,080 --> 00:02:45,800 就会有一次进程切换 70 00:02:45,840 --> 00:02:46,840 那这样以来的话 71 00:02:46,880 --> 00:02:49,040 为了保证系统运行的效率 72 00:02:49,080 --> 00:02:52,080 这个切换的速度必须非常快 73 00:02:52,120 --> 00:02:53,120 所以通常情况下 74 00:02:53,160 --> 00:02:55,560 它都是由汇编来实现的 75 00:02:55,600 --> 00:02:58,760 那具体说起来我们在这里 76 00:02:58,800 --> 00:03:00,680 要保存一些什么样的信息呢 77 00:03:00,720 --> 00:03:03,000 实际上保存就是进程在整个 78 00:03:03,040 --> 00:03:06,880 生命周期当中它所维护的信息 79 00:03:06,920 --> 00:03:07,880 大致说起来呢 80 00:03:07,920 --> 00:03:09,080 包括这样几个方面 81 00:03:09,120 --> 00:03:10,280 寄存器 82 00:03:10,320 --> 00:03:12,560 那我们在CPU里头通用寄存器 83 00:03:12,600 --> 00:03:15,280 好CPU里头状态信息 84 00:03:15,320 --> 00:03:18,040 然后就内存地址空间里的信息 85 00:03:18,080 --> 00:03:19,280 那你说通常情况下我们 86 00:03:19,320 --> 00:03:20,240 在切换的时候 87 00:03:20,280 --> 00:03:22,440 内存里头的这些信息 88 00:03:22,480 --> 00:03:24,360 是不会被另外一个进程 89 00:03:24,400 --> 00:03:26,000 因为它各占一段区域 90 00:03:26,040 --> 00:03:31,280 是不会被另一个进程所替代的 91 00:03:31,320 --> 00:03:32,720 所以在这里头呢 92 00:03:32,760 --> 00:03:33,800 那内存里的地址 93 00:03:33,840 --> 00:03:36,040 空间内容大部分不用保存 94 00:03:36,080 --> 00:03:37,680 下面我们通过一个图式 95 00:03:37,720 --> 00:03:40,480 来说明进程的切换过程 96 00:03:40,520 --> 00:03:41,280 那在这里头呢 97 00:03:41,320 --> 00:03:42,400 我们假定系统当中有 98 00:03:42,440 --> 00:03:46,240 P0和P1两个进程 99 00:03:46,280 --> 00:03:47,600 它们的执行过程当中呢 100 00:03:47,640 --> 00:03:50,440 先是P0处于用户态执行 101 00:03:50,480 --> 00:03:52,160 那执行过程当中 102 00:03:52,200 --> 00:03:54,520 P1处于空闲状态 103 00:03:54,560 --> 00:03:55,880 那可能会是就绪 104 00:03:55,920 --> 00:03:57,880 也可能会是等待 105 00:03:57,920 --> 00:04:01,480 好那在这里头呢 106 00:04:01,520 --> 00:04:02,520 执行的过程当中 107 00:04:02,560 --> 00:04:04,240 碰着一个系统调用 108 00:04:04,280 --> 00:04:05,120 或者是中断 109 00:04:05,160 --> 00:04:06,920 假定我们这是时钟中断 110 00:04:06,960 --> 00:04:08,360 那它的时间片用完了 111 00:04:08,400 --> 00:04:10,520 到这儿来之后切换到内核 112 00:04:10,560 --> 00:04:12,760 那切换过程当中 113 00:04:12,800 --> 00:04:15,400 它需要保护现场到PCB0 114 00:04:15,440 --> 00:04:18,200 也就进程零的进程控制块里头 115 00:04:18,240 --> 00:04:20,240 把它的当时执行到状态 116 00:04:20,280 --> 00:04:21,520 都保存下来 117 00:04:21,560 --> 00:04:23,000 保存下来之后那这时候说 118 00:04:23,040 --> 00:04:24,400 如果说时钟中断 119 00:04:24,440 --> 00:04:25,920 时间片用完 120 00:04:25,960 --> 00:04:27,640 那这时候我们需要选择 121 00:04:27,680 --> 00:04:28,760 下一个就绪进程 122 00:04:28,800 --> 00:04:29,960 假定这时候 123 00:04:30,000 --> 00:04:32,280 你选择下一个进程是P1 124 00:04:32,320 --> 00:04:36,000 好那到P1需要恢复P1现场 125 00:04:36,040 --> 00:04:37,040 恢复完之后 126 00:04:37,080 --> 00:04:37,960 那这时候呢 127 00:04:38,000 --> 00:04:40,520 状态就已经切到进程P1了 128 00:04:40,560 --> 00:04:44,440 好那这时候P1开始运行 129 00:04:44,480 --> 00:04:45,360 继续运行 130 00:04:45,400 --> 00:04:47,120 好运行到一段时间之后呢 131 00:04:47,160 --> 00:04:49,800 假定说它的时间片又用完了 132 00:04:49,840 --> 00:04:52,080 这时候又产生时钟中断 133 00:04:52,120 --> 00:04:54,640 时钟中断又切回到内核状态 134 00:04:54,680 --> 00:04:56,040 好那这时候它要保存 135 00:04:56,080 --> 00:04:58,720 进程P1的现场 136 00:04:58,760 --> 00:05:02,360 到它对应的进程控制块PCB1 137 00:05:02,400 --> 00:05:05,120 好那在这里头我们假定你又决策 138 00:05:05,160 --> 00:05:07,080 我需要选下一个进程 139 00:05:07,120 --> 00:05:09,000 这时候又选P0 140 00:05:09,040 --> 00:05:12,000 那这时候恢复P0的现场 141 00:05:12,040 --> 00:05:14,400 那这时候从PCB0里头 142 00:05:14,440 --> 00:05:16,120 这两个是对应的 143 00:05:16,160 --> 00:05:18,960 PCB0里头恢复现场 144 00:05:19,000 --> 00:05:20,560 好恢复进程状态之后 145 00:05:20,600 --> 00:05:22,880 它切到进程0 146 00:05:22,920 --> 00:05:24,840 然后继续执行 147 00:05:24,880 --> 00:05:25,960 到这个地方呢 148 00:05:26,000 --> 00:05:28,400 我们一个从进程零切换过去 149 00:05:28,440 --> 00:05:30,520 再切换回来一个完整的过程 150 00:05:30,560 --> 00:05:31,960 就在这儿展现出来了 151 00:05:32,000 --> 00:05:33,400 跟它相关的呢 152 00:05:33,440 --> 00:05:35,480 执行正常执行 153 00:05:35,520 --> 00:05:36,800 然后这个地方呢 154 00:05:36,840 --> 00:05:39,080 是保存现场到PCB0 155 00:05:39,120 --> 00:05:41,200 从PCB0里恢复现场 156 00:05:41,240 --> 00:05:43,040 好中间是PCB1 157 00:05:43,080 --> 00:05:47,160 这边恢复执行到保存 158 00:05:47,200 --> 00:05:48,880 那从这一过程来讲 159 00:05:48,920 --> 00:05:50,600 它是比较清楚了 160 00:05:50,640 --> 00:05:53,280 那这时候我们要在PCB里 161 00:05:53,320 --> 00:05:54,520 记住一些什么信息了 162 00:05:54,560 --> 00:05:58,360 记录进程的运行状态 163 00:05:58,400 --> 00:06:01,000 好那状态呢是放这里头 164 00:06:01,040 --> 00:06:02,640 然后我们把状态呢 165 00:06:02,680 --> 00:06:03,920 它相同的状态 166 00:06:03,960 --> 00:06:05,000 我放到一个队列里头 167 00:06:05,040 --> 00:06:06,960 这时候维护出若干个队列 168 00:06:07,000 --> 00:06:08,880 比如说就绪队列 169 00:06:08,920 --> 00:06:10,440 它有一个起头 170 00:06:10,480 --> 00:06:13,200 然后相应的进程串在一起 171 00:06:13,240 --> 00:06:14,680 那可以用各种各样的办法 172 00:06:14,720 --> 00:06:17,880 来形成这样一个队列 173 00:06:17,920 --> 00:06:19,400 好如果是等待呢 174 00:06:19,440 --> 00:06:20,480 我们就会把它分成 175 00:06:20,520 --> 00:06:22,720 若干种不同类型的等待 176 00:06:22,760 --> 00:06:24,280 比如说在这里头每个设备 177 00:06:24,320 --> 00:06:26,160 等待的设备不同 178 00:06:26,200 --> 00:06:27,640 那它有各自一个队列 179 00:06:27,680 --> 00:06:28,960 比如这里列出来的 180 00:06:29,000 --> 00:06:31,680 磁带01和磁盘0 181 00:06:31,720 --> 00:06:33,400 好然后还有一类呢 182 00:06:33,440 --> 00:06:35,440 就是处于这个 183 00:06:35,480 --> 00:06:37,840 运行到退出的状态的 184 00:06:37,880 --> 00:06:39,600 这时候它的收尾状态 185 00:06:39,640 --> 00:06:41,920 这时候僵尸队列那它对应的 186 00:06:41,960 --> 00:06:44,120 在这里头那处于这样一种状态 187 00:06:44,160 --> 00:06:45,520 这是进程块里头 188 00:06:45,560 --> 00:06:48,560 保存信息和它们如何来组织 189 00:06:48,600 --> 00:06:49,880 好那实际上接下来 190 00:06:49,920 --> 00:06:51,480 我们是说到底会有 191 00:06:51,520 --> 00:06:52,760 一些什么样信息保存到 192 00:06:52,800 --> 00:06:54,160 进程控制块里头呢 193 00:06:54,200 --> 00:06:55,400 那不同的系统里 194 00:06:55,440 --> 00:06:57,400 进程控制块呢是不一样的 195 00:06:57,440 --> 00:07:00,440 比如说在我们 ucore和ucore plus里头 196 00:07:00,480 --> 00:07:04,280 有一个数据结构叫PROC STRUCT 197 00:07:04,320 --> 00:07:05,960 好在这个数据结构里头呢 198 00:07:06,000 --> 00:07:08,720 它保存了进程的相关信息 199 00:07:08,760 --> 00:07:09,640 大致的信息 200 00:07:09,680 --> 00:07:10,960 我们可以把它分成这样几类 201 00:07:11,000 --> 00:07:14,720 一类是进程的标识信息 202 00:07:14,760 --> 00:07:16,400 比如说它的执行 203 00:07:16,440 --> 00:07:18,000 是哪一个可执行文件 204 00:07:18,040 --> 00:07:20,000 它的ID是多少 205 00:07:20,040 --> 00:07:21,400 它的进程ID 206 00:07:21,440 --> 00:07:23,600 好然后它的父进程是谁 207 00:07:23,640 --> 00:07:25,200 好这是一类 208 00:07:25,240 --> 00:07:27,960 然后再一类是进程的状态信息 209 00:07:28,000 --> 00:07:30,240 比如说我们在这里头CPU里头 210 00:07:30,280 --> 00:07:32,320 状态寄存器的相关信息 211 00:07:32,360 --> 00:07:36,160 然后地址空间的起头的位置 212 00:07:36,200 --> 00:07:39,080 第一级页表的起始地址在哪 213 00:07:39,120 --> 00:07:41,400 然后进程的状态和它 214 00:07:41,440 --> 00:07:43,440 是否允许调度等这样一些 215 00:07:43,480 --> 00:07:45,880 执行状态的信息 216 00:07:45,920 --> 00:07:49,040 还有一类呢是进程所占用的资源 217 00:07:49,080 --> 00:07:50,920 比如说它占用的存储资源 218 00:07:50,960 --> 00:07:52,160 那所有分配给 219 00:07:52,200 --> 00:07:53,320 它存储那组织成 220 00:07:53,360 --> 00:07:56,560 相关的数据结构MM 221 00:07:56,600 --> 00:07:58,200 好那上边那个是 222 00:07:58,240 --> 00:08:00,960 它占用的内核堆栈 223 00:08:01,000 --> 00:08:01,960 还有一类呢 224 00:08:02,000 --> 00:08:04,320 是我们保护现场用的 225 00:08:04,360 --> 00:08:07,000 那在中断和进程切换的时候 226 00:08:07,040 --> 00:08:08,360 它都需要保护现场 227 00:08:08,400 --> 00:08:09,920 那这些现场的内容呢 228 00:08:09,960 --> 00:08:11,120 我们刚才说是保存到 229 00:08:11,160 --> 00:08:12,320 进程控制块里头 230 00:08:12,360 --> 00:08:14,160 实际上就是指这个地方 231 00:08:14,200 --> 00:08:16,360 好你两个进程需要复用的部分 232 00:08:16,400 --> 00:08:18,000 就需要在这儿做保存 233 00:08:18,040 --> 00:08:19,040 除此之外呢 234 00:08:19,080 --> 00:08:20,040 这些进程它在 235 00:08:20,080 --> 00:08:22,000 不同的时候处于不同状态 236 00:08:22,040 --> 00:08:24,040 这些状态组成不同队列 237 00:08:24,080 --> 00:08:25,240 那在这里还有几个 238 00:08:25,280 --> 00:08:27,200 相关的指针结构 239 00:08:27,240 --> 00:08:29,200 用于描述我当前进程 240 00:08:29,240 --> 00:08:32,400 到底是在哪一个队列当中 241 00:08:32,440 --> 00:08:34,280 好有了这些信息之后 242 00:08:34,320 --> 00:08:36,280 那我们这个操作系统 243 00:08:36,320 --> 00:08:38,640 就对进程的执行状态呢 244 00:08:38,680 --> 00:08:40,440 有了一个准确的把握了 245 00:08:40,480 --> 00:08:41,680 好我们在ucore里用到 246 00:08:41,720 --> 00:08:43,520 还是比较简单的 247 00:08:43,560 --> 00:08:46,000 对于实际的像Linux windows 248 00:08:46,040 --> 00:08:47,240 那么这时候它的数据结构 249 00:08:47,280 --> 00:08:49,720 比我们要多得多 250 00:08:49,760 --> 00:08:53,440 这是进程控制块的数据结构 251 00:08:53,480 --> 00:08:56,440 在ucore里的完整定义 252 00:08:56,480 --> 00:08:58,320 在我们这里头呢 253 00:08:58,360 --> 00:09:00,120 大家需要注意到的是 254 00:09:00,160 --> 00:09:01,760 我们刚开始说的 255 00:09:01,800 --> 00:09:03,840 这是进程的状态 256 00:09:03,880 --> 00:09:06,360 进程的ID信息 257 00:09:06,400 --> 00:09:10,600 进程ID 线程ID 组ID 258 00:09:10,640 --> 00:09:17,040 然后是进程执行的相关信息 259 00:09:17,080 --> 00:09:20,360 是否需要调度它的父进程是谁 260 00:09:20,400 --> 00:09:22,560 然后这个地方是 261 00:09:22,600 --> 00:09:26,880 进程的内存管理的数据结构 262 00:09:26,920 --> 00:09:28,560 然后下边两个呢 263 00:09:28,600 --> 00:09:31,240 是进程的现场保护的 264 00:09:31,280 --> 00:09:35,040 上下文现场和中断保护现场 265 00:09:35,080 --> 00:09:37,360 然后底下是CR3 266 00:09:37,400 --> 00:09:41,680 CR3呢实际上是页表的起始地址 267 00:09:41,720 --> 00:09:42,920 然后它的标志位 268 00:09:42,960 --> 00:09:46,200 269 00:09:46,240 --> 00:09:49,040 可执行文件的进程的名字 270 00:09:49,080 --> 00:09:52,600 然后是进程的哈希表和链表 271 00:09:52,640 --> 00:09:55,920 那这和我们前面讲的 272 00:09:55,960 --> 00:09:57,520 这里的数据结构呢 273 00:09:57,560 --> 00:09:59,360 是能够完全对得上的 274 00:09:59,400 --> 00:10:02,440 我们在这儿呢是介绍的大致情况 275 00:10:02,480 --> 00:10:06,920 在这里头呢实际上是完整的列表 276 00:10:06,960 --> 00:10:09,280 希望同学下去之后有机会 277 00:10:09,320 --> 00:10:10,920 把这个完整的列表呢 278 00:10:10,960 --> 00:10:15,560 能够有一个基本的了解 279 00:10:15,600 --> 00:10:16,600 好那在这里 280 00:10:16,640 --> 00:10:17,920 我们需要特别说明呢 281 00:10:17,960 --> 00:10:21,400 是内存地址空间的数据结构mm_struct 282 00:10:21,440 --> 00:10:22,840 那在这个数据结构当中呢 283 00:10:22,880 --> 00:10:24,440 我们关心的内容是说 284 00:10:24,480 --> 00:10:28,520 第一个它到底有哪些内存块 285 00:10:28,560 --> 00:10:31,040 内存逻辑地址空间里头 286 00:10:31,080 --> 00:10:33,040 映射是在哪个地方 287 00:10:33,080 --> 00:10:35,280 对应的地址空间是啥样的 288 00:10:35,320 --> 00:10:36,520 好这是第一个 289 00:10:36,560 --> 00:10:37,360 在这里呢 290 00:10:37,400 --> 00:10:41,440 是它的第一级页表的起始地址 291 00:10:41,480 --> 00:10:42,800 pde_t *pgdir 292 00:10:42,840 --> 00:10:45,200 那这是地址空间的起头 293 00:10:45,240 --> 00:10:46,840 然后说如果它有共享的话 294 00:10:46,880 --> 00:10:50,080 那么它又共享了几次 295 00:10:50,120 --> 00:10:51,640 然后如果说在这里头 296 00:10:51,680 --> 00:10:53,560 有跟外存之间的置换 297 00:10:53,600 --> 00:10:56,600 那这时候置换相关数据结构 298 00:10:56,640 --> 00:10:58,120 好有了这些之后 299 00:10:58,160 --> 00:11:01,280 那我们对进程的运行情况就了解了 300 00:11:01,320 --> 00:11:09,160 这是内存管理的内存的数据结构 301 00:11:09,200 --> 00:11:10,640 对应 到代码当中呢 302 00:11:10,680 --> 00:11:14,200 是这个数据结构mm_struct 303 00:11:14,240 --> 00:11:17,440 在这里头我们关注的几个内容呢 304 00:11:17,480 --> 00:11:21,360 是它也会组织成映射的链表 305 00:11:21,400 --> 00:11:27,880 然后这是页表的起始地址的指针 306 00:11:27,920 --> 00:11:30,240 然后这是引用次数 307 00:11:30,280 --> 00:11:34,600 和对mm数据结构进行写的时候 308 00:11:34,640 --> 00:11:39,080 它相应的锁标志信号量 309 00:11:39,120 --> 00:11:43,160 好然后再一个进程里头 310 00:11:43,200 --> 00:11:45,120 ucore里头的进程队列 311 00:11:45,160 --> 00:11:46,320 它怎么来组织呢 312 00:11:46,360 --> 00:11:47,280 那在这里头呢 313 00:11:47,320 --> 00:11:49,760 我们看到基本上是链表 314 00:11:49,800 --> 00:11:50,920 你比如说在这里头 315 00:11:50,960 --> 00:11:54,240 我们有双向 链表 316 00:11:54,280 --> 00:11:55,240 然后说如果说 317 00:11:55,280 --> 00:11:57,440 你这个链表很长 318 00:11:57,480 --> 00:11:59,480 那这时候检索的开销是非常大的 319 00:11:59,520 --> 00:12:02,920 所以在ucore里头又加了hash list 320 00:12:02,960 --> 00:12:04,240 如果说这里链表长的话 321 00:12:04,280 --> 00:12:05,760 先加一级hash队列 322 00:12:05,800 --> 00:12:07,800 然后每一级队列里头呢 323 00:12:07,840 --> 00:12:08,880 hash值相同的 324 00:12:08,920 --> 00:12:11,400 再组成相应的自己的队列 325 00:12:11,440 --> 00:12:13,000 那有了这些办法之后 326 00:12:13,040 --> 00:12:14,280 我们就知道它在这里头 327 00:12:14,320 --> 00:12:17,040 到底它的队列是如何来组织的 328 00:12:17,080 --> 00:12:19,360 好那么具体的在ucore里头 329 00:12:19,400 --> 00:12:21,440 它的一个切换过程是什么样子呢 330 00:12:21,480 --> 00:12:22,680 不管由于什么原因 331 00:12:22,720 --> 00:12:24,760 最后只要你导致切换 332 00:12:24,800 --> 00:12:27,920 都会到schedule内核函数里头来 333 00:12:27,960 --> 00:12:29,000 好到这个函数里头 334 00:12:29,040 --> 00:12:30,120 它干些啥呢 335 00:12:30,160 --> 00:12:32,000 它首先清楚调度标志 336 00:12:32,040 --> 00:12:33,800 那我现在正在调度 337 00:12:33,840 --> 00:12:35,960 不能再进行改调度 标志 338 00:12:36,000 --> 00:12:38,160 然后我这儿呢 339 00:12:38,200 --> 00:12:40,560 查找新的就绪进程 340 00:12:40,600 --> 00:12:41,600 一种可能情况 341 00:12:41,640 --> 00:12:43,000 就是查找完了之后我有可能 342 00:12:43,040 --> 00:12:45,400 还是找回来还是我自己 343 00:12:45,440 --> 00:12:46,720 那这种情况是存在的 344 00:12:46,760 --> 00:12:48,520 好找着一个新的进程 345 00:12:48,560 --> 00:12:51,560 然后说我修改进程的状态标志 346 00:12:51,600 --> 00:12:53,520 那把前头那一个改成 347 00:12:53,560 --> 00:12:56,920 是就绪或者是等待 348 00:12:56,960 --> 00:12:58,080 然后把新那一个 349 00:12:58,120 --> 00:12:59,720 改成是运行状态 350 00:12:59,760 --> 00:13:01,960 好然后接下来进行切换 351 00:13:02,000 --> 00:13:04,480 切换这儿有一个switch_to 352 00:13:04,520 --> 00:13:07,520 这是最后切换的代码 353 00:13:07,560 --> 00:13:08,520 好那在这里头 354 00:13:08,560 --> 00:13:10,760 这个过程大致的流程就有了 355 00:13:10,800 --> 00:13:14,440 好那么实际说起来我们看看这里头 356 00:13:14,480 --> 00:13:15,360 这个图里说的呢 357 00:13:15,400 --> 00:13:19,320 是ucore里头的函数调用关系 358 00:13:19,360 --> 00:13:20,560 那不管是你由于 359 00:13:20,600 --> 00:13:22,880 各种各样的情况出来之后呢 360 00:13:22,920 --> 00:13:25,640 它最后都会到schedule这地方来 361 00:13:25,680 --> 00:13:26,640 比如说在我们这里 362 00:13:26,680 --> 00:13:29,040 是由于退出所过来的情况 363 00:13:29,080 --> 00:13:31,320 好到这个schedule里头来了之后呢 364 00:13:31,360 --> 00:13:34,680 它到这个地方去做选择 365 00:13:34,720 --> 00:13:36,880 和这地方是在做切换 366 00:13:36,920 --> 00:13:37,800 那到这个地方 367 00:13:37,840 --> 00:13:39,680 最后到switch_to这一段汇编呢 368 00:13:39,720 --> 00:13:41,320 它是完成切换的 369 00:13:41,360 --> 00:13:43,040 而准确的切换代码 370 00:13:43,080 --> 00:13:44,080 是什么样的呢 371 00:13:44,120 --> 00:13:46,720 准确的切换代码是跟你平台相关的 372 00:13:46,760 --> 00:13:49,400 每一个CPU平台上 373 00:13:49,440 --> 00:13:52,240 它所需要保存的寄存器是不一样的 374 00:13:52,280 --> 00:13:54,240 而为了保存的速度比较快 375 00:13:54,280 --> 00:13:55,520 恢复速度比较快 376 00:13:55,560 --> 00:13:57,480 那在这里头呢 377 00:13:57,520 --> 00:13:59,360 我们所有切换代码switch_to() 378 00:13:59,400 --> 00:14:01,040 都是用汇编来写成的 379 00:14:01,080 --> 00:14:02,160 那大致的格局呢 380 00:14:02,200 --> 00:14:05,600 是前半段保存切换过去之后 381 00:14:05,640 --> 00:14:07,120 实际上改CR3 382 00:14:07,160 --> 00:14:09,360 改完之后下半段恢复 383 00:14:09,400 --> 00:14:11,520 然后就继续执行了 384 00:14:11,560 --> 00:14:18,160 好这是进程切换的函数调用关系图 385 00:14:18,200 --> 00:14:21,400 那对应到我们实际系统当中呢 386 00:14:21,440 --> 00:14:25,960 这是我们的切换函数它的实现 387 00:14:26,000 --> 00:14:27,880 那我们需要关注的问题 388 00:14:27,920 --> 00:14:30,280 是在于这个地方在切换的时候 389 00:14:30,320 --> 00:14:33,280 它会去改进程的状态 390 00:14:33,320 --> 00:14:35,560 391 00:14:35,600 --> 00:14:41,640 然后选择下一个被调度的进程 392 00:14:41,680 --> 00:14:46,600 从调度队列当中把这个进程取出来 393 00:14:46,640 --> 00:14:50,680 然后后面进行一系列的判断 394 00:14:50,720 --> 00:14:52,080 那我们在这儿呢 395 00:14:52,120 --> 00:14:52,960 这一部分内容呢 396 00:14:53,000 --> 00:14:57,920 我们直接看关注的是它的调用图 397 00:14:57,960 --> 00:14:59,120 那在这儿我们可以看到 398 00:14:59,160 --> 00:15:01,720 399 00:15:01,760 --> 00:15:05,560 schedule()这个函数它调用的地方呢 400 00:15:05,600 --> 00:15:09,280 基本上是我们各种事件出现的时候 401 00:15:09,320 --> 00:15:12,960 你比如说数据发送 402 00:15:13,000 --> 00:15:18,840 然后这个后面会讲到信号量的触发 403 00:15:18,880 --> 00:15:21,440 然后接收到事件 404 00:15:21,480 --> 00:15:23,160 CPU处于空闲状态 405 00:15:23,200 --> 00:15:25,800 406 00:15:25,840 --> 00:15:28,240 发送消息等等一些信息 407 00:15:28,280 --> 00:15:30,280 最后都会导致到我这schedule() 408 00:15:30,320 --> 00:15:34,360 schedule()里呢我们刚才说到proc_run() 409 00:15:34,400 --> 00:15:36,000 这是我们这里关键的函数 410 00:15:36,040 --> 00:15:37,200 到这个地方下来之后有一个 411 00:15:37,240 --> 00:15:38,840 switch_to() 412 00:15:38,880 --> 00:15:40,280 在这个函数里头呢 413 00:15:40,320 --> 00:15:42,600 实际上我们通过跳转 414 00:15:42,640 --> 00:15:47,160 转到我们的汇编代码上了 415 00:15:47,200 --> 00:15:48,680 这个汇编代码呢 416 00:15:48,720 --> 00:15:52,480 就是我们这里的这一段 417 00:15:52,520 --> 00:15:56,120 在这里头呢前面是保存 418 00:15:56,160 --> 00:15:59,720 上一个进程的寄存器的工作 419 00:15:59,760 --> 00:16:03,920 下边是从堆栈当中恢复 420 00:16:03,960 --> 00:16:11,080 进入运行状态的进程的寄存器的值 421 00:16:11,120 --> 00:16:13,160 恢复完毕之后然后返回 422 00:16:13,200 --> 00:16:15,480 那就新的进程就开始继续运行了 423 00:16:15,520 --> 00:16:17,560 那在刚才那个地方呢 424 00:16:17,600 --> 00:16:19,040 switch实际上是通过这里头 425 00:16:19,080 --> 00:16:21,120 这一句C语言的函数 426 00:16:21,160 --> 00:16:23,760 最后跳转到刚才的 427 00:16:23,800 --> 00:16:25,120 这个汇编代码当中来 428 00:16:25,160 --> 00:16:25,200 429 00:16:25,240 --> 00:16:25,280