0 00:00:00,000 --> 00:00:06,800 1 00:00:06,840 --> 00:00:09,520 既然ring 3的应用程序 2 00:00:09,560 --> 00:00:12,000 无法去有效的访问ring 0的数据的话 3 00:00:12,040 --> 00:00:13,320 那么我们怎么能够实现 4 00:00:13,360 --> 00:00:15,400 我们前面说的一个很重要的一个因素 5 00:00:15,440 --> 00:00:17,440 就是我们的操作系统 6 00:00:17,480 --> 00:00:19,880 要给我们的应用程序提供服务 7 00:00:19,920 --> 00:00:21,200 那既然它都不能访问了 8 00:00:21,240 --> 00:00:22,800 它怎么能提供服务呢 9 00:00:22,840 --> 00:00:26,240 其实我们还是需要有一种手段 10 00:00:26,280 --> 00:00:28,920 来实现不同特权级的一个跳转 11 00:00:28,960 --> 00:00:31,200 比如说我们应用程序选择了内核的访问 12 00:00:31,240 --> 00:00:34,360 通过一种机制要让我们控制流 13 00:00:34,400 --> 00:00:37,400 从应用程序跳到内核态去执行 14 00:00:37,440 --> 00:00:39,240 这是怎么来实现的 15 00:00:39,280 --> 00:00:42,120 在X86硬件架构里面有多种实现方式 16 00:00:42,160 --> 00:00:44,000 实现不同特权级的跳转 17 00:00:44,040 --> 00:00:46,080 但是在这里面我们只用其中一种 18 00:00:46,120 --> 00:00:47,240 就是基于中断 19 00:00:47,280 --> 00:00:50,640 通过中断来实现这个优先级 20 00:00:50,680 --> 00:00:52,680 或者特权级的转换 21 00:00:52,720 --> 00:00:54,720 那怎么通过中断来实现 22 00:00:54,760 --> 00:00:58,000 这里面有必要对中断要做一定的讲解 23 00:00:58,040 --> 00:00:59,760 我们前面其实在lab1里面 24 00:00:59,800 --> 00:01:02,040 已经对中断有所了解 25 00:01:02,080 --> 00:01:04,840 其实中断它会有一个中断门 26 00:01:04,880 --> 00:01:07,000 那么有了中断门之后 我们其实就可以 27 00:01:07,040 --> 00:01:09,440 通过我们前面讲到的中断描述符表 28 00:01:09,480 --> 00:01:13,280 建立好这个中断门来实现跳转 29 00:01:13,320 --> 00:01:14,680 这里面可以看到 30 00:01:14,720 --> 00:01:17,120 这里面中断的特权级的改变 31 00:01:17,160 --> 00:01:18,840 首先我们可以看到 32 00:01:18,880 --> 00:01:21,880 我们正在执行的时候 33 00:01:21,920 --> 00:01:24,760 它是在用这么一个栈在执行 34 00:01:24,800 --> 00:01:28,600 然后如果说这时候产生了中断 35 00:01:28,640 --> 00:01:29,720 中断有很多种方式 36 00:01:29,760 --> 00:01:32,080 比如说异常 外部中断 37 00:01:32,120 --> 00:01:33,880 或者是软中断等等 38 00:01:33,920 --> 00:01:35,120 我们说软中断就相当于是 39 00:01:35,160 --> 00:01:37,720 我们通过INT这条指令来产生中断 40 00:01:37,760 --> 00:01:40,440 41 00:01:40,480 --> 00:01:42,600 当我们这个中断产生之后 42 00:01:42,640 --> 00:01:43,640 会出现一个变化 43 00:01:43,680 --> 00:01:44,960 那么这是产生中断之前 44 00:01:45,000 --> 00:01:46,480 这是产生中断之后 45 00:01:46,520 --> 00:01:48,840 那么我们一旦产生了中断 46 00:01:48,880 --> 00:01:52,320 在我们的内核态ring 0这个栈里面 47 00:01:52,360 --> 00:01:54,760 会压入一系列的一些数据 48 00:01:54,800 --> 00:01:58,280 以便于恢复被打断的程序继续执行 49 00:01:58,320 --> 00:02:01,960 压东西包含了压在内核ring 0 50 00:02:02,000 --> 00:02:03,160 这个栈里面的 51 00:02:03,200 --> 00:02:06,640 包括什么SS ES EFLAGS CS 和EIP 52 00:02:06,680 --> 00:02:08,880 那么前两个SS ES代表的是 53 00:02:08,920 --> 00:02:11,160 当前被打断的程序 54 00:02:11,200 --> 00:02:13,320 它的一个堆栈信息 55 00:02:13,360 --> 00:02:14,400 那么EFLAGS代表 56 00:02:14,440 --> 00:02:16,520 它当前执行的时候一些标志位 57 00:02:16,560 --> 00:02:18,000 比如是否溢出 58 00:02:18,040 --> 00:02:20,720 这些所谓的标志执行加 减这些 59 00:02:20,760 --> 00:02:22,640 可以看出来是否溢出 60 00:02:22,680 --> 00:02:24,000 那么这些标志实际上是 61 00:02:24,040 --> 00:02:25,640 放在EFLAGS里面的 62 00:02:25,680 --> 00:02:28,240 还有就是它的返回地址CS和EIP 63 00:02:28,280 --> 00:02:30,640 就是打断之后 这个地址 64 00:02:30,680 --> 00:02:33,240 如果执行完毕这个中断处理例程之后 65 00:02:33,280 --> 00:02:35,000 还要回去继续执行 66 00:02:35,040 --> 00:02:37,000 这保存了它回去的地址 67 00:02:37,040 --> 00:02:38,480 也可能还会有一些是错误代码 68 00:02:38,520 --> 00:02:40,000 这个异常相关 69 00:02:40,040 --> 00:02:42,640 那这是产生中断之前 70 00:02:42,680 --> 00:02:43,720 这是产生中断之后 71 00:02:43,760 --> 00:02:46,160 那么可以看到它的堆栈会产生变化 72 00:02:46,200 --> 00:02:47,760 那么这个栈我们再强调一下这个栈 73 00:02:47,800 --> 00:02:49,960 属于内核态的ring 0态的这个栈 74 00:02:50,000 --> 00:02:52,240 75 00:02:52,280 --> 00:02:56,200 好 我们说产生中断之后你一定是说 76 00:02:56,240 --> 00:02:57,960 它会用到我们ring 0的栈 77 00:02:58,000 --> 00:02:59,640 那我们现在要考虑一下 78 00:02:59,680 --> 00:03:01,920 我们怎么能够从一个特权级 79 00:03:01,960 --> 00:03:03,680 跳到另外一个特权级里面去 80 00:03:03,720 --> 00:03:05,800 那我们开始完成lab1的时候 81 00:03:05,840 --> 00:03:07,480 其实已经注意到我们都一直 82 00:03:07,520 --> 00:03:09,720 在内核态里面来执行代码 83 00:03:09,760 --> 00:03:11,280 我们一个challenge的一个练习 84 00:03:11,320 --> 00:03:13,760 想让大家做一个不同特权级的切换 85 00:03:13,800 --> 00:03:15,680 那其实就是在这里面有所体现 86 00:03:15,720 --> 00:03:16,320 你怎么能实现 87 00:03:16,360 --> 00:03:19,240 从一个一开始处于内核态ring 0 88 00:03:19,280 --> 00:03:21,200 这个特权级很高的ring 0这个态 89 00:03:21,240 --> 00:03:22,840 往应用程序去转换 90 00:03:22,880 --> 00:03:25,000 怎么跳到用户态去让应用程序去执行 91 00:03:25,040 --> 00:03:29,120 那么这个就在于你怎么去构造一个 92 00:03:29,160 --> 00:03:31,840 所谓的一个特殊的栈来完成这个工作 93 00:03:31,880 --> 00:03:33,040 我们可以看看 94 00:03:33,080 --> 00:03:34,360 95 00:03:34,400 --> 00:03:37,240 这是我们说的内核栈 96 00:03:37,280 --> 00:03:40,000 那么一旦产生一个中断之后 97 00:03:40,040 --> 00:03:43,400 它其实会有一些压栈 98 00:03:43,440 --> 00:03:45,200 那这些栈的信息需要注意 99 00:03:45,240 --> 00:03:46,840 因为你当前正在内核里面去执行 100 00:03:46,880 --> 00:03:51,040 所以在内核里面处于ring 0这个态的时候 101 00:03:51,080 --> 00:03:52,960 产生的中断还是ring 0 102 00:03:53,000 --> 00:03:54,320 它的特权级没有发生变化 103 00:03:54,360 --> 00:03:57,200 所以它会把这个 104 00:03:57,240 --> 00:04:00,840 EFLAGS CS EIP和Error Code存进去 105 00:04:00,880 --> 00:04:02,560 106 00:04:02,600 --> 00:04:05,600 好我们现在希望的是让它回到什么地方 107 00:04:05,640 --> 00:04:07,640 回到ring 3里面 108 00:04:07,680 --> 00:04:09,120 就回到应用程序里面去执行 109 00:04:09,160 --> 00:04:10,240 那么首先构造一个环境 110 00:04:10,280 --> 00:04:11,400 让它能回到ring 3 111 00:04:11,440 --> 00:04:12,960 那我们就模仿了一个 112 00:04:13,000 --> 00:04:16,760 就是ring 3产生中断的时候的一个现场 113 00:04:16,800 --> 00:04:18,880 就是产生终端的这个现场 114 00:04:18,920 --> 00:04:21,200 跟同特权级的比起来 115 00:04:21,240 --> 00:04:24,360 如果从ring 3产生的中断 116 00:04:24,400 --> 00:04:25,720 跳掉ring 0里面去之后 117 00:04:25,760 --> 00:04:27,360 它会多存一些信息 118 00:04:27,400 --> 00:04:30,360 我们前面讲到了会存两个SS和ESP 119 00:04:30,400 --> 00:04:34,040 而且SS里面那个RPL是3需要注意 120 00:04:34,080 --> 00:04:35,760 这个RPL是3也意味着 121 00:04:35,800 --> 00:04:36,600 意味着什么呢 122 00:04:36,640 --> 00:04:40,200 意味着这个SS其实是在用的那个数据段 123 00:04:40,240 --> 00:04:41,400 用的是用户态的数据段 124 00:04:41,440 --> 00:04:45,120 而不是内核态的数据段 所以它RPL是3 125 00:04:45,160 --> 00:04:49,600 那同理我们前面在ring 0里面 126 00:04:49,640 --> 00:04:51,800 产生的中断它这个CPL是0 127 00:04:51,840 --> 00:04:56,040 代表了它当前运行在内核里面 128 00:04:56,080 --> 00:04:58,360 这个时候我们希望它回去 129 00:04:58,400 --> 00:05:01,080 能够跳到ring 3去执行 130 00:05:01,120 --> 00:05:02,840 所以我们会把它的CS 131 00:05:02,880 --> 00:05:08,120 会改成3 改成3之后 132 00:05:08,160 --> 00:05:11,360 也意味着如果说把这个栈构造好之后 133 00:05:11,400 --> 00:05:13,040 我们最后通过一条特殊的指令 134 00:05:13,080 --> 00:05:14,440 通过这条指令叫做什么 135 00:05:14,480 --> 00:05:18,640 IRET这条指令可以完成这个转换 136 00:05:18,680 --> 00:05:19,560 137 00:05:19,600 --> 00:05:22,200 OK 一旦IRET指令执行之后 138 00:05:22,240 --> 00:05:25,040 它就会把这些信息给弹出栈 139 00:05:25,080 --> 00:05:27,880 来把这个内容恢复到对应的寄存器里面 140 00:05:27,920 --> 00:05:32,080 包含了EIP CS EFLAGS ESP和SS 141 00:05:32,120 --> 00:05:34,680 那一旦把这些值附给了 142 00:05:34,720 --> 00:05:37,280 我们那些寄存器之后 143 00:05:37,320 --> 00:05:39,240 可以看到这时候它的运行环境 144 00:05:39,280 --> 00:05:41,240 已经变成了用户态 为什么这么说 145 00:05:41,280 --> 00:05:46,160 因为它访问的数据 在SS这个访问数据 146 00:05:46,200 --> 00:05:50,440 它访问的代码CS 都是3 147 00:05:50,480 --> 00:05:51,280 所以说这时候 148 00:05:51,320 --> 00:05:55,160 如果我们这个跳过去的应用程序 149 00:05:55,200 --> 00:05:59,040 想去访问处于一个内核态的一个数据的话 150 00:05:59,080 --> 00:06:03,560 会报错 OK 这也意味着我们这个SS和 151 00:06:03,600 --> 00:06:06,920 内核里面的这个SS的栈不是一个数据段的 152 00:06:06,960 --> 00:06:08,880 应该是不同的数据段 153 00:06:08,920 --> 00:06:11,920 这实际上是一种特权级的转换 154 00:06:11,960 --> 00:06:14,280 155 00:06:14,320 --> 00:06:17,200 你可以看到当这个发生转换之后 156 00:06:17,240 --> 00:06:20,320 这时还是内核栈它把这信息弹出来了 157 00:06:20,360 --> 00:06:24,360 同时需要注意同时新的一个栈到这儿来 158 00:06:24,400 --> 00:06:25,560 新的一个栈已经形成 159 00:06:25,600 --> 00:06:28,360 这个栈实际上是我们说处于用户态的一个栈 160 00:06:28,400 --> 00:06:31,800 它的SS和ESP已经变到这儿来了 161 00:06:31,840 --> 00:06:37,800 而且它的代码也是变到CS和EIP指向代码 162 00:06:37,840 --> 00:06:39,800 如果这个CS EIP不做改变 163 00:06:39,840 --> 00:06:41,120 那么它还是会直到 164 00:06:41,160 --> 00:06:43,560 它产生中断的下一条指令去执行 165 00:06:43,600 --> 00:06:46,120 166 00:06:46,160 --> 00:06:49,680 好 那我们前面讲的是从ring 0 167 00:06:49,720 --> 00:06:51,800 跳到ring 3怎么来完成 168 00:06:51,840 --> 00:06:53,800 其实通过构造一个 169 00:06:53,840 --> 00:06:56,520 能够返回到ring 3的一个栈 170 00:06:56,560 --> 00:06:59,120 内核的一个模拟的一个中断栈 171 00:06:59,160 --> 00:07:01,480 来完成一个执行过程 172 00:07:01,520 --> 00:07:02,840 那么它最后通过IRET指令 173 00:07:02,880 --> 00:07:05,440 就可以完成数据的更新 174 00:07:05,480 --> 00:07:06,440 这一寄存器的更新 175 00:07:06,480 --> 00:07:09,320 来从而实现特权级的一个转换 176 00:07:09,360 --> 00:07:11,400 那另外一点就是怎么能够 177 00:07:11,440 --> 00:07:15,480 再从用户态跳到内核态去执行 178 00:07:15,520 --> 00:07:17,200 那这也是我们需要考虑的问题 179 00:07:17,240 --> 00:07:20,760 实际上这种机制也是被我们的应用程序 180 00:07:20,800 --> 00:07:22,480 无论是Windows Linux等等 181 00:07:22,520 --> 00:07:24,320 通常的这些操作系统 182 00:07:24,360 --> 00:07:26,280 它的应用程序都采取这种方式 183 00:07:26,320 --> 00:07:30,440 通过所谓的一个软中断或者叫trap 184 00:07:30,480 --> 00:07:34,040 来完成从用户态到内核态的转变 185 00:07:34,080 --> 00:07:37,880 那么这里面我们看看如果是基于X86架构 186 00:07:37,920 --> 00:07:41,240 怎么来完成从ring 3到ring 0的一个转变 187 00:07:41,280 --> 00:07:42,600 188 00:07:42,640 --> 00:07:43,240 这也是一样 189 00:07:43,280 --> 00:07:46,480 这是一个处于正在执行的 190 00:07:46,520 --> 00:07:48,600 一个程序的堆栈信息 191 00:07:48,640 --> 00:07:54,240 那么一旦产生了一个中断 192 00:07:54,280 --> 00:07:56,960 那它实际上就已经实现了跳转 193 00:07:57,000 --> 00:07:58,080 那为了能够实现跳转 194 00:07:58,120 --> 00:07:59,960 你首先要把这个中断门给建立好 195 00:08:00,000 --> 00:08:02,520 就是IDT里面 要把中断门建立好 196 00:08:02,560 --> 00:08:05,000 中断门有一个中断描述符 197 00:08:05,040 --> 00:08:06,960 它会指出来产生了某一个中断之后 198 00:08:07,000 --> 00:08:10,480 它应该跳到哪儿去执行 但是在那个软件 199 00:08:10,520 --> 00:08:11,320 中断服务例程 200 00:08:11,360 --> 00:08:13,160 操作系统里面中断服务例程执行之前 201 00:08:13,200 --> 00:08:15,720 我们CPU已经一旦产生中断 202 00:08:15,760 --> 00:08:17,480 一定要保存一些信息 这些信息是什么 203 00:08:17,520 --> 00:08:22,080 刚才说到的就是SS ESP EFLAGS CS和EIP 204 00:08:22,120 --> 00:08:23,720 当一个应用程序运行到用户态的时候 205 00:08:23,760 --> 00:08:25,440 产生了中断 那我们的硬件 206 00:08:25,480 --> 00:08:26,840 会在处于ring 0态的 207 00:08:26,880 --> 00:08:29,680 那个栈里面会存这些信息 208 00:08:29,720 --> 00:08:31,520 这些信息其实是被打断那一刻 209 00:08:31,560 --> 00:08:34,920 那个应用程序它的堆栈 210 00:08:34,960 --> 00:08:39,400 它的执行的地址都保存在这儿 211 00:08:39,440 --> 00:08:43,440 这个信息有了 如果处理完这个 212 00:08:43,480 --> 00:08:44,520 中断服务例程之后 213 00:08:44,560 --> 00:08:46,040 我们再执行IRET这条指令 214 00:08:46,080 --> 00:08:47,560 就是中断返回指令 215 00:08:47,600 --> 00:08:49,160 它就会根据这里面的信息 216 00:08:49,200 --> 00:08:52,560 再重新返回到ring 3里面去 217 00:08:52,600 --> 00:08:54,160 那我们这时候不希望它返回到ring 3 218 00:08:54,200 --> 00:08:56,480 我们希望它返回到ring 0怎么办 219 00:08:56,520 --> 00:08:57,480 220 00:08:57,520 --> 00:08:59,880 大家想想 其实和我们刚才的方法一样的 221 00:08:59,920 --> 00:09:03,680 我们可以通过对这个堆栈的修改 222 00:09:03,720 --> 00:09:04,640 就是内核态的修改 223 00:09:04,680 --> 00:09:06,720 使得它执行完IRET之后 224 00:09:06,760 --> 00:09:08,400 就是IRET这条指令之后 225 00:09:08,440 --> 00:09:12,640 可以继续留在内核态里面去执行 226 00:09:12,680 --> 00:09:14,560 227 00:09:14,600 --> 00:09:15,800 我们做了什么修改 228 00:09:15,840 --> 00:09:16,840 229 00:09:16,880 --> 00:09:21,080 首先就需要注意这个SS和ESP不需要了 230 00:09:21,120 --> 00:09:23,240 因为我们不需要回到用户态去 这是一个 231 00:09:23,280 --> 00:09:26,920 第二个我们会把CS会做一个修改 232 00:09:26,960 --> 00:09:31,160 指向内核态的那个代码段 这是一个 233 00:09:31,200 --> 00:09:34,280 第二个要把CS的CPL设置为0 234 00:09:34,320 --> 00:09:36,000 这里面也实际上是明确了 235 00:09:36,040 --> 00:09:39,000 内核态的那个代码段的CPL就是0 236 00:09:39,040 --> 00:09:42,120 通过把这个内核里面的堆栈 237 00:09:42,160 --> 00:09:44,720 改成这么一个堆栈之后 OK 238 00:09:44,760 --> 00:09:46,080 当然你要根据你要执行 239 00:09:46,120 --> 00:09:47,360 具体要访问到哪个地方 240 00:09:47,400 --> 00:09:49,200 去改变所谓的EIP 241 00:09:49,240 --> 00:09:50,600 假设如果不改的话 242 00:09:50,640 --> 00:09:54,680 那么这时候再去通过 243 00:09:54,720 --> 00:09:58,000 执行退出IRET这条指令 244 00:09:58,040 --> 00:10:01,680 中断返回指令 那么我们CPU硬件会 245 00:10:01,720 --> 00:10:04,560 把这个里面的堆栈信息给取出来 246 00:10:04,600 --> 00:10:08,960 然后返回到EIP和CS 247 00:10:09,000 --> 00:10:10,960 指向的这个地址里面去执行 248 00:10:11,000 --> 00:10:12,800 但需要注意在这时候 249 00:10:12,840 --> 00:10:14,720 依然是在ring 0里面执行的 250 00:10:14,760 --> 00:10:17,320 通过这种方式可以实现从ring 3 251 00:10:17,360 --> 00:10:19,480 到ring 0的一个转变 252 00:10:19,520 --> 00:10:21,960 那么如果大家能够把这个方法 253 00:10:22,000 --> 00:10:23,640 用在我们lab1的challenge 254 00:10:23,680 --> 00:10:26,160 这个练习里面的话就可以完成那个实验 255 00:10:26,200 --> 00:10:28,640 那如果有兴趣的同学也可以做尝试 256 00:10:28,680 --> 00:10:30,560 257 00:10:30,600 --> 00:10:34,400 我们再想一想当一个应用程序 258 00:10:34,440 --> 00:10:36,480 通过不同手段实现从一个特权级到 259 00:10:36,520 --> 00:10:39,480 另外一个特权级的一个切换之后 260 00:10:39,520 --> 00:10:40,560 那到了另外一个特权级 261 00:10:40,600 --> 00:10:42,760 它怎么知道那个特权级里面的 262 00:10:42,800 --> 00:10:46,840 CS SS位于什么地方 这是一个问题 263 00:10:46,880 --> 00:10:50,400 CS有同学说 它的CS和EIP就表明它的地址 264 00:10:50,440 --> 00:10:52,240 这个地址我们可以通过它那个 265 00:10:52,280 --> 00:10:54,440 IDT描述符表里面建好了 266 00:10:54,480 --> 00:10:57,000 中断描述符表建好了 267 00:10:57,040 --> 00:10:57,840 当产生某一个中断 268 00:10:57,880 --> 00:10:59,280 应该跳到什么地方去执行 269 00:10:59,320 --> 00:11:02,040 这个地址可以搞定 270 00:11:02,080 --> 00:11:03,640 但是另一方面 271 00:11:03,680 --> 00:11:08,080 它的这个堆栈应该在哪儿 272 00:11:08,120 --> 00:11:10,200 这个信息在哪儿 273 00:11:10,240 --> 00:11:13,520 那这个信息其实是在IDT里面是没有的 274 00:11:13,560 --> 00:11:15,360 那么是在另外一个 275 00:11:15,400 --> 00:11:19,240 我们称之为任务状态段 276 00:11:19,280 --> 00:11:21,800 Task State Segment 277 00:11:21,840 --> 00:11:26,000 这么一个段保存了不同特权级的 278 00:11:26,040 --> 00:11:29,200 它所用到的很多寄存器里面的信息 279 00:11:29,240 --> 00:11:33,120 比如我们说的SS ESP等等 280 00:11:33,160 --> 00:11:37,040 那么虽然有很多 它很全面 281 00:11:37,080 --> 00:11:38,320 其实我们关注什么 282 00:11:38,360 --> 00:11:42,200 关注的是不同特权级里面的那个堆栈信息 283 00:11:42,240 --> 00:11:47,080 就是它的SS和ESP 那么可以看出来 284 00:11:47,120 --> 00:11:51,080 对于不同特权级的SS ESP 285 00:11:51,120 --> 00:11:52,400 这里面都有保存 286 00:11:52,440 --> 00:11:55,840 你看ESP0 ESP1 ESP2 287 00:11:55,880 --> 00:12:00,000 这是保存的 还有SS0 SS1 SS2等等 288 00:12:00,040 --> 00:12:02,880 这三个代表了它保存了什么 289 00:12:02,920 --> 00:12:06,160 保存了从ring 0到ring 2 290 00:12:06,200 --> 00:12:09,720 这三个特权级里面的堆栈信息 291 00:12:09,760 --> 00:12:12,720 所以为什么刚才说当一个应用程序 292 00:12:12,760 --> 00:12:15,560 在ring 3这个应用程序产生了一个中断 293 00:12:15,600 --> 00:12:18,760 或者说执行一个软中断的指令 INT 294 00:12:18,800 --> 00:12:22,680 这个INT指令之后 它可以跳到内核里面去执行 295 00:12:22,720 --> 00:12:24,840 但跳是跳过来了 它跳过来之后 296 00:12:24,880 --> 00:12:31,760 我们的CPU会根据TSS这里面存的这个信息 297 00:12:31,800 --> 00:12:34,680 来设置新的堆栈 这是一个 298 00:12:34,720 --> 00:12:36,400 然后再根据我们IDT表里面的那个 299 00:12:36,440 --> 00:12:41,200 中断描述符表的信息来设置新的地址 300 00:12:41,240 --> 00:12:43,280 所以说我们可以看到这里面比较清楚 301 00:12:43,320 --> 00:12:47,640 一旦从一个特权级跳到另外一个特权级 302 00:12:47,680 --> 00:12:50,840 特别是从高特权级ring 3跳到ring 0的时候 303 00:12:50,880 --> 00:12:53,120 我们的地址会发生变化 304 00:12:53,160 --> 00:12:54,760 我们的堆栈会发生变化 305 00:12:54,800 --> 00:12:56,440 而这个地址和堆栈都分别由 306 00:12:56,480 --> 00:12:59,200 不同的硬件机构来保存 307 00:12:59,240 --> 00:13:02,560 所以说这里面我们需要也要去设置好 308 00:13:02,600 --> 00:13:03,840 我们操作系统需要去设置好 309 00:13:03,880 --> 00:13:05,840 TSS里面的这些内容 310 00:13:05,880 --> 00:13:08,920 311 00:13:08,960 --> 00:13:12,440 那么TSS它其实是一个特殊的段 312 00:13:12,480 --> 00:13:14,080 这个段位于内存中 313 00:13:14,120 --> 00:13:17,600 那它到底怎么去访问得到 314 00:13:17,640 --> 00:13:20,920 那运用到我们前面讲到的全局描述符表 315 00:13:20,960 --> 00:13:22,320 我们的全局描述符表里面 316 00:13:22,360 --> 00:13:26,240 有专门有一项是专门来指向所谓的TSS 317 00:13:26,280 --> 00:13:29,080 就是任务状态段这么一个信息 318 00:13:29,120 --> 00:13:34,440 称之为有一个特定的一个TSS的一个描述符 319 00:13:34,480 --> 00:13:36,880 放在我们全局描述符表里面 320 00:13:36,920 --> 00:13:40,160 那我们可以看到在这个全局描述符表里面 321 00:13:40,200 --> 00:13:42,760 它保存了这个TSS的一个描述符 322 00:13:42,800 --> 00:13:44,400 TSS Descriptor 323 00:13:44,440 --> 00:13:46,480 在这个描述符里面很重要的一个 324 00:13:46,520 --> 00:13:48,600 它保存了它的地址 就是TSS这个地址 325 00:13:48,640 --> 00:13:49,920 那么根据这里面保存的地址 326 00:13:49,960 --> 00:13:54,480 我们可以找到TSS里面的这个内容 327 00:13:54,520 --> 00:13:57,840 这就是说我们CPU怎么能够去找到 328 00:13:57,880 --> 00:13:59,000 当产生一个中断 329 00:13:59,040 --> 00:14:00,160 从一个特权级跳到另一个特权级 330 00:14:00,200 --> 00:14:03,200 找到另一个特权级里面的那些堆栈信息 331 00:14:03,240 --> 00:14:05,400 甚至还有其它信息 但这里面对ucore而言 332 00:14:05,440 --> 00:14:06,600 我们就用最简单的两个 333 00:14:06,640 --> 00:14:09,680 就是它的堆栈SS和ESP 334 00:14:09,720 --> 00:14:12,360 335 00:14:12,400 --> 00:14:15,960 很明显那CPU我们硬件这么去找 336 00:14:16,000 --> 00:14:20,080 那么这里面的内容需要我们的软件去填写 337 00:14:20,120 --> 00:14:21,800 这也是跟前面介绍是一样 338 00:14:21,840 --> 00:14:24,680 我们的软件会去填写我们段描述符表 339 00:14:24,720 --> 00:14:25,880 就是全局描述符表 340 00:14:25,920 --> 00:14:28,640 我们软件会去填写IDT 中断描述符表 341 00:14:28,680 --> 00:14:32,880 同样我们的软件会把这个任务状态段 342 00:14:32,920 --> 00:14:35,000 里面的内容给填好 343 00:14:35,040 --> 00:14:37,760 那么一旦我们的TSS里面的内容 344 00:14:37,800 --> 00:14:39,400 这个段里面的内容填好之后 345 00:14:39,440 --> 00:14:40,400 我们需要我们的CPU 346 00:14:40,440 --> 00:14:41,720 知道这个段位于什么地方 347 00:14:41,760 --> 00:14:43,920 以便后续一旦产生切换之后 348 00:14:43,960 --> 00:14:45,640 它到哪儿去找这个信息 349 00:14:45,680 --> 00:14:47,560 对应这个比如SS ESP 350 00:14:47,600 --> 00:14:48,840 这个内容到哪儿去找 351 00:14:48,880 --> 00:14:50,800 那首先要知道这个段的基址 352 00:14:50,840 --> 00:14:53,320 这个段的起始地址在哪儿保存 353 00:14:53,360 --> 00:14:54,240 我们前面已经讲到了 354 00:14:54,280 --> 00:14:55,760 在全局描述符表里面 355 00:14:55,800 --> 00:14:59,000 你要去填写一项TSS的一个描述符 356 00:14:59,040 --> 00:15:04,200 它会指向这个任务段的相应的内容 357 00:15:04,240 --> 00:15:09,440 但是也需要注意这里面保存的信息 358 00:15:09,480 --> 00:15:11,240 这也是内存里面的单元 359 00:15:11,280 --> 00:15:13,280 所以说我们的硬件还有一个优化 360 00:15:13,320 --> 00:15:15,320 它会有一个专门的Task Register 361 00:15:15,360 --> 00:15:18,640 来缓存这个TSS里面的内容 362 00:15:18,680 --> 00:15:20,400 从而使得我们每次 363 00:15:20,440 --> 00:15:23,200 要CPU访问这个段里面的 364 00:15:23,240 --> 00:15:26,480 Task State Segment这个段里面的内容的时候 365 00:15:26,520 --> 00:15:28,360 它要去根据这个寄存器里面的内容 366 00:15:28,400 --> 00:15:30,600 直接找到这个相应的位置 367 00:15:30,640 --> 00:15:32,440 那么这个寄存器是一个特殊的寄存器 368 00:15:32,480 --> 00:15:35,760 那我们会在完成对TSS的初始化之后 369 00:15:35,800 --> 00:15:38,760 通过这个寄存器来加载它的基址 370 00:15:38,800 --> 00:15:42,040 这两步都可以有效的找到我们这个 371 00:15:42,080 --> 00:15:44,040 Task State Segment 372 00:15:44,080 --> 00:15:48,320 373 00:15:48,360 --> 00:15:49,160 那我们前面讲的是 374 00:15:49,200 --> 00:15:51,640 大致的一个对硬件的介绍 375 00:15:51,680 --> 00:15:53,360 那其实我们ucore在操作系统里面 376 00:15:53,400 --> 00:15:58,640 也对此有相应的一些初始化的工作都涉及在 377 00:15:58,680 --> 00:16:01,120 分配一个TSS memory实际上分配一块内存 378 00:16:01,160 --> 00:16:01,920 要在这个内存里面 379 00:16:01,960 --> 00:16:06,000 把它作为一个Task State Segment 380 00:16:06,040 --> 00:16:07,640 来完成内容的填写 381 00:16:07,680 --> 00:16:09,920 再做初始化 对内存做初始化 382 00:16:09,960 --> 00:16:13,600 特别要设置对我们的SS0和ESP0 383 00:16:13,640 --> 00:16:19,000 再进一步会在GDT里面去填写TSS描述符 384 00:16:19,040 --> 00:16:22,560 最后还要设置TSS selector 385 00:16:22,600 --> 00:16:27,560 这是我们说Task Register相应的设置工作 386 00:16:27,600 --> 00:16:30,360 这里面在代码里面有对应的环节 387 00:16:30,400 --> 00:16:33,040 接下来我们可以看一下代码怎么来表述的 388 00:16:33,080 --> 00:16:35,520 好 那前面就把关于特权级的 389 00:16:35,560 --> 00:16:36,800 给大家做了一个介绍 390 00:16:36,840 --> 00:16:38,160 相关的一些进一步的信息 391 00:16:38,200 --> 00:16:40,800 可以看很详细的 392 00:16:40,840 --> 00:16:44,160 有关intel架构的一个技术文档 393 00:16:44,200 --> 00:16:46,840 它有专门的一个软件开发者的文档 394 00:16:46,880 --> 00:16:48,960 会有更详细的信息 这里面列出来了 395 00:16:49,000 --> 00:16:50,080 大致的一个位置 396 00:16:50,120 --> 00:16:54,040 小节一下我们这里面重点讲了特权级 397 00:16:54,080 --> 00:16:56,680 intelX86 X86架构里面有几个特权级 398 00:16:56,720 --> 00:16:59,960 我们怎么来完成特权级的一个切换 399 00:17:00,000 --> 00:17:02,440 你怎么知道你到底处于哪个特权级 400 00:17:02,480 --> 00:17:05,000 怎么去判断你的这个访问 401 00:17:05,040 --> 00:17:08,560 是否违背了特权级的一个规则 402 00:17:08,600 --> 00:17:11,880 以及最后还讲到了怎么去建立好 403 00:17:11,920 --> 00:17:14,360 从不同特权级转换的一个相应的机制 404 00:17:14,400 --> 00:17:17,280 特别是涉及到TSS这么一个机制 405 00:17:17,320 --> 00:17:20,560 把TSS设置好之后才能更好的完成 406 00:17:20,600 --> 00:17:22,680 从一个特权级到另一个特权级的转换 407 00:17:22,720 --> 00:17:25,240 好 这是这一部分的内容 408 00:17:25,280 --> 00:17:25,720 409 00:17:25,760 --> 00:17:26,200