硬中断和软中断

在实模式下,有BIOS中断,进入保护模式,需要建立IDT中断. 进入内核,我们还有守护进程实现的软中断

深入浅出硬中断

按照一般情况,中断可分为以下方面

  • 可屏蔽中断(maskable interrupt)。硬件中断的一类,可通过在中断屏蔽寄存器中设定位掩码来关闭。
  • 非可屏蔽中断(non-maskable interrupt,NMI)。硬件中断的一类,无法通过在中断屏蔽寄存器中设定位掩码来关闭。典型例子是时钟中断(一个硬件时钟以恒定频率—如50Hz—发出的中断)。非屏蔽中断主要用于断电、电源故障等必须立即处理的情况.
  • 处理器间中断(interprocessor interrupt)。一种特殊的硬件中断。由处理器发出,被其它处理器接收。仅见于多处理器系统,以便于处理器间通信或同步。
  • 伪中断(spurious interrupt)。一类不希望被产生的硬件中断。发生的原因有很多种,如中断线路上电气信号异常,或是中断请求设备本身有问题。
  • 软件中断。是一条CPU指令,用以自陷一个中断。由于软中断指令通常要运行一个切换CPU至内核态(Kernel Mode/Ring 0)的子例程,它常被用作实现系统调用(System call)。

使用中断可以

  • 提高计算机系统效率。计算机系统中处理机的工作速度远高于外围设备的工作速度。通过中断可以协调它们之间的工作。当外围设备需要与处理机交换信息时,由外围设备向处理机发出中断请求,处理机及时响应并作相应处理。不交换信息时,处理机和外围设备处于各自独立的并行工作状态。
  • 维持系统可靠正常工作。现代计算机中,程序员不能直接干预和操纵机器,必须通过中断系统向操作系统发出请求,由操作系统来实现人为干预。主存储器中往往有多道程序和各自的存储空间。在程序运行过程中,如出现越界访问,有可能引起程序混乱或相互破坏信息。为避免这类事件的发生,由存储管理部件进行监测,一旦发生越界访问,向处理机发出中断请求,处理机立即采取保护措施。
  • 满足实时处理要求。在实时系统中,各种监测和控制装置随机地向处理机发出中断请求,处理机随时响应并进行处理。
  • 提供故障现场处理手段。处理机中设有各种故障检测和错误诊断的部件,一旦发现故障或错误,立即发出中断请求,进行故障现场记录和隔离,为进一步处理提供必要的依据。

80386处理器

从最开始的80386处理器开始,看看Intel设计它的时候是如何处理中断这个东西的。看看那些伸出来的引脚,下面是它的引脚标注图

20221017230223

80386处理器为中断留出的两个引脚:其中INTR是可屏蔽中断输入口,NMI是不可屏蔽中断输入口。

那么多外部设备,而这只有一个引脚(暂时只考虑可屏蔽中断),这里就需要为CPU配备一个管理中断的秘书——可编程中断控制器PIC。

这个秘书需要干哪些活呢?外部设备的中断都从它来进入中央处理器,所以它负责从外设接收中断信号,并根据优先级向CPU发起中断请求。

可编程中断控制器(PIC)8592A

20221017230636

其中IR0-IR7共8个引脚负责连接外部设备,8259A PIC的每个IR口都连接着一条IRQ线,用于接收外设的中断信号。INT负责连接CPU的INTR引脚,用于向CPU发起中断请求。

通常情况下,使用两片8259A芯片进行级联,一片连接CPU,称为主片,另一片连接到主PIC的IR2引脚,称为从片,这样总共就可以连接8+7=15个外设了

20221017230831

在8259A中,默认情况下的优先级是主片IR0的中断请求优先级最高,主片IR7最低,从片IR0-7所有中断请求优先级都相当于IR2。

所以IRQ线的优先级由高到低次序为IRQ0,IRQ1,IRQ8-15,IRQ3-7。这是默认情况,可以通过编程改变。

在8259a芯片内部有几个重要的寄存器:

  • 中断请求寄存器: IRR,8bit,对应IR0-IR7,当对应引脚产生中断信号时,该bit位置1。
  • 中断服务寄存器: ISR,8bit,对应IR0-IR7,当对应引脚的中断正在被CPU处理时,该bit位置1。
  • 中断屏蔽寄存器: IMR,8bit,对应IR0-IR7,当对应位为1时,表示屏蔽该引脚产生的中断信号。

