保护模式下的中断

简介: 保护模式下的中断

中断的基本概念

  • 程序中断是指在计算机执行现行程序的过程中,出现某些急需处理的异常情况或特殊请求,CPU 暂时中止现行程序,而转去对这些异常情况或特殊请求进行处理,在处理完毕后 CPU 又自动返回到现行程序的断点处,继续执行原程序
  • 用生活中的例子来理解,比如你正在用手机玩游戏,这时候突然来电话了,玩游戏就被电话给中断了,于是,你需要先接听电话,挂了电话后又继续玩游戏
  • 我们接下来讲的中断都是针对 x86 处理器(对于其它处理器也是大同小异)

中断的分类

  • 在 x86 系列处理器中,中断的分类如下:

内部中断

  • 所谓的内部中断,是在 CPU 内部产生并进行处理的。比如:
  • CPU 遇到一条除以 0 的指令时,将产生 0 号中断,并调用相应的中断处理程序
  • CPU 遇到一条不存在的非法指令时,将产生 6 号中断,并调用相应的中断处理程序
  • 对于内部中断,有时候也称之为异常
  • 软中断也属于内部中断,是非常有用的,它是由 int 指令触发的。比如 int3 这条指令,gdb 就是利用它来实现对应用程序的调试(这里就不深入研究了)

外部中断

  • 所谓的外部中断,是在 CPU 外部产生
  • x86 CPU 上有 2 个中断引脚:INT 和 INTR,分别对应:不可屏蔽中断和可屏蔽中断
  • 所谓不可屏蔽,就是说:中断不可以被忽视,CPU 必须处理这个中断,如果不处理,程序就没法继续执行
  • 而对于可屏蔽中断,CPU 可以忽略它不执行,因为这类中断不会对系统的执行造成致命的影响

中断号

  • 中断号在有的也叫中断向量号,在 x86 处理器中,一共支持 256 个中断,每一个中断都分配了一个中断号,从 0 到 255
  • 其中,0 ~ 31 号中断向量被保留,用来处理异常和非屏蔽中断(其中只有 2 号向量用于非屏蔽中断,其余全部是异常)
  • 当 BIOS 或者操作系统提供了异常处理程序之后,当一个异常产生时,就会通过中断向量表找到响应的异常处理程序
  • 从中断号 32 开始,全部分配给外部中断,比如:系统定时器中断 IRQ0,分配的就是 32 号中断;Linux 的系统调用,分配的就是 128 号中断

中断向量表和中断服务程序

  • 当一个中断发生的时候,CPU 获取到该中断对应的中断号,下一步就是要确定调用哪一个函数来处理这个中断,这个函数就称作中断服务程序(Interrupt Service Routine,ISR),有时候也称作中断处理程序、中断处理函数,本质都一样
  • 中断向量表(IVT,Interrupt VectorTable)中断号和中断处理函数之间的重要桥梁

  • 从图中可以看出,每一个中断向量都指向对应的中断处理函数,我们把中断向量当做函数指针(中断服务程序入口地址)
  • 中断向量表英文名:Interrupt VectorTable (缩写:IVT)
  • 中断向量表的本质:就是一段从实际物理地址 0x0 开始,连续 1024 个字节的内存空间(每个中断向量占 4 个字节(2 个字节的段地址,2 个字节的偏移地址),共 256 个中断向量),我们可以把它当成一个 C 语言数组:unsigned int VectorTable[256],数组的每个单元中存放一个中断服务程序的入口地址
  • 当中断发生时,CPU 是怎么找到对应得中断服务程序的呢?
  • 当中断发生后,CPU 会根据自己产生的中断号自动从中断向量表中找到对应的中断向量(类似于数组访问 VectorTable[中断号]),然后跳转到这个中断向量指向的地址处执行(函数指针)
  • 中断号的产生和跳转都是 CPU 实现,软件只负责中断向量表的构建和中断服务程序具体实现即可

中断现场的保护和恢复

  • 当一个中断发生的时候,肯定有一个正在执行的程序被打断,当中断处理函数执行结束之后,这个被打断的程序需要从刚才被打断的地方继续执行(暂时先不要考虑从中断返回点,进行多任务切换的事情)
  • 而一个程序执行的上下文环境,就是处理器中的各种寄存器内容:代码段寄存器 cs,指令指针寄存器 sp,标志寄存器 EFLAGS
  • 但是,在中断处理程序中,也需要使用这些寄存器
  • 处理器中的这些寄存器,就是每一个程序执行时上下文信息的存储容器,当然也包括中断处理程序!
  • 因此,在进入中断处理程序之前,CPU 会自动的把这些寄存器 push 到栈中保存起来,然后再跳转到中断处理程序中去执行
  • 当中断处理程序执行结束后,CPU 会从栈中弹出这些内容,恢复到相应的寄存器中,于是被打断的程序就可以继续执行了

