QuietHeart's Site

关于中断


这里,对工作时候关于中断方面的一些知识进行了简单介绍与总结,内容不是特别准确,原理应该差不多的。

另外借着中断还介绍了一些关于cpu模式以及系统调用相关的知识。

1 1、 cpsrspsr

cpsr (current program status register)是 cpu core 上的寄存器。这里的 cpu core 实际就是cpu片子上面的核心部分,和cpu片子的厂商无关,而和体系相关,例如arm体系的 cpu core ,可能有多种厂商的cpu。

cpsr 中的内容大致如下:

  1. 表示 over flow 等的位

    省略。

  2. 保留位

    省略。

  3. I位F位 (一般为第6,7两个位)

    irq 或者 fiq 位, irq 就是普通中断屏蔽位, fiq 就是快速中断屏蔽位,从这两个位可以在全局上设置是否屏蔽所有中断,一般设置为1表示屏蔽了所有的中断。

  4. cpu模式的位(例如总长一般5位)

    这些位用来表示cpu处于何种工作模式。例如:

    1. 用户模式( usr ):普通用户工作的模式,是受限制的模式,此模式下面不能修改 cpsr ,所以也不能设置中断屏蔽。
    2. 特权模式( svc ):一般普通用户调用系统调用的时候,会进入到这个模式,进入这个模式之后可以修改 cpsr 了。
    3. 普通中断模式( irq ):进入普通中断的模式,可以修改 cpsr
    4. 快速中断模式( fiq ):进入快速中断的模式,可以修改 cpsr
    5. 数据异常模式:例如访问非法内存。
    6. 指令异常模式:
    7. 未定义模式( und ):例如执行了一个cpu没有定义的指令。

      当执行到一个cpu没有定义的指令的时候,会进入这个模式,然后我们可以通过给这个模式注册一个处理函数来进行相应的动作。如何执行一个cpu没有定义的指令呢?可以在cpu加载程序之后修改程序的代码段。其实调试的时候就用到了这个模式。

    以上的模式中,可以这样分类: usr 是正常模式,其他的是特殊权限模式。正常模式无法进行 cpsr 操作,特权模式可以。还可以分类成 usr 是普通模式,其他为异常模式,异常模式中利用 spsr 中保留的 cpsr 复本,可以进行恢复 cpsrcpsr 只有一个, spsr 可以有多个,例如每个模式一个。

    注:[2019-11-18 一 10:43] 复习阅读感觉,这里描述的正常模式/特殊权限模式,普通模式/异常模式,配对成:普通模式/特权模式,正常模式/异常模式理解起来比较直观。

2 2、中断控制器

这里的中断控制器,是cpu片子中的一个部分(不是 cpu core ),由厂商来确定其具体的情况。例如支持多少个中断等等。每个厂商其名字不同,例如 armv11 的可能名为 avicarmv9 的可能为 gic 等,这里就将中断控制器简称为 intc 了。用来控制cpu片子上面各种设备产生的中断(可知中断是和cpu相关的,并不是和系统相关)。

intc 中有多个寄存器,这些寄存器可以控制不同的中断的行为。重要的寄存器如下:

  1. 使能寄存器组

    这些寄存器中用每一位表示一个中断号对应的中断;假设有120个中断,32位cpu,那么至少4个32位的寄存器,每位表示是否屏蔽相应中断号的中断。

  2. 中断是否发生寄存器组

    这些寄存器中用每一位表示一个中断号对应的中断是否发生。

  3. 中断类型寄存器组

    这组组寄存器表示每种中断向量产生中断的类型,类型有:

    • 上升沿产生中断( rasing edge )
    • 下降沿产生中断( falling edge )
    • 高电平产生中断( high level )
    • 低电平产生中断( low level )
    • 只要跳变时候就产生中断( pulse )

    因为类型有多种,所以每个中断向量的中断类型需要占用寄存器组中的多个位来表示。这个设置是在使能中断之前就设置好的,设置成什么样,中断就会根据相应的设置来产生了。所以这里的设置一定要根据硬件的设计初衷来进行设置。例如,硬件想要低电平产生中断,却设置成了高电平类型,那么每次产生中断的时候都是高电平了,和实际设计的硬件的初衷就不一样了。

  4. 其它

    另外,还有表示中断优先级别的寄存器组以及其它的寄存器组。