还有一个中断优先级判决器: PR,当中断引脚有信号时,结合这次产生中断的IRQ号和ISR中记录的当前正在处理的中断信息,根据优先级来决定是否把这个新的中断信号报告给CPU,以此来产生中断嵌套。

20221017231335

中断物理过程

现在假设我们敲击了一个键盘按键,键盘有中断事件产生,这一事件通过IRQ1这根线告知了主PIC,主PIC经过内部一些判断处理后通过INT发送电信号到CPU侧的INTR。

CPU在执行完当前的指令后,检查到INTR有信号,说明有中断请求来了,再检查eflags中的IF不为零,表示当前允许中断,则发送信号给PIC的-INTA,告诉它把本次中断的向量号发送过来。

主PIC收到-INTA管脚上的信号后,通过D0-D7引脚,输出此次中断的中断向量号到数据总线(这里简化了交互过程,实际上有两次INTA信号的发送)。

中断号的产生

操作系统在初始化的时候,会通过对8259a芯片编程(读写I/O端口),将指定PIC芯片的起始向量号,并要求低三位为0,起始向量号按照8对齐

这样规定的原因是,当中断发生时,低三位将自动填充对应的IRQ号,这样就可以和起始向量号相加直接送给数据总线从而被CPU拿到。

具体到Windows中,系统初始化的时候对PIC的编程为:指定主片的起始中断向量号为0x30,指定从片的起始中断向量号为0x38。

这样,通过中断控制器连接的15个外设将被平坦的映射到IDT中0x30-0x40这一范围中。

在使用8259A中断控制器的计算机上,通过IRQ线连接的那15个外设可屏蔽中断是被操作系统线性的映射到了IDT中的一个范围段。

在Windows中是0x30-0x40(PS:在Linux中是0x20-0x2F),同时指定了中断控制器的中断方式为边沿触发

结束模式为普通结束模式(也就是需要CPU侧告知中断处理有没有结束并设置对应bit位,不能自动设置)。

保护模式中断

在保护模式下,中断向量表可以在内存中自由浮动。就像GDT被GDTR指向一样,中断向量表被IDTR(Interrupt Descriptor Table Register,中断描述符表寄存器)指向。

中断描述符表寄存器

CPU中预留了一个 IDTR寄存器,操作系统通过LIDT指令,将中断描述符表的地址放在这个寄存器里.

20221018004722

IDTR寄存器里的值一共有48位,前16位是中断描述符表的大小(字节数),后32位是中断描述符表的起始内存地址,即idt_table 的位置

GDTR和IDTR在格式上完全相同,均包含一个32bit的基地址和16bit的界限。相比之下,CPU中的另外两个关键寄存器LDTR和TR则表现出了相似性,都是16bit大小,分别包含指向LDT和TSS的选择子。

中断描述符表IDT

80386支持的中断类型和8086一样,都是256种。而中断描述符指的是包括中断门,陷阱们和任务门在内的门描述符,门描述符的大小和段描述符一样都是64位,即8字节,因此中断描述符表的实际逻辑大小最大为2KB。

20221102002638

当中断发生时,CPU用中断向量乘以8(因为中断描述符是8字节的)去中断描述符表中找到对应的中断处理门描述符,进行相应的中断处理。

20221018005030

从表项上来看,除了指出中断处理程序的目标地址(16bit选择子和32bit偏移)外,IDT表项还为了进行特权级检测而加入的DPL域。此外,IDT表项还包含一个P比特。

门描述符

段描述符是对某一指定范围的内存段的描述(起始地址、段界限以及特权级),而门描述符则是针对程序控制转移而提出的,指向的都是代码段。

门描述符一共有四种,即调用门、任务门、中断门和陷阱门。

  • 调用门可以使用call far或者jmp far指令进行控制转移。通过调用门进行的程序控制转移,可以改变当前特权级CPL。调用门只能存在于GDT、LDT中,不能存在于IDT中。
  • 任务门主要用于进行任务的切换,因此其中的内容便是对应任务的TSS段选择子,结构比较简单。任务门既能在GDT、LDT中,也能存放于IDT中。
  • 中断门和陷阱门都用于中断处理,标识对应的中断处理程序入口。因此都包含了中断处理程序的16位代码段选择子和32位的段内偏移地址。
  • 中断门和陷阱门只能存放于中断描述符表IDT中,而不能存放在GDT、LDT中。
  • 中断门的TYPE字段固定为1110,而陷阱门的TYPE字段固定为1111(示意图中的D在32位模式下为1,在16位模式下为0)。
  • 通过中断门进入中断服务程序时,CPU会自动的关中断(将EFLAGS的IF位置0),这意味着中断门实现的中断服务程序默认不支持中断嵌套(有需要可以在程序中主动的开中断)。
  • 而通过陷阱门进入中断服务程序时,CPU不会复位IF的值,而是保持不变,这意味着陷阱门默认是支持进行中断嵌套的。