中断处理程序的安装

  • 既然通过中断向量,找到了中断处理程序,那么这些中断处理程序都是谁放在内存中的呢?
  • 如果您看过一些比较底层的计算机书籍,就能看到一般都会举例:如何手动的把一个普通函数设置为一个中断处理函数
  • 操作步骤是:
  • 在代码中,写一个普通函数
  • 把这个函数的指令码,搬运到内存中的某一个位置
  • 把这个位置(段地址:偏移量),作为一个中断向量,设置到中断向量表中
  • 此时,如果发生了该中断,你所提供的函数就作为中断处理函数被执行了
  • 当然了,在一个计算机系统中,BIOS、操作系统和各种外设,会自动为我们提供很多基本的中断处理函数的
  • 比如:BIOS 中就提供了软中断、内部中断、硬件中断等处理函数,这些函数是固化在 BIOS 的代码中的(映射到 BIOS 所在的 ROM 芯片上),BIOS 只需要把这些处理函数的地址,写入到中断向量表中的相应位置即可
  • 内存中的某些位置是映射到外设的 ROM,在这些外设的 ROM 中也存在一些外设自带的程序。BIOS 在启动时,会扫描这些映射到外设的内存空间,通过某些关键字信息,如果发现外设有自带的程序,就会去执行。这些外设程序一般是进行一些自身的初始化,并填写相关的中断向量表,使它们指向外设自带的中断处理程序
  • 对于操作系统来说就更不用说了,它会重新安排自己需要的中断处理函数,这部分内容接下来我们就会介绍到

保护模式下的中断

  • 注意了,前面所说的中断都是实模式下的中断处理方式,中断向量表的建立以及中断服务程序的实现全部是由 BIOS 完成。因此我们在实模式下才能直接使用 int 中断
  • 疑问:既然中断是由 BIOS 实现的,那么为啥还要费那么多力气讲解呢?
  • 中断不仅实模式下有,保护模式下也有,操作系统自古以来就是中断驱动的,有了实模式下的中断讲解,我们才能更好的理解保护模式下的中断实现
  • 有一个疑问:既然实模式下已经有中断处理了,为啥保护模式下又要实现一遍?
  • 原因是保护模式下有了保护机制,我们不能够直接访问内存了,中断处理也必须融入保护的思想,而这种思想的体现就是中断描述符表

中断描述符表

  • 在保护模式下,中断描述符表取代了实模式下的中断向量表
  • 中断描述符表(Interrupt Descriptor Table,IDT)是保护模式下用于存储中断处理程序入口的表,当 CPU 接收一个中断时,需要用中断向量在此表中检索对应的描述符,在该描述符中找到中断处理程序的起始地址,然后执行中断处理程序
  • 中断描述符表中有什么?
  • 哈哈,你肯定会说,中断描述符表中当然是中断描述符啦。其实中断描述符表可以包含中断门,陷阱门和任务门三种描述符
  • 门,顾名思义,是通往某处的入口,在计算机中,用门来表示一段程序的入口。拿它和段描述符对比一下就容易理解了,段描述符中描述的是一片内存区域,而门描述符中描述的是一段代码
  • 中断描述符表(IDT)中存放的三种类型的门描述符格式

  • 下面简要说下这几种门描述符
  • 任务门:任务门和任务状态段(Task Status Segment,TSS)是 Intel 处理器在硬件一级提供的任务切换机制,所以任务门需要和 TSS 配合在一起使用,在任务门中记录的是 TSS 选择子,偏移量未使用。任务门可以存在于全局描述符表 GDT、局部描述符表 LDT、中断描述符表 IDT 中。描述符中任务门的 type 值为二进制 0101。顺便说一句大多数操作系统(包括 Linux)都未用 TSS 实现任务切换,咱们这里也不讨论啦
  • 中断门:中断门包含了中断处理程序所在段的段选择子和段内偏移地址。当通过此方式进入中断后,标志寄存器 eflags 中的 IF 位自动置 0,也就是在进入中断后,自动把中断关闭,避免中断嵌套。Linux 就是利用中断门实现的系统调用,就是那个著名的 int 0x80。中断门只允许存在于 IDT 中。描述符中中断门的 type 值为二进制 1110
  • 陷阱门:陷阱门和中断门非常相似,区别是由陷阱门进入中断后,标志寄存器 eflags 中的 IF 位不会自动置 0。陷阱门只允许存在于 IDT 中。描述符中陷阱门的 type 值为二进制 1111
  • 回顾一下,我们前面是不是学过调用门,跟上面的三种门有啥关系呢?其实没啥关系,仅仅只是描述符的数据格式相近罢了。现在我们再来回顾一下调用门

  • 调用门是提供给用户进程进入特权 0 级的方式,其 DPL 为 3。它不能用 int 指令调用,只能用 call 和 jmp 指令。调用门可以安装在 GDT 和 LDT 中。描述符中调用门的 type 值为二进制 1100

