0%

Linux中断

中断

内核需要管理系统中存在的各种设备的状态,比如鼠标是否按下了,键盘是否按下了,按下了什么健等。通常来说,这种查询状态的可以用轮询来实现,但是缺点太多了,可能设备状态没有得到读取,也可能设备状态一直没有变化但还是维持轮询状态,浪费资源。

因此引入了中断机制。中断机制是设备在需要的时候通知内核,让内核能够得到相应的信息,然后再作出处理的一种机制。

同步中断

同步中断是由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
2
3
4
5
6
7
8
9
10
11
12
13
typedef struct irq_desc {
/* IRQ的状态 */
unsigned int status; /* IRQ status */
hw_irq_controller *handler;
/* IRQ产生中断时需要调用的程序服务 */
struct irqaction *action; /* IRQ action list */
unsigned int depth; /* nested irq disables */
/* 中断发生的次数 */
unsigned int irq_count; /* For detecting broken interrupts */
/* 未处理的中断数 */
unsigned int irqs_unhandled;
spinlock_t lock;
} ____cacheline_aligned irq_desc_t;

中断控制器描述符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
2
3
4
5
6
7
8
9
10
11
12
int request_irq(
/* 要申请的IRQ线 */
unsigned int irq,
/* 中断处理方法 */
irqreturn_t (*handler)(int, void *, struct pt_regs *,),
/* irq类型 */
unsigned long irq_flags,
/* 设备名 */
const char * devname,
/* 设备ID */
void *dev_id
)

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将中断的工作划分为两个部分,即中断的上半部和下半部。上半部是实际响应中断的程序,下半部是是其他一些可以延缓处理的部分,在下半部分处理期间,中断还是打开的。例如网卡的中断接收过程就是这样。