中断处理流程

实模式下,中断的处理过程很单一。当中断被触发时,CPU保护现场,跳转中断处理程序,执行完毕后恢复现场,继续执行原程序。

但是,在保护模式下,从整体上看,80386的中断处理全流程可以分为判断是否存在中断请求,如存在中断则进行中断服务处理,中断服务处理返回后恢复现场这三部分。

在当前指令即将结束的前一个机器周期时,CPU会按照中断优先级先后顺序依次判断是否存在对应的中断请求。

  • 先判断是否存在软中断异常,如果存在则进入中断服务处理,中断类型码由异常的类型决定;
  • 如果不存在软中断异常,接着判断是否存在NMI不可屏蔽中断,如果存在则进入中断服务处理,中断类型码固定为2;
  • 如果不存在NMI不可屏蔽中断,接着判断是否存在INTR可屏蔽中断请求,如果存在INTR且IF=1开中断,则进入中断服务处理,中断类型码由发起INTR的外设给出;
  • 如果不存在INTR可屏蔽中断,最后判断当前是否开启了单步调试(标志寄存器EFLAGS的TF是否为1),如果TF=1,则进入中断服务处理,中断类型码固定为1;
  • 经过上述判断后,CPU认为当前没有中断请求,将正常执行下一条指令。

当检测到中断请求后,CPU将会有一连串的硬件操作,跳转中断服务。

  • CPU从中断请求信号中获取对应的中断类型码(中断向量)。
  • 根据中断向量从中断描述符表中查找对应的门描述符,根据门描述符的类型进行中断服务的处理(任务门进行任务切换,中断门和陷阱门则进行中断服务例程的调用)。
  • 保护现场,按照门描述符的规则处理。如果是中断门或陷阱门,先将当前的EFLAGS标志寄存器入栈,并设置IF、TF的值(陷阱门的IF不变,TF置0,而中断门需要IF、TF都置0),最后按照顺序压入当前的CS、再压入当前的EIP,以便中断服务例程返回后恢复现场。
  • 如果是任务门,则进行之前博客中所说的任务切换,保存当前任务的TSS快照,并加载任务门中的中断服务任务的TSS。
  • 如果对应的中断描述符是中断门或陷阱门,跳转进入对应的中断服务例程;如果对应的中断描述符是任务门,进行中断服务任务的调度,挂起当前任务,令中断服务任务获得CPU控制权。

无论是中断服务例程或是另一个中断服务任务,正常情况下处理完中断服务后都需要返回并恢复现场。

  • 中断门或陷阱门中断服务例程的返回,按照之前入栈的相反顺序将EIP、CS先后出栈;再将EFLAGS标志寄存器出栈,恢复现场(IRET指令)。
  • 任务门中断任务的返回,同样是通过IRET指令进行的。在任务切换中提到的,执行中断服务任务代码中的IRET指令时,如果EFLAGS标志寄存器的NT嵌套任务位为1时,则会触发任务切换。
  • CPU通过当前TSS中的前一个任务TSS字段,将被打断的任务TSS恢复,达到中断服务任务返回,恢复现场的目的。

软断点的原理

软件断点:由非法指令异常实现,适用于运行于内存中的程序(软件实现)。以x86为例,向某个地址打入断点,实际上就是往该地址写入断点指令INT 3,即0xCC。目标程序运行到这条指令之后就会触发SIGTRAP信号,gdb捕获到这个信号,根据目标程序当前停止位置查询gdb维护的断点链表,若发现在该地址确实存在断点,则可判定为断点命中

INT3指令是专门用来支持调试的一条指令,它对应的机器码是0xCC。当cpu执行到这条指令是会产生异常并调用相应的异常处理程序(3号中断)进行进一步的处理。

指令原理

