0 00:00:00,000 --> 00:00:06,800 1 00:00:06,840 --> 00:00:10,160 好 我们看一下怎么来完成执行一个 2 00:00:10,200 --> 00:00:11,880 ELF格式的二进制代码 3 00:00:11,920 --> 00:00:14,000 这里面最主要就是这个函数 4 00:00:14,040 --> 00:00:19,360 do_execve 这个函数怎么来实现的 5 00:00:19,400 --> 00:00:20,200 那这个过程呢 6 00:00:20,240 --> 00:00:22,200 其实我们用一个通俗的比喻来说 7 00:00:22,240 --> 00:00:24,000 就好比说一个海螺本来 8 00:00:24,040 --> 00:00:26,600 是可以正常存在的一个生物 9 00:00:26,640 --> 00:00:29,120 结果有一个小螃蟹把海螺里面肉 10 00:00:29,160 --> 00:00:31,480 给吃掉了换了自己住在里面 11 00:00:31,520 --> 00:00:33,000 那结果导致呢这个海螺 12 00:00:33,040 --> 00:00:35,200 有了新的生命它成了寄居蟹 13 00:00:35,240 --> 00:00:36,960 这是一个简单一个比喻 14 00:00:37,000 --> 00:00:39,120 其实我们说这个do_execve 15 00:00:39,160 --> 00:00:40,880 干的是同样类型类型的事情 16 00:00:40,920 --> 00:00:42,560 首先它需要干的一步 17 00:00:42,600 --> 00:00:44,560 就是要把以前的资源 18 00:00:44,600 --> 00:00:46,640 这里面主要是内存空间给去掉 19 00:00:46,680 --> 00:00:48,360 但是还保留它的pid 20 00:00:48,400 --> 00:00:49,880 这个壳还留着 21 00:00:49,920 --> 00:00:52,680 然后呢再去把自己的内容换进去 22 00:00:52,720 --> 00:00:53,640 那我们重点要看看 23 00:00:53,680 --> 00:00:55,560 怎么来换自己的内容 24 00:00:55,600 --> 00:00:56,520 第一步我们先看 25 00:00:56,560 --> 00:00:58,840 怎么来把这个空间给清空 26 00:00:58,880 --> 00:00:59,800 怎么做的呢 27 00:00:59,840 --> 00:01:02,680 首先它是把它的这个页表 28 00:01:02,720 --> 00:01:06,120 cr3这个页表基址指向了bootcr3 29 00:01:06,160 --> 00:01:07,200 这bootcr3其实是我们 30 00:01:07,240 --> 00:01:09,160 ucore内核里面一个页表 31 00:01:09,200 --> 00:01:11,080 走向内核页表里面去执行了 32 00:01:11,120 --> 00:01:13,040 第二步呢完成了什么呢 33 00:01:13,080 --> 00:01:16,160 这三步退出mmap 34 00:01:16,200 --> 00:01:18,280 put_pgdir和mm_destroy 35 00:01:18,320 --> 00:01:19,680 那可以大致理解为 36 00:01:19,720 --> 00:01:21,080 它把这个进程的 37 00:01:21,120 --> 00:01:23,600 内存管理那一块的区域给清空 38 00:01:23,640 --> 00:01:26,120 把对应的页表清空 39 00:01:26,160 --> 00:01:27,360 所以说呢导致 40 00:01:27,400 --> 00:01:30,080 这里面的内存已经没有了 41 00:01:30,120 --> 00:01:31,800 OK 那第一步完成 42 00:01:31,840 --> 00:01:33,680 把这壳给清空了 43 00:01:33,720 --> 00:01:37,360 第二步要填入新的内存 44 00:01:37,400 --> 00:01:38,680 那这个填新内容呢 45 00:01:38,720 --> 00:01:41,480 重点是这个load_icode这里面完成的 46 00:01:41,520 --> 00:01:42,600 虽然是这一个函数 47 00:01:42,640 --> 00:01:44,200 但是这个函数完成功能确实比较多 48 00:01:44,240 --> 00:01:46,840 我们会单独对此做一个讲解 49 00:01:46,880 --> 00:01:49,440 那这个load_icode它会把执行程序 50 00:01:49,480 --> 00:01:51,040 比如说我们前面说的hello world 51 00:01:51,080 --> 00:01:54,360 给加载到这个新的壳里面去 52 00:01:54,400 --> 00:01:56,560 建立新的内存的映射关系 53 00:01:56,600 --> 00:02:01,000 从而可以去完成新的执行 54 00:02:01,040 --> 00:02:02,840 我们看看如果说 55 00:02:02,880 --> 00:02:05,360 这个load_icode能够执行完毕 56 00:02:05,400 --> 00:02:06,320 能够正确执行完毕 57 00:02:06,360 --> 00:02:09,880 那么这个整个这个函数都do_execve呢 58 00:02:09,920 --> 00:02:11,840 就可以正确返回了 59 00:02:11,880 --> 00:02:14,040 这个是它的一个大致执行过程 60 00:02:14,080 --> 00:02:16,680 看起来其实重点是在 61 00:02:16,720 --> 00:02:21,720 这个load_icode这里面来实现的 62 00:02:21,760 --> 00:02:23,720 接下来我们可以看看这个load_icode 63 00:02:23,760 --> 00:02:26,880 怎么来把这个同样的一个进程 64 00:02:26,920 --> 00:02:31,000 但是内容完全做了替换 65 00:02:31,040 --> 00:02:33,600 它的过程大致这么来可以看 66 00:02:33,640 --> 00:02:35,720 第一个前面已经就是说 67 00:02:35,760 --> 00:02:39,360 把整个内存管理mm struct给清空了 68 00:02:39,400 --> 00:02:41,640 那么首先要创建一个新的memory space 69 00:02:41,680 --> 00:02:43,000 新的内存space 70 00:02:43,040 --> 00:02:46,880 那可以看到mm_creat和setup_pgdir 71 00:02:46,920 --> 00:02:48,760 从字面意思可以理解出来 72 00:02:48,800 --> 00:02:49,640 这个进程的 73 00:02:49,680 --> 00:02:52,320 内存管理一个空间呢重新创建 74 00:02:52,360 --> 00:02:54,280 建立好新的一个页表 75 00:02:54,320 --> 00:02:56,280 这是第一部分的工作 76 00:02:56,320 --> 00:02:59,920 空间先留出来 77 00:02:59,960 --> 00:03:01,600 那么一旦留出来之后 78 00:03:01,640 --> 00:03:03,520 你可以看到这里面的结构 79 00:03:03,560 --> 00:03:04,760 已经发生了变化 80 00:03:04,800 --> 00:03:08,960 它的内存已经指向不同的地方 81 00:03:09,000 --> 00:03:11,600 第二个呢 要填上我们说 82 00:03:11,640 --> 00:03:13,280 执行代码的内容 83 00:03:13,320 --> 00:03:14,640 这个执行代码内容在哪呢 84 00:03:14,680 --> 00:03:15,720 我们前面已经讲到了 85 00:03:15,760 --> 00:03:16,560 我们的bootloader 86 00:03:16,600 --> 00:03:18,760 一开始在加载ucore的时候呢 87 00:03:18,800 --> 00:03:20,880 顺道把那个放在hello world 88 00:03:20,920 --> 00:03:23,440 执行程序呢也一并加到内存中来了 89 00:03:23,480 --> 00:03:25,880 所以说我们只要知道那个hello world 90 00:03:25,920 --> 00:03:27,560 在内存中起始地址 91 00:03:27,600 --> 00:03:30,440 去解析那个ELF格式的执行程序 92 00:03:30,480 --> 00:03:33,080 就可以找到hello world 93 00:03:33,120 --> 00:03:35,960 它对应的代码段 数据段什么地方 94 00:03:36,000 --> 00:03:37,800 那可以看到在这里面它会重点关注 95 00:03:37,840 --> 00:03:41,320 是这个ELF格式的header它在什么地方 96 00:03:41,360 --> 00:03:44,600 以及根据header呢会进一步找到什么呢 97 00:03:44,640 --> 00:03:45,800 找到它的各个Section 98 00:03:45,840 --> 00:03:46,680 就是各个段 99 00:03:46,720 --> 00:03:47,560 比如说我们代码段 100 00:03:47,600 --> 00:03:49,680 数据段在哪 101 00:03:49,720 --> 00:03:51,360 这是把相应的代码段 102 00:03:51,400 --> 00:03:55,000 数据段找着之后 103 00:03:55,040 --> 00:03:58,040 根据这个代码段和数据段所设定的地址 104 00:03:58,080 --> 00:04:01,560 这里面会设定它的虚地址来建立一个vma 105 00:04:01,600 --> 00:04:02,480 这个vma这个结构 106 00:04:02,520 --> 00:04:04,600 大家想想我们在哪讲过呢 107 00:04:04,640 --> 00:04:06,800 我们在lab3里面其实已经提到一个VMA 108 00:04:06,840 --> 00:04:10,320 就是认为这个进程合法的地址空间 109 00:04:10,360 --> 00:04:12,280 我们用VMA这个结构来管理 110 00:04:12,320 --> 00:04:13,400 那这个结构呢 111 00:04:13,440 --> 00:04:15,760 如果是代码段那么它具有什么属性呢 112 00:04:15,800 --> 00:04:17,160 具有可执行的属性 113 00:04:17,200 --> 00:04:18,560 如果是数据段呢 114 00:04:18,600 --> 00:04:20,840 可读可写 115 00:04:20,880 --> 00:04:22,360 那么这块区域呢 116 00:04:22,400 --> 00:04:26,520 通过mm_map来完成了对 117 00:04:26,560 --> 00:04:29,480 这个合法空间的一个建立 118 00:04:29,520 --> 00:04:30,320 大家需要注意 119 00:04:30,360 --> 00:04:34,120 这个建立完之后还没有建立页表 120 00:04:34,160 --> 00:04:35,320 它只是标识了这个 121 00:04:35,360 --> 00:04:36,720 说这个是合法的 122 00:04:36,760 --> 00:04:37,520 在这一块 123 00:04:37,560 --> 00:04:38,680 再接下来呢 124 00:04:38,720 --> 00:04:40,160 一边从我们刚才说 125 00:04:40,200 --> 00:04:42,560 那个放hello world内存空间呢 126 00:04:42,600 --> 00:04:45,120 把相应的各个section考进来 127 00:04:45,160 --> 00:04:46,360 这里面可以看到 128 00:04:46,400 --> 00:04:47,560 这边是拷贝 129 00:04:47,600 --> 00:04:49,040 同时还要完成一个什么呢 130 00:04:49,080 --> 00:04:51,320 就是拷到一个物理内存空间之后 131 00:04:51,360 --> 00:04:52,920 来建立相应的虚地址 132 00:04:52,960 --> 00:04:54,520 和物理地址的映射关系 133 00:04:54,560 --> 00:04:58,360 那么这一块就是拷贝内容 134 00:04:58,400 --> 00:05:00,040 拷贝这个执行代码内容 135 00:05:00,080 --> 00:05:02,160 到我们这个进程空间里面去 136 00:05:02,200 --> 00:05:04,160 这样呢就形成了一个 137 00:05:04,200 --> 00:05:06,520 就是新的一个地址 138 00:05:06,560 --> 00:05:07,440 这个地址里面存的是 139 00:05:07,480 --> 00:05:09,160 我们新的执行代码 140 00:05:09,200 --> 00:05:10,320 且这个虚拟地址 141 00:05:10,360 --> 00:05:11,600 和物理地址映射关系 142 00:05:11,640 --> 00:05:13,400 也已经建好了 143 00:05:13,440 --> 00:05:14,760 再接下来干什么事情呢 144 00:05:14,800 --> 00:05:16,160 准备all zero memory 145 00:05:16,200 --> 00:05:17,360 什么叫all zero memory 146 00:05:17,400 --> 00:05:20,120 实际上说我们有一些bss段 147 00:05:20,160 --> 00:05:22,120 我们执行程序里面有一个bss段 148 00:05:22,160 --> 00:05:24,040 这个段里面的数据呢需要清空 149 00:05:24,080 --> 00:05:26,720 那么我会把这个区域给清空 150 00:05:26,760 --> 00:05:31,160 这是初始化的一些数据 151 00:05:31,200 --> 00:05:32,720 好 那我们可以看到 152 00:05:32,760 --> 00:05:34,000 刚才已经把 153 00:05:34,040 --> 00:05:36,760 ELF格式的二进制代码的内容 154 00:05:36,800 --> 00:05:37,880 从一个memory空间呢 155 00:05:37,920 --> 00:05:40,120 搬到我们进程空间里面去了 156 00:05:40,160 --> 00:05:41,600 搬到进程空间里去之后 157 00:05:41,640 --> 00:05:43,240 接下来还不能运行 158 00:05:43,280 --> 00:05:44,520 因为我们还没有设置 159 00:05:44,560 --> 00:05:46,480 相应的堆栈空间 160 00:05:46,520 --> 00:05:47,560 这个空间呢 161 00:05:47,600 --> 00:05:49,600 是不在我们的binary code里面的 162 00:05:49,640 --> 00:05:52,440 我们需要去构造一个user stack 163 00:05:52,480 --> 00:05:54,040 就用户态的一个stack 164 00:05:54,080 --> 00:05:55,800 从而可以确保我们应用程序去 165 00:05:55,840 --> 00:05:57,720 有效去执行各种各样的 166 00:05:57,760 --> 00:05:59,080 函数调用关系 167 00:05:59,120 --> 00:06:00,840 可以看到这里面又重新建立一个 168 00:06:00,880 --> 00:06:03,720 mm_map的user stack 169 00:06:03,760 --> 00:06:05,360 以及它的一个相应的一个 170 00:06:05,400 --> 00:06:07,120 页表的映射关系 171 00:06:07,160 --> 00:06:08,920 那就是我们前面说user stack的 172 00:06:08,960 --> 00:06:14,200 那一块区域呢给它建立好了 173 00:06:14,240 --> 00:06:16,400 那 建好这个映射关系之后 174 00:06:16,440 --> 00:06:17,760 也意味着这个内存空间 175 00:06:17,800 --> 00:06:20,000 这一块区域基本上是完成了 176 00:06:20,040 --> 00:06:22,920 包含了代码段 数据段 栈 177 00:06:22,960 --> 00:06:25,280 我们的stack都已经建立好 178 00:06:25,320 --> 00:06:25,960 建好之后 179 00:06:26,000 --> 00:06:28,560 我们最后还需要把我们刚才 180 00:06:28,600 --> 00:06:30,200 我们的页表的起始地址 181 00:06:30,240 --> 00:06:32,640 从ucore内核的起始地址呢 182 00:06:32,680 --> 00:06:34,480 换到我们新的一个起始地址 183 00:06:34,520 --> 00:06:37,080 我们新建好一个mm->pgdir 184 00:06:37,120 --> 00:06:38,600 因为我们已经创建的新的页表 185 00:06:38,640 --> 00:06:40,120 整个页表是新创建的 186 00:06:40,160 --> 00:06:41,760 当把这个完成之后 187 00:06:41,800 --> 00:06:46,360 也意味着这个的用户空间已经完成 188 00:06:46,400 --> 00:06:46,720 189 00:06:46,760 --> 00:06:47,160 190 00:06:47,200 --> 00:06:47,240