中断上半部与下半部
引子:
CPU和外围硬件设备的速度不是一个数量级的。让硬件在需要的时候再向内核发出信号(变内核主动为硬件主动),这就是中断机制。
1.不同的设备对应的中断不同,而每个中断都通过一个唯一的数字标识。这些中断值通常称为中断请求线(IRQ)。
注:与异常的区别,中断不需要考虑与处理器时钟同步,而异常在产生时必须考虑与处理器时钟同步。实际上,异常也常常称为同步中断。中断就是由硬件产生的异步中断(还有一些软中断),而异常就是由处理器本身产生的同步中断。
在响应一个特定中断的时候,内核会执行一个函数,该函数叫做中断处理程序(interrupt handler)或中断服务例程(interrupt service routine,ISR)。
中断处理程序与其他内核函数的真正区别在于:中断处理程序是被内核调用来响应中断的,而它们运行于我们称之为中断上下文的特殊上下文中。
网络设备的中断处理程序除了要对硬件应答,还要把来自硬件的网络数据包拷贝到内存,对其进行处理后再交给合适的协议栈或应用程序。
中断为了满足程序运行的快,又想程序完成的工作量大。需要分为上半部(top half)和下半部(bottom half).
2.
驱动程序可以通过下面的函数注册并激活一个中断处理程序,以便处理中断:
/*request_irq:分配一条给定的中断线*/
int requset_irq(unsigned int irq, //要分配的中断号
irqreturn_t (*handler)(int,void *,struct pt_regs *), //指向处理这个中断的实际中断程序
unsigned long irqflags,
const char * devname,//与中断相关的设备的ASCII文本表示法
void *dev_id) //用于共享中断线,dev_id将提供唯一的标志信息(cookie),以便从共享中断线的诸多中断处理程序中删除指定的那个
irqflags可以为0,也可以是下列一个或多个标志的位掩码:
SA_INTERRUPT:除了时钟中断外,绝大多数中断都不使用该标志
SA_SAMPLE_RANDOM:此标志表明这个设备产生的中断对内核熵池有贡献内核熵池负责提供从各种随机事件导出的真正的随机数
SA_SHIRQ:此标志表明可以在多个中断处理程序之间共享中断线。
(位掩码部分还要再看一下)
request_irq()函数可能会睡眠,不能在中断上下文或其他不允许阻塞的代码中调用该函数。成功返回0.
request_irq()函数会引起睡眠,在注册过程中,内核需要在/proc/irq文件创建一个与中断对应的项。函数proc_mkdir()就是用来创建这个新的procfs项的。proc_makedir()通过调用函数proc_create()对这个新的profs项进行设置,而proc_create()会调用函数kmalloc()来请求内存分配,而kmalloc()是可以睡眠的。
void free_irq(unsigned int irq,void *dev_id)来释放中断线
典型的中断处理程序声明:
static irqreturn_t intr_handler(int irq,void *dev_id,struct pt_regs *regs)
中断处理程序可能返回两个特殊的值:IRQ_NONE和IRQ_HANDLED
同一个中断处理程序绝对不会被同时调用以处理嵌套的中断.
当执行一个中断处理程序或下半部时,内核处于中断上下文(interrupt context)中.进程上下文是一种内核所处的操作模式,内核代表进程执行系统调用或运行内核线程.在进程上下文中,可以通过current宏关联当前进程.此外,因为进程是以进程上下文的形式连接到内核中的,因此,在进程上下文可以睡眠,也可以调用调度程序.
中断上下文不可以睡眠.
中断处理程序有自己的栈,每个处理器一个,大小为一页,这个栈就称为中断栈
/proc/interrupts文件存放的是系统中与中断相关的统计信息.
中断控制
用于禁止当前处理器(仅仅是当前处理器)上的本地中断,随后又激活它们的语句为:
local_irq_disable(); //cli
local_irq_enable(); //sti
在禁止中断之前保存中断系统的状态会更加安全一些。在准备激活中断时,只需把中断恢复到它们原来的状态。
unsigned long flags
local_irq_save(flags);
local_irq_restore(flags);
宏irqs_disable()定义在<asm/system.h>中。如果本地处理器上的中断系统被禁止,则它返回非0,否则,返回0.
在<asm/hardirq.h>中定义的两个宏提供一个用来检查内核的当前上下文的接口,它们是:
in_interrupt()
in_irq() //只有在内核确实正在执行中断处理程序时才返回非0
中断处理流程就被分为两个部分:第一个部分是中断处理程序(上半部).另外就是执行与中断处理密切相关但中断处理程序本身不执行的工作,也就是下半部的任务。
内核提供了三种不同形式的下半部实现机制:软中断(softirq)、tasklet和工作队列。另外,如果你必须保证在一个确定的时间段过去以后再运行时,你需要使用内核定时器。
3.
软中断是在编译期间静态分配的。不像tasklet那样能被动态地注册或去除。软中断由softirq_action结构表示,定义在<linux/interrupt.h>中
struct softirq_action{
void (*action)(struct softirq_action *); /*待执行的函数*/
void *data; /*传给函数的参数*/
};
kernel/softirq.c中定义了一个包含有32个该结构体的数组。
static struct softirq_action softirq_vec[32];
每个被注册的软中断都占据该数组的一项。因此最多可能有32个软中断。
http://www.ibm.com/developerworks/cn/linux/kernel/interrupt/
http://blog.chinaunix.net/space.php?uid=20940095&do=blog&id=252237
Linux内核的Softirq机制
Softirq的详细分析
http://blog.chinaunix.net/space.php?uid=20940095&do=blog&id=252237
http://www.linuxforum.net/forum/showflat.php?Board=linuxK&Number=86447
http://linux.chinaunix.net/techdoc/system/2007/06/12/959951.shtml
http://www.cnblogs.com/hanyan225/archive/2011/7/19.html
http://hi.baidu.com/caosicong/blog/item/50a504896361b2b00f2444f9.html
http://linux.chinaunix.net/techdoc/system/2007/06/12/959951.shtml
http://www.linuxforum.net/forum/showflat.php?Cat=&Board=linuxK%20&Number=95050&page=&view=&sb=&vc=1
现在的
struct softirq_action
{
void (*action)(struct softirq_action *);
};没有了数据域,如何处理。另外内核把整个结构体都传递给软中断处理程序。这个又是如何处理的。
http://topic.csdn.net/u/20111102/17/37fddef7-b770-4756-9899-083c5fd891f2.html
softirq_action 没有了 data
4.
大部分情况下都是用tasklet。
tasklet由两类软中断代表:HI_SOFTIRQ和TASKLET_SOFTIRQ.这两者之间唯一的实际区别在于HI_SOFTIRQ类型的软中断先于TASKLET_SOFTIRQ类型的软中断执行。
struct tasklet_struct {
struct tasklet_struct *next; /*链表中的下一个tasklet*/
unsigned long state; /*tasklet的状态*/
atomic_t count; /*引用计数器*/
void (*func)(unsigned long); /*tasklet处理函数*/
unsigned long data; /*给tasklet处理函数的参数*/
};
state成员只能在0、TASKLET_STATE_SCHED和TASKLET_STATE_RUN之间取指。TASKLET_STATE_SCHED表明tasklet已被调度,正准备投入
运行,TASKLET_STATE_RUN表明该tasklet正在运行。TASKLET_STATE_RUN只有在多处理器的系统上才会作为一种优化来使用
count是tasklet的引用计数器。如果它不为0,则tasklet被禁止,不允许执行;只有当它为0时,tasklet才被激活,并且在被设置为挂起状态时,该tasklet才能够执行。
http://blog.chinaunix.net/space.php?uid=20940095&do=blog&id=263632
tasklet由tasklet_schedule()和tasklet_hi_schedule()函数进行调度,它们接受一个指向tasklet_struct结构的指针作为参数。
由于软中断实现,所以tasklet不能睡眠。这意味着就不能在tasklet中使用信号量或者其他什么阻塞式的函数。由于tasklet运行时不允许响应中断,所以使用时必须预防(比如屏蔽中断然后获取一个锁)。
http://cupidove.blog.163.com/blog/static/1005662201182244445452/
http://blog.chinaunix.net/space.php?uid=9657310&do=blog&id=1998936
5.
工作队列(work queue)可以把工作推后,交由一个内核线程去执行。这个下半部分总是会在进程上下文中执行。通过工作队列执行的代码能占尽进程上下文的所有优势。最重要的就是工作队列允许重新调度甚至是睡眠。
通常,在工作队列和软中断/tasklet中作出选择非常容易。如果推后执行的任务需要睡眠,那么就选择工作队列。如果推后执行的任务不需要睡眠,那么就选择软中断或tasklet。
如果需要一个可以重新调度的实体来执行你的下半部处理,应该使用工作队列。它是唯一能在进程上下文运行的下半部实现的机制,也只有它才可以睡眠。
如果不需要用一个内核线程来推后执行工作,那么就考虑使用tasklet吧。
http://blog.sina.com.cn/s/blog_642e41c20100qs11.html
http://www.linuxidc.com/Linux/2011-11/47658.htm
工作队列子系统是一个用于创建内核线程的接口,通过它创建的进程负责执行由内核其他部分排到队列里的任务。它创建的这些内核线程被称为工作者线程(worker thread)。
6.中断处理流程
1.进入中断处理程序--->2.保存关 键上下文---->3.开中断(sti指令)--->4.进入中断处理程序的handler--->5.关中断(cli指 令)---->6.写EOI寄存器(表示中断处理完成)---->7.开中断。