第五章 中断和中断服务程序
一,处理器与外围设备进行通信有两种方式:
1,轮询(效率低下)
2,中断
二,中断原理
当我们在敲击键盘的时候,键盘控制器会发送一个中断给处理器,告诉OS有中断产生,处理器停下当前的工作,转而由内 核调用中断服务程序。(中断控制器发送中断给处理器的时候,处理器根据中断号查找中断向量表,找到中断服务程序的入口地址,才能去执行中断服务程序)。
三,中断处理程序
1,在响应中断的时候,内核会执行一个函数--中断服务程序(interrupt handler)或者叫做中断服务例程(interrupt service routine,ISR)。中断处理程序是设备驱动程序的一部分。
2,中断处理程序与内核其他函数的区别在于:中断处理程序是由内核调用来响应中断的,运行于中断上下文(interrupt context)。
3,中断处理程序应该尽量快的执行,尽可能快地恢复中断代码的执行。
四,上半部与下半部的对比
又想让中断服务程序尽量快地执行,同时又想让程序完成尽可能多的工作,这两个目标显然是有矛盾的,基于这样的矛盾,我们把中断服务程序划分为两个部分:上半部(top half)和下半部(bottom half)。
1,上半部:接收到一个中断就马上开始执行,具有严格的时限要求,比如 对硬件设备的响应和对硬件进行复位,这些工作都是在所有中断被禁止 的情况下完成的。中断服务程序是上半部。
2,下半部:没有特别严格的时限要求,允许稍后完成的工作被划分到下半部来做。一般来说中断服务程序返回的时候会立刻执行下半部。
五,注册中断服务程序
驱动程序通过以下的函数来注册一个中断服务程序:
int request_irq(unsigned int irq,//中断号
irqreturn_t (*handler)(int,void *,struct pt_regs*),//中断服务程序
unsigned long irqflags,
const char *devname,//产生中断的设备名
void *dev_id)
1,irqflags参数可以是0,也可是几个掩码(SA_INTERRUPT,SA_SAMPLE_RANDOM,SA_SHIRQ)的或操作。
SA_INTERRUPT:表明该中断处理程序是快速中断处理程序(fast interrupt handler)。加此标志说明中断服务程序在禁止 所有中断的情况之下执行。如果没有这个标志的话,除了正在运行
的中断服务程序对应的那条中断线屏蔽之外,其他
的中断都是处于激活状态。
SA_SAMPLE_RANDOM:(待考查)。
SA_SHIRQ:表示可以在多个中断处理程序间共享中断线。在同一个中断线上的每个中断处理程序必须设置该标志。
2,dev_id主要用于共享中断线。当一个中断处理程序需要释放的时候,dev_id将提供唯一的信息,用于标识具体删除哪个中断处理程序,如果没有该标志,内核无法知道同一个中断线上到底要删除哪个中断服务程序。如果无需共享中断线,那么该指针设置为NULL即可。
request_irq如果调用成功则返回0,失败的话返回非0,最常见的是返回_EBUSY,表示此时中断线正在被使用,或者没有设置SA_SHIRQ标志。
***********************注意点**********************
request_irq函数可能导致睡眠,所以该函数不能用在中断上下文和不能阻塞的进程中。原因是:在注册中断处理程序的过程中,内核需要在/proc/irq文件创建一个与中断对应的项,proc_makedir ()就是用来创建新的procfs项的。proc_makedir()通过proc_create()来对procfs进行设置,而proc_create()会调用kmalloc()来请求分配内存。而kmalloc()是可以睡眠的。
************************注意点*********************
释放中断处理程序:
void free_irq(unsigned int irq,void *dev_id);
如果指定
的中断线未设置共享标志,那么在删除中断处理程序的同时禁用这条中断线,如果中断线是共享的,那么根据dev_id来删除指定
的中断处理程序。而只有等到该中断线上的所有中断处理程序都删除完了才会禁用该中断线。
六,编写中断处理程序
1,static irqreturn_t (*handler)(int irq,void *dev_id,struct pt_regs* regs)irq指中断号,dev_id和request_irq中的dev_id必须一致。
七,中断上下文
当执行一个中断处理程序或者下半部时,内核运行在中断上下文。因为中断没有进程的背景,所以中断上下文中不能睡眠(内核调度的单位是进程,要进行的是进程上下文切换)。另外,中断处理程序并没有自己的栈,它共享被中断进程的内核栈。如果没有正在运行的进程,它就使用idle进程栈。
第六章 中断下半部(bottom half)
一,什么是下半部?
下半部就是与中断处理密切相关,但是中断处理程序本身不执行的工作。
二,为什么要用下半部?
中断处理程序执行的时间应该尽量的短,因为在中断服务例程执行期间,当前
的中断线会被屏蔽(如果设
置了SA_INTTERUPT则会屏蔽所有
的中断),这样其他
的中断就极有可能无法得到处理器的响应。为此,
为了尽量缩短中断服务程序的执行,我们要把一些对时间要求不严格的工作推迟去作。这就是为什么需要
使用下半部的原因。
三,下半部实现方法
有软中断、tasklet、工作队列,其中tasklet是基于软中断实现的。软中断是一组静态定义的下半部
接口,一共有32个,可以在所有处理器上同时执行(甚至相同类型的软中断可以同时执行)。
而tasklet则
没有这么宽松的条件--相同类型的tasklet是不能同时执行的。对于大部分下半部处理来说,tasklet
就足够了,像网络这样要求非常高的才需要软中断。另外,软中断是在编译期静态注册的,而tasklet可
以通过代码动态注册。
四,软中断的实现
软中断由softirq_action结构表示,定义在<
linux/interrupt.h>中
struct softirq_action{
void (*action)(struct softirq_action*);//for process the bottom half
void *data;//parameter for the function above
在<kernel/softirq.c>中包含了拥有32个该结构成员的数组,每个被注册的softirq占据数组的一项,
所以,最多应该可以有32个softirq,这是一个定值,无法动态改变。
1,软中断处理程序action的函数原型
void softirq_handler(struct softirq_action*);
软中断不会被另外一个软中断抢占,唯一可以抢占软中断的只有中断服务程序。但是,其他类型的软
中断,甚至是同类型的软中断可以在其他处理器上面运行。
2,执行软中断
一个软中断注册之后只有被标记了才会执行,这被称作触发软中断(raising the softirq)。一般的,
中断处理程序(上半部)在返回前会标记软中断,使其在稍后执行。
在 以下地方,softirq会被检查和执行:
1)处理完一个硬件中断(中断服务程序上半部)
2)在ksoftirqd内核线程中
3)在那些显示检查和执行待处理的软中断的代码中,如网络子系统
不管是在什么地方,以什么样的方式来唤起softirq,softirq都要在do_softirq()中执行。该函数
实现比较简单,就是用循环遍历待处理的softirq,每个都调用一下。
3,使用软中断
软中断留给系统中对时间有严格要求以及最重要的下半部使用,目前,只有网络子系统和SCSI直接使用软中断,像
内核定时器和tasklet都是建立在软中断上的。
1)分配索引
在编译期间,通过<
linux/interrupt.h>中的枚举类型来静态声明一个软中断。内核用从0开始的索引来表示一种
相对优先级。index小的比大的优先级要高。建立一个新的软中断必须在此枚举类型中加入新的项。而且加入的
的时候必须根据你希望赋予它的优先级来决定加入到什么位置。一般加入到网络相关的项之后,最后一项之前。
2)注册软中断处理程序
在运行时通过open_softirq()注册软中断处理程序。该函数有3个参数:软中断索引号,中断处理函数指针,还
有就是data数据域指针。
3)触发软中断
在枚举类型列表中添加新项以及通过open_softirq()之后软中断服务程序就可以执行了,raising_softirq()函数
可以将软中断设置为一个挂起状态,让它在下次do_softirq()的时候投入运行。
五,Tasklets
tasklets是建立在软中断上的,换句话说,tasklets本身就是一种软中断。tasklets由两类软中断表示:HI_SOFTIRQ和
TASKLET_SOFTIRQ,两者的唯一区别就是前者比后者的优先级高。
1,tasklet结构体,定义在<
linux/interrupt.h>中
struct tasklet_struct{
struct tasklet_struct *next;
unsigned long state;
atomic_t count;
void (*func)(unsigned long);
unsigned long data;
}
func函数指针是tasklet中断处理函数,data是函数参数,state是tasklet的状态,有0,TASKLET_STATE_SCHED和
TASKLET_STATE_RUN。TASKLET_STATE_SCHED表示tasklet已经被调度,准备投入运行,而TASKLET_STATE_RUN则
表示tasklet正在运行。count是引用计数器,当count不为0时,tasklet被禁止,为0时,tasklet才被激活。
2,调度tasklets
已调度的tasklet存放在两个单处理器数据结构:tasklet_vec和tasklet_hi_vec。这两个结构都是由tasklet_struct
组成的,每一个节点代表不同的tasklet。
tasklets由tasklet_schedule()和tasklet_hi_schedule()函数进行调用。以下是tasklet_schedule()的执行细节:
1)如果tasklet的状态是TASKLET_STATE_SCHED,则说明tasklet已经被调度,函数立即返回。
2)保存中断状态,然后禁止中断,这样可以保证处理器数据不会混乱。
3)把需要调度的tasklet加到tasklet_vec或tasklet_hi_vec链表中。
4)唤起TASKLET_SOFTIRQ或HI_SOFTIRQ,这样下一次在do_softirq()中就会执行该tasklet。
5)恢复中断到原状态并返回。