使用Rust开发操作系统(中断描述符表--IDT)

关于异常,中断

中断大多都是由外部硬件产生的,例如,键盘,硬盘,光驱等,产生中断后会向处理器发送事件请求信号,中断请求信号可能是数据的读写操作,也可能是控制外部设备,这种中断称为硬件中断,还有一种是软件中断,例如使用INT n指令,中断可以在程序执行的过程中触发,每种架构都会对处理器的中断/异常将其归类,并使用数字对同一种类型的中断/异常进行标示(唯一的),这个标示称为中断向量,处理器通过向量号从IDT(中断描述符表Interrupt Descriptor Table)索引出中断/异常处理程序的入口地址,向量号的数值范围是0-255,其中0-31号(共32个)向量被Intel作为异常向量(个别保留使用),剩余32-255供用户使用

在这篇文章中我们只关注与中断向量表,并使用Rust来实现IDT,硬件中断编程和异常处理会有单独的文章介绍

中断描述符表

IDT借助门描述符将中断/异常向量号与处理程序关联起来(就像GDT一样),IDT是一个门描述符数组,每个门描述符占8Byte(64位),第一个表项是有效的,为了使处理器达到最佳性能,将IDT按照8Byte边界对齐,处理器借助IDTR寄存器定位出IDT的位置,使用IDT前需要使用LIDT指令将IDT的线性地址(32位)和长度(16位)加载到IDTR寄存器中(我们可以复用之前编写的DescriptorTablePointer),LIDT指令只能在CPL=0时执行

IDT表项

IDT表项是由门描述符组成,可以使用陷进门,中断门,任务门等3类门(在IA-32e中没有任务门)

保护模式的IDT

中断门描述符和陷进门描述符都包含远跳转地址(段选择子和段内偏移),远跳转为处理器提供中断/异常处理程序的入口地址,以下是中断门描述符和陷进门描述符的结构
中断门描述符结构如下

|63 -  48|47|46 - 45|44|43|42|41|40|39-37|36  -    32|31  -   16 | 15 - 0 |
+--------+--+-------+--+--+--+--+--+-----+-----------+-----------+--------+
| Offset |P |  DPL  |0 |D |1 |1 |0 | 0   | reserved  | Selector  | Offset |
+--------+--+-------+--+--+--+--+--+-----+-----------+-----------+--------+

陷进门描述符结构如下

|63 -  48|47|46 - 45|44|43|42|41|40|39-37|36  -    32|31  -   16 | 15 - 0 |
+--------+--+-------+--+--+--+--+--+-----+-----------+-----------+--------+
| Offset |P |  DPL  |0 |D |1 |1 |1 | 0   | reserved  | Selector  | Offset |
+--------+--+-------+--+--+--+--+--+-----+-----------+-----------+--------+

中断门和陷进门不同的地方就是在对IF标志位的操作上,处理器执行通过中断门描述符执行程序时,处理器会复位IF标志位防止其他中断请求干扰当前中断程序的执行,处理器会在最后执行IRET指令还原保存的EFLAGS寄存器的值,陷进门不会对IF标志位进行操作

IDT索引过程如下
在这里插入图片描述

IA-32e模式的IDT

IA-32e模式的中断/异常处理机制和保护模式的处理机制相似,IA-32e中断发生时的栈空间保存方式由选择性保存(特权级变化时保存),改为无条件保存,IA-32e模式引入了全新的中断栈切换机制

中断门和陷进门描述符结构如下

|   127              -              96             |   95     -         64|
+--------------------------------------------------+----------------------+
|                    reserved                      |     Segment Offset   |
+--------------------------------------------------+----------------------+

|  63  -  48   |47|46-45|44|43-40|39-37|36|35|34-32|31  - 16| 15   -   0  |
+--------------+--+-----+--+-----+-----+--+--+-----+--------+-------------+
| SgemntOffset |P | DPL |0 | Type|  0  |0 |0 | IST |Selecotr|SegmentOffset|
+--------------+--+-----+--+-----+-----+--+--+-----+--------+-------------+

中断门和陷进门描述符都用8B(64位)扩展至16B(128位),高64位保存段内偏移(32-64),低64位用于IST功能

IST只有在IA-32e模式下有效,程序通过IST功能可以让处理无条件的进行栈切换,在IDT的任意一个门描述符都可以使用IST机制或原来的栈切换机制,IST复位时使用旧的栈切换机制,否则使用IST机制

IST位区域用于IST栈表索引,当确定目标IST后,处理器会强制将SS段寄存器赋值为NULL段选择子,并将中断栈地址加载到RSP寄存器中,最后将原SS,RSP,RFLAGS,CS和RIP值压入新栈中

中断堆栈帧

普通的函数通过CALL指令调用,在调用前CPU会将当前的执行地址压入栈中,当函数使用RET指令返回时,CPU会将上次执行的地址从栈中弹出并跳转

函数调用的示意图如下
在这里插入图片描述函数调用后示意图如下
在这里插入图片描述函数远调用示意图如下
在这里插入图片描述函数远返回示意图如下
在这里插入图片描述还记得我们之前写过set_cs函数吗?其中我们用到了一个指令就是LRET我们将段选择子压入栈中并使用LRET指令完成了一个远返回,这样相当于间接设置了CS寄存器,用到的就是这个原理

对于异常/中断的处理,不仅仅只保存返回地址这么简单了,由于中断处理程序通常会在不同的上下文中运行,当一个异常发生时CPU将会执行以下步骤

  1. 对齐堆栈指针: 异常可能在执行任何指令时发生,所以栈指针也可能指向任意地址,一些CPU的指令要求栈指针必须以16字节边界上对齐,因此,在发生中断后CPU需要立即执行这种对齐
  2. 切换堆栈: 在特权级改变时将会切换堆栈(例如内核切换到用户),例如当用户模式程序中发生CPU异常时。可以使用中断堆栈表为特定中断切换配置堆栈(IST)
  3. 保存当前的栈帧(压入栈中):当一个异常/中断发生时,CPU会将当前的SS寄存器和RSP寄存器得值压入栈中,这样从中断处理程序返回时,这可以恢复原始堆栈指针
  4. 将保存并更新RFLAGS:RFLAGS寄存器包含了各种控制和状态位,进入中断时,CPU更改IF标志位(中断门)并将旧值压入栈中
  5. 压入栈指针: 在跳转到异常/中断处理程序之前,CPU将会把RIPCS寄存器的值压入栈中,这与普通函数一样
  6. 保存错误码(如果有的话):对于一些特殊的异常(例如#PF)CPU会将用于描述错误信息的错误码压入栈中
  7. 调用异常处理程序:CPU从IDT中的相应字段读取中断处理程序功能的地址和段描述符,然后,通过将值加载到rip和cs寄存器中来调用此处理程序。
    发生中断前后栈指针的变化如下
    在这里插入图片描述

开始干活

拆分中断门/陷进门的结构

我们要创建IDT的结构,提供配套的操作方法以及辅助创建IDT的子结构
我们先明确以下I

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值