练习六呢 主要是能够去
建立中断向量表
那这个相对来说复杂一点
它需要知道向量表的这个细节
就是一个中断向量表的一项
到底应该是包含哪些中断描述符
中断描述符到底是由哪些信息组成的
可以看出来 在这里面呢
跟中断相关的集中在这个地方
我们说
要让X86的中断系统能够正常工作
那需要做几件事情
这个事情 首先在初始化阶段能够看到
pic_init 这是完成中断控制器的初始化
这是一个外设
是一个特殊的设备
8259的一个中断控制器
这里面有一些相关的细节
这些细节也是主要是
完成对8259一个管理和配制
那么我们大致可以理解为
把8259配置好之后
相应的外设就能够产生中断
并被CPU所接收和处理
为接受后续的时钟中断打开一个基础
那有了这个跟外设相关的
中断控制器初始化完毕之后呢
我们还需要让CPU建立好一个
中断描述符表
也或者简称为中断向量表
那么中断描述符表呢
就是这里面 叫IDT
它的一个初始化过程
那么IDT 我们在前面介绍基础知识的时候
已经跟大家讲过
就是80386这个中断描述符表的
每一项代表什么意思
这里面就把这个项建立好
这里面这个索引
这个中断描述符表可以理解为
是一个大的数组
每一个索引 能代表一个中断号
当一个中断产生之后
它会有对应这个中断号
比如说时钟中断 它会有对应的中断号
这个中断号会用来作为index
来查IDT描述符表
这个表里面对应index一项呢
记录了当产生中断之后
所要去应对中断服务例程的地址
这个地址有两部分信息
一部分是所谓段描述符
或者叫段选择子
第二部分是这个偏移
可以根据段选择子中的内容
来查找我们前面介绍过的段描述符
段描述符存在什么地方呢
段描述符存在全局描述符表中
它一样 因为有了选择子这个index
就可以基于这个index
来查找段描述符
查找段描述符之后 我们就知道
这个有关这个中断服务例程的基址
再加上它的offset
就形成了中断服务例程的这个提示地址
这个整个查找过程是由硬件完成的
但是IDT这个表
就中断描述符表
和我们刚才说的全局描述符表
是由我们软件来建立的
这里面都是由uCore来建立的
怎么能够让我们CPU知道说你建好这个表呢
那这条特殊的指令
比如LIDT 就是load idt
就完成了对中断描述符表的加载
从而使CPU知道中断描述符表在什么地方
它就知道了
这是它的一个起始地址
当建立完这个8259
这个中断控制器的初始化
以及IDT中断描述符表初始化之后
最后还需要一步 就是Enable中断
就是使能中断 这个是在这儿
叫intr enable
如果说你某一个外设
要想让它能够产生中断的话
那么你对特定的外设也做相应的初始化
所以说如果我们这里面
是需要处理时钟中断的话
那么时钟一个初始化过程也需要完成
这里面呢 也是有很多跟外设相关的
跟时钟外设相关的
8253这个芯片相关的一些设置
我们不用太追究细节
可以大致理解为它每一百个tick
会产生一次中断
这里就是大致的一个介绍
当完成了这个时钟外设的初始化之后呢
有了这三步
PIC IDT Clock这三步初始化
那么我们就会使能中断
使能中断就是一个很特殊的指令
STI 这也是一条机器指令
那么这条指令在什么地方呢
STI其实也是一个内嵌汇编
就是我们说的内联汇编
ASM就是STI一条指令
这条指令就完成了使能中断这么一个控制
那么CLI呢
实际上是另外一条指令
就是屏蔽中断
那其实有一个小问题
一开始Bootloader启动的时候
是处于中断使能
还是中断是屏蔽的呢
你怎么知道的呢
这个问题大家可以考虑一下
好 完成了这个中断向量表的初始化之后
其实包括各种外设的初始化之后
就可以产生中断了
产生中断之后
我们的中断服务例程怎么来响应它
这个是需要进一步去完成的内容
做实验内容需要去完成的
那我们在这里面呢
其实是在trap.c里面会有一个函数
这个trap函数来接管
但是比较有意思的一个问题是
这个trap是中断服务例程的入口地址吗
其实不是
中断服务例程的入口地址
在这个文件里面 叫vector.S
这个汇编文件里面
在这里面定义了255个中断号的
所对应的起始地址
当然我们这里面用不了这么多
我们大致可以看着在前面有相应的一些处理
那么它处理的入口
都是叫__alltraps这个地方
当产生64号中断的时候
我们建立中断描述符表
会使得我们CPU指针
执行指令这个指针
跳到EIP指到这个地方来
vector64 这个地址
会进一步跳到__alltraps里面
__alltraps在trap entry里面
这是__alltraps
__alltraps会保存一系列的寄存器
这里面就讲到了
我们讲80386这个中断处理机制
会讲到硬件产生中断之后会保存
保存被打断的地址和它的flag寄存器等等
但是它保存并不完整 那么为了能够
回到被打断的地方重新执行呢
我们需要确保
环境能够完全恢复到跟以前一样
所以我们把后续
在执行中断服务例程中
用到的寄存器的内容呢
都要先保存起来
以避免破坏返回去的那个环境
这里面看到叫push DS ES等等
这都是完成保存
那这些保存信息都
放在这个内核的中断站里面
最后这里看到
它这里面会有一个call trap
那么这个call trap 实际上调用了一个函数
这个函数实际上在trap.c里面
就在这儿
那么trap.c呢会进一步调trap dispatch
那trap dispatch呢
就会查找这个相应中断号
如果说它发现是时钟中断
这里面有这么一个标记
时钟中断号需要注意这个
加起来值就是32
如果发现32
它认为时钟中断会把这个ticks
这是全局的变量
把ticks做一个加操作
如果ticks满了一定次数
这里面设置的次数是100次吧
那就会print一个ticks
就打印出一个信息出来
那这个信息出在什么地方呢
这里很重要的结构
就是trapframe 这个数据结构
在这个结构里面 我们可以看到
实际上它保存了被打断那一刻的
很重要的寄存器的信息
比如说下面这一段
这一段是我们说到的
是在这个硬件一旦产生中断之后
我们硬件CPU会自动保存一些信息
而接下来这些信息呢
是我们的软件来保存的
那么这一条信息是什么呢
这条信息是考虑的是
将来有可能出现从用户态产生中断
会切换到内核态
那么就会多保存一些信息
就是ESP SS
那么这条信息呢
实际上是我们说
如果出现不同特权级的转换
比如说用户态产生中断之后
会切到内核态
那么这就出现一个特权级的转换
那么它需要保存更多的一些信息
要把用户态栈的信息给保存下来
就保存这个地方
以便于能够正确的恢复
从内核态恢复到用户态去进一步执行
对于X86而言
用户态一般我们设置在特权级3
而内核态设置在特权级0在这里面
这是练习六大致的一个介绍
扩展练习呢 其实是给一些
觉得前面六个练习
还觉得不够挑战的同学准备的
主要是能够实现不同特权级的一个切换
怎么能够通过中断机制
来实现不同特权级的切换
这里面重点就是要设置好
返回到某一个特权级
它所保存的信息
保存那些寄存器的信息应该怎么设置
这里面有很多小的细节
如果有同学感兴趣的话可以进一步去学习
那么我们就把练习一到扩展练习
都给大家做了一个简单的介绍
希望大家能够基于刚才介绍更好的完成实验
有同学也说 这个一开始看代码量其实挺大的
我们也给大家说到了
就是关于每一个lab
其实有一系列小的Project组成的
如果大家比较好奇说
想看到这个lab是怎么一步一步建立起来
那么我们还可以查找相应的Project的信息
我们也会在网上公布
大家可以去看一看
到底是如何一步一步的
一个一个小的Project
构成了一个大的lab
然后最终形成一共8个lab
那么它其实有几十个小的Project组成
这是我们这个lab1的一个介绍
好 谢谢大家