0 00:00:00,000 --> 00:00:06,840 1 00:00:06,920 --> 00:00:08,440 那这个执行流程呢 2 00:00:08,480 --> 00:00:09,640 我们可以看到 3 00:00:09,680 --> 00:00:11,560 它涉及到了几部分工作 4 00:00:11,600 --> 00:00:13,840 第一部分是我们说ucore os 5 00:00:13,880 --> 00:00:15,760 最开始初始化的kern_init 6 00:00:15,800 --> 00:00:18,200 它会进一步调用什么跟进程相关的 7 00:00:18,240 --> 00:00:20,120 我们说这里面内核线程创建相关的 8 00:00:20,160 --> 00:00:21,600 proc_init 9 00:00:21,640 --> 00:00:24,120 那proc_init会完成一系列的 10 00:00:24,160 --> 00:00:25,960 比如说我们创建内核线程 11 00:00:26,000 --> 00:00:28,160 去执行内核线程整个过程 12 00:00:28,200 --> 00:00:31,240 那这里面会涉及到很多函数 13 00:00:31,280 --> 00:00:32,560 这里面重点关注呢 14 00:00:32,600 --> 00:00:34,280 有两个一个是proc_init 15 00:00:34,320 --> 00:00:36,280 以及还一个do_fork 16 00:00:36,320 --> 00:00:39,200 这两个函数涉及到我们进程创建 17 00:00:39,240 --> 00:00:41,440 和执行很多细节一些信息 18 00:00:41,480 --> 00:00:44,040 我们在后面会逐一展开讲解 19 00:00:44,080 --> 00:00:45,000 好 那我们来看看 20 00:00:45,040 --> 00:00:47,440 怎么来完成内核线程的创建 21 00:00:47,480 --> 00:00:50,000 首先我们创建第0号内核线程 22 00:00:50,040 --> 00:00:51,240 这为什么是第0号 23 00:00:51,280 --> 00:00:53,240 跟我们计算机这个术语相关 24 00:00:53,280 --> 00:00:55,680 我们经常表示首个是从0开始 25 00:00:55,720 --> 00:00:58,080 那么这是第0个内核线程 26 00:00:58,120 --> 00:01:00,320 那么其实它这个内核线程呢比较特殊 27 00:01:00,360 --> 00:01:02,160 我们叫idleproc 28 00:01:02,200 --> 00:01:04,760 它代表了我们ucore os 29 00:01:04,800 --> 00:01:06,200 来完成一系列的管理工作 30 00:01:06,240 --> 00:01:09,280 包括后面的完成的对 31 00:01:09,320 --> 00:01:11,960 所谓的内核线程init proc 32 00:01:12,000 --> 00:01:14,440 创建和调度执行过程 33 00:01:14,480 --> 00:01:16,040 所以这是一个说 34 00:01:16,080 --> 00:01:18,440 代表一个ucore os一个管理 35 00:01:18,480 --> 00:01:21,280 在这样既然代表ucore os管理 36 00:01:21,320 --> 00:01:23,560 它需要完成自身的一些初始化工作 37 00:01:23,600 --> 00:01:25,720 比如说它要创建自己的TCB 38 00:01:25,760 --> 00:01:27,160 为此它要alloc_proc 39 00:01:27,200 --> 00:01:29,160 来得到一个TCB的内存块 40 00:01:29,200 --> 00:01:30,640 那TCB的内存块呢 41 00:01:30,680 --> 00:01:32,480 会完成相应的初始化工作 42 00:01:32,520 --> 00:01:33,920 来表明它的身份 43 00:01:33,960 --> 00:01:35,320 那到底完成哪些细节呢 44 00:01:35,360 --> 00:01:38,280 我们可以看看 45 00:01:38,320 --> 00:01:40,600 那可以看到这里面包含了什么 46 00:01:40,640 --> 00:01:41,720 第一个是PID 47 00:01:41,760 --> 00:01:44,560 就是它的本身一个ID标识0 48 00:01:44,600 --> 00:01:46,840 正好和他第0进程是对应的 49 00:01:46,880 --> 00:01:48,800 第二个呢它的状态 50 00:01:48,840 --> 00:01:50,840 它当前是可以runnable 51 00:01:50,880 --> 00:01:52,520 就是当前处于就绪态 52 00:01:52,560 --> 00:01:54,760 第三个是它的堆栈 53 00:01:54,800 --> 00:01:57,320 这里面专门给它设置一个内核堆栈 54 00:01:57,360 --> 00:01:59,320 专门有一个bootstack这么一个地方 55 00:01:59,360 --> 00:02:03,200 第四个need_resched 就是它需要被调度 56 00:02:03,240 --> 00:02:03,720 为什么呢 57 00:02:03,760 --> 00:02:04,760 因为它被调度之后呢 58 00:02:04,800 --> 00:02:07,040 才能切换到其它的内核调用去执行 59 00:02:07,080 --> 00:02:09,360 我们前面讲到我们这个要完成 60 00:02:09,400 --> 00:02:12,960 一个显示字符串的一个内核线程 61 00:02:13,000 --> 00:02:16,560 那需要把这个线程呢从idle_proc 62 00:02:16,600 --> 00:02:19,480 切换到我们显示字符串的init_proc里去 63 00:02:19,520 --> 00:02:20,880 所以这里面需要设置它是 64 00:02:20,920 --> 00:02:23,400 需要能够去允许被重新调度 65 00:02:23,440 --> 00:02:25,520 最后呢设置一个它的名字idle 66 00:02:25,560 --> 00:02:28,040 这就是它最基本的初始化功能 67 00:02:28,080 --> 00:02:30,360 有了这个之后呢我们继续往下执行 68 00:02:30,400 --> 00:02:32,240 去创建我们说的第一个 69 00:02:32,280 --> 00:02:36,400 能显示字符串的一个内核线程 70 00:02:36,440 --> 00:02:37,880 那么这个内核线程呢 71 00:02:37,920 --> 00:02:40,280 就跟前面的idle_proc比起来 72 00:02:40,320 --> 00:02:42,480 它初始化工作完成更加复杂一些 73 00:02:42,520 --> 00:02:44,200 因为它要做好各种各样的准备 74 00:02:44,240 --> 00:02:45,640 它的地址 75 00:02:45,680 --> 00:02:47,080 它的执行 76 00:02:47,120 --> 00:02:48,560 它的上下文等等 77 00:02:48,600 --> 00:02:50,120 只有做好这些准备之后 78 00:02:50,160 --> 00:02:54,640 才能够正确进行线程间的切换 79 00:02:54,680 --> 00:02:55,320 可以看到这里面 80 00:02:55,360 --> 00:02:56,880 采取一个重要的函数是什么呢 81 00:02:56,920 --> 00:02:57,960 是do_fork 82 00:02:58,000 --> 00:03:01,160 它完成了后续一系列这个初始化工作 83 00:03:01,200 --> 00:03:03,760 那我们说在这里面它初始化工作 84 00:03:03,800 --> 00:03:06,440 其实要把我们刚才线程控制块里面 85 00:03:06,480 --> 00:03:08,280 很多数据结构呢 86 00:03:08,320 --> 00:03:10,680 都要进行进一步的初始化 87 00:03:10,720 --> 00:03:12,080 我们这里面挑一些重点部分 88 00:03:12,120 --> 00:03:13,640 给大家做一个讲解 89 00:03:13,680 --> 00:03:15,080 这里面我们讲哪部分呢 90 00:03:15,120 --> 00:03:16,280 第一部分是trapframe 91 00:03:16,320 --> 00:03:18,360 就是怎么能够对这个进程 92 00:03:18,400 --> 00:03:20,520 或者线程控制块的trapframe 93 00:03:20,560 --> 00:03:21,640 进行初始化 94 00:03:21,680 --> 00:03:23,040 它涉及到怎么去能够 95 00:03:23,080 --> 00:03:24,920 跳到它的入口地址去 96 00:03:24,960 --> 00:03:27,640 正确执行线程这么一工作 这一块 97 00:03:27,680 --> 00:03:29,240 第二个呢是跟context相关 98 00:03:29,280 --> 00:03:31,120 完成线程切换的时候 context相关 99 00:03:31,160 --> 00:03:32,520 这两部分是我们这里面 100 00:03:32,560 --> 00:03:33,880 会给大家描述重点 101 00:03:33,920 --> 00:03:36,240 也是比较难以去理解的 102 00:03:36,280 --> 00:03:37,800 那我们看第一部分trapframe 103 00:03:37,840 --> 00:03:39,120 它初始化什么地方 104 00:03:39,160 --> 00:03:41,640 初始化关于它地址空间 105 00:03:41,680 --> 00:03:42,280 可以看到他用到的 106 00:03:42,320 --> 00:03:45,080 kernel_cs和kernel_ds 107 00:03:45,120 --> 00:03:45,880 这什么意思呢 108 00:03:45,920 --> 00:03:48,000 意味着我们内核现成它的代码段 109 00:03:48,040 --> 00:03:51,000 和数据段是在内核里面的 110 00:03:51,040 --> 00:03:54,400 它不是user(用户态)的cs和user(用户态)的ds 111 00:03:54,440 --> 00:03:55,800 Ok这第一步 112 00:03:55,840 --> 00:03:57,640 第二步呢我们要设置好 113 00:03:57,680 --> 00:04:00,120 我们说这个线程的起始地址在哪 114 00:04:00,160 --> 00:04:02,320 因为当切换完成之后呢 115 00:04:02,360 --> 00:04:03,720 我们这个线程要从 116 00:04:03,760 --> 00:04:05,480 它的起始地址开始执行 117 00:04:05,520 --> 00:04:07,920 所以你可以看到它有两个地方 118 00:04:07,960 --> 00:04:09,600 一个是kernel thread entry 还有一个fn 119 00:04:09,640 --> 00:04:14,280 fn呢是代表我们说实际的入口地址 120 00:04:14,320 --> 00:04:16,760 但是在执行实际的入口地址之前 121 00:04:16,800 --> 00:04:18,320 我们需要完成简单初始化工作 122 00:04:18,360 --> 00:04:20,880 这就是kernel thread entry 123 00:04:20,920 --> 00:04:23,240 这是最开始的初始化的地方 124 00:04:23,280 --> 00:04:25,560 当把相应的一些初始化 125 00:04:25,600 --> 00:04:27,320 一些变量设置完之后呢 126 00:04:27,360 --> 00:04:29,440 会去进一步去调这个fn 127 00:04:29,480 --> 00:04:31,120 这个function的入口地址 128 00:04:31,160 --> 00:04:32,120 从而可以完成 129 00:04:32,160 --> 00:04:34,240 比如说打印字符串这个工作 130 00:04:34,280 --> 00:04:36,040 这是跟它相关的 131 00:04:36,080 --> 00:04:39,440 这个arg是设置跟这个fn相关一些参数 132 00:04:39,480 --> 00:04:40,880 比如说你要传一个字符串 133 00:04:40,920 --> 00:04:42,960 它的起始地址在什么地方 134 00:04:43,000 --> 00:04:46,520 这是相关的一部分入口的初始化设置 135 00:04:46,560 --> 00:04:48,560 这是第二部分 136 00:04:48,600 --> 00:04:51,040 那第三部分呢是关于trapframe的一些 137 00:04:51,080 --> 00:04:54,920 有关它的esp和它的eip的一个设置 138 00:04:54,960 --> 00:04:56,600 那这里面esp设置是 139 00:04:56,640 --> 00:04:59,640 中断的相应的堆栈一个信息 140 00:04:59,680 --> 00:05:04,560 我们可以看到tf_esp实际上设当前一个esp值 141 00:05:04,600 --> 00:05:06,720 然后还有一个context.-esp 142 00:05:06,760 --> 00:05:08,760 设是proc->_tf 143 00:05:08,800 --> 00:05:11,400 也意味着我们内核的堆栈里面 144 00:05:11,440 --> 00:05:15,080 有一块区域保存了我们trapframe所有内容 145 00:05:15,120 --> 00:05:17,560 我们trapframe跟这个进程相关所有内容 146 00:05:17,600 --> 00:05:19,600 它保存在这个内核堆栈里面 147 00:05:19,640 --> 00:05:20,960 这是一个信息 148 00:05:21,000 --> 00:05:24,240 第二信息是说它有一个eip设置的forkret 149 00:05:24,280 --> 00:05:25,560 那么forkret什么意思 150 00:05:25,600 --> 00:05:28,640 实际上需要注意context.eip 151 00:05:28,680 --> 00:05:32,480 那意味着我们当完成了context switch之后呢 152 00:05:32,520 --> 00:05:35,040 我们当前这个init proc 153 00:05:35,080 --> 00:05:37,280 它首先执行的forkret 154 00:05:37,320 --> 00:05:39,560 那它和我们刚才讲到的 155 00:05:39,600 --> 00:05:41,520 函数的本身的入口地址 156 00:05:41,560 --> 00:05:44,120 比如说fn或者说那个 kernel thread entry 157 00:05:44,160 --> 00:05:46,000 那个地方是不一样 158 00:05:46,040 --> 00:05:47,680 这个forkret主要完成什么呢 159 00:05:47,720 --> 00:05:52,480 它主要完成对返回中断一个处理过程 160 00:05:52,520 --> 00:05:55,120 也意味着它会去设置相应一些操作 161 00:05:55,160 --> 00:05:56,920 最后执行iret 162 00:05:56,960 --> 00:06:01,000 然后iret会根据trap-frame里面设置的信息 163 00:06:01,040 --> 00:06:03,480 来跳到我们说init proc 164 00:06:03,520 --> 00:06:05,880 这个内核线程入口地址去执行 165 00:06:05,920 --> 00:06:08,760 也意味着forkret是进行了 166 00:06:08,800 --> 00:06:11,720 中断的一个恢复执行的一个过程 167 00:06:11,760 --> 00:06:14,120 当设置完trap-frame和context之后呢 168 00:06:14,160 --> 00:06:16,960 我们还需要去进一步去设置有关init proc 169 00:06:17,000 --> 00:06:19,400 其它一些执行相关的一些信息 170 00:06:19,440 --> 00:06:21,080 比如说它的stack 171 00:06:21,120 --> 00:06:23,240 它的内核堆栈 172 00:06:23,280 --> 00:06:25,480 设置好内核堆栈之后 173 00:06:25,520 --> 00:06:26,480 我们可以看到 174 00:06:26,520 --> 00:06:28,240 我们就可以说这个相应一些 175 00:06:28,280 --> 00:06:30,280 初始化工作基本上准备就绪了 176 00:06:30,320 --> 00:06:32,480 我们就可以把这个init proc 177 00:06:32,520 --> 00:06:34,880 放到就绪队列里面去 管理起来 178 00:06:34,920 --> 00:06:38,720 以及把它的状态设置为runnable 179 00:06:38,760 --> 00:06:40,280 更刚才一样设成runnable 180 180 00:06:40,320 --> 00:06:46,000 这代表它可以去被调度去执行了 181 00:06:46,040 --> 00:06:47,720 那当设置完之后 182 00:06:47,760 --> 00:06:49,360 我们接下来工作就要执行它 183 00:06:49,400 --> 00:06:51,680 那么执行是从idle_proc里面去执行的 184 00:06:51,720 --> 00:06:52,920 因为idle_proc 185 00:06:52,960 --> 00:06:56,120 就是我们当前正在执行的ucore os 186 00:06:56,160 --> 00:06:57,720 那么他干什么事情 187 00:06:57,760 --> 00:06:58,600 怎么开始执行 188 00:06:58,640 --> 00:07:00,120 我们可以看一看 189 00:07:00,160 --> 00:07:02,520 首先它要设置我们现在 190 00:07:02,560 --> 00:07:03,800 不能够重新调度 191 00:07:03,840 --> 00:07:05,160 我们现在已经处于调度状态了 192 00:07:05,200 --> 00:07:06,320 我们不能在这个调度状态中 193 00:07:06,360 --> 00:07:08,440 再去递归去调度 194 00:07:08,480 --> 00:07:09,760 这是不允许的 195 00:07:09,800 --> 00:07:11,200 就是unable reschedule 196 00:07:11,240 --> 00:07:12,040 这是在调度时候 197 00:07:12,080 --> 00:07:14,240 要完成第一步工作 198 00:07:14,280 --> 00:07:15,240 第二步工作 199 00:07:15,280 --> 00:07:17,080 我们要找到当前线程队列里面 200 00:07:17,120 --> 00:07:19,240 哪些是属于就绪态的 201 00:07:19,280 --> 00:07:21,840 我们可以看到它这里面至少存在 202 00:07:21,880 --> 00:07:23,240 init_proc 203 00:07:23,280 --> 00:07:26,240 找到这一个 找到这个之后 204 00:07:26,280 --> 00:07:28,280 就开始执行proc 就开始执行 205 00:07:28,320 --> 00:07:29,760 其实还没有完成切换 206 00:07:29,800 --> 00:07:32,600 做好相应的准备 207 00:07:32,640 --> 00:07:34,320 所谓开始执行就要切换什么呢 208 00:07:34,360 --> 00:07:37,800 第一要切换两个线程的kernel_stack 209 00:07:37,840 --> 00:07:40,240 第二步要切换所谓他的地址空间 210 00:07:40,280 --> 00:07:42,920 但是需要注意这个切换地址空间呢 211 00:07:42,960 --> 00:07:45,360 对于我们后续lab5的用户态 212 00:07:45,400 --> 00:07:47,000 的进程是有关系的 213 00:07:47,040 --> 00:07:48,240 但是对我们内核线程来说 214 00:07:48,280 --> 00:07:49,880 它们是共用一个地址空间 215 00:07:49,920 --> 00:07:51,800 所以这一块其实没有太大意义 216 00:07:51,840 --> 00:07:54,800 再接下来是切换context 217 00:07:54,840 --> 00:07:55,760 这context是我们前面 218 00:07:55,800 --> 00:07:58,080 专门给大家介绍context数据结构 219 00:07:58,120 --> 00:07:59,440 我们后续会给大家展开 220 00:07:59,480 --> 00:08:02,360 看看怎么来切换context 221 00:08:02,400 --> 00:08:04,200 一旦切换完context之后 222 00:08:04,240 --> 00:08:06,480 会完成一个return一个返回 223 00:08:06,520 --> 00:08:07,400 那么也意味着它会 224 00:08:07,440 --> 00:08:09,120 执行到forkret这个地方去 225 00:08:09,160 --> 00:08:11,360 然后进一步去根据trap frame 226 00:08:11,400 --> 00:08:13,600 来完成ret一个返回 227 00:08:13,640 --> 00:08:15,120 然后从而可以跳到 228 00:08:15,160 --> 00:08:17,640 跳到我们说内核线程入口地址 229 00:08:17,680 --> 00:08:19,520 就是kernel thread entry 230 00:08:19,560 --> 00:08:21,560 kernel thread entry再会去执行什么呢 231 00:08:21,600 --> 00:08:23,160 执行那个function:fn 232 00:08:23,200 --> 00:08:24,840 从而可以得到我们说去 233 00:08:24,880 --> 00:08:26,440 完成那个打印字符串一个功能的 234 00:08:26,480 --> 00:08:28,800 那个函数的一个执行过程 235 00:08:28,840 --> 00:08:32,160 这是整个这个执行内核线程 236 00:08:32,200 --> 00:08:34,200 一个过程的一个描述 237 00:08:34,240 --> 00:08:36,680 那么我们会对这个switch context 238 00:08:36,720 --> 00:08:38,480 做进一步的讲解 239 00:08:38,520 --> 00:08:39,680 那我们看看switch context 240 00:08:39,720 --> 00:08:41,280 实际上是一个函数 241 00:08:41,320 --> 00:08:44,040 实际上被c语言调用的一个函数 242 00:08:44,080 --> 00:08:45,080 那么它本身的实现 243 00:08:45,120 --> 00:08:46,600 需要注意它本身的实现 244 00:08:46,640 --> 00:08:47,520 是用汇编语言来实现的 245 00:08:47,560 --> 00:08:48,400 因为这里面涉及到 246 00:08:48,440 --> 00:08:50,320 大量对寄存器一些操作 247 00:08:50,360 --> 00:08:51,960 那我们可以去理解一下 248 00:08:52,000 --> 00:08:54,440 from是从 比如说我们这里面具体讲 249 00:08:54,480 --> 00:08:56,320 就是从idle_proc切换到to 250 00:08:56,360 --> 00:08:56,880 to是什么呢 251 00:08:56,920 --> 00:08:58,200 To是init_proc 252 00:08:58,240 --> 00:09:01,720 怎么从from的切换到to的这个context 253 00:09:01,760 --> 00:09:03,240 实际上我们说这里面 254 00:09:03,280 --> 00:09:04,400 要做两部分事情 255 00:09:04,440 --> 00:09:05,760 第一部分是save 256 00:09:05,800 --> 00:09:07,960 save这个idle_proc的context 257 00:09:08,000 --> 00:09:08,680 这是第一步 258 00:09:08,720 --> 00:09:11,920 第二步呢是restore恢复 259 00:09:11,960 --> 00:09:13,800 "to"thread是什么呢 260 00:09:13,840 --> 00:09:17,600 就是我们说的init_proc它的context 261 00:09:17,640 --> 00:09:22,440 保存(save)恢复(restore) ret就完成了这个过程 262 00:09:22,480 --> 00:09:23,720 那么具体怎么做的 263 00:09:23,760 --> 00:09:25,120 我们再逐一看一下 264 00:09:25,160 --> 00:09:28,640 这个参数我们说的idle_proc的context 265 00:09:28,680 --> 00:09:33,960 这个参数呢是我们说init_proc的context 266 00:09:34,000 --> 00:09:37,200 ok 我们进一步看一看它的汇编代码 267 00:09:37,240 --> 00:09:39,080 首先因为它这是一个参数 268 00:09:39,120 --> 00:09:40,880 我们再回顾一下我们在lab1时候 269 00:09:40,920 --> 00:09:44,160 讲解过函数调用栈里面的存储信息 270 00:09:44,200 --> 00:09:45,640 就是我们一个函数 271 00:09:45,680 --> 00:09:46,480 调另一个函数的时候 272 00:09:46,520 --> 00:09:49,280 它压的相应栈的结构 273 00:09:49,320 --> 00:09:50,600 我们可以知道esp 274 00:09:50,640 --> 00:09:52,280 向上那四个字节存的是什么呢 275 00:09:52,320 --> 00:09:54,800 存的是那个参数from这个参数 276 00:09:54,840 --> 00:09:55,480 这个from是什么呢 277 00:09:55,520 --> 00:09:57,920 就是idle_proc context的地址 278 00:09:57,960 --> 00:09:59,480 好 有了这个地址之后 279 00:09:59,520 --> 00:10:01,640 我们接下来就可以 280 00:10:01,680 --> 00:10:04,080 完成一系列的保存工作 281 00:10:04,120 --> 00:10:06,840 比如说move esp到这个地方来 282 00:10:06,880 --> 00:10:09,440 就把esp的内容保存到了 283 00:10:09,480 --> 00:10:13,160 我们说的这个idle_proc的context里面去 284 00:10:13,200 --> 00:10:15,720 同时呢还需要注意这个"popl"是比较特殊的 285 00:10:15,760 --> 00:10:18,240 "popl 0(%eax)" 286 00:10:18,280 --> 00:10:19,600 它通过一种特殊的手段 287 00:10:19,640 --> 00:10:24,800 来完成了把当前的idle_proc的eip保存到了 288 00:10:24,840 --> 00:10:26,000 也一样保存到了 289 00:10:26,040 --> 00:10:28,320 我们说swap_context里面去了 290 00:10:28,360 --> 00:10:30,080 那会完成一系列保存工作 291 00:10:30,120 --> 00:10:32,440 把context把信息给填满 292 00:10:32,480 --> 00:10:33,960 也意味着它会完成对 293 00:10:34,000 --> 00:10:36,920 idle_proc的context的完整的保存 294 00:10:36,960 --> 00:10:37,840 把这些寄存器的内容 295 00:10:37,880 --> 00:10:40,440 全都保存到相应的数据结构里面去了 296 00:10:40,480 --> 00:10:42,400 这是说保存这一步 297 00:10:42,440 --> 00:10:44,600 save这一步 298 00:10:44,640 --> 00:10:47,280 第二个呢还要干的事情就是要恢复 299 00:10:47,320 --> 00:10:50,440 就是restore "to" thread context 300 00:10:50,480 --> 00:10:52,920 那么"to" thread就是我们说的init_proc 301 00:10:52,960 --> 00:10:55,080 那么这里面的context 302 00:10:55,120 --> 00:10:57,760 它在前面已经做好初始化了 303 00:10:57,800 --> 00:10:59,640 而我们前面的idle_proc呢 304 00:10:59,680 --> 00:11:01,720 是在刚才做了保存 305 00:11:01,760 --> 00:11:02,840 那么这个初始化信息 306 00:11:02,880 --> 00:11:03,960 要全部都要读出来 307 00:11:04,000 --> 00:11:05,920 从哪读出来呢 308 00:11:05,960 --> 00:11:10,120 可以看着它又取到了"movl 4(%esp),%eax" 309 00:11:10,160 --> 00:11:11,120 这实际上就取到了 310 00:11:11,160 --> 00:11:14,240 我们说init_proc的context的指针 311 00:11:14,280 --> 00:11:16,680 然后把这些内存里面的信息 312 00:11:16,720 --> 00:11:19,080 再逐一倒到相应的寄存器里面 313 00:11:19,120 --> 00:11:20,440 比如说ebp ebi等等 314 00:11:20,480 --> 00:11:23,800 就是完成了对所有寄存器恢复 315 00:11:23,840 --> 00:11:28,720 然后到最后一步需要注意"pushl 0(%eax)" 316 00:11:28,760 --> 00:11:31,520 实际上把这个eax给push进去了 317 00:11:31,560 --> 00:11:33,160 为什么要push到栈里面去 318 00:11:33,200 --> 00:11:35,200 因为你最后还要执行一个ret 319 00:11:35,240 --> 00:11:37,360 ret会从这个栈里面把这个地址取出来 320 00:11:37,400 --> 00:11:40,080 从而可以完成这个切换 321 00:11:40,120 --> 00:11:43,880 所以说我们想想这个eip等于多少呢 322 00:11:43,920 --> 00:11:45,360 在做前面初始化的时候 323 00:11:45,400 --> 00:11:47,880 我们这个eip赋成了什么呢 324 00:11:47,920 --> 00:11:50,320 赋成了fork ret那个地址 325 00:11:50,360 --> 00:11:51,920 大家有没有印象可以看一看 326 00:11:51,960 --> 00:11:54,520 所以说一旦在执行完这个 327 00:11:54,560 --> 00:11:57,920 ret这个return之后呢 328 00:11:57,960 --> 00:11:59,880 我们这时候呢已经切换到了 329 00:11:59,920 --> 00:12:02,440 init proc的最开始地方 330 00:12:02,480 --> 00:12:06,000 就是forkret起始点去执行 331 00:12:06,040 --> 00:12:08,720 这就是它的context swich完成的工作 332 00:12:08,760 --> 00:12:11,240 所有这些工作在内核里面来完成 333 00:12:11,280 --> 00:12:13,240 好 大家分析到这一步之后呢 334 00:12:13,280 --> 00:12:15,800 大家结合实验可以进一步去展开看看 335 00:12:15,840 --> 00:12:18,160 当fork ret继续往下执行之后 336 00:12:18,200 --> 00:12:20,480 会怎么来完成打印字符串 337 00:12:20,520 --> 00:12:21,760 这么一个完整的过程 338 00:12:21,800 --> 00:12:22,720 这需要大家在实验中 339 00:12:22,760 --> 00:12:25,000 去进一步去体会和理解 340 00:12:25,040 --> 00:12:26,880 好 这部分内容就介绍到这儿 341 00:12:26,920 --> 00:12:27,200 342 00:12:27,240 --> 00:12:27,280