中断
内核需要管理系统中存在的各种设备的状态,比如鼠标是否按下了,键盘是否按下了,按下了什么健等。通常来说,这种查询状态的可以用轮询来实现,但是缺点太多了,可能设备状态没有得到读取,也可能设备状态一直没有变化但还是维持轮询状态,浪费资源。
因此引入了中断机制。中断机制是设备在需要的时候通知内核,让内核能够得到相应的信息,然后再作出处理的一种机制。
同步中断
同步中断是由CPU自身产生的中断信号,只有在指令执行完成之后才会发出中断信号。
通常来说,同步中断成为异常,异常又产生于CPU执行过程中的指令,比如除0。异常又分为故障、陷阱与终止。
故障
在引起异常的指令之前,将异常报告给系统,处理之后再返回到之前执行的位置。
陷入
陷入是在引起异常的指令之后,将异常情况通知给系统处理。
终止
终止是系统出现严重情况的时候,通知系统的异常。产生终止时,被执行的指令是不能恢复正常执行的,比如说硬件故障引起的异常。
异步中断
异步中断是由其他设备产生的中断,可以在指令执行的任何时候产生。
可屏蔽中断
可屏蔽中断是CPU可以选择屏蔽掉的中断信号,比如打印机中断之类的信号。这类中断需要通过中断控制器来发送中断信号给CPU
不可屏蔽中断
这类中断是不可以屏蔽的,一旦产生,CPU必须进行处理。
中断的处理
中断在产生之后,根据中断源说提供的中断向量,从中断描述符表中获取相应的处理程序地址,然后执行。
中断向量表
实模式下的中断向量表由中断向量构成,每个中断向量对应着一个处理程序的入口。
进入保护模式之后,中断向量表改名为中断描述符表,每个表项成为门描述符
。门
意味着中断发生的时候,必须先经过门
的检查,然后才能进入中断处理程序。
中断服务程序
中断服务程序必须在设备驱动程序中定义,并且在使用request_irq
申请IRQ
线的时候,关联到申请的IRQ
线上。
中断服务程序的返回值是一个特殊值-irqreturn_t
,当为IRQ_NONE
(0)时,表示不处理所接收到的中断请求;当为IRQ_HANDLED
(1)时,表示接收到了正确的中断请求,并做出了正确的处理。
中断服务程序是不可重入的,也就是说当前有中断服务程序正在执行时,相应中断线上的所有处理器都会被屏蔽,保证同一个中断程序不会被同时调用。
重要数据结构
中断描述符irq_desc
数据结构irq_desc
用于描述IRQ
线的属性与状态,成为中断描述符。每一个IRQ
都有自己的irq_desc
对象,所有的对象组织在一起成为一个数组。在2.6.0版本中,irq_desc
结构如下:
1 | typedef struct irq_desc { |
中断控制器描述符irq_chip
用于描述不同类型的中断控制器。
中断服务程序描述符irqaction
多个设备可以共享一个IRQ
线,所以使用irqaction
来区分不同的设备。在上述的irq_desc
结构体中,有这么一段:
1 | struct irqaction *action |
这就是使用该IRQ
线的设备队列,该队列中的所有中断服务程序将被依次执行。
中断子系统初始化
Linux的中断处理机制主要包括三个方面的内容:
- 中断子系统初始化,初始化中断描述符表等。
- 中断或异常处理,实际的中断处理过程。
- 中断API,提供一组API给驱动程序调用。
中断描述符的初始化
知道是在内核引导阶段以及初始化阶段执行的就可以了。
中断请求队列的初始化
也是在内核初始化阶段处理的。
中断或者异常的处理
- 中断处理流程:设备产生中断,通过中断控制器将中断信号发送给CPU处理,然后获取中断向量号找到相应的门描述符,从而获取中断服务程序的地址并执行。
- 异常处理流程:异常不需要通过中断控制器发送电信号,只需要CPU找到相应的门描述符,获取相应的异常服务处理程序。
中断API
内核提供了一组接口用于控制系统上的中断状态,开发驱动需要了解这些接口的使用。
request_irq函数
request_irq
的主要任务是为IRQ
线的中断请求队列创建上文提到的irqaction
结点。此函数定义如下:
1 | int request_irq( |
free_irq函数
free_irq
函数的作用与request_irq
函数的作用刚好相反,是从设备描述符中移除相应设备的结点。原型如下:
1 | void free_irq(unsigned int irq, void *dev_id) |
激活与禁止
通常来说,中断处理程序处理中断的时候,如果想要避免并发带来的问题,则需要禁止中断线确保不会抢占代码。
函数 | 描述 |
---|---|
local_irq_disable() | 禁止当前CPU的中断 |
local_irq_enable() | 激活当前CPU的中断 |
local_irq_save() | 禁止当前CPU的中断并保存标志寄存器的内容 |
local_irq_restore() | 恢复当前CPU的中断状态,必须与local_irq_save()函数处于同一函数当中 |
disable() | 禁止指定的中断线,返回前会确保当前中断线上的所有中断处理程序已经退出 |
enable_irq() | 激活指定的中断线 |
disable_irq_nosync() | 禁止指定的中断线,但不会确保当前中断线的中断处理程序已经退出 |
in_irq() | 判断内核是否在执行中断函数 |
in_softrq() | 判断是否正在处理软中断 |
in_interrupt() | 判断是否处于中断上下文,比如正在执行中断程序或者下半部处理程序 |
多处理器系统中的中断处理
处理器间中断
现代操作系统中,通常需要多个处理器之间进行协调工作,这是通过处理器间中断IPI
来实现的。
中断亲和力
指将一个或者多个中断服务程序绑定到特定的CPU上去运行。
中断负载均衡
将重负载的CPU上的中断转移到比较空闲的CPU上进行处理。
中断的下半部
为了解决一次响应需要处理的大量数据的中断,Linux将中断的工作划分为两个部分,即中断的上半部和下半部。上半部是实际响应中断的程序,下半部是是其他一些可以延缓处理的部分,在下半部分处理期间,中断还是打开的。例如网卡的中断接收过程就是这样。