cpu在指令完int3指令后会引发异常,此异常会使操作系统从中断向量表中调用3号中断处理程序,此中断处理程序即函数 nt!KiTrap03( ),此函数进行一些处理后又会继续调用nt!KiDisPatchException( )函数来进行异常的分发。而此函数会先去检查是否存在调试器(即程序是否正在被调试),如果存在调试器则把异常交给调试器,调试器处理完之后在返回到。如果调试器不处理或者就不存在调试器则异常会传递到程序自身的异常处理中,然后返回。

20221017233131

在调试器中的应用

我们都知道调试器中有int3断点,而int3断点就是基于int3指令实现的。以OD为例当我们在某一汇编指令处设下断点后,调试器会把所设断点地址处的第一个字节改为0xCC(即INT3指令),并把原字节保存。之所以我们看起来OD的此地址处字节没有发生任何变化是因为OD为了维持汇编代码的可读性并没有将改变后的指令进行重新反汇编。

当cpu执行到断点时就会执行0xCC(即INT3指令),接着产生异常去执行函数nt!KiTrap03( ),此函数会将指令指针(eip)的值减一,因为其刚执行完0xCC此时eip指向0xCC的下一个字节,所以减一后eip重新指向0xCC(断点处)。接着会调用nt!KiDisPatchException( )函数并将异常分发给调试器,而OD调试器将先还原此断点处的原字节,然后使返回程序将停在此断点处等待用户的进一步操作。

然后为了使下次运行到此处时断点还有效,程序会利用单步异常来把断点处的值再改为0xCC。其在执行完断点处的指令后,会产生单步异常从而被调试器捕捉,然后调试器会将此断点处的值更改为0xCC。

int3和int 3

int3机器码为0xcc,int 3机器码为0xcd 03

二者不光机器码不同,系统会对int3指令一些特殊待遇而int 3却没有此待遇。

int3优点:数量没有限制,操作简单。

int3缺点:因为改变机器码所以易被检测,只能在代码段中使用,而且因为其是基于中断的所以当中断描述符表被破坏时其将无效。

硬断点的原理

硬件断点:由硬件特性实现(数量有限),适用于直接在flash中运行的程序。使用非侵入型附加硬件实现的断点。 位置在只读内存 (ROM) 或闪存中时,硬件断点是停止执行的唯一方法。 使用硬件断点通常会导致处理器完全停止运行。 对于实时系统而言,这种方法是不可取的。

X86系统提供8个调试寄存器(DR0DR7)和2个MSR用于硬件调试。其中前四个DR0DR3是硬件断点寄存器,可以放入内存地址或者IO地址,还可以设置为执行、修改等条件。CPU在执行的到这里并满足条件会自动停下来。

硬件断点十分强大,但缺点是只有四个,这也是为什么所有调试器的硬件断点只能设置4个原因。我们在调试不能修改的ROM时,只能选择这个,所以要省着点用,在一般情况下还是尽量选择软件断点。

还有个INT 1是单步调试命令,这里略过。

揭秘内核软中断

软中断的一种典型应用就是所谓的"下半部"(bottom half),它的得名来自于将硬件中断处理分离成"上半部"和"下半部"两个阶段的机制:上半部在屏蔽中断的上下文中运行,用于完成关键性的处理动作;而下半部则相对来说并不是非常紧急的,通常还是比较耗时的,因此由系统自行安排运行时机,不在中断服务上下文中执行。bottom half的应用也是激励内核发展出的软中断机制的原因。

软中断是利用硬件中断的概念,用软件方式进行模拟,实现宏观上的异步执行效果。很多情况下,软中断和"信号"有些类似,同时,软中断又是和硬中断相对应的,"硬中断是外部设备对CPU的中断","软中断通常是硬中断服务程序对内核的中断","信号则是由内核(或其他进程)对某个进程的中断"。

内核软中断守护进程

简单说就是有一个单独的守护进程,不断轮询一组标志位,如果哪个标志位有值了,那去这个标志位对应的软中断向量表数组的相应位置,找到软中断处理函数,然后跳过去执行。

20221018005417

软中断流程

软中断标志位的一位对应着软中断向量表中的一个元素,中断向量表这个数组大小是32,而中断标志位也有32个。

20221018010051

  • 一组软中断标志位,对应着软中断向量表中每个中断处理函数,有一个内核守护进程不断循环判断中断标志位,如果为1就调用对应的中断处理函数。
  • 由各个子系统调用open_softirq,负责把软中断向量表赋上值。
  • 由各个需要触发软中断的地方调用raise_softirq_irqoff,修改中断标志位的值。
  • 软中断守护线程循环判断中断标志位并调用对应的处理函数。