0 00:00:00,000 --> 00:00:15,720 1 00:00:15,760 --> 00:00:17,320 各位同学大家好 2 00:00:17,360 --> 00:00:24,960 那我们接下来给大家介绍一下Lab 1的实验过程 3 00:00:25,000 --> 00:00:29,200 Lab 1主要是关于操作系统如何启动 4 00:00:29,240 --> 00:00:33,160 以及如何去和中断函数调用栈 5 00:00:33,200 --> 00:00:38,120 相关的一些知识 给大家做一个介绍 6 00:00:38,160 --> 00:00:40,800 这里面呢 会从以下几个方面 7 00:00:40,840 --> 00:00:42,680 比如说X86的启动顺序 8 00:00:42,720 --> 00:00:46,480 这里面涉及到当硬件一启动之后 9 00:00:46,520 --> 00:00:49,920 怎么把操作系统放到内存中去运行 10 00:00:49,960 --> 00:00:54,240 这是讲这一块涉及到的一些基本的知识 11 00:00:54,280 --> 00:00:56,480 第二个是讲C函数调用 12 00:00:56,520 --> 00:00:57,080 因为我们知道 13 00:00:57,120 --> 00:01:00,760 在操作系统里面有很多的函数之间(的调用) 14 00:01:00,800 --> 00:01:03,640 C以及函数之间 C和汇编之间的调用关系 15 00:01:03,680 --> 00:01:06,880 那么它到底在具体细节上面 16 00:01:06,920 --> 00:01:08,360 怎么来实现一个函数调用呢 17 00:01:08,400 --> 00:01:10,200 我们要有所了解 18 00:01:10,240 --> 00:01:12,320 以及还有一部分是关于GCC 19 00:01:12,360 --> 00:01:15,080 GCC是我们编译内核的一个编译器 20 00:01:15,120 --> 00:01:17,400 它里面有很方便的内联汇编 21 00:01:17,440 --> 00:01:21,200 有了内联汇编之后我们可以在C语言里面 22 00:01:21,240 --> 00:01:24,000 就是C的文件里面嵌入汇编代码 23 00:01:24,040 --> 00:01:27,080 就不用单独写一个汇编的一个文件 24 00:01:27,120 --> 00:01:29,200 这样相对来说编起来会更加容易 25 00:01:29,240 --> 00:01:32,080 但是它有它的一些相应的规则 26 00:01:32,120 --> 00:01:33,560 这样的规则给大家做一个简单的介绍 27 00:01:33,600 --> 00:01:39,880 使得大家能够看懂 在我们的Lab 1中出现的汇编代码 28 00:01:39,920 --> 00:01:42,440 再接下来是关于中断这一块 29 00:01:42,480 --> 00:01:44,920 一旦我们的操作系统启动之后 30 00:01:44,960 --> 00:01:48,480 它很重要的事情就是要能够接管中断 31 00:01:48,520 --> 00:01:51,440 中断有很多功能 我们这边会讲一下 32 00:01:51,480 --> 00:01:54,640 在X86-32这个硬件下 它的中断处理 33 00:01:54,680 --> 00:01:57,000 也就是uCore的中断处理是怎么来完成的 34 00:01:57,040 --> 00:01:59,240 它怎么和硬件的这些特征来结合 35 00:01:59,280 --> 00:02:02,680 来完成跟中断相关的一系列的管理工作 36 00:02:02,720 --> 00:02:04,520 最后是小结 37 00:02:04,560 --> 00:02:10,040 这是我们Lab 1大致要讲的内容 38 00:02:10,080 --> 00:02:12,120 首先我们看一下这个启动过程 39 00:02:12,160 --> 00:02:14,400 那对于X86的启动过程来说 40 00:02:14,440 --> 00:02:17,760 我们需要了解它这个硬件平台从加电一下开始 41 00:02:17,800 --> 00:02:19,800 你一按电源开关之后 42 00:02:19,840 --> 00:02:22,600 那大致的一个整个执行过程是怎么一回事 43 00:02:22,640 --> 00:02:26,880 这需要有了解 怎么就启动到我们的uCore操作系统去了 44 00:02:26,920 --> 00:02:31,800 第二个要理解这个X86的一个实模式 保护模式 45 00:02:31,840 --> 00:02:34,320 我们前面在lab0的时候给大家介绍过 46 00:02:34,360 --> 00:02:37,520 实模式 保护模式有它的不同的特征 47 00:02:37,560 --> 00:02:40,960 那我们在这里面 为什么一开始的实模式 48 00:02:41,000 --> 00:02:44,520 后面又进入了保护模式 在保护模式下干什么事情 49 00:02:44,560 --> 00:02:48,280 以及去理解一旦进入保护模式之后 50 00:02:48,320 --> 00:02:50,560 段机制是怎么一回事 51 00:02:50,600 --> 00:02:57,240 这个是X86启动 在启动相关的一些知识需要去了解 52 00:02:57,280 --> 00:03:01,160 首先看一下 当我们的X86硬件加电之后 53 00:03:01,200 --> 00:03:02,840 会出现什么现象 54 00:03:02,880 --> 00:03:08,640 这个其实是讲了关于因特尔不同的处理器 55 00:03:08,680 --> 00:03:12,000 基本上是80386以后出现的处理器 56 00:03:12,040 --> 00:03:15,640 当一加电 相当于做了一次热起之后 57 00:03:15,680 --> 00:03:16,600 会出现什么现象 58 00:03:16,640 --> 00:03:19,080 那么它首先肯定会有一个初始状态 59 00:03:19,120 --> 00:03:23,200 那么可以看到这所有的寄存器列在这 60 00:03:23,240 --> 00:03:26,560 它的初始状态下 都有一个缺省的值 61 00:03:26,600 --> 00:03:30,000 那么跟我们uCore相关的寄存器有哪些 62 00:03:30,040 --> 00:03:32,280 大家需要注意 63 00:03:32,320 --> 00:03:39,320 这里面最主要的是它的启动地址 64 00:03:39,360 --> 00:03:40,400 那启动地址在哪呢 65 00:03:40,440 --> 00:03:43,200 第一个 段地址CS 66 00:03:43,240 --> 00:03:45,960 CS和EIP结合在一起 67 00:03:46,000 --> 00:03:47,560 来决定它启动的第一条地址 68 00:03:47,600 --> 00:03:48,840 那么EIP在这儿 69 00:03:48,880 --> 00:03:51,120 这两个结合在一起 70 00:03:51,160 --> 00:03:55,000 形成了启动之后 加电之后的一个地址 71 00:03:55,040 --> 00:03:58,160 只要一开电 那么CS EIP就设置成这么一个值 72 00:03:58,200 --> 00:04:03,800 这个值就决定了它在哪个地址取得相应的指令去执行 73 00:04:03,840 --> 00:04:05,480 这也是需要注意的 74 00:04:05,520 --> 00:04:08,240 其它还有一些标志位等等也很重要 75 00:04:08,280 --> 00:04:10,400 比如EFLAGS 我们前面介绍的标志位 76 00:04:10,440 --> 00:04:12,000 还有一些控制寄存器 77 00:04:12,040 --> 00:04:14,560 比如说我们后面会讲到CR0控制寄存器 78 00:04:14,600 --> 00:04:16,200 一般应用程序都不会碰到 79 00:04:16,240 --> 00:04:18,840 但是在我们操作系统里面会需要跟它打交道 80 00:04:18,880 --> 00:04:22,200 还有其他一些通用寄存器 也都会有相应的初始值 81 00:04:22,240 --> 00:04:23,920 这个比较简单都是零 82 00:04:23,960 --> 00:04:25,840 那我们重点关注的是什么呢 83 00:04:25,880 --> 00:04:31,600 先看一下CS和EIP 84 00:04:31,640 --> 00:04:33,120 那刚才已经看到了 85 00:04:33,160 --> 00:04:35,240 CS在初始的时候是F000 86 00:04:35,280 --> 00:04:37,960 EIP它是32位的 所以说四个零 87 00:04:38,000 --> 00:04:40,560 三个F一个0 88 00:04:40,600 --> 00:04:41,560 但是我们需要注意 89 00:04:41,600 --> 00:04:45,120 当X86一开始加电时候 启动是实模式 90 00:04:45,160 --> 00:04:48,080 早期的为了向下兼容 91 00:04:48,120 --> 00:04:51,720 以前的80 86一开始启动是16位的实模式 92 00:04:51,760 --> 00:04:54,680 在这种情况下 寻址按照实模式的寻址方式 93 00:04:54,720 --> 00:04:57,440 所以说 它是Base加上EIP 94 00:04:57,480 --> 00:04:59,840 CS的Base是多少呢 95 00:04:59,880 --> 00:05:01,240 这里要介绍一下 96 00:05:01,280 --> 00:05:02,920 CS是段寄存器 97 00:05:02,960 --> 00:05:07,080 段寄存器里面有一个隐含的叫做Base的内容 98 00:05:07,120 --> 00:05:08,560 这个Base代表基址 99 00:05:08,600 --> 00:05:11,800 这个基址 其实就是存的值 100 00:05:11,840 --> 00:05:15,000 刚才已经看到了是FFFF0000 101 00:05:15,040 --> 00:05:19,000 再加上刚才的EIP是FFF0 102 00:05:19,040 --> 00:05:23,840 所以说它们启动最终的一个地址是FFFFFFF0这个地址 103 00:05:23,880 --> 00:05:26,520 这个地址 其实就是我们加电之后 104 00:05:26,560 --> 00:05:29,280 要去取得那个内存所在的地址 105 00:05:29,320 --> 00:05:31,320 这个内存什么地方呢 比较奇怪 106 00:05:31,360 --> 00:05:35,520 这个内存是BIOS我们说 在PC中的一个固件 107 00:05:35,560 --> 00:05:39,920 叫EPROM 它所在的一块内存区域中的一个地址 108 00:05:39,960 --> 00:05:44,880 但是需要注意这个地址是只读的一块地址 109 00:05:44,920 --> 00:05:48,920 然后从这个地址会取得第一条指令 110 00:05:48,960 --> 00:05:52,480 这条指令一般是一条长跳转指令 111 00:05:52,520 --> 00:05:55,360 会跳到BIOS中去做初始化工作 112 00:05:55,400 --> 00:05:58,520 所以你可以看到 一开始的时候 113 00:05:58,560 --> 00:06:00,640 它会从这么一个特殊的地址 114 00:06:00,680 --> 00:06:11,400 会跳到一个可以被访问的1M的内存空间里面去执行 115 00:06:11,440 --> 00:06:14,880 这其实也是符合我们说的 在实模式环境下 116 00:06:14,920 --> 00:06:20,560 其实它的寻址空间只有1M这么一个特征 117 00:06:20,600 --> 00:06:22,560 我们接下来的实验其实有一个环节 118 00:06:22,600 --> 00:06:26,640 看看到底这个BIOS从第一条指令 119 00:06:26,680 --> 00:06:29,320 到底从哪开始执行 跳到哪去 120 00:06:29,360 --> 00:06:32,520 我们是在Lab 1有一个小的练习 121 00:06:32,560 --> 00:06:35,280 让大家去尝试去找一找 122 00:06:35,320 --> 00:06:37,480 好 当处于实模式 123 00:06:37,520 --> 00:06:41,640 根据我们说的CS EIP它的寻址 124 00:06:41,680 --> 00:06:43,480 CS它包含的是什么呢 125 00:06:43,520 --> 00:06:46,280 到了实模式里面 它这里面一共是 126 00:06:46,320 --> 00:06:49,000 CS是16位的一个段寄存器 127 00:06:49,040 --> 00:06:51,680 然后加上一个offset offset就是EIP 128 00:06:51,720 --> 00:06:52,840 里面也是有16位的地址 129 00:06:52,880 --> 00:06:56,760 一共为什么形成20位的地址 130 00:06:56,800 --> 00:07:01,360 是在于我们16位的段寄存器左移了四位 131 00:07:01,400 --> 00:07:05,920 左移了四位之后再叠加上我们的IP地址 IP里面的值 132 00:07:05,960 --> 00:07:09,080 才形成了所谓的最终的在实模式下的寻址方式 133 00:07:09,120 --> 00:07:12,520 所以它的寻址CS:IP 134 00:07:12,560 --> 00:07:15,400 其实这个:IP的意思就相当于CS左移了四位 135 00:07:15,440 --> 00:07:17,600 再加上IP形成的地址 136 00:07:17,640 --> 00:07:21,720 这是在实模式情况下的寻址方式 137 00:07:21,760 --> 00:07:26,120 这里面没有后面讲到的段机制 或者页机制 138 00:07:26,160 --> 00:07:30,200 因为还没有进入所谓的保护模式 139 00:07:30,240 --> 00:07:35,160 好 那假设BIOS完成它的工作 140 00:07:35,200 --> 00:07:40,080 BIOS做什么工作呢 其实主要是做一些硬件的初始化工作 141 00:07:40,120 --> 00:07:41,320 底层的硬件初始化工作 142 00:07:41,360 --> 00:07:45,400 保证这个机器能够进行后续的正常工作 143 00:07:45,440 --> 00:07:49,400 完成很多各种外设 CPU内存的质检 144 00:07:49,440 --> 00:07:52,600 完成这个检查之后它会干什么事情 145 00:07:52,640 --> 00:07:54,040 很重要 在这点呢 146 00:07:54,080 --> 00:08:01,240 这个固件会去加载磁盘或者硬盘的第一个主引导扇区 147 00:08:01,280 --> 00:08:03,840 这个主引导扇区是零号扇区 148 00:08:03,880 --> 00:08:07,320 把这里面的内容读到内存中来 149 00:08:07,360 --> 00:08:08,520 一个扇区的内容是多少呢 150 00:08:08,560 --> 00:08:09,960 一个扇区是512个字节 151 00:08:10,000 --> 00:08:11,720 会把这512个字节的内容 152 00:08:11,760 --> 00:08:15,880 读到一个固定的地址 0X7C00处 153 00:08:15,920 --> 00:08:17,760 读到这个地址 相当于是把这一块磁盘 154 00:08:17,800 --> 00:08:21,000 空间的信息加载到内存中去 155 00:08:21,040 --> 00:08:26,760 同时把它的IP地址跳到这个地方来 跳到0X7C00 156 00:08:26,800 --> 00:08:34,840 这样 使得它可以去执行这个扇区里面的代码 157 00:08:34,880 --> 00:08:36,520 那这个扇区里面的代码是什么呢 158 00:08:36,560 --> 00:08:39,240 其实我们说Lab 1一里面的bootloader 159 00:08:39,280 --> 00:08:43,320 就属于512个字节这么一个特殊的执行代码 160 00:08:43,360 --> 00:08:45,000 它完成什么工作呢 161 00:08:45,040 --> 00:08:50,600 它完成来对我们说的这个操作系统 uCore的进一步加载 162 00:08:50,640 --> 00:08:53,040 有同学比较好奇 163 00:08:53,080 --> 00:09:00,240 说那为什么我们的BIOS不直接加载uCore操作系统 164 00:09:00,280 --> 00:09:03,400 大家想想 这里面其实取决于BIOS的能力问题 165 00:09:03,440 --> 00:09:05,360 因为刚开始在设计的时候 166 00:09:05,400 --> 00:09:08,120 它完成的功能就只是加载一个扇区 167 00:09:08,160 --> 00:09:09,880 而我们知道一个操作系统 168 00:09:09,920 --> 00:09:14,760 它的代码容量是大于512个字节 它会比较大 169 00:09:14,800 --> 00:09:16,880 那这种情况下 你如果说靠BIOS 170 00:09:16,920 --> 00:09:21,440 来负责加载一个复杂的很大容量的一个OS的话 171 00:09:21,480 --> 00:09:22,160 其实不太现实 172 00:09:22,200 --> 00:09:24,560 增加BIOS工作的难度 173 00:09:24,600 --> 00:09:28,960 所以说干脆BIOS只加载一个扇区 174 00:09:29,000 --> 00:09:32,600 而这个扇区里面的代码会完成后续的加载工作 175 00:09:32,640 --> 00:09:35,560 那这个扇区我们称之为Bootloader 176 00:09:35,600 --> 00:09:38,320 177 00:09:38,360 --> 00:09:44,920 好 既然我们说这个主引导扇区里面存着Bootloader的代码 178 00:09:44,960 --> 00:09:46,880 那么它要干什么事情呢 179 00:09:46,920 --> 00:09:48,360 别小看这512个字节 180 00:09:48,400 --> 00:09:53,280 在这里面真正执行的代码可能只有不到512个字节 181 00:09:53,320 --> 00:09:54,720 可能只有400多个字节 182 00:09:54,760 --> 00:09:57,160 这么点字节数 它能实现什么功能 183 00:09:57,200 --> 00:10:00,120 大家想想 其实它完成的事还挺多的 184 00:10:00,160 --> 00:10:05,640 第一个它要干的事情 首先要从实模式切换到保护模式 185 00:10:05,680 --> 00:10:10,920 为后续我操作系统的执行做准备 186 00:10:10,960 --> 00:10:12,280 这是干的第一个事情 187 00:10:12,320 --> 00:10:16,880 就是从实模式的16位的寻址空间切换到了32位的寻址空间 188 00:10:16,920 --> 00:10:20,200 从1M的寻址到了4G的寻址 189 00:10:20,240 --> 00:10:22,280 这是它干的第一个事情 190 00:10:22,320 --> 00:10:27,080 一旦Enable(使能)了这个保护模式 191 00:10:27,120 --> 00:10:31,240 也就意味着这个所谓的段机制也就自动的加载上来了 192 00:10:31,280 --> 00:10:34,520 就也使能了 也就是段机制可以正常工作了 193 00:10:34,560 --> 00:10:38,800 为此你要让它正常工作 必然要做相应的一些初始化工作 194 00:10:38,840 --> 00:10:40,640 这个事我们后面会讲到 195 00:10:40,680 --> 00:10:41,800 假设它进入了保护模式 196 00:10:41,840 --> 00:10:43,200 干的第二件事情干什么呢 197 00:10:43,240 --> 00:10:47,120 就是读取kernel 就是uCore的代码 198 00:10:47,160 --> 00:10:49,480 又从哪读呢 也是一样 存在我们的硬盘中 199 00:10:49,520 --> 00:10:53,160 它需要从硬盘里面把uCore的代码 200 00:10:53,200 --> 00:10:56,640 再从我们的硬盘的扇区里面读到内存中来 201 00:10:56,680 --> 00:10:58,440 但是这里面也可以看到 202 00:10:58,480 --> 00:11:02,400 即使是我们最开始的Lab 1涉及到的uCore代码量 203 00:11:02,440 --> 00:11:03,840 也不仅仅是一个扇区 204 00:11:03,880 --> 00:11:05,640 它会涉及到多个扇区 205 00:11:05,680 --> 00:11:08,120 所以它会去读取多个扇区的内容 206 00:11:08,160 --> 00:11:09,880 然后读到内存的固定地点 207 00:11:09,920 --> 00:11:13,160 然后也一样 和类似于BIOS干的工作一样 208 00:11:13,200 --> 00:11:18,400 最后把控制权交给uCore操作系统进一步执行 209 00:11:18,440 --> 00:11:21,560 怎么交 就只是把它所谓的EIP的值 210 00:11:21,600 --> 00:11:27,840 就是CS EIP的值指向我们操作系统内核所在内存中的起始点 211 00:11:27,880 --> 00:11:30,560 这个entry 有个入口点 跳这来 212 00:11:30,600 --> 00:11:34,560 之后就相当于是把控制权交给了uCore OS去执行 213 00:11:34,600 --> 00:11:36,200 这是Bootloader做的事情 214 00:11:36,240 --> 00:11:38,320 最后我们会结合代码给大家展示一下 215 00:11:38,360 --> 00:11:40,800 就是它到底怎么来完成它的功能 216 00:11:40,840 --> 00:11:42,320 217 00:11:42,360 --> 00:11:44,400 好 在这里面呢 218 00:11:44,440 --> 00:11:45,720 首先需要理解个概念 219 00:11:45,760 --> 00:11:48,680 我们基本上是按照Bootloader这个执行过程 220 00:11:48,720 --> 00:11:51,120 来讲解这里面涉及到的一些 221 00:11:51,160 --> 00:11:53,880 跟X86相关的一些硬件细节 222 00:11:53,920 --> 00:11:54,960 那这些细节呢 223 00:11:55,000 --> 00:11:58,920 和我们操作系统里面对应的内存访问机制 224 00:11:58,960 --> 00:12:01,120 中断管理机制有直接的联系 225 00:12:01,160 --> 00:12:02,440 所以大家可以去看一看 226 00:12:02,480 --> 00:12:06,080 是否这里面讲的内容和那里面能够有个对应 227 00:12:06,120 --> 00:12:07,320 但是这里面的内容 228 00:12:07,360 --> 00:12:11,080 相对来说跟我们原理课讲的信息相比 229 00:12:11,120 --> 00:12:14,680 它更加琐碎一些 更加细节一些 230 00:12:14,720 --> 00:12:17,960 但这个琐碎和细节 可以让你更清楚的了解 231 00:12:18,000 --> 00:12:21,360 一个OS怎么来完成相应的中断管理 232 00:12:21,400 --> 00:12:25,840 怎么来完成相应的段的处理机制 233 00:12:25,880 --> 00:12:28,960 第一个可以看看 所谓的段机制 234 00:12:29,000 --> 00:12:30,880 段机制什么意思呢 其实你看看 235 00:12:30,920 --> 00:12:33,880 这里面段寄存器起了一个特殊的作用 236 00:12:33,920 --> 00:12:37,400 它这里面呢 它起了一个指针的作用 237 00:12:37,440 --> 00:12:39,720 它指向了段描述符 238 00:12:39,760 --> 00:12:41,440 在段描述符里面呢 239 00:12:41,480 --> 00:12:45,120 描述了一个段落的起始地址和它的大小 240 00:12:45,160 --> 00:12:47,800 这是最直接的两个特点 241 00:12:47,840 --> 00:12:51,400 所以说 我们说可以根据CS里面的Index的值 242 00:12:51,440 --> 00:12:55,680 来找到uCore代码段的起始地址在什么地方 243 00:12:55,720 --> 00:12:57,360 它的大小是什么地方 244 00:12:57,400 --> 00:12:59,160 这是我们认为可以这么来做的 245 00:12:59,200 --> 00:13:04,080 同理 数据段也可以用其它的一些特定的一些寄存器 246 00:13:04,120 --> 00:13:08,240 段寄存器 比如ES 或者DS等等来表述 247 00:13:08,280 --> 00:13:10,360 我们的堆栈段也可以用SS来表述 248 00:13:10,400 --> 00:13:12,240 这样看起来比较自然 249 00:13:12,280 --> 00:13:16,080 但是需要注意 但是由于我们后面还有页机制 250 00:13:16,120 --> 00:13:20,280 所以在段机制这一块 它的映射关系就搞得尽量简单 251 00:13:20,320 --> 00:13:23,640 什么叫简单呢 就是说它的限制 252 00:13:23,680 --> 00:13:28,000 在uCore设置里面它的限制是4G 253 00:13:28,040 --> 00:13:29,840 也就是说它的段落大小是4G 254 00:13:29,880 --> 00:13:32,680 它的段的起始地址从零开始 255 00:13:32,720 --> 00:13:36,480 其实也是意味着它的空间顶满4G空间 256 00:13:36,520 --> 00:13:40,400 应该说它是想起到一个分段的功能 257 00:13:40,440 --> 00:13:42,880 这个功能把它弱化了 258 00:13:42,920 --> 00:13:44,680 基本上是没有这个功能了 259 00:13:44,720 --> 00:13:45,800 它后面还有其他的功能 260 00:13:45,840 --> 00:13:47,480 还有特权级的一个保护 261 00:13:47,520 --> 00:13:51,120 想通过一个段 比如代码段 数据段 262 00:13:51,160 --> 00:13:53,480 想用段机制来实现这种分割 263 00:13:53,520 --> 00:13:55,000 在uCore里面没有做到 264 00:13:55,040 --> 00:13:56,880 这里面也有一个因是在于 265 00:13:56,920 --> 00:14:00,360 这份功能的实现 和后续的页机制的实现 266 00:14:00,400 --> 00:14:02,120 在功能上有一定的重叠 267 00:14:02,160 --> 00:14:04,040 我可以用段机制来实现这种分段 268 00:14:04,080 --> 00:14:06,440 我也可以用页机制来实现分段 269 00:14:06,480 --> 00:14:07,760 所以说相对而言 270 00:14:07,800 --> 00:14:09,600 我们更倾向于用页机制来实现分段 271 00:14:09,640 --> 00:14:13,760 这是我们后面 再讲Lab 2的时候会涉及到的内容 272 00:14:13,800 --> 00:14:18,040 这里面是给大家提醒一下 273 00:14:18,080 --> 00:14:20,960 好 但是这个段模式你不能消掉 274 00:14:21,000 --> 00:14:22,040 因为只要起了保护模式 275 00:14:22,080 --> 00:14:26,960 在X86里面 规定这个段就Enable了 276 00:14:27,000 --> 00:14:31,000 而且你的页机制还是建立在段机制的基础之上实现的 277 00:14:31,040 --> 00:14:35,800 那我们可以看到 能够把段机制建立好 278 00:14:35,840 --> 00:14:37,960 虽然它完成的功能是一个 279 00:14:38,000 --> 00:14:40,680 近似于对等的这么一个映射关系 280 00:14:40,720 --> 00:14:44,000 但是我们要把映射关系给建立好 281 00:14:44,040 --> 00:14:49,440 在一个段寄存器里面 会保存一块区域叫做段选择址 282 00:14:49,480 --> 00:14:51,840 这个选择址就是我们刚才说的是Index 283 00:14:51,880 --> 00:14:53,320 它的值就代表Index 284 00:14:53,360 --> 00:15:00,440 这个Index会查找一个在段描述符表里面的一个项 285 00:15:00,480 --> 00:15:05,520 叫做段descripter 就是段描述符 286 00:15:05,560 --> 00:15:08,400 找到段描述符之后 这个描述符是对应什么来的 287 00:15:08,440 --> 00:15:10,920 它是对应Index 因为这可以意味是一个数组 288 00:15:10,960 --> 00:15:12,320 找到这么一项 289 00:15:12,360 --> 00:15:16,840 然后这里面 段描述符里面会存着刚才说的很重要的两个信息 290 00:15:16,880 --> 00:15:20,040 就是它的起始地址和它的大小 291 00:15:20,080 --> 00:15:22,520 那起始地址加上一个Offset 292 00:15:22,560 --> 00:15:27,960 这里Offset就是我们的EIP 293 00:15:28,000 --> 00:15:30,240 就是偏移量 EIP偏移量 294 00:15:30,280 --> 00:15:38,000 那么这个EIP加上由CS 或者说其他段寄存器所指出来的基址 295 00:15:38,040 --> 00:15:40,520 叠加在一起形成线性地址 296 00:15:40,560 --> 00:15:42,760 在这里面 我们前面讲到 297 00:15:42,800 --> 00:15:44,920 由于还没有启动页机制 298 00:15:44,960 --> 00:15:51,520 所以说线性地址就等同于物理地址 299 00:15:51,560 --> 00:15:53,040 所以说我们可以看到 300 00:15:53,080 --> 00:15:55,360 所谓的段机制其实是一种映射关系 301 00:15:55,400 --> 00:15:59,080 如果我们在这个段描述符里面 302 00:15:59,120 --> 00:16:02,920 把这个Base的Address设成零 303 00:16:02,960 --> 00:16:09,160 那也意味着你的EIP的这个值也就是它的物理基址 304 00:16:09,200 --> 00:16:11,520 这是最简洁的一种设置方式 305 00:16:11,560 --> 00:16:16,320 306 00:16:16,360 --> 00:16:18,400 好 我们另外也可以看看 307 00:16:18,440 --> 00:16:21,440 怎么能够把这个机制给建立好 308 00:16:21,480 --> 00:16:24,840 这里面很重要一点就是你需要有一个大的数组 309 00:16:24,880 --> 00:16:26,840 把各个段描述符给装进去 310 00:16:26,880 --> 00:16:29,920 数组在哪 数组是由我们操作系统来建立的 311 00:16:29,960 --> 00:16:35,360 那我们把它称之为全局描述符表 简称GDT 312 00:16:35,400 --> 00:16:40,040 那么这个GDT是由我们Bootloader来建立的 313 00:16:40,080 --> 00:16:42,240 虽然只有不到512个字节 314 00:16:42,280 --> 00:16:44,320 但是它可以把这个建立好 315 00:16:44,360 --> 00:16:50,160 它会描述好段描述符表的一个大致的空间 316 00:16:50,200 --> 00:16:53,120 然后给出它的位置和它的大小 317 00:16:53,160 --> 00:16:56,920 然后通过一个特殊的指令 比如GDT 318 00:16:56,960 --> 00:17:00,200 然后把这个GDT Descriptor这个地方列出来 319 00:17:00,240 --> 00:17:02,080 这里面给出了一个GDT的一个描述 320 00:17:02,120 --> 00:17:07,640 就可以让我们的CPU能够找到段表的起始地址 321 00:17:07,680 --> 00:17:10,760 GDT全局描述表我们也简称段表 322 00:17:10,800 --> 00:17:14,240 这个表的起始地址之后 323 00:17:14,280 --> 00:17:19,080 通过内部的寄存器叫做GDTR这个寄存器 324 00:17:19,120 --> 00:17:23,360 来保存这个相应的地址信息 325 00:17:23,400 --> 00:17:27,440 然后使得我们各个CS DS SS等等 326 00:17:27,480 --> 00:17:31,640 它可以和我们GDT表建立对应关系 327 00:17:31,680 --> 00:17:33,600 当然这个GDT表是你设置好的 328 00:17:33,640 --> 00:17:36,160 假设有四项 有五项 329 00:17:36,200 --> 00:17:39,680 那么你得CS可能二 三 四 五都有可能 330 00:17:39,720 --> 00:17:45,840 从而可以指向GDT大数组里面所对应的描述符 331 00:17:45,880 --> 00:17:49,520 当然在这里面你首先理解GDT之后 332 00:17:49,560 --> 00:17:53,320 再看GDT里面的每一项 333 00:17:53,360 --> 00:17:59,120 这个表称之为段描述符的一个很详细的表述 334 00:17:59,160 --> 00:18:01,720 虽然细节很多 但其实我们最主要关注两项 335 00:18:01,760 --> 00:18:03,000 我们前面说过的 336 00:18:03,040 --> 00:18:07,160 第一个就是基址(Base)在什么地方 337 00:18:07,200 --> 00:18:12,080 第二个这个段的长度(Limit)是多大 338 00:18:12,120 --> 00:18:14,680 我们也讲到了 在uCore里面把这个功能给它弱化 339 00:18:14,720 --> 00:18:17,720 基址都是零 段的长度都是4G 340 00:18:17,760 --> 00:18:24,200 所以在这里面也等同于一个最简单的对等映射 341 00:18:24,240 --> 00:18:25,960 好 我们在看看这边 342 00:18:26,000 --> 00:18:29,680 这边表明刚才说到的各个段基寄存器 343 00:18:29,720 --> 00:18:33,040 它怎么来产生这个Index 344 00:18:33,080 --> 00:18:38,400 就是这里面的全局描述表中的一个索引值 345 00:18:38,440 --> 00:18:41,960 其实大家都知道 前面说到了段寄存器 346 00:18:42,000 --> 00:18:44,760 它一共大致有16位 347 00:18:44,800 --> 00:18:57,840 其中的高13位放的就是GDT的Index 348 00:18:57,880 --> 00:19:01,800 然后接下来的两位 放的是一个叫做RP 349 00:19:01,840 --> 00:19:09,640 表明这个段当前的段的优先级的级别 350 00:19:09,680 --> 00:19:12,240 在X86里面 它用了两个bit来表示这个优先级别 351 00:19:12,280 --> 00:19:16,600 意味着它可以表示0 1 2 3四个特权级 352 00:19:16,640 --> 00:19:18,560 优先级 或者叫特权级 353 00:19:18,600 --> 00:19:23,520 那我们一般说我们操作系统放在最高的级别是0特权级 354 00:19:23,560 --> 00:19:27,040 而我们的应用程序会放在3这个特权级里面 355 00:19:27,080 --> 00:19:30,800 所以说这个标记 段寄存器里面的这个标记 356 00:19:30,840 --> 00:19:32,560 表明了它当前执行的时候 357 00:19:32,600 --> 00:19:37,120 特别是CS 当前执行的时候所处的特权级 358 00:19:37,160 --> 00:19:39,600 这个TI我们暂时忽略 我们一般设置为零 359 00:19:39,640 --> 00:19:40,880 因为我们这边用到是GDT 360 00:19:40,920 --> 00:19:45,280 我们这里面整个没有用到所谓的LDT 361 00:19:45,320 --> 00:19:46,840 本地描述符表没有使用 362 00:19:46,880 --> 00:19:50,120 我们使用的是全局描述符表 363 00:19:50,160 --> 00:19:57,840 好 解释了段选择址 全局描述符表 364 00:19:57,880 --> 00:20:01,720 然后是什么呢 这个是段描述符 365 00:20:01,760 --> 00:20:06,640 这个是指向全局描述符表起始地址GDTR的一个寄存器 366 00:20:06,680 --> 00:20:09,720 有了这些之后呢 我们就可以看到 367 00:20:09,760 --> 00:20:13,000 映射关系就建立好了 368 00:20:13,040 --> 00:20:15,080 建立好这个映射关系之后 369 00:20:15,120 --> 00:20:16,320 你还没有使能(Enable)它 370 00:20:16,360 --> 00:20:20,520 使能代表现在进入了保护模式 371 00:20:20,560 --> 00:20:22,800 段机制也能够正常工作 372 00:20:22,840 --> 00:20:25,480 当你建好所有的这些前期的准备工作之后 373 00:20:25,520 --> 00:20:28,040 我们还差最后一步 最后一步是靠什么呢 374 00:20:28,080 --> 00:20:32,920 靠对一个特定的寄存器 系统寄存器CRO 375 00:20:32,960 --> 00:20:35,360 这个寄存器我们称之为系统性寄存器 376 00:20:35,400 --> 00:20:37,720 或者控制寄存器 对于这个寄存器 377 00:20:37,760 --> 00:20:45,440 把它的第0号bit置成1 378 00:20:45,480 --> 00:20:47,360 那么就意味着现在的系统 379 00:20:47,400 --> 00:20:51,240 我们的CPU会进入到保护模式 380 00:20:51,280 --> 00:20:54,640 好 可以看到我们前面再总结一下 381 00:20:54,680 --> 00:20:56,280 你需要建好一个GDT 382 00:20:56,320 --> 00:21:01,120 这里面GDT里面的每一项是一个段描述符 383 00:21:01,160 --> 00:21:05,400 然后我还要把相应的段的段寄存器 384 00:21:05,440 --> 00:21:08,720 CS DS等设置好对应的Index 385 00:21:08,760 --> 00:21:12,680 使得CS DS等这些段寄存器 386 00:21:12,720 --> 00:21:17,040 能够指向全局描述符表GDT对应的项 387 00:21:17,080 --> 00:21:19,160 这个项我们称之为段描述符 388 00:21:19,200 --> 00:21:22,280 这个描述符指出映射关系 389 00:21:22,320 --> 00:21:26,280 从而可以在使能了保护机制之后 390 00:21:26,320 --> 00:21:28,480 使段机制能够正常的工作 391 00:21:28,520 --> 00:21:31,320 这实际上就是说启动保护模式 392 00:21:31,360 --> 00:21:33,680 在X86里面启动保护模式 393 00:21:33,720 --> 00:21:39,200 Bootloader要干的基本的一些事情 394 00:21:39,240 --> 00:21:40,880 好 进入了保护模式之后 395 00:21:40,920 --> 00:21:43,080 Bootloader要干第二个很重要的事情 396 00:21:43,120 --> 00:21:47,640 干什么呢 就是要加载uCore OS 397 00:21:47,680 --> 00:21:49,560 那我们的uCore OS编译出来 398 00:21:49,600 --> 00:21:54,720 后面会给大家讲Demo这个的源代码 399 00:21:54,760 --> 00:21:58,040 编译完之后会生成一个elf格式的一个执行程序 400 00:21:58,080 --> 00:22:00,800 elf格式的执行程序是我们在Linux里面 401 00:22:00,840 --> 00:22:04,640 很常用的一种执行文件的格式 402 00:22:04,680 --> 00:22:09,840 那么我们需要了解到uCore 它的名字叫kernel 403 00:22:09,880 --> 00:22:12,600 kernel这个文件它的内部的信息 404 00:22:12,640 --> 00:22:15,600 从而使得我们Bootloader能够根据这个文件的格式 405 00:22:15,640 --> 00:22:19,440 能够把uCore相应的代码 数据 406 00:22:19,480 --> 00:22:21,320 给放到内存中相应的地址 407 00:22:21,360 --> 00:22:25,640 这是我们说接下来BootLoader要干的很重要的第二个事情 408 00:22:25,680 --> 00:22:26,800 那它怎么能放呢 409 00:22:26,840 --> 00:22:31,840 需要去解析这个ELF格式的信息 410 00:22:31,880 --> 00:22:34,440 ELF里面有一个头 叫ELFheader 411 00:22:34,480 --> 00:22:38,880 ELFheader里面呢 指出了一个Program Header 412 00:22:38,920 --> 00:22:41,120 程序段的头 413 00:22:41,160 --> 00:22:43,360 程序段里面包含了代码段 数据段等等 414 00:22:43,400 --> 00:22:44,760 各种各样的程序段 415 00:22:44,800 --> 00:22:46,280 它会把这个头这个信息给表述出来 416 00:22:46,320 --> 00:22:52,120 比如说这里面有一个叫做Program Header的Table的Offset 417 00:22:52,160 --> 00:22:54,080 相当于这个ELFheader的偏移地址 418 00:22:54,120 --> 00:22:55,440 它的起始地址在什么地方 419 00:22:55,480 --> 00:22:59,240 以及它的个数 这里面有个个数PHnumber 420 00:22:59,280 --> 00:23:01,320 有了这两个信息之后 421 00:23:01,360 --> 00:23:06,560 我们可以进一步查找Program header这个结构 422 00:23:06,600 --> 00:23:08,840 知道它的偏移位置之后 423 00:23:08,880 --> 00:23:10,760 把结构里的信息读出来 424 00:23:10,800 --> 00:23:12,160 很重要的两点 425 00:23:12,200 --> 00:23:14,720 它的虚地址要往哪个地方放 426 00:23:14,760 --> 00:23:17,800 因为你这个编出来的代码是要在某一个特定的地址上运行的 427 00:23:17,840 --> 00:23:20,320 它的位置在什么地方 这有个va 428 00:23:20,360 --> 00:23:22,360 以及这一块 这个Program Header 429 00:23:22,400 --> 00:23:25,160 比如我们说的可能是一个代码段 430 00:23:25,200 --> 00:23:27,120 它的起始地址在什么地方 431 00:23:27,160 --> 00:23:32,840 它的size是多少 代码段有多大 432 00:23:32,880 --> 00:23:41,920 这两个信息可以便于把内存中的相应一块区域 433 00:23:41,960 --> 00:23:46,240 用于存放我们的uCore代码段或者数据段 434 00:23:46,280 --> 00:23:49,680 还是从这个文件的哪个位置开始读呢 435 00:23:49,720 --> 00:23:51,560 这里有个Offset 436 00:23:51,600 --> 00:23:57,640 从这可以知道我们从文件哪个位置把代码段读进来 437 00:23:57,680 --> 00:24:00,160 把代码段读进来 把数据段读进来 438 00:24:00,200 --> 00:24:06,080 这是说我们这个加载ELF格式的uCore OS的一个大致的流程 439 00:24:06,120 --> 00:24:09,520 大致就是说它能够识别出很重要的一些关键信息 440 00:24:09,560 --> 00:24:11,160 然后把相应的代码段 数据段 441 00:24:11,200 --> 00:24:14,840 从我们的文件读到我们的内存中来 442 00:24:14,880 --> 00:24:19,440 另一个问题 我们这边还没讲到文件系统 443 00:24:19,480 --> 00:24:21,720 它读的是什么 444 00:24:21,760 --> 00:24:24,640 其实它读的是最原始的磁盘扇区 445 00:24:24,680 --> 00:24:26,320 它是把一个一个的磁盘扇区 446 00:24:26,360 --> 00:24:29,800 刚才说的Bootloader之后的连续的磁盘扇区 447 00:24:29,840 --> 00:24:32,840 连续读了四个磁盘扇区 读到内存里面来 448 00:24:32,880 --> 00:24:37,560 然后开始完成相应的分析工作 449 00:24:37,600 --> 00:24:40,520 但是需要注意 随着里面的uCore的size进一步增加 450 00:24:40,560 --> 00:24:43,640 也许后面还不止四个扇区 451 00:24:43,680 --> 00:24:49,800 可能还有更多的扇区 这是很正常的 452 00:24:49,840 --> 00:24:56,040 好 那我们大致就把Bootloader启动的过程 453 00:24:56,080 --> 00:25:00,920 它进入保护模式 加载uCore操作系统 454 00:25:00,960 --> 00:25:02,000 给大家做了简单介绍 455 00:25:02,040 --> 00:25:08,280 这里面会涉及到了一些保护模式 段机制的建立等等 456 00:25:08,320 --> 00:25:15,120 这一块的信息大家可以阅读相关的一些文档和网站 457 00:25:15,160 --> 00:25:18,400 这里面确实和硬件相关的信息比较大 458 00:25:18,440 --> 00:25:20,240 我希望大家能够读懂代码 459 00:25:20,280 --> 00:25:21,720 能够理解它什么意思 460 00:25:21,760 --> 00:25:22,440 基本上就OK了 461 00:25:22,480 --> 00:25:22,520 462 00:25:22,560 --> 00:25:22,600