linux中断模块

中断

并发指的是单位时间内的累积工作量,比如每秒并发数是 100,这是指一秒内累积的请求量总和为 100 个请求,属于并发。并行是指真正同时进行的工作量,比如并行 100 个请求量是指任意瞬间都有 100 个请求在发生,所以单核 CPU 谈并发,多核 CPU 谈并行

操作系统是中断驱动的,首先,操作系统是个死循环。其实,这个死循环本身做不了什么大事,仅仅是保证操作系统能够周而复始地运行下去,而运行的目的是为了等候某些事情发生。

中断分类

把中断按事件来源分类,来自 CPU 外部的中断就称为外部中断,来自 CPU 内部的中断称为内部中断。其实还可以再细分,外部中断按是否导致宕机来划分,可分为可屏蔽中断和不可屏蔽中断两种,而内部中断按中断是否正常来划分,可分为软中断和异常。

外部中断

外部中断是指来自 CPU 外部的中断,而外部的中断源必须是某个硬件,所以外部中断又称为硬件中断。

CPU 提供统一的接口作为中断信号的公共线路,所有来自外设的中断信号都共享公共线路连接到 CPU。CPU 为大家提供了两条信号线。外部硬件的中断是通过两根信号线通知 CPU 的,这两根信号线就是 INTR(INTeRrupt)和 NMI(Non Maskable Interrupt)。

只要从 INTR 引脚收到的中断都是不影响系统运行的,可以随时处理,甚至 CPU 可以不处理。而只要从 NMI 引脚收到的中断,那基本上全是硬伤,CPU 都没有运行下去的必要了。

可屏蔽中断是通过 INTR 引脚进入 CPU 的,外部设备如硬盘、网卡等发出的中断都是可屏蔽中断。

可屏蔽的意思是此外部设备发出的中断,CPU 可以不理会,因为它不会让系统宕机,所以可以通过 eflags寄存器的 IF 位将所有这些外部设备的中断屏蔽。指令cli 使 IF 位为 0,这称为关中断,指令 sti 使 IF 位为 1,这称为开中断。另外,这些设备都是接在某个中断代理设备的,通过该中断代理也可以单独屏蔽某个设备的中断。

对于这类可屏蔽中断,CPU 可以选择不用理会,甚至,即使在理会后,也可以像 Linux 那样,把中断分为上半部和下半部分开处理。

上半部和下半部

把中断处理程序中需要立即执行的部分(分分钟不能耽误的部分)划分到上半部,这部分是要限时执行的,所以通常情况下只完成中断应答或硬件复位等重要紧迫的工作。而中断处理程序中那些不紧急的部分则被推迟到下半部中去完成。

由于中断处理程序的上半部是刻不容缓要执行的,所以上半部是在关中断不被打扰的情况下执行的。当上半部执行完成后就把中断打开了,下半部也属于中断处理程序,所以中断处理程序下半部则是在开中断的情况下执行的,如果有新的中断发生,原来这个旧中断的下半部就会被换下 CPU,先执行新的中断处理程序的上半部,等待线程调度机制为旧中断处理程序择一日期(就是指调度算法认为的某个恰当时机)后,再调度其上 CPU 完成其下半部的执行。

即使像上面所说的那么紧急的中断,CPU 也是可以置之不理的,因为它不会让 CPU 宕机。下面说点让 CPU 宕机的中断—不可屏蔽中断。

不可屏蔽中断是通过 NMI 引脚进入 CPU 的,它表示系统中发生了致命的错误,它等同于宣布:计算机的运行到此结束了。

CPU 收到中断后,通过中断向量表或中断描述 符表去执行相应的处理。

可屏蔽中断并不会导致致命问题,它的数量是有限的,所以每一种中断源都可以获得一个中断向量号。而不可屏蔽中断引起的致命错误原因有很多。**不可屏蔽中断的中断向量号为 2**

内部中断

内部中断可分为软中断和异常。

软中断,就是由软件主动发起的中断,因为它来自于软件,所以称之为软中断。由于该中断是软件运行中主动发起的,所以它是主观上的,并不是客观上的某种内部错误。

