0 00:00:00,000 --> 00:00:07,000 1 00:00:07,080 --> 00:00:08,680 那前面呢我们可以看到 2 00:00:08,720 --> 00:00:10,360 我们通过这个执行相应的 3 00:00:10,400 --> 00:00:12,240 do_execve这个函数呢 4 00:00:12,280 --> 00:00:14,480 能够创建出一个进程 5 00:00:14,520 --> 00:00:15,560 那么创建了一个鸡 6 00:00:15,600 --> 00:00:17,320 那么鸡怎么下蛋呢 7 00:00:17,360 --> 00:00:18,400 那我们可以看 8 00:00:18,440 --> 00:00:20,960 这个父进程是如何复制子进程的 9 00:00:21,000 --> 00:00:22,880 这是我们这一次讲的 10 00:00:22,920 --> 00:00:25,360 内容就是进程复制 11 00:00:25,400 --> 00:00:26,520 鸡怎么生蛋 12 00:00:26,560 --> 00:00:27,800 那么在原理课可以看到 13 00:00:27,840 --> 00:00:29,680 这里面讲到了有一个父进程 14 00:00:29,720 --> 00:00:31,760 通过一个fork就完成了 15 00:00:31,800 --> 00:00:33,880 把这个所有代码段 数据段 堆栈等等 16 00:00:33,920 --> 00:00:35,600 复制到另一个区域里面去 17 00:00:35,640 --> 00:00:36,600 产生一个子进程 18 00:00:36,640 --> 00:00:38,040 它们的PID不一样 19 00:00:38,080 --> 00:00:39,760 这是原理课大致的讲的内容 20 00:00:39,800 --> 00:00:40,520 我们可以看看 21 00:00:40,560 --> 00:00:42,440 如果说对应到我们Lab5实验 22 00:00:42,480 --> 00:00:44,040 这个原理上的内容 23 00:00:44,080 --> 00:00:48,040 怎么来具体的实现 24 00:00:48,080 --> 00:00:50,960 那这个函数呢叫do_fork 25 00:00:51,000 --> 00:00:52,080 一个内核函数 26 00:00:52,120 --> 00:00:53,520 它里面带了三个参数 27 00:00:53,560 --> 00:00:55,160 memory相关一个参数跟 28 00:00:55,200 --> 00:00:57,160 stack相关的有两个参数 29 00:00:57,200 --> 00:00:58,120 那么这个参数呢 30 00:00:58,160 --> 00:00:59,880 决定了就是说我们怎么去完成 31 00:00:59,920 --> 00:01:02,920 内存空间管理的一个拷贝 32 00:01:02,960 --> 00:01:05,680 这两块跟用户堆栈相关 33 00:01:05,720 --> 00:01:07,880 跟我们这个它的trapframe相关 34 00:01:07,920 --> 00:01:08,640 那也是一样 35 00:01:08,680 --> 00:01:10,360 就是通过这两个信息 36 00:01:10,400 --> 00:01:11,880 我们的子进程需要 37 00:01:11,920 --> 00:01:13,520 用到父进程的相关信息 38 00:01:13,560 --> 00:01:15,440 要复制一部分内容还要做一并修改 39 00:01:15,480 --> 00:01:17,120 所以这是三个重要的参数 40 00:01:17,160 --> 00:01:20,240 来完成了对这个do_fork一个调用 41 00:01:20,280 --> 00:01:23,720 我们看它怎么具体一步步实现的 42 00:01:23,760 --> 00:01:26,880 首先我们说父进程要创建子进程 43 00:01:26,920 --> 00:01:29,560 子进程和父进程是两个不同的进程 44 00:01:29,600 --> 00:01:31,720 所以说它有自己的进程控制块 45 00:01:31,760 --> 00:01:34,160 那创建了这个进程控制块之后呢 46 00:01:34,200 --> 00:01:36,160 我们子进程就有了自己的身份了 47 00:01:36,200 --> 00:01:37,320 也有它一个壳 48 00:01:37,360 --> 00:01:38,440 但光这个还不够 49 00:01:38,480 --> 00:01:39,600 我们还需要进一步去初始化 50 00:01:39,640 --> 00:01:42,880 很多其它新的一些必须需要具备的内容 51 00:01:42,920 --> 00:01:44,800 比如说第一个是kernel stack 52 00:01:44,840 --> 00:01:45,960 它在内核里面要能够执行 53 00:01:46,000 --> 00:01:47,400 它需要自己的栈 54 00:01:47,440 --> 00:01:49,960 这个kernel stack通过分配它相应的空间 55 00:01:50,000 --> 00:01:52,480 通过alloc_ pages得到一些空间 56 00:01:52,520 --> 00:01:53,920 然后通过这个proc->kstack 57 00:01:53,960 --> 00:01:57,720 来得到它这个内核里面这个虚地址 58 00:01:57,760 --> 00:02:00,520 从而可以把这个kernel stack给建立好 59 00:02:00,560 --> 00:02:02,000 这是完成建立功能 60 00:02:02,040 --> 00:02:05,160 还没有具体去执行 61 00:02:05,200 --> 00:02:07,400 再接下来呢我们需要干什么呢 62 00:02:07,440 --> 00:02:09,600 复制父进程的内存 63 00:02:09,640 --> 00:02:11,720 当我们创建个新的kernel stack之后呢 64 00:02:11,760 --> 00:02:14,920 我们的代码段 数据段希望能够 65 00:02:14,960 --> 00:02:17,640 复制父进程的同样的内容 66 00:02:17,680 --> 00:02:19,560 这我们说父进程创建子进程 67 00:02:19,600 --> 00:02:21,120 需要完成重要一个功能 68 00:02:21,160 --> 00:02:22,360 这里面用什么呢 69 00:02:22,400 --> 00:02:24,200 copy_mm和copy_range 70 00:02:24,240 --> 00:02:26,440 这两个来完成相应的功能 71 00:02:26,480 --> 00:02:27,480 那么copy_mm呢 72 00:02:27,520 --> 00:02:29,920 是为新进程创建一个新的VMA 73 00:02:29,960 --> 00:02:32,440 这是新进程创建好的 74 00:02:32,480 --> 00:02:34,360 第二个呢copy_range 75 00:02:34,400 --> 00:02:37,160 是把实际的这个代码段和数据段 76 00:02:37,200 --> 00:02:39,120 搬到新的子进程里面去 77 00:02:39,160 --> 00:02:41,880 同时再设置好相应的这个 78 00:02:41,920 --> 00:02:43,160 虚实映射关系就是把 79 00:02:43,200 --> 00:02:45,600 这个页表里面内容重新设置 80 00:02:45,640 --> 00:02:47,680 使得我们的子进程 81 00:02:47,720 --> 00:02:50,240 具有自己的内存管理一个架构 82 00:02:50,280 --> 00:02:54,080 mm_struct以及对应自己一个新的页表 83 00:02:54,120 --> 00:02:57,760 内容是复制了一份 84 00:02:57,800 --> 00:03:00,680 好 当我们把这个父进程的内存空间 85 00:03:00,720 --> 00:03:02,680 拷贝到子进程里面去之后呢 86 00:03:02,720 --> 00:03:03,840 再接下来我们需要 87 00:03:03,880 --> 00:03:06,280 让这个子进程能够正确的执行 88 00:03:06,320 --> 00:03:11,640 也意味着它要正确设置好trapframe和context 89 00:03:11,680 --> 00:03:13,960 这个context在我们讲kernel thread的时候呢 90 00:03:14,000 --> 00:03:17,400 给大家提到过trapframe之前也讲过 91 00:03:17,440 --> 00:03:19,200 这trapframe为什么要设置新的值呢 92 00:03:19,240 --> 00:03:20,360 可以看到 93 00:03:20,400 --> 00:03:22,520 因为当我们这个父进程和子进程 94 00:03:22,560 --> 00:03:24,560 都要做返回到用户态去执行的时候 95 00:03:24,600 --> 00:03:27,720 它们的状态是不一样的 96 00:03:27,760 --> 00:03:29,360 这里面可以看到它这里面干什么事情 97 00:03:29,400 --> 00:03:32,000 第一个要拷贝父进程的trapframe 98 00:03:32,040 --> 00:03:33,480 拷贝完之后还要做一定改动 99 00:03:33,520 --> 00:03:36,160 它的eax esp eip和copy_thread 100 00:03:36,200 --> 00:03:38,280 要做相应一些重新设置 101 00:03:38,320 --> 00:03:41,160 来完成这个拷贝和更新的工作 102 00:03:41,200 --> 00:03:42,280 大家可以去看一下 103 00:03:42,320 --> 00:03:43,960 这里面怎么来完成的 104 00:03:44,000 --> 00:03:46,040 假设做完这个之后 105 00:03:46,080 --> 00:03:48,320 我们还需要把我们新创建好 106 00:03:48,360 --> 00:03:50,800 这个子进程放到我们的 107 00:03:50,840 --> 00:03:52,360 进程队列里面去 108 00:03:52,400 --> 00:03:55,120 我们说有一个进程队列来管理了 109 00:03:55,160 --> 00:03:59,200 所有在操作系统里面存在的进程 110 00:03:59,240 --> 00:04:02,000 这里面有一个proc list 111 00:04:02,040 --> 00:04:05,040 然后呢一旦挂到这个队列里面去之后呢 112 00:04:05,080 --> 00:04:06,320 我们就可以用 113 00:04:06,360 --> 00:04:08,360 我们的进程管理的这个子系统呢 114 00:04:08,400 --> 00:04:12,440 来对这里面的进程做调度管理 115 00:04:12,480 --> 00:04:14,680 当然调度我们在Lab6中 116 00:04:14,720 --> 00:04:17,400 会做进一步的深入讲解 117 00:04:17,440 --> 00:04:19,960 一旦能够把这部分事情做完之后 118 00:04:20,000 --> 00:04:22,600 也意味着我们子进程 119 00:04:22,640 --> 00:04:25,800 已经创建完毕且可以去执行了 120 00:04:25,840 --> 00:04:27,760 可以去到running态 121 00:04:27,800 --> 00:04:32,960 然后跳到我们内容空间去执行 122 00:04:33,000 --> 00:04:36,160 好 那它们执行有什么区别呢 123 00:04:36,200 --> 00:04:37,440 我们前面讲到 说 124 00:04:37,480 --> 00:04:40,440 我们的父进程创建完子进程之后 125 00:04:40,480 --> 00:04:42,040 它子进程的返回值和 126 00:04:42,080 --> 00:04:43,800 父进程的返回值是不一样的 127 00:04:43,840 --> 00:04:45,080 do_fork的返回 128 00:04:45,120 --> 00:04:47,880 父进程返回是子进程的PID 129 00:04:47,920 --> 00:04:49,480 而子进程返回是零 130 00:04:49,520 --> 00:04:50,880 那在这里面怎么体现 131 00:04:50,920 --> 00:04:52,640 其实在最后这一步 132 00:04:52,680 --> 00:04:55,640 还会有一个把这个return做一个修改 133 00:04:55,680 --> 00:04:58,960 如果是父进程的话它会returnbPID回去 134 00:04:59,000 --> 00:05:01,800 所以说你可以看着父进程它的调用返回值 135 00:05:01,840 --> 00:05:04,480 正好是那个子进程的PID 136 00:05:04,520 --> 00:05:06,160 同时呢子进程他做的事不一样 137 00:05:06,200 --> 00:05:09,280 子进程它把它那个返回值就eax 138 00:05:09,320 --> 00:05:10,400 设成是零 139 00:05:10,440 --> 00:05:11,640 寄存器设置值是零 140 00:05:11,680 --> 00:05:14,000 那使得这个子进程的do_fork 141 00:05:14,040 --> 00:05:15,040 系统调用执行完毕之后 142 00:05:15,080 --> 00:05:16,360 它返回是零 143 00:05:16,400 --> 00:05:19,760 和我们父进程do_fork返回值是不一样的 144 00:05:19,800 --> 00:05:21,240 但是它们的控制点都是在 145 00:05:21,280 --> 00:05:23,280 do_fork返回这一点去执行的 146 00:05:23,320 --> 00:05:26,600 大家再回顾一下我们原理课讲的内容 147 00:05:26,640 --> 00:05:28,000 执行完do_fork之后 148 00:05:28,040 --> 00:05:30,480 我们父进程会得到一个子进程的PID 149 00:05:30,520 --> 00:05:32,280 而我们子进程呢 150 00:05:32,320 --> 00:05:35,200 会得到一个零继续执行 151 00:05:35,240 --> 00:05:35,880 它们就已经成了 152 00:05:35,920 --> 00:05:38,600 两条控制流去执行去了 153 00:05:38,640 --> 00:05:40,760 那大家对这个执行过程 154 00:05:40,800 --> 00:05:41,720 有深入理解之后呢 155 00:05:41,760 --> 00:05:43,120 对照执行代码就可以 156 00:05:43,160 --> 00:05:45,480 更好完成我们练习二的内容 157 00:05:45,520 --> 00:05:46,040 158 00:05:46,080 --> 00:05:46,120