0 00:00:00,000 --> 00:00:06,320 1 00:00:06,400 --> 00:00:08,560 好 接下来给大家介绍一下 2 00:00:08,600 --> 00:00:10,760 X86中的中断处理的过程 3 00:00:10,800 --> 00:00:12,200 这里面会涉及到三部分 4 00:00:12,240 --> 00:00:16,880 第一个是谁产生了中断 就是X86的中断源 这是第一个 5 00:00:16,920 --> 00:00:20,200 第二个需要了解是CPU和操作系统 6 00:00:20,240 --> 00:00:23,160 软件和硬件如何结合在一起来处理中断 7 00:00:23,200 --> 00:00:25,640 第三部分是说你为了能够处理中断 8 00:00:25,680 --> 00:00:28,400 前面要做很多的初始化的工作 9 00:00:28,440 --> 00:00:30,400 把这个前期的环境给建好 10 00:00:30,440 --> 00:00:34,480 能够对中断向量表进行初始化 11 00:00:34,520 --> 00:00:36,120 这是三块内容 12 00:00:36,160 --> 00:00:37,600 那么有了这个知识之后 13 00:00:37,640 --> 00:00:41,320 大家能够对基于X86这个硬件的中断处理 14 00:00:41,360 --> 00:00:42,600 有更深入的理解 15 00:00:42,640 --> 00:00:45,560 16 00:00:45,600 --> 00:00:46,960 其实在这个原理课中 17 00:00:47,000 --> 00:00:51,560 我们也提到了中段 异常 比如说陷入等等 18 00:00:51,600 --> 00:00:56,840 那中断 异常在具体的CPU上面它有不同的表现形式 19 00:00:56,880 --> 00:00:58,600 那对于X86而言 20 00:00:58,640 --> 00:01:02,720 它把中断 异常作为特定的两种不同类型来分别处理 21 00:01:02,760 --> 00:01:05,280 但是它的实现机制是统一的 22 00:01:05,320 --> 00:01:06,480 对于中断而言 23 00:01:06,520 --> 00:01:09,120 我们可以看到外设会产生中断 24 00:01:09,160 --> 00:01:13,120 外设包括了硬盘 网卡 时钟 串口等等 25 00:01:13,160 --> 00:01:14,960 我们的软件也会产生中断 26 00:01:15,000 --> 00:01:16,200 软件通过什么呢 27 00:01:16,240 --> 00:01:17,560 通过刚才说的int 28 00:01:17,600 --> 00:01:20,600 比如int80这种产生的软中断 29 00:01:20,640 --> 00:01:21,720 那么软中断用在什么地方呢 30 00:01:21,760 --> 00:01:23,320 用在我们的系统调用 31 00:01:23,360 --> 00:01:28,760 我们的应用程序可以通过软中断来获得我们操作系统提供的服务 32 00:01:28,800 --> 00:01:34,800 异常是另一类 异常是我们的程序在执行过程中做了不应该做的事情 33 00:01:34,840 --> 00:01:35,920 比如说除零错 34 00:01:35,960 --> 00:01:40,400 或者访问了一个非法的地址等等都会产生异常 35 00:01:40,440 --> 00:01:42,920 那异常也有严重程度一般的 36 00:01:42,960 --> 00:01:44,440 还有很严重的异常 37 00:01:44,480 --> 00:01:45,520 针对不同类型的异常 38 00:01:45,560 --> 00:01:48,600 我们的操作系统应该有不同的处理方式 39 00:01:48,640 --> 00:01:51,440 它的来源可以看到主要有三类 40 00:01:51,480 --> 00:01:54,400 外部中断 内部中断或者称之为软中断 41 00:01:54,440 --> 00:01:56,040 还有异常 42 00:01:56,080 --> 00:02:01,640 它们的特点 它们产生的时机 它们的严重程度 43 00:02:01,680 --> 00:02:04,600 他们的目标是什么 都是不一样的 44 00:02:04,640 --> 00:02:06,000 这边有个了解 45 00:02:06,040 --> 00:02:09,800 那中断产生之后 我们操作系统怎么办 46 00:02:09,840 --> 00:02:11,800 一般来说中断是由于外设 47 00:02:11,840 --> 00:02:13,640 假设是外设产生的中断 48 00:02:13,680 --> 00:02:18,120 我们操作系统就应该能够理解外设需要我们做什么 49 00:02:18,160 --> 00:02:20,080 然后产生相应的反馈 50 00:02:20,120 --> 00:02:21,960 比如说举个简单例子 51 00:02:22,000 --> 00:02:25,880 网卡产生的中断 那它得到了一个数据包产生了中断 52 00:02:25,920 --> 00:02:29,120 那我们操作系统就应该对这个数据包做进一步的处理 53 00:02:29,160 --> 00:02:31,080 来发给相应的应用程序 54 00:02:31,120 --> 00:02:34,560 需要这个数据的应用程序去做操作 55 00:02:34,600 --> 00:02:36,800 这实际上是我们操作系统干的一个事情 56 00:02:36,840 --> 00:02:38,800 它需要去做相应的响应 57 00:02:38,840 --> 00:02:42,480 那如果说是一个异常产生了 58 00:02:42,520 --> 00:02:43,920 那么异常产生之后呢 59 00:02:43,960 --> 00:02:47,240 我们的操作系统要根据这个异常的严重程度 60 00:02:47,280 --> 00:02:49,280 才能把运行的这个程序 我们称之为进程 61 00:02:49,320 --> 00:02:51,600 把这个进程给kill掉 给杀死 62 00:02:51,640 --> 00:02:56,280 也有可能是我们的应用程序产生的软中断 63 00:02:56,320 --> 00:02:59,880 那我们的操作系统就可以去完成这个服务 64 00:02:59,920 --> 00:03:02,840 最终让我们的应用程序可以得到这样的服务 65 00:03:02,880 --> 00:03:04,520 这是不同的处理方式 66 00:03:04,560 --> 00:03:08,320 那无论哪种方式 其实一个中断 异常 67 00:03:08,360 --> 00:03:11,960 或者说是我们称之为的软中断 68 00:03:12,000 --> 00:03:15,240 两种:硬中断 或者叫外中断 69 00:03:15,280 --> 00:03:17,200 内中断 (也叫)软中断等等 70 00:03:17,240 --> 00:03:20,080 这些都和所谓的中断号有一个对应关系 71 00:03:20,120 --> 00:03:22,400 比如产生的中断 它对应有一个中断号 72 00:03:22,440 --> 00:03:27,800 那么中断号唯一标示了这一个中断的特征 这是一个 73 00:03:27,840 --> 00:03:30,080 第二个 对于每一个中断号 74 00:03:30,120 --> 00:03:33,000 都有相应的中断处理的一个例程 75 00:03:33,040 --> 00:03:35,160 来完成对应的操作 76 00:03:35,200 --> 00:03:38,360 这个是我们操作系统需要去建立好的 77 00:03:38,400 --> 00:03:41,200 每一个中断或者异常都有一个中断服务例程 78 00:03:41,240 --> 00:03:47,240 简称ISR 就是Interrupt Service Routine和它关联 79 00:03:47,280 --> 00:03:52,000 关联之后一旦产生了某种类型的中断或异常 80 00:03:52,040 --> 00:03:56,320 就可以调用相应的中断服务例程去执行相应的操作 81 00:03:56,360 --> 00:03:59,360 那这个关联的建立 82 00:03:59,400 --> 00:04:03,000 是我们说操作系统需要去考虑和实现的 83 00:04:03,040 --> 00:04:05,120 我们需要去完成相应的处理过程 84 00:04:05,160 --> 00:04:08,840 但这个处理过程 也和我们的具体硬件是相关的 85 00:04:08,880 --> 00:04:11,880 所以说我们是需要去了解在X86环境下 86 00:04:11,920 --> 00:04:14,800 怎么来完成这个关联的建立 87 00:04:14,840 --> 00:04:15,840 那我们可以看看 88 00:04:15,880 --> 00:04:18,800 在X86环境中它有一系列的硬件机制 89 00:04:18,840 --> 00:04:21,840 来支持这种对应关系的一个建立 90 00:04:21,880 --> 00:04:26,800 它有一个IDT 就是中断描述符表 91 00:04:26,840 --> 00:04:30,000 跟我们刚才看到这个全局描述符表很类似 92 00:04:30,040 --> 00:04:31,760 只是它专门用来描述中断的 93 00:04:31,800 --> 00:04:33,560 这里面也是一个大的数组 94 00:04:33,600 --> 00:04:37,240 里面的每一项我们称之为中断门或者陷阱门 95 00:04:37,280 --> 00:04:40,240 trap就是我们说的软中断 96 00:04:40,280 --> 00:04:43,400 中断门或者陷阱门对应相应的中断号 97 00:04:43,440 --> 00:04:45,120 一个中断号可以有一个index 98 00:04:45,160 --> 00:04:46,520 我们根据这个中断号 99 00:04:46,560 --> 00:04:51,840 可以找到它所谓的一个中断门或者陷阱门 100 00:04:51,880 --> 00:04:53,320 基于这个中断门或者陷阱门 101 00:04:53,360 --> 00:04:55,440 我们可以进一步获取到 102 00:04:55,480 --> 00:04:59,520 跟这个中断门 陷阱门相关的段的选择址 103 00:04:59,560 --> 00:05:04,680 我们前面讲的段机制里面 有段的选择址和它的段类的偏移 104 00:05:04,720 --> 00:05:07,120 有了这两个信息 我们就可以知道 105 00:05:07,160 --> 00:05:11,120 一个中断服务例程的地址 就在这里面得到了表述 106 00:05:11,160 --> 00:05:16,240 所以说可以看到 IDT结合我们刚才看到的GDT两个合在一起 107 00:05:16,280 --> 00:05:19,320 就可以完成硬件的中断 108 00:05:19,360 --> 00:05:24,000 或者是异常和中断服务例程对应的链接关系的建立 109 00:05:24,040 --> 00:05:24,920 就可以搞定了 110 00:05:24,960 --> 00:05:28,080 那么这个表本身呢 它有一个起始地址 111 00:05:28,120 --> 00:05:30,040 放在IDTR里面去 112 00:05:30,080 --> 00:05:31,680 需要告诉我们的机器 113 00:05:31,720 --> 00:05:35,320 告诉我们的CPU说你这个IDT在什么地方 114 00:05:35,360 --> 00:05:36,680 所以说可以看出来 115 00:05:36,720 --> 00:05:39,800 这个IDT也是需要我们的操作系统来建立的 116 00:05:39,840 --> 00:05:40,600 这是第一个 117 00:05:40,640 --> 00:05:42,560 第二个它的起始地址 118 00:05:42,600 --> 00:05:46,120 要通过一个特定的指令来告诉我们的CPU 119 00:05:46,160 --> 00:05:48,600 说这个IDT在什么地方 120 00:05:48,640 --> 00:05:50,800 那么我们后面会讲到 121 00:05:50,840 --> 00:05:55,040 要怎么来完成相应的工作 122 00:05:55,080 --> 00:05:59,360 好 对于刚才提到的IDT表中的每一项 123 00:05:59,400 --> 00:06:02,120 我们称之为中断门或者陷阱门呢 124 00:06:02,160 --> 00:06:03,680 它有它相应的格式 125 00:06:03,720 --> 00:06:08,040 这里面最主要的两个一个是段描述符 126 00:06:08,080 --> 00:06:10,560 第二个是它的offset 这两块 127 00:06:10,600 --> 00:06:12,440 这两个信息其实也就意味着 128 00:06:12,480 --> 00:06:18,520 它的中断服务例程的起始地址是知道的了 129 00:06:18,560 --> 00:06:21,280 这是一个大致的一个展示图 130 00:06:21,320 --> 00:06:22,720 产生了一个中断之后 131 00:06:22,760 --> 00:06:26,640 根据这个中断我们可以知道它的中断号 132 00:06:26,680 --> 00:06:30,680 CPU会根据这个中断号来查这个IDT到底属于哪一项 133 00:06:30,720 --> 00:06:32,040 知道它的index 134 00:06:32,080 --> 00:06:35,200 找到相应的中断门或者陷阱门 135 00:06:35,240 --> 00:06:38,480 然后从这里面取出它的段选择址 136 00:06:38,520 --> 00:06:42,360 以这个选择址作为index进一步查找GDT 137 00:06:42,400 --> 00:06:46,040 我们前面讲了GDT 就是全局描述符表 138 00:06:46,080 --> 00:06:48,120 既然它作为index来查 查什么东西呢 139 00:06:48,160 --> 00:06:50,200 GDT里面存的是什么呢 140 00:06:50,240 --> 00:06:52,320 存的是段描述符 141 00:06:52,360 --> 00:06:57,640 段描述符里面有一个基地址 Base address 142 00:06:57,680 --> 00:07:01,040 再加上它谁在里面存的Offset 143 00:07:01,080 --> 00:07:04,480 合在一起就形成了相应的线性地址 144 00:07:04,520 --> 00:07:09,720 从而可以指向我们说的ISR 中断服务例程 145 00:07:09,760 --> 00:07:11,680 所以说一旦产生了某一个中断 146 00:07:11,720 --> 00:07:15,680 CPU可以自动的在硬件这个层面访问这两个表 147 00:07:15,720 --> 00:07:19,480 需要注意这两个表 是我们uCore建立好的 148 00:07:19,520 --> 00:07:22,560 一旦建立好之后 那我们的CPU就可以基于这两个表 149 00:07:22,600 --> 00:07:27,440 来查到相应的中断需要对应的中断处理例程 150 00:07:27,480 --> 00:07:30,040 当然这个例程是我们操作系统来实现的 151 00:07:30,080 --> 00:07:32,480 这样可以确保 一旦产生了某个异常 152 00:07:32,520 --> 00:07:33,720 或者某个中断之后 153 00:07:33,760 --> 00:07:36,120 我们的操作系统能够及时的响应 154 00:07:36,160 --> 00:07:39,200 去调用相应的函数来完成相应的处理 155 00:07:39,240 --> 00:07:46,040 这就是中断处理的初始化的过程 156 00:07:46,080 --> 00:07:49,080 另一方面需要注意的是 当产生中断之后 157 00:07:49,120 --> 00:07:53,160 中断会打断当前正在执行的程序 158 00:07:53,200 --> 00:07:55,800 然后去执行刚才说到的中断服务例程 159 00:07:55,840 --> 00:08:01,400 执行完毕之后再返回到当前被打断的程序继续让这个程序去执行 160 00:08:01,440 --> 00:08:05,040 那么这有一个打断和恢复 就是打断就需要一个保存 161 00:08:05,080 --> 00:08:08,040 最后要返回去要恢复 这么一个过程 162 00:08:08,080 --> 00:08:12,800 那么我们前面讲到了 在不同的特权级 163 00:08:12,840 --> 00:08:14,600 它的处理方式是不一样的 164 00:08:14,640 --> 00:08:17,800 特权级是由谁来决定的 是我们的段描述符里面会有看到 165 00:08:17,840 --> 00:08:21,600 那么段描述符里面会设定它到底处于哪个特权级 166 00:08:21,640 --> 00:08:25,760 比如说我们的CS它的低两位 167 00:08:25,800 --> 00:08:29,040 它如果低两位是0 代表是运行在内核态 168 00:08:29,080 --> 00:08:33,040 那么CS它的最低两位是3 169 00:08:33,080 --> 00:08:35,640 代表运行在用户态 170 00:08:35,680 --> 00:08:39,320 在内核态产生的中断依然在内核态 171 00:08:39,360 --> 00:08:43,720 但是在用户态产生的中断也会跳到内核态里面去 172 00:08:43,760 --> 00:08:45,400 那这是两种不同的方式 173 00:08:45,440 --> 00:08:48,160 因为这里面产生了特权级的变化 174 00:08:48,200 --> 00:08:50,400 对于这种特权级变和没变呢 175 00:08:50,440 --> 00:08:54,080 中断的保存与恢复也是不一样的 176 00:08:54,120 --> 00:08:59,080 我们可以看一看 这边是代表是说产生了中断之后 177 00:08:59,120 --> 00:09:03,840 在同一个特权 意味着在内核态里面产生的中断依然在内核态 178 00:09:03,880 --> 00:09:05,400 这时候会发生什么变化 179 00:09:05,440 --> 00:09:08,120 第一个可以看到 它的Stack 180 00:09:08,160 --> 00:09:10,520 它的栈还是用同一个栈 181 00:09:10,560 --> 00:09:14,920 没有发生变化 只是在这个栈上面压了一些寄存器内容 182 00:09:14,960 --> 00:09:17,480 被打断的那一刻寄存器的内容 183 00:09:17,520 --> 00:09:19,880 第一个是什么呢 184 00:09:19,920 --> 00:09:22,120 可以看到有Error code 185 00:09:22,160 --> 00:09:26,720 这个Error code代表是特意的严重的异常 186 00:09:26,760 --> 00:09:31,480 不是每一个中断或者异常都会产生Error code 187 00:09:31,520 --> 00:09:33,400 第二个会压入EIP和CS 188 00:09:33,440 --> 00:09:36,160 是当前被打断的那个地址 189 00:09:36,200 --> 00:09:39,560 或者是当前被打断的下一条地址 190 00:09:39,600 --> 00:09:41,160 第三个是EFLAGS 191 00:09:41,200 --> 00:09:45,640 当前被打断的时候的标志性的内容 192 00:09:45,680 --> 00:09:47,400 第三个是由我们的硬件 193 00:09:47,440 --> 00:09:51,400 一旦产生中断的时候硬件会压栈压进去 194 00:09:51,440 --> 00:09:55,520 但可以看到它是压在同一个栈里面 195 00:09:55,560 --> 00:10:00,920 第二个如果说当发生中断的时候处于不同特权级 196 00:10:00,960 --> 00:10:06,640 意味着产生中断那一刻 我们的应用程序正在用户态执行 197 00:10:06,680 --> 00:10:08,600 在用户态执行的时候 我们可以看到 198 00:10:08,640 --> 00:10:12,400 第一从用户态到内核态 199 00:10:12,440 --> 00:10:15,000 他们用的是不同的栈 200 00:10:15,040 --> 00:10:18,280 Stack1 Stack2 用的是不同的栈 201 00:10:18,320 --> 00:10:23,880 所以说当由于特权级变化产生了中断的时候呢 202 00:10:23,920 --> 00:10:26,080 除了压刚才说的那些内容之外 203 00:10:26,120 --> 00:10:29,640 还有很重要的两个信息是ESP和SS 204 00:10:29,680 --> 00:10:34,520 这两个内容是当时产生中断的时候 205 00:10:34,560 --> 00:10:37,160 在用户态里面的那个栈的地址 206 00:10:37,200 --> 00:10:39,480 就是SS和ESP 207 00:10:39,520 --> 00:10:41,680 可以看出来它们有很大的区别 208 00:10:41,720 --> 00:10:44,640 很明显在执行完毕要恢复的时候 209 00:10:44,680 --> 00:10:46,040 对于这边而言 210 00:10:46,080 --> 00:10:48,040 它还是恢复到同一个特权级 211 00:10:48,080 --> 00:10:50,520 还是在同一个栈里面继续往下走 212 00:10:50,560 --> 00:10:52,760 对于这边而言呢 213 00:10:52,800 --> 00:10:55,640 它一旦恢复到用户态去执行 214 00:10:55,680 --> 00:11:00,600 也会产生变化 不会用内核态去执行 215 00:11:00,640 --> 00:11:06,440 这是不同特权级下 中断切换对堆栈的影响 216 00:11:06,480 --> 00:11:07,680 给大家做一个简单的介绍 217 00:11:07,720 --> 00:11:11,800 那么X86 当它完成中断服务例程处理完之后 218 00:11:11,840 --> 00:11:14,640 我们还需要返回到被打断程序继续执行 219 00:11:14,680 --> 00:11:16,960 这里面对于中断服务例程来说 220 00:11:17,000 --> 00:11:20,240 它会通过一个iret指令来完成这个返回 221 00:11:20,280 --> 00:11:22,000 但对于我们通常的程序来说 222 00:11:22,040 --> 00:11:26,440 它是通过ret和retf完成函数的返回 223 00:11:26,480 --> 00:11:28,840 而这个是中断服务例程的返回 224 00:11:28,880 --> 00:11:30,840 也意味着他们的处理方式是不一样的 225 00:11:30,880 --> 00:11:34,520 对于没有改变特权级的方式我们可以看到 226 00:11:34,560 --> 00:11:37,200 它其实是把在同一个栈里面 227 00:11:37,240 --> 00:11:39,120 把这个弹出 228 00:11:39,160 --> 00:11:45,720 根据CS和EIP 来跳到当前被打断那个地方继续执行 229 00:11:45,760 --> 00:11:48,600 同时还要恢复它的Eflage的值 230 00:11:48,640 --> 00:11:51,160 这是iret弹出来的时候干的事情 231 00:11:51,200 --> 00:11:55,240 但对于ret而言 它只是弹出了EIP 232 00:11:55,280 --> 00:11:59,640 跳到当时调的那个下一条指令去执行 233 00:11:59,680 --> 00:12:03,000 对retf而言 除了弹EIP之外 234 00:12:03,040 --> 00:12:07,200 还会把CS也给弹出来 恢复CS 235 00:12:07,240 --> 00:12:10,600 实行一种远程跳转的功能 236 00:12:10,640 --> 00:12:11,680 这是他们处理不同 237 00:12:11,720 --> 00:12:12,720 当然这里面说到的是 238 00:12:12,760 --> 00:12:17,240 对于没有特级变化的情况的中断的返回 239 00:12:17,280 --> 00:12:20,720 对于特权级变化的中断的返回会我们可以看到 240 00:12:20,760 --> 00:12:24,600 它弹出的东西更多 这些都要恢复 241 00:12:24,640 --> 00:12:29,120 EIP CS EFLAGE还有 ESP SS 242 00:12:29,160 --> 00:12:32,440 当这个中断服务例程要返回的时候 243 00:12:32,480 --> 00:12:36,720 它需要完成这个弹出的工作 244 00:12:36,760 --> 00:12:41,360 从而可以确保被打断的用户态的程序能够正常地继续执行 245 00:12:41,400 --> 00:12:43,480 不受到影响 246 00:12:43,520 --> 00:12:45,520 当然其实这里面还需要注意 247 00:12:45,560 --> 00:12:47,880 这只是硬件完成的功劳 248 00:12:47,920 --> 00:12:51,760 如果中断服务例程需要对其它寄存器进行修改的话 249 00:12:51,800 --> 00:12:55,600 那么在修改之前 你的中断服务例程需要把寄存器保存起来 250 00:12:55,640 --> 00:13:00,760 在快结束的时候 在iret返回的时候 251 00:13:00,800 --> 00:13:04,000 需要把寄存器恢复回来 然后再恢复这些寄存器 252 00:13:04,040 --> 00:13:10,720 从而可以确保 跳回到这个被中断的应用程序时 253 00:13:10,760 --> 00:13:12,480 程序执行的时候才可以正确执行 254 00:13:12,520 --> 00:13:14,880 大家可以想像 万一这里面的某一个寄存器的值 255 00:13:14,920 --> 00:13:17,920 由于中断服务例程的改变发生了改变 256 00:13:17,960 --> 00:13:21,560 其实你就不能保证我们的应用程序 257 00:13:21,600 --> 00:13:24,280 能够按照它没打断的那样去继续执行了 258 00:13:24,320 --> 00:13:28,600 这实际上说的是中断服务例程要完成的工作 259 00:13:28,640 --> 00:13:31,200 可以看出来 它很重要的是要完成 260 00:13:31,240 --> 00:13:36,120 整个这个被打断程序的一个状态的保存与恢复 261 00:13:36,160 --> 00:13:39,720 这是由我们的硬件 这是硬件自动干的 262 00:13:39,760 --> 00:13:43,800 还有一部分没有描述 我们的软件 我们的操作系统 263 00:13:43,840 --> 00:13:47,600 在中断服务例程处理过程中需要去保存和恢复的 264 00:13:47,640 --> 00:13:49,480 这是通过这两块的结合 265 00:13:49,520 --> 00:13:56,160 才能够确保整个的中断处理过程的正确的执行 266 00:13:56,200 --> 00:13:57,760 接下来我们再看一下 267 00:13:57,800 --> 00:14:01,000 通过中断处理来实现所谓的系统调用 268 00:14:01,040 --> 00:14:03,360 系统调用其实可以理解为一种特殊的中断 269 00:14:03,400 --> 00:14:07,160 它称之为trap 陷入 或者叫软中断 270 00:14:07,200 --> 00:14:08,600 有不同的称呼方式 271 00:14:08,640 --> 00:14:10,240 我们这里面可以看到 272 00:14:10,280 --> 00:14:13,600 我们的应用程序通过系统调用访问OS的内核服务 273 00:14:13,640 --> 00:14:15,680 这一段按照它的实现来说 274 00:14:15,720 --> 00:14:18,280 应该属lab5的内容 275 00:14:18,320 --> 00:14:20,280 直到有了用户态的进程之后 276 00:14:20,320 --> 00:14:24,440 才会通过系统调用来获得OS的服务 277 00:14:24,480 --> 00:14:25,840 但是这里面给大家做个简单的介绍 278 00:14:25,880 --> 00:14:28,480 因为从具体的实践上来说 279 00:14:28,520 --> 00:14:32,200 系统调用的这个机制的建立 和我们中断机制的建立 280 00:14:32,240 --> 00:14:35,520 其实是很接近的 基本上没什么区别 281 00:14:35,560 --> 00:14:37,120 只有一点微小的差别 282 00:14:37,160 --> 00:14:41,360 在实践上面需要考虑 需要如何指定中断 283 00:14:41,400 --> 00:14:45,760 如何完成从用户态到内核态的切换 284 00:14:45,800 --> 00:14:48,880 以及从内核态回到用户态去 285 00:14:48,920 --> 00:14:51,720 这一块有些特殊的一些方法 286 00:14:51,760 --> 00:14:53,760 或者是你通过一种特殊的指令 287 00:14:53,800 --> 00:14:56,040 SYSENTER或者SYSEXIT 288 00:14:56,080 --> 00:15:00,520 这种特殊的新的一些机器指令可以完成相应的功劳 289 00:15:00,560 --> 00:15:03,840 我们在uCore用的还是传统的嵌入的方式 290 00:15:03,880 --> 00:15:05,400 比如说int 80 291 00:15:05,440 --> 00:15:09,520 这种通过软中断的方式来完成系统调用 292 00:15:09,560 --> 00:15:10,840 但是为了完成系统调用 293 00:15:10,880 --> 00:15:15,560 你需要在建立IDT 中断描述符表的时候 294 00:15:15,600 --> 00:15:17,960 要对此要特殊考虑 295 00:15:18,000 --> 00:15:20,840 这跟其他的中断的处理不太一样 296 00:15:20,880 --> 00:15:26,440 因为这里面很明确的指出了是从用户态执行int80 297 00:15:26,480 --> 00:15:30,880 或者int 某一个数能够切换到内核态 298 00:15:30,920 --> 00:15:33,600 它有一个从低优先级到高优先级的转变 299 00:15:33,640 --> 00:15:35,880 这个机制需要我们在IDT表里面 300 00:15:35,920 --> 00:15:39,760 给它设置好相应的权限才能够完成这种转变 301 00:15:39,800 --> 00:15:41,280 好 那我们可以看到 302 00:15:41,320 --> 00:15:47,480 后面的demo也会给大家做进一步的讲解 303 00:15:47,520 --> 00:15:49,880 这是相关的一些参考资料 304 00:15:49,920 --> 00:15:54,240 那我们这里面大量的出现的这个 305 00:15:54,280 --> 00:15:57,720 IA-32 Architectures Software Developer's Manual 306 00:15:57,760 --> 00:16:00,640 这是一个Intel英文的文档 很详细 307 00:16:00,680 --> 00:16:03,120 但是也比较琐碎 308 00:16:03,160 --> 00:16:05,160 也希望大家有时间的话 309 00:16:05,200 --> 00:16:07,120 可以去仔细看一看 310 00:16:07,160 --> 00:16:12,200 相信对你这个代码的理解会有更深入的一个掌握 311 00:16:12,240 --> 00:16:13,400 312 00:16:13,440 --> 00:16:16,080 好 那我们最后来小结一下 313 00:16:16,120 --> 00:16:16,720 我们可以看到 314 00:16:16,760 --> 00:16:21,880 在Lab 1里面我们介绍到其实很多还都是基本的知识 315 00:16:21,920 --> 00:16:23,440 还没有真正讲代码 316 00:16:23,480 --> 00:16:26,800 那我们后面在demo会给大家做一个代码的讲解 317 00:16:26,840 --> 00:16:30,800 可以看到 我们需要去理解保护模式 段机制 318 00:16:30,840 --> 00:16:34,360 从而可以知道我们的uCore操作系统启动之后 319 00:16:34,400 --> 00:16:36,240 处于一种什么样的运行状态 320 00:16:36,280 --> 00:16:39,680 它怎么被我们的Bootloader给加载到内存当中去的 321 00:16:39,720 --> 00:16:44,920 第二个 操作系统中它怎么来完成函数调用关系 322 00:16:44,960 --> 00:16:46,680 怎么来建立这个函数的调用站 323 00:16:46,720 --> 00:16:49,480 其实这实际上靠我们的编译器GCC来完成的 324 00:16:49,520 --> 00:16:51,960 但是我们可以通过一种方法 325 00:16:52,000 --> 00:16:53,840 在我们Lab 1里面需要能够把这个 326 00:16:53,880 --> 00:16:56,600 GCC建立的调用栈给展示出来 327 00:16:56,640 --> 00:16:59,280 展示出来之后 便于我们后续的 328 00:16:59,320 --> 00:17:03,640 对这个出现错误的时候 或者说对它进行一些更深入的理解 329 00:17:03,680 --> 00:17:05,720 知道函数调用怎么产生的 330 00:17:05,760 --> 00:17:07,840 它为了完成某个事情 它调用了多少函数 331 00:17:07,880 --> 00:17:09,320 形成了怎么一个调用关系 332 00:17:09,360 --> 00:17:13,320 这实际上是说C函数调用是需要去了解的 333 00:17:13,360 --> 00:17:17,400 另外 我们也知道操作系统用到很多的一些特权指令 334 00:17:17,440 --> 00:17:19,600 而这些特权指令或者机器指令呢 335 00:17:19,640 --> 00:17:21,720 没法用C语言来表述 336 00:17:21,760 --> 00:17:25,200 为此可以用汇编语言 或者用内联汇编 337 00:17:25,240 --> 00:17:27,960 在C的文件里面嵌入一些汇编 338 00:17:28,000 --> 00:17:30,600 更简洁的实现一些特定的功能 339 00:17:30,640 --> 00:17:36,400 比如说加载页表 加载全局描述符表 加载中断描述符表等等 340 00:17:36,440 --> 00:17:39,040 这些都可以通过内联汇编来实现 341 00:17:39,080 --> 00:17:42,600 为此给大家介绍了一下内联汇编大致的含义 342 00:17:42,640 --> 00:17:45,840 使得大家在阅读这个uCore代码 343 00:17:45,880 --> 00:17:48,680 特别是碰到内联汇编的时候不会感到陌生 344 00:17:48,720 --> 00:17:50,600 知道它大致的一个含义 345 00:17:50,640 --> 00:17:54,720 最后给大家介绍一下关于X86下面的中断处理机制 346 00:17:54,760 --> 00:17:57,120 这也是我们Lab 1里面一个很重要的环节 347 00:17:57,160 --> 00:17:59,000 知道怎么中断建立的 348 00:17:59,040 --> 00:18:01,520 一个外设产生的一个中断 349 00:18:01,560 --> 00:18:05,480 或者应用程序产生的异常等等 350 00:18:05,520 --> 00:18:09,200 那我们应该有一套机制能够应对这种情况 351 00:18:09,240 --> 00:18:10,920 能够及时的响应这种情况 352 00:18:10,960 --> 00:18:12,640 正确的响应这个外设的请求 353 00:18:12,680 --> 00:18:18,040 或者是说能够及时的处理这些异常 错误的状态 354 00:18:18,080 --> 00:18:19,960 这都是我们操作系统去完成的功能 355 00:18:20,000 --> 00:18:21,160 所以在这里面呢 356 00:18:21,200 --> 00:18:22,640 给大家做了一个简单的介绍 357 00:18:22,680 --> 00:18:24,440 看看在X86硬件情况下 358 00:18:24,480 --> 00:18:26,960 怎么能有效的去对它进行管理 359 00:18:27,000 --> 00:18:31,320 好 这也是我们Lab 1的基本知识的部分 360 00:18:31,360 --> 00:18:32,120 谢谢大家 361 00:18:32,160 --> 00:18:32,200