用 gdb 或 bochs 调试程序时,实际上就是调试器 fork 了一个子进程, 子进程用于运行被调试的程序。调试器中经常要设置断点,其原理就是父进程修改了子进程的指令,将其用 int3 指令替换,从而子进程调用了 int3 指令触发中断。**断点本质上是指令的地址**,调试器(父进程)将被调试进程(子进程)断点起始地址的第 1 个字节备份好之后,在原地将该指令的第 1 字节修改为 0xcc(int3 指令的机器码)。

这样指令执行到断点处时,会去执行机器码为 0xcc 的 int3 指令,该指令会触发 3 号中断,从而会去执行 3 号中断对应的中断处理程序,由于中断处理程序在运行时也要用到寄存器,为了保存所调试进程的现场,该中断处理程序必须先将当前的<u>寄存器和相关内存单元</u>压栈保存,用户在查看寄存器和变量时就是从栈中获取的。当恢复执行所调试的进程时,中断处理程序需要将之前备份的 1 字节还原至断点处,然后恢复各寄存器和内存单元的值,修改返回地址为断点地址,用 iret 指令退出中断,返回到用户进程继续执行。

异常是另一种内部中断,是指令执行期间 CPU 内部产生的错误引起的。

由于是运行时错误,所以它不受标志寄存器 eflags 中的 IF 位影响,无法向用户隐瞒

总结:只要中断关系到“正常”运行,就不受 IF 位影响。如软中断、NMI、异常等。

并不是所有的异常都很致命,按照轻重程度,可以分为以下三种。

(1)Fault,也称为故障。这种错误是可以被修复的一种类型,属于最轻的一种异常,它给软件一次“改过自新”的机会。最典型的例子就是操作系统课程中所说的缺页异常 page fault,话说 Linux 的虚拟内存就是基于 page fault 的,这充分说明这种异常是极易被修复的,甚至是有益的。

(2)Trap,也称为陷阱,这一名称很形象地说明软件掉进了 CPU 设下的陷阱,导致停了下来。比如 <u>int3 指令</u>便引发此类异常,为了让中断处理程序返回后能够继续向下执行,CPU将中断处理程序的返回地址指向导致异常指令的下一个指令地址。

(3)Abort,也称为终止,从名字上看,这是最严重的异常类型,一旦出现,由于错误无法修复,程序将无法继续运行,操作系统为了自保,只能将此程序从进程表中去掉。导致此异常的错误通常是硬件错误,或者某些系统数据结构出错。

中断机制的本质是来了一个中断信号后,调用相应的中断处理程序。所以,CPU 不管有多少种类型的中断,为了统一中断管理,把来自外部设备、内部指令的各种中断类型统统归结为一种管理方式,即为每个中断信号分配一个整数,用此整数作为中断的 ID,而这个整数就是所谓的中断向量,然后用此 ID 作为中断描述符表中的索引,这样就能找到对应的表项,进而从中找到对应的中断处理程序。

中断向量的作用和选择子类似,它们都用来在描述符表中索引一个描述符

异常和不可屏蔽中断的中断向量号是由 CPU 自动提供的,而来自外部设备的可屏蔽中断号是由中断代理提供的(咱们这里的中断代理是 8259A),软中断是由软件提供的。

中断描述符表(IDT)

中断描述符表(Interrupt Descriptor Table,IDT)是保护模式下用于存储中断处理程序入口的表,当CPU 接收一个中断时,需要用中断向量在此表中检索对应的描述符,在该描述符中找到中断处理程序的起始地址,然后执行中断处理程序。

表中不仅仅有中断描述符,还可以有任务门描述符和陷阱门描述符。

中断描述符表的描述符有自己的名称—门。段描述符中描述的是一片内存区域,而门描述符中描述的是一段代码。门描述符中添加了各种属性,这就是进门的条件。当处理器把这些条件检查通过后就不再限制程序了。

中断门包含了中断处理程序所在段的段选择子和段内偏移地址。当通过此方式进入中断后,标志寄存器 eflags 中的 IF 位自动置 0,也就是在进入中断后,自动把中断关闭,避免中断嵌套。Linux 就是利用中断门实现的系统调用,就是那个著名的 int 0x80。中断门只允许存在于 IDT 中。描述符中中断门的 type 值为二进制 1110,其结构如图 7-3 所示。

陷阱门和中断门非常相似,区别是由陷阱门进入中断后,标志寄存器 eflags 中的 IF 位不会自动置 0。陷阱门只允许存在于 IDT 中。描述符中陷阱门的 type 值为二进制 1111。其结构如图 7-4 所示。

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

