中断和中断处理
中断可以看成是外部产生的电信号,中断随时可能到来,它不需要考虑与时钟的同步;异常与中断不同,它在产生时必须考虑与处理器时钟同步。异常也成为同步中断。
中断处理程序
内核在响应中断的时候,会执行一个函数,称为中断处理程序。中断处理程序时设备驱动的一部分,它运行于中断上下文的特殊上下文中。
上半部与下半部的对比
中断处理程序是上半部——接收一个中断,它就立即开始执行,但只做有严格时限的工作。能够被允许稍后完成的工作会推迟到下半部。此后,在适合的时机,下半部会被开中断执行。
注册中断处理程序
中断处理程序标志
例子
卸载
编写中断处理程序
第一个参数是中断处理程序要响应的中断号,如今这个参数没有太大用处;第二个需要与中断处理程序注册时传递给request_irq()的参数dev一致,用来区分同一个中断处理程序的多个设备。
返回值IRQ_NONE表示检测到一个中断,但是中断对应的设备并不是在注册处理函数期间指定的产生源。另外一个为IRQ_HANDLED表示正常处理结束。
重入和中断处理程序
当一个中断处理程序被执行时,对应的中断线上的中断请求会被屏蔽掉;不同中断线上的中断请求不会被屏蔽。
共享中断处理程序
内核收到一个中断后,会依次调用在该中断线上的每一个处理程序,因此一个处理程序必须知道他是否为这个中断负责(编写处理程序时必须有这种检查)。这需要硬件提供支持以便处理程序进行检查。
中断上下文
中断上下文与进程毫无关系,中断上下文不可以睡眠,它具有严格的执行时间限制,应该尽快完成。如果需要进行繁重的工作,应把工作分离出来,放在下半部执行。
中断处理机制的实现
/proc/interrupts
中断控制
锁提供保护机制,防止来自其他处理器的并发访问;禁止中断则防止来自其他中断处理程序的并发访问。
禁止和激活中断
禁止当前处理器上的本地中断(仅仅是当前处理器),随后又激活它们:
local_irq_disable();
local_irq_enable();
禁止中断线
中断系统的状态
通常需要了解当前是否正在处于中断上下文,或者中断的状态(禁止的或者激活的)。
//如果本地处理器禁止中断,则返回非0;否则返回0
irqs_disable();
整理
小结
下半部和推后执行的动作
中断处理程序只是中断处理流程的上半部分
下半部
下半部的任务就是执行与中断处理密切相关但中断处理程序本身不执行的工作。最好的情况是,中断处理程序将所有工作交给下半部执行,我们希望中断处理程序完成的工作越少越好。
具体任务该放在哪个部分执行并没有严格的限制,但是有一些可以提供借鉴的规则:
为什么要用下半部
中断处理程序执行时会屏蔽相应的中断线或者当前处理器上的所有中断,因此中断被屏蔽的时间对系统的响应能力和性能都至关重要。
下半部的环境
内核提供了三种不同形式的下半部实现机制:软中断、tasklet和工作队列。
软中断
软中断的实现
软中断处理程序
执行软中断
其中,pending为待处理软中断的位图
使用软中断
内核定时器和tasklet都是建立在软中断之上。tasklet可以动态生成,由于它们对加锁的要求不高,所以使用起来也很方便,性能也不错。当然,对于时间要求严格并能自己高效地完成加锁操作的应用,软中断会是正确的选择。
分配索引
注册你的处理程序
触发你的软中断
tasklet
tasklet是利用软中断实现的一种下半部的机制,所以它本身也是软中断。
tasklet的实现
tasklet由两类软中断代表:HI_SOFTIRQ和TASKLET_SOFTIRQ。
tasklet结构体
调度tasklet
使用tasklet
声明你自己的tasklet
静态声明
DECLARE_TASKLIST(name,func,data);
DECLARE_TASKLIST_DISABLED(name,func,data);
动态声明
tasklet_init(t,tasklet_handler,dev);
编写你自己的tasklet处理程序
调度你自己的tasklet
tasklet_schedule(&my_tasklet);
ksoftirqd
工作队列
工作队列中的任务将会交由一个内核线程执行,运行于进程上下问之中。工作队列运行重新调度甚至睡眠,如果退后执行的任务需要睡眠,那么就选择工作队列。
工作队列的实现
数据结构
总结
使用工作队列
创建推后执行的工作
工作队列处理函数
该函数不能访问用户空间
对工作进行调度
刷新操作
创建新的工作队列
下半部机制的选择
在下半部之间加锁
禁止下半部
这些函数并不能禁止工作队列的执行,因为它运行于进程上下文中。
进程上下文和中断上下文
-
为什么可能导致睡眠的函数都不能在中断上下文中使用呢?
首先睡眠的含义是将进程置于“睡眠”状态,在这个状态的进程不能被调度执行。然后,在一定的时机,这个进程可能会被重新置为“运行”状态,从而可能被调度执行。 可见,“睡眠”与“运行”是针对进程而言的,代表进程的task_struct结构记录着进程的状态。中断不是一个进程,它并不存在task_struct,所以它是不可调度的。 -
中断上下文为什么不存在对应的task_struct结构呢?
中断的产生是很频繁的,并且中断处理过程会很快。如果为中断上下文维护一个对应的task_struct结构,那么这个结构频繁地分配、回收,并且影响调度器的管理,这样会对整个系统的吞吐量有所影响。因此,在Linux中进程和中断被区分开来。 -
任意时刻,处理器总处于以下状态中的一种:
内核态,运行于进程上下文,内核代表进程运行于内核空间;
内核态,运行于中断上下文,内核代表硬件运行于内核空间;
用户态,运行于用户地址空间。 -
所谓进程上下文,就是一个进程在执行的时候,CPU的所有寄存器中的值、进程的状态以及堆栈上的内容,当内核需要切换到另一个进程时,它需要保存当前进程的所有状态,即保存当前进程的进程上下文,以便再次执行该进程时,能够恢复切换时的状态,继续执行。
-
所谓中断上下文,就是硬件通过触发信号,导致内核调用中断处理程序,进入内核空间。这个过程中,硬件的一些变量和参数也要传递给内核,内核通过这些参数进行中断处理。中断上下文,其实也可以看作就是硬件传递过来的这些参数和内核需要保存的一些其他环境(主要是当前被中断的进程环境)。