保护模式下的中断处理

  • 处理器对异常和中断处理过程的调用操作方法与使用 CALL 指令调用程序过程和任务的方法类似。当响应一个异常或中断时,处理器使用异常或中断的向量作为 IDT 表中的索引,在 IDT 表中找到对应的中断描述符后,该描述符中仅有偏移值,想要找到一个程序的确定地址,肯定还需要段基址啊,那么段基址从哪来呢?我们又看到,该描述符中包含了选择符,这个选择符就是中断描述符表在描述符表 GDT 或 LDT 中对应的选择符,于是,由这个选择符就找到了 GDT 或 LDT 中对应的段描述符,从段描述符中获得段基址。此时段基址,偏移值是不是都有了,于是 CPU 不就能找到 [段基址:偏移值] 所指向的中断服务程序入口了嘛。当然了,在跳转之前肯定还有属性,特权级检查等保护机制啦
  • 对比中断向量表,中断描述符表有两个区别
  • 中断描述符表地址不限制,在哪里都可以
  • 中断描述符表中的每个描述符用 8 字节描述
  • 不管是实模式还是保护模式,当中断发生后,中断源所对应的中断号是固定的,所以中断向量表和中断描述符表中顺序是一致的,这是由 CPU 决定的,软件必须根据硬件规定构建中断向量表或中断描述符表
  • IDT 中必须提供每种中断所对应的中断服务程序(ISR)入口地址

总结

  • 从功能的角度看,中断有 2 个作用:
  • 提供执行异步序列的机制
  • 给应用程序提供进入系统层的入口
  • 关于第 2 点,Linux 中也是通过 int 0x80 中断,让应用层的程序有机会进入到系统代码中去执行
  • 因为应用层与操作系统层的代码,是工作在不同的安全级别。为了系统的安全,Linux 操作系统提供了这样的一个机制,让低安全级别的应用程序,进入到高安全级别的操作系统代码中去执行,毕竟所有的硬件等系统资源都是由操作系统来统一管理的
  • 我们再从中断处理程序的安装角度来看,中断本质上就是增加了一层间接性:通过固定位置的中断向量表,让中断处理函数的实际地址可以被动态的放在任意位置
目录
相关文章
|
3月前
|
资源调度 调度 UED
CPU执行系统调用时发生中断,操作系统还能切回中断前的系统调用继续执行吗?
系统调用服务例程在执行过程中,通常不会被中断。系统调用服务例程的执行是一个原子操作,即在执行期间不会被中断。这是为了确保在系统调用服务例程执行期间对内核数据结构的一致性和完整性。
|
4月前
|
编译器 程序员
进入保护模式
进入保护模式
33 0
|
4月前
|
Linux
保护模式中的特权级
保护模式中的特权级
26 0
|
4月前
|
存储 程序员 数据安全/隐私保护
保护模式
保护模式
74 0
|
4月前
|
编译器 C语言 芯片
内核里的中断
内核里的中断
35 0
|
4月前
|
安全 Linux Windows
中断处理与特权级转移
中断处理与特权级转移
50 0
|
7月前
|
传感器 调度
什么是中断系统?
一、什么是中断系统 中断系统是计算机系统中的一种机制,它允许外部设备和程序请求处理器的注意力,以便进行特定的操作。当一个中断请求被触发时,处理器会暂停当前正在执行的程序,转而执行与中断相关的程序或服务例程。中断系统可以提高计算机系统的效率和响应速度,因为它允许处理器在等待某些事件的同时执行其他任务。常见的中断包括硬件中断(例如键盘输入、鼠标移动、网络数据传输等)和软件中断(例如操作系统调度、系统调用等)。 二、中断系统的特点 中断系统具有以下特点: 1. 实时性:中断系统能够及时响应外部设备的请求,提高计算机系统的响应速度和效率。 2. 可靠性:中断系统能够保证中断请求的可靠性,确保外部设备的
190 0
|
12月前
|
编解码 编译器
4.1保护模式
4.1保护模式
111 0
详解中断系统
本文针对地详解了中断系统
221 0
|
芯片
【嵌入式开发】ARM 关闭中断 ( CPRS 中断控制位 | 中断使能寄存器 | 中断屏蔽寄存器 | 关闭中断 | 汇编代码编写 )(一)
【嵌入式开发】ARM 关闭中断 ( CPRS 中断控制位 | 中断使能寄存器 | 中断屏蔽寄存器 | 关闭中断 | 汇编代码编写 )(一)
738 0
【嵌入式开发】ARM 关闭中断 ( CPRS 中断控制位 | 中断使能寄存器 | 中断屏蔽寄存器 | 关闭中断 | 汇编代码编写 )(一)