一个中断源就会产生一个中断向量,每个中断向量都对应中断描述符表中的一个门描述符,任何中断源都通过中断向量对应到中断描述符表中的门描述符,通过该门描述符就找到了对应的中断处理程序。

对比中断向量表,中断描述符表有两个区别。

(1)中断描述符表地址不限制,在哪里都可以。

(2)中断描述符表中的每个描述符用 8 字节描述。

IDT 的位置不固定,当中断发生时,CPU 是如何找到它的呢?

在 CPU 内部有个中断描述符表寄存器(Interrupt Descriptor Table Register,IDTR),该寄存器分为两部分:第 0~15 位是表界限,即 IDT 大小减 1,第 16~47 位是 IDT 的基地址,这和之前介绍的 GDTR是一样的原理。

中断描述符表地址肯定要加载到这个寄存器中,只有寄存器 IDTR指向了 IDT,当 CPU 接收到中断向量号时才能找到中断向量处理程序,这样中断系统才能正常运作。

注意,GDT 中的第 0个段描述符是不可用的,但 IDT 却无此限制,第 0 个门描述符也是可用的,中断向量号为 0 的中断是除法错。同加载 GDTR 一样,加载 IDTR 也有个专门的指令—lidt

中断处理过程及保护 

完整的中断过程分为 CPU 外和 CPU 内两部分,过程中涉及到特权级检查,也就是本节所说的保护。

CPU 外:外部设备的中断由中断代理芯片(Intel 8259A)接收,处理后将该中断的中断向量号发送到 CPU。

CPU 内:CPU 执行该中断向量号对应的中断处理程序。

这里只讨论处理器内的过程,这是软件能控制的部分:

(1)处理器根据中断向量号定位中断门描述符。

中断向量号是中断描述符的索引,当处理器收到一个外部中断向量号后,它用此向量号在中断描述符表中查询对应的中断描述符,然后再去执行该中断描述符中的中断处理程序。

当前进程被中断打断后,为了从中断返回后能继续运行该进程,处理器自动把 CS 和 EIP 的当前值保存到中断处理程序使用的栈中。除了要保存CS、EIP 外,还需要保存标志寄存器 EFLAGS,如果涉及到特权级变化,还要压入 SS 和 ESP 寄存器。

(2)处理器进行特权级检查。

当前特权级 CPL 必须在门描述符 DPL 和门中目标代码段 DPL 之间。这是为了防止位于3 特权级下的用户程序主动调用某些只为内核服务的例程。

(3)执行中断处理程序。

特权级检查通过后,将门描述符 目标代码段选择子 加载到 代码段寄存器 CS 中,把门描述符中 中断处理程序的偏移地址 加载到 EIP,开始执行中断处理程序。

从中断返回的指令是 iret,它从栈中弹出数据到寄存器 cs、eip、eflags 等,根据特权级是否改变,判断是否要恢复旧栈。当中断处理程序执行完成返回后,通过 iret 指令从栈中恢复 eflags 的内容。

TF 表示 Trap Flag,也就是陷阱标志位,这用在调试环境中,当TF 为 0 时表示禁止单步执行

NT 位表示 Nest Task Flag,即任务嵌套标志位。任务嵌套调用是指CPU 将当前正执行的旧任务挂起,转去执行另外的新任务,待新任务执行完后,CPU 再回到旧任务继续执行。

为什么 CPU 执行完旧任务后还能回到新任务呢?

(1)将旧任务 TSS 选择子写到了新任务 TSS 中的“上一个任务 TSS 的指针”字段中。

(2)将新任务标志寄存器 eflags 中的 NT 位置 1,表示新任务之所以能够执行,是因为有别的任务调用了它。

怎样回到旧任务呢?

通过 iret 指令。iret 指令因此有了两个功能,一是从中断返回,另外一个就是返回到调用自己执行的那个旧任务,这也相当于执行一个任务。当 CPU 执行 iret 时,它会去检查 NT 位的值,如果 NT 位为 1,这说明当前任务是被嵌套执行的,因此会从自己 TSS 中“上一个任务 TSS 的指针”字段中获取旧任务,然后去执行该任务。如果 NT 位的值为 0,这表示当前是在中断处理环境下,于是就执行正常的中断退出流程。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值