0 00:00:00,000 --> 00:00:06,960 1 00:00:07,000 --> 00:00:08,560 好 那接下来我们看一下 2 00:00:08,600 --> 00:00:10,360 这个进程的内存布局 3 00:00:10,400 --> 00:00:12,360 这个布局呢和我们之前的 4 00:00:12,400 --> 00:00:13,280 前面的实验 5 00:00:13,320 --> 00:00:14,680 它的内存布局是不一样的 6 00:00:14,720 --> 00:00:15,560 尤其它虚存管理 7 00:00:15,600 --> 00:00:18,240 有它自己的一些考虑 8 00:00:18,280 --> 00:00:20,280 9 00:00:20,320 --> 00:00:21,440 那首先我们可以看到 10 00:00:21,480 --> 00:00:24,960 我们的这个内核的虚拟内存布局 11 00:00:25,000 --> 00:00:28,520 那这部分和就是前面的lab是一样的 12 00:00:28,560 --> 00:00:30,760 lab4之前是一样的 13 00:00:30,800 --> 00:00:32,280 它有一块第一个是 14 00:00:32,320 --> 00:00:34,200 对实际物理空间的一个映射 15 00:00:34,240 --> 00:00:38,800 从0xc0000000一直到0xf8000000 16 00:00:38,840 --> 00:00:39,760 这一块区域呢 17 00:00:39,800 --> 00:00:41,840 我们会映射为一个物理空间 18 00:00:41,880 --> 00:00:44,320 它们呢只是相差0xc0000000 19 00:00:44,360 --> 00:00:47,040 这么一个差值的一个一一映射 20 00:00:47,080 --> 00:00:48,800 然后呢还有一块是页表的建立 21 00:00:48,840 --> 00:00:50,640 就是当前的page table 22 00:00:50,680 --> 00:00:52,600 这个page table呢 在一开始时候 23 00:00:52,640 --> 00:00:56,560 只是存在在这一块虚拟空间里面 24 00:00:56,600 --> 00:00:57,320 管理的呢 25 00:00:57,360 --> 00:00:59,520 管理是整个这个内核空间 26 00:00:59,560 --> 00:01:01,400 里面的一个映射关系 27 00:01:01,440 --> 00:01:02,560 这是之前的 28 00:01:02,600 --> 00:01:05,800 但一旦有了这个用户进程之后 29 00:01:05,840 --> 00:01:07,800 我们的页表需要扩展 30 00:01:07,840 --> 00:01:09,240 我们的段表也需要扩展 31 00:01:09,280 --> 00:01:10,240 我们前面讲段表里面 32 00:01:10,280 --> 00:01:13,640 会增加一个用户的代码段和数据段 33 00:01:13,680 --> 00:01:16,320 它特权级是3 34 00:01:16,360 --> 00:01:18,080 而不是我们之前说的0 35 00:01:18,120 --> 00:01:20,120 所以说你可以看到特权级它降低了 36 00:01:20,160 --> 00:01:21,840 3是最低的一个特权级 37 00:01:21,880 --> 00:01:23,680 同时它的page table里面 38 00:01:23,720 --> 00:01:27,400 也需要管理用户态的空间 39 00:01:27,440 --> 00:01:30,080 40 00:01:30,120 --> 00:01:31,360 那我们来看一下 41 00:01:31,400 --> 00:01:34,480 这个关于用户空间的这个进程 42 00:01:34,520 --> 00:01:36,320 它的一个内存布局怎么回事 43 00:01:36,360 --> 00:01:37,800 那么如果是个用户进程 44 00:01:37,840 --> 00:01:39,400 它的空间是这么一个区域 45 00:01:39,440 --> 00:01:40,920 在一个低端的位置 46 00:01:40,960 --> 00:01:42,000 那低端位置里面 47 00:01:42,040 --> 00:01:45,080 虽然0xc0000000以下的地址呢 48 00:01:45,120 --> 00:01:46,960 都其实分给了用户空间 49 00:01:47,000 --> 00:01:48,840 但其实我们用户空间的程序呢 50 00:01:48,880 --> 00:01:51,000 只用了一部分 51 00:01:51,040 --> 00:01:52,720 可以看到这两个部分 52 00:01:52,760 --> 00:01:53,600 是最主要内容 53 00:01:53,640 --> 00:01:55,800 第一个它有一个堆栈 54 00:01:55,840 --> 00:01:57,080 第二个有它的代码段 55 00:01:57,120 --> 00:01:58,360 数据段和它的堆 56 00:01:58,400 --> 00:02:00,560 这个堆是用于动态内存分配用的 57 00:02:00,600 --> 00:02:01,640 那么这两块区域 58 00:02:01,680 --> 00:02:03,600 是合法使用的区域 没有问题 59 00:02:03,640 --> 00:02:05,080 就是写应用程序的时候 60 00:02:05,120 --> 00:02:06,360 可以充分的利用这两个区域 61 00:02:06,400 --> 00:02:07,880 完成函数调用 62 00:02:07,920 --> 00:02:08,880 完成数据访问 63 00:02:08,920 --> 00:02:13,560 完成一些计算等等 64 00:02:13,600 --> 00:02:14,800 这个区域呢 65 00:02:14,840 --> 00:02:16,040 STAB有点奇怪 66 00:02:16,080 --> 00:02:17,360 这个区域在这个位置 67 00:02:17,400 --> 00:02:18,880 那么放什么东西呢 68 00:02:18,920 --> 00:02:20,360 放的是调试信息 69 00:02:20,400 --> 00:02:22,480 我们说一个应用程序可以被调试 70 00:02:22,520 --> 00:02:23,440 可以被debug 71 00:02:23,480 --> 00:02:24,240 那是由于它里面 72 00:02:24,280 --> 00:02:28,040 有一个很重要一个源码的数据和 73 00:02:28,080 --> 00:02:30,280 符号和具体的地址一个对应关系 74 00:02:30,320 --> 00:02:33,280 在这里就存在一个关系便于我们debug 75 00:02:33,320 --> 00:02:37,320 当然我们这里面可以暂时不用理会 76 00:02:37,360 --> 00:02:40,960 另外一块有三个invalid memory这个区域 77 00:02:41,000 --> 00:02:43,160 它们其实都在低地址空间 78 00:02:43,200 --> 00:02:47,880 但是我们把设置成invalid memory就是非法区域 79 00:02:47,920 --> 00:02:48,920 那非法区域什么意思呢 80 00:02:48,960 --> 00:02:50,080 就如果你的应用程序 81 00:02:50,120 --> 00:02:52,360 访问了这块区域的地址 82 00:02:52,400 --> 00:02:54,800 它会报错 会产生page fault 83 00:02:54,840 --> 00:02:56,600 那为什么设成非法区域呢 84 00:02:56,640 --> 00:02:58,600 是在于我们应用程序通常来说 85 00:02:58,640 --> 00:03:00,360 比如说你有一个空指针 86 00:03:00,400 --> 00:03:01,360 空指针是0 87 00:03:01,400 --> 00:03:02,160 空指针一般是0 88 00:03:02,200 --> 00:03:03,920 这个0地址呢非法的 89 00:03:03,960 --> 00:03:04,880 只要你访到这儿 90 00:03:04,920 --> 00:03:05,640 它的页表没有对应的映射关系 91 00:03:05,680 --> 00:03:07,440 它就会产生page fault 92 00:03:07,480 --> 00:03:09,520 可以看出来前面讲页表的那一块 93 00:03:09,560 --> 00:03:10,440 需要把这个 94 00:03:10,480 --> 00:03:12,960 整个这个空间呢给建立有效的映射 95 00:03:13,000 --> 00:03:17,560 这两块user stack和heap program建立映射 96 00:03:17,600 --> 00:03:20,600 但是这个红色的invalid memory这一块呢 97 00:03:20,640 --> 00:03:21,520 不建立映射 98 00:03:21,560 --> 00:03:24,080 它可以确保当我们的应用程序 99 00:03:24,120 --> 00:03:25,880 访问到这块区域的时候 100 00:03:25,920 --> 00:03:27,320 会产生page fault 101 00:03:27,360 --> 00:03:28,960 那这个产生这个好处什么呢 102 00:03:29,000 --> 00:03:30,080 其实是便于我们 103 00:03:30,120 --> 00:03:32,000 查出我们这个程序的问题 104 00:03:32,040 --> 00:03:33,680 第二个呢我们也说到 105 00:03:33,720 --> 00:03:35,400 通过内核的page table呢 106 00:03:35,440 --> 00:03:36,120 我们可以对 107 00:03:36,160 --> 00:03:39,280 进程的访问空间做个限制 108 00:03:39,320 --> 00:03:41,200 可以使得你不能够随意访问 109 00:03:41,240 --> 00:03:42,600 任意的地址空间 110 00:03:42,640 --> 00:03:44,080 这也是我们说进程管理 111 00:03:44,120 --> 00:03:46,240 很重要的一个功能 112 00:03:46,280 --> 00:03:48,800 113 00:03:48,840 --> 00:03:50,880 接下来我们看一下怎么能够 114 00:03:50,920 --> 00:03:54,080 就是加载ELF格式的二进制代码 115 00:03:54,120 --> 00:03:55,960 比如说我们刚才说的hello 116 00:03:56,000 --> 00:03:58,560 能够到我们这个用户的进程空间去执行 117 00:03:58,600 --> 00:04:01,720 这是我们第二部要考虑的问题 118 00:04:01,760 --> 00:04:03,720 首先给大家简单介绍 119 00:04:03,760 --> 00:04:07,160 它大致的执行过程 120 00:04:07,200 --> 00:04:08,680 我们说这个图呢 121 00:04:08,720 --> 00:04:10,840 可以看出来是我们一般应用程序 122 00:04:10,880 --> 00:04:12,160 是放在什么地方呢 123 00:04:12,200 --> 00:04:13,480 放在我们硬盘里面的 124 00:04:13,520 --> 00:04:14,880 比如放在执行程序 125 00:04:14,920 --> 00:04:16,600 然后呢通过操作系统呢 126 00:04:16,640 --> 00:04:19,120 会加载到内存中去执行 127 00:04:19,160 --> 00:04:21,000 这是我们通常说的就是 128 00:04:21,040 --> 00:04:23,360 一般操作系统做的这个功能 129 00:04:23,400 --> 00:04:25,560 我们现在在lab5这个阶段呢还做不到 130 00:04:25,600 --> 00:04:26,960 但是当我们完成最后一个lab 131 00:04:27,000 --> 00:04:29,440 lab8的时候我们可以达到同样的功能 132 00:04:29,480 --> 00:04:32,360 那我们现在呢更简单一点 133 00:04:32,400 --> 00:04:33,200 那是什么呢 134 00:04:33,240 --> 00:04:36,200 是没有我们的这个文件系统 135 00:04:36,240 --> 00:04:37,680 和我们的存储 136 00:04:37,720 --> 00:04:39,960 没有storage 没有file system 137 00:04:40,000 --> 00:04:41,360 我们就memory OK 138 00:04:41,400 --> 00:04:43,280 我们一开始时候我们的bootloader 139 00:04:43,320 --> 00:04:45,960 已经把我们的kernel和程序 140 00:04:46,000 --> 00:04:47,160 放到内存中来了 141 00:04:47,200 --> 00:04:49,520 那这个程序呢 142 00:04:49,560 --> 00:04:51,000 是这么一个架构 143 00:04:51,040 --> 00:04:54,400 一开始的时候呢ucore kernel 144 00:04:54,440 --> 00:04:57,520 和我们这个应用程序是放在一起的 145 00:04:57,560 --> 00:04:59,120 存在我们的地址上面 146 00:04:59,160 --> 00:05:00,600 然后我们的bootloader呢 147 00:05:00,640 --> 00:05:02,760 一下把这些东西都加载到memory中来了 148 00:05:02,800 --> 00:05:05,760 那可以导致一个什么后果呢 149 00:05:05,800 --> 00:05:08,880 在后续的创建进程过程中呢 150 00:05:08,920 --> 00:05:11,560 我们会创建一个壳 151 00:05:11,600 --> 00:05:13,560 然后从直接从内存中 152 00:05:13,600 --> 00:05:16,320 把这个program放进来去执行 153 00:05:16,360 --> 00:05:18,680 那么通过do_execve 154 00:05:18,720 --> 00:05:24,400 这么一个内核的函数来完成什么呢 155 00:05:24,440 --> 00:05:26,960 把这个用户进程创建好 156 00:05:27,000 --> 00:05:29,560 且把相应的这个程序 157 00:05:29,600 --> 00:05:31,240 放在中间去执行 158 00:05:31,280 --> 00:05:35,400 那么这是说do_execve这么一个函数 159 00:05:35,440 --> 00:05:36,360 它要完成主要功能也是 160 00:05:36,400 --> 00:05:39,280 我们后面要重点讲解的一个功能 161 00:05:39,320 --> 00:05:46,760 就是怎么去执行一个用户的进程 162 00:05:46,800 --> 00:05:48,800 那我们再看一下这里面提到 163 00:05:48,840 --> 00:05:50,760 用户进程壳的概念 164 00:05:50,800 --> 00:05:52,960 我们在lab4中用到什么呢 165 00:05:53,000 --> 00:05:54,680 用到是一个叫做TCB 166 00:05:54,720 --> 00:05:56,600 就是线程控制块 167 00:05:56,640 --> 00:05:58,080 我们这边是什么 168 00:05:58,120 --> 00:05:59,320 进程控制块 169 00:05:59,360 --> 00:06:00,400 进程控制块和线程控制块 170 00:06:00,440 --> 00:06:03,040 有什么区别呢 171 00:06:03,080 --> 00:06:04,080 大家想一想 172 00:06:04,120 --> 00:06:05,560 对这个控制块 173 00:06:05,600 --> 00:06:10,040 本身的数据结构而言 174 00:06:10,080 --> 00:06:13,480 可以看到它也是用的proc_struct 175 00:06:13,520 --> 00:06:16,120 就是我们说在lab4中 176 00:06:16,160 --> 00:06:18,080 同样的一个数据结构 177 00:06:18,120 --> 00:06:19,680 完全一样 没有区别 178 00:06:19,720 --> 00:06:22,480 大家可以回顾一下在lab4中 179 00:06:22,520 --> 00:06:23,920 我们重点讲解了分析了 180 00:06:23,960 --> 00:06:25,600 这么一个TCB的一个结构 181 00:06:25,640 --> 00:06:28,200 叫Thread Control Block 那我们这里 182 00:06:28,240 --> 00:06:31,240 把它换一个名字叫Process Control Block 183 00:06:31,280 --> 00:06:32,840 但是用的结构是完全一样 184 00:06:32,880 --> 00:06:35,040 可以看出来在ucore里面呢 185 00:06:35,080 --> 00:06:37,680 我们的进程管理和线程管理 186 00:06:37,720 --> 00:06:39,600 所共用同一个数据结构 187 00:06:39,640 --> 00:06:41,240 就是所谓的进程控制块 188 00:06:41,280 --> 00:06:43,360 和线程控制块其实是一样的 189 00:06:43,400 --> 00:06:45,160 这是没有什么区别的 190 00:06:45,200 --> 00:06:45,240 191 00:06:45,280 --> 00:06:45,640 192 00:06:45,680 --> 00:06:45,680