这里,对工作时候关于中断方面的一些知识进行了简单介绍与总结,内容不是特别准确,原理应该差不多的。
另外借着中断还介绍了一些关于cpu模式以及系统调用相关的知识。
1 1、 cpsr
和 spsr
cpsr
(current program status register)是 cpu core
上的寄存器。这里的 cpu core
实际就是cpu片子上面的核心部分,和cpu片子的厂商无关,而和体系相关,例如arm体系的 cpu core
,可能有多种厂商的cpu。
cpsr
中的内容大致如下:
表示
over flow
等的位省略。
保留位
省略。
I位
和F位
(一般为第6,7两个位)即
irq
或者fiq
位,irq
就是普通中断屏蔽位,fiq
就是快速中断屏蔽位,从这两个位可以在全局上设置是否屏蔽所有中断,一般设置为1表示屏蔽了所有的中断。cpu模式的位(例如总长一般5位)
这些位用来表示cpu处于何种工作模式。例如:
- 用户模式(
usr
):普通用户工作的模式,是受限制的模式,此模式下面不能修改cpsr
,所以也不能设置中断屏蔽。 - 特权模式(
svc
):一般普通用户调用系统调用的时候,会进入到这个模式,进入这个模式之后可以修改cpsr
了。 - 普通中断模式(
irq
):进入普通中断的模式,可以修改cpsr
。 - 快速中断模式(
fiq
):进入快速中断的模式,可以修改cpsr
。 - 数据异常模式:例如访问非法内存。
- 指令异常模式:
未定义模式(
und
):例如执行了一个cpu没有定义的指令。当执行到一个cpu没有定义的指令的时候,会进入这个模式,然后我们可以通过给这个模式注册一个处理函数来进行相应的动作。如何执行一个cpu没有定义的指令呢?可以在cpu加载程序之后修改程序的代码段。其实调试的时候就用到了这个模式。
以上的模式中,可以这样分类:
usr
是正常模式,其他的是特殊权限模式。正常模式无法进行cpsr
操作,特权模式可以。还可以分类成usr
是普通模式,其他为异常模式,异常模式中利用spsr
中保留的cpsr
复本,可以进行恢复cpsr
。cpsr
只有一个,spsr
可以有多个,例如每个模式一个。注:
复习阅读感觉,这里描述的正常模式/特殊权限模式,普通模式/异常模式,配对成:普通模式/特权模式,正常模式/异常模式理解起来比较直观。- 用户模式(
2 2、中断控制器
这里的中断控制器,是cpu片子中的一个部分(不是 cpu core
),由厂商来确定其具体的情况。例如支持多少个中断等等。每个厂商其名字不同,例如 armv11
的可能名为 avic
, armv9
的可能为 gic
等,这里就将中断控制器简称为 intc
了。用来控制cpu片子上面各种设备产生的中断(可知中断是和cpu相关的,并不是和系统相关)。
intc
中有多个寄存器,这些寄存器可以控制不同的中断的行为。重要的寄存器如下:
使能寄存器组
这些寄存器中用每一位表示一个中断号对应的中断;假设有120个中断,32位cpu,那么至少4个32位的寄存器,每位表示是否屏蔽相应中断号的中断。
中断是否发生寄存器组
这些寄存器中用每一位表示一个中断号对应的中断是否发生。
中断类型寄存器组
这组组寄存器表示每种中断向量产生中断的类型,类型有:
- 上升沿产生中断(
rasing edge
) - 下降沿产生中断(
falling edge
) - 高电平产生中断(
high level
) - 低电平产生中断(
low level
) - 只要跳变时候就产生中断(
pulse
)
因为类型有多种,所以每个中断向量的中断类型需要占用寄存器组中的多个位来表示。这个设置是在使能中断之前就设置好的,设置成什么样,中断就会根据相应的设置来产生了。所以这里的设置一定要根据硬件的设计初衷来进行设置。例如,硬件想要低电平产生中断,却设置成了高电平类型,那么每次产生中断的时候都是高电平了,和实际设计的硬件的初衷就不一样了。
- 上升沿产生中断(
其它
另外,还有表示中断优先级别的寄存器组以及其它的寄存器组。
3 3、中断发生的时候
产生中断的时候,其实是:
- 首先检查
cpu core
中的cpsr
(current program status register)寄存器,检查其中的I位
、F位
(即irq
或者fiq
位,irq
就是普通中断屏蔽位,fiq
就是快速中断屏蔽位,一般是第6、7位并且设置为1表示屏蔽了所有的中断),从这两个位可以设置是否全局屏蔽所有中断。 - 如果没有屏蔽所有中断,那么再检查cpu片子上的
intc
(interrupt controller),检查intc
上面表示中断是否屏蔽的寄存器组,这个寄存器组中的相应于产生的中断的中断号对应的位如果设置为屏蔽的话,则屏蔽该中断;否则就表示没有屏蔽该中断。 - 如果没有屏蔽该中断,那么设置
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、系统调用
大致如下:
系统调用通过软中断机制实现。
具体在内核中有一个关于软中断向量的表,其中的每个表项对应一个系统调用。
一般用户调用系统调用的时候,产生软中断,从
usr
模式切换到svc
模式。例如用户调用的
glibc
中的read
系统调用中应该有一条swi
(software interrupt)类似的汇编指令,以及相应的系统调用号,产生软中断,导致进入内核中的相应部分。- 进入内核,根据
swi
指定的软中断号等确定到具体的系统调用,以及参数,最后执行内核的系统调用相关代码。
5 5、调试
调试的时候,利用了cpu中的 und
模式。例如程序执行的时候,会一直运行下去,而调试的时候,需要设置断点等调试操作。
以 gdb
调试为例,本来没有设置断点( break
)的指令的话,那么就在设置断点的时候:
- 将代码段中相应断点处的指令替换为一个没有的cpu指令(后面的指令偏移不要变化)
- 程序当运行到这里的时候,因为执行了一个不支持的cpu指令,所以cpu会进入
und
模式 - 然后调用在
und
模式的处理函数,调试器可以在其中打印调试的相关信息。然后等待用户输入continue
等各种调试命令。 - 如果运行
continue
调试命令,则在und
的处理函数中将刚才break
处替换的cpu不支持的指令恢复成替换前的指令,然后修改PC
(程序计数寄存器),重新执行该处指令,cpu这时就又进入了正常的模式,跑完了程序,这就实现了continue
。 - 如果
step
,则在und
的处理函数中将刚才break
处替换的cpu不支持的指令恢复成替换前的指令,然后修改PC(程序计数寄存器)重新执行该处指令,同时将下一条指令设置成一个cpu不支持的指令,这样cpu进入正常模式之后,执行一条语句之后,又遇到了不支持的指令,再进入und
模式,进行类似前面第2步中的过程,实现了单步调试执行。