3 3、中断发生的时候

产生中断的时候,其实是:

  1. 首先检查 cpu core 中的 cpsr (current program status register)寄存器,检查其中的 I位F位 (即 irq 或者 fiq 位, irq 就是普通中断屏蔽位, fiq 就是快速中断屏蔽位,一般是第6、7位并且设置为1表示屏蔽了所有的中断),从这两个位可以设置是否全局屏蔽所有中断。
  2. 如果没有屏蔽所有中断,那么再检查cpu片子上的 intc (interrupt controller),检查 intc 上面表示中断是否屏蔽的寄存器组,这个寄存器组中的相应于产生的中断的中断号对应的位如果设置为屏蔽的话,则屏蔽该中断;否则就表示没有屏蔽该中断。
  3. 如果没有屏蔽该中断,那么设置 intc 中的表示中断是否发生的寄存器组,设置相应于发生中断的中断号对应的位,表示该中断发生了。如果中断处理完毕,则再将该位清0(这点很重要)。

Linux内核中,使用 request_irq 注册中断,注册之前,中断相关信息(例如哪个中断号对应哪个中断类型等)在内核初始化的时候已经通过一个表项指定好了,当然可以注册之后再修改例如中断类型等信息。具体这个表中包含哪些内容,根据硬件而定。代码可以参见: Kernel/arch/arm/mach-diablo/irq.c 中类似的文件中定义。

可以有如下类似代码:

static struct irq_info __initdata irq_info_array[NR_IRQS] = {
...
}

内核初始化intc相关信息的时候会用到这个表,例如

static struct irq_chip irq_chip_level = {
    .name    = "INTC-level",
    .mask    = diablo_mask_irq,
    .unmask  = diablo_unmask_irq,
    .enable  = diablo_unmask_irq,
    .disable = diablo_mask_irq,
    .eoi     = diablo_ack_irq_level,
};

表项。

4 4、系统调用

大致如下:

  1. 系统调用通过软中断机制实现。

    具体在内核中有一个关于软中断向量的表,其中的每个表项对应一个系统调用。

  2. 一般用户调用系统调用的时候,产生软中断,从 usr 模式切换到 svc 模式。

    例如用户调用的 glibc 中的 read 系统调用中应该有一条 swi (software interrupt)类似的汇编指令,以及相应的系统调用号,产生软中断,导致进入内核中的相应部分。

  3. 进入内核,根据 swi 指定的软中断号等确定到具体的系统调用,以及参数,最后执行内核的系统调用相关代码。

5 5、调试

调试的时候,利用了cpu中的 und 模式。例如程序执行的时候,会一直运行下去,而调试的时候,需要设置断点等调试操作。

gdb 调试为例,本来没有设置断点( break )的指令的话,那么就在设置断点的时候:

  1. 将代码段中相应断点处的指令替换为一个没有的cpu指令(后面的指令偏移不要变化)
  2. 程序当运行到这里的时候,因为执行了一个不支持的cpu指令,所以cpu会进入 und 模式
  3. 然后调用在 und 模式的处理函数,调试器可以在其中打印调试的相关信息。然后等待用户输入 continue 等各种调试命令。
  4. 如果运行 continue 调试命令,则在 und 的处理函数中将刚才 break 处替换的cpu不支持的指令恢复成替换前的指令,然后修改 PC (程序计数寄存器),重新执行该处指令,cpu这时就又进入了正常的模式,跑完了程序,这就实现了 continue
  5. 如果 step ,则在 und 的处理函数中将刚才 break 处替换的cpu不支持的指令恢复成替换前的指令,然后修改PC(程序计数寄存器)重新执行该处指令,同时将下一条指令设置成一个cpu不支持的指令,这样cpu进入正常模式之后,执行一条语句之后,又遇到了不支持的指令,再进入 und 模式,进行类似前面第2步中的过程,实现了单步调试执行。