@[toc]并发
中断上半部、下半部的概念
设备的中断会打断内核进程中的正常调度和运行,系统对更高吞吐率的追求势必要求中断服务程序尽可能短小精悍。可是,这个良好的愿望每每与现实并不吻合。在大多数真实的系统中,当中断到来时,要完成的工做每每并不会是短小的,它可能要进行较大量的耗时处理。
下图描述了Linux内核的中断处理机制。为了在中断执行时间尽可能短和中断处理需完成的工做尽可能大之间找到一个平衡点,Linux将中断处理程序分解为两个半部:顶半部和底半部。
函数
顶半部用于完成尽可能少的比较紧急的功能,它每每只是简单地读取寄存器中的中断状态,并在清除中断标志后就进行“登记中断”的工做。“登记中断”意味着将底半部处理程序挂到该设备的底半部执行队列中去。这样,顶半部执行的速度就会很快,从而能够服务更多的中断请求。高并发
如今,中断处理工做的重心就落在了底半部的头上,需用它来完成中断事件的绝大多数任务。底半部几乎作了中断处理程序全部的事情,并且能够被新的中断打断,这也是底半部和顶半部的最大不一样,由于顶半部每每被设计成不可中断。底半部相对来讲并非很是紧急的,并且相对比较耗时,不在硬件中断服务程序中执行。spa
尽管顶半部、底半部的结合可以善系统的响应能力,可是,僵化地认为Linux设备驱动中的中断处理必定要分两个半部则是不对的。若是中断要处理的工做自己不多,则彻底能够直接在顶半部所有完成。操作系统
其余操做系统中对中断的处理也采用了相似于 Linux的方法,真正的硬件中断服务程序都斥尽可能短。所以,许多操做系统都提供了中断上下文和非中断上下文相结合的机制,将中断的耗时工做保留到非中断上下文去执行。.net
实现中断下半部的三种方法
软中断
软中断( Softirq)也是一种传统的底半部处理机制,它的执行时机一般是顶半部返回的时候, tasklet是基于软中断实现的,所以也运行于软中断上下文。线程
在Linux内核中,用 softing_action结构体表征一个软中断,这个结构体包含软中断处理函数指针和传递给该函数的参数。使用 open_softirq()函数能够注册软中断对应的处理函数,而 raise_softirq()函数能够触发一个软中断。设计
软中断和 tasklet运行于软中断上下文,仍然属于原子上下文的一种,而工做队列则运行于进程上下文。所以,在软中断和 tasklet处理函数中不容许睡眠,而在工做队列处理函数中容许睡眠。指针
local_bh_disable()和 llocal_bh_enable()是内核中用于禁止和使能软中断及 tasklet底半部机制的函数rest
软中断模版
asmlinkage void do_softirq(void)
{
__u32 pending;
unsigned long flags;
/* 判断是否在中断处理中,若是正在中断处理,就直接返回 */
if (in_interrupt())
return;
/* 保存当前寄存器的值 */
local_irq_save(flags);
/* 取得当前已注册软中断的位图 */
pending = local_softirq_pending();
/* 循环处理全部已注册的软中断 */
if (pending)
__do_softirq();
/* 恢复寄存器的值到中断处理前 */
local_irq_restore(flags);
}
tasklet
tasklet的使用较简单,它的执行上下文是软中断,执行时机一般是顶半部返回的时候。咱们只须要定义 tasklet及其处理函数,并将二者关联则可,例如
void my_tasklet_func(unsigned long); /*定义一个处理函数*/
DECLARE_TASKLET(my_tasklet, my_tasklet_func, data);
/*定义一个tasklet结构my_tasklet,与my_tasklet_func(data)函数相关联*/
代码DECLARE_TASKLET(my_tasklet,my_tasklet_func,data)实现了定义名称为my_tasklet的tasklet,并将其与my_tasklet_func()这个函数绑定,而传入这个函数的参数为data。
在须要调度tasklet的时候引用一个tasklet_schedule()函数就能使系统在适当的时候进行调度运行:
tasklet_schedule(&my_tasklet);
使用tasklet做为底半部处理中断的设备驱动程序模板下所示(仅包含与中断相关的部
分)。
tasklet函数模版
/* 定义tasklet和底半部函数并将它们关联 */
void xxx_do_tasklet(unsigned long);
DECLARE_TASKLET(xxx_tasklet, xxx_do_tasklet, 0);
/* 中断处理底半部 */
void xxx_do_tasklet(unsigned long)
...
/* 中断处理顶半部 */
irqreturn_t xxx_interrupt(int irq, void *dev_id)
{
...
tasklet_schedule(&xxx_tasklet);
...
}
/* 设备驱动模块加载函数 */
int __init xxx_init(void)
{
...
/* 申请中断 */
result = request_irq(xxx_irq, xxx_interrupt,
0, "xxx", NULL);
...
return IRQ_HANDLED;
}
/* 设备驱动模块卸载函数 */
void __exit xxx_exit(void)
{
...
/* 释放中断 */
free_irq(xxx_irq, xxx_interrupt);
...
}
上述程序在模块加载函数中申请中断(第24~25行),并在模块卸载函数free_irq(xxx_irq, xxx_interrupt);中释放它。对应于xxx_irq的中断处理程序被设置为xxx_interrupt()函数,在这个函数中,tasklet_schedule(&xxx_tasklet)调度被定义的tasklet函数xxx_do_tasklet()在适当的时候执行。
工做队列
工做队列的使用方法和tasklet很是类似,可是工做队列的执行上下文是内核线程,所以能够调度和睡眠。下面的代码用于定义一个工做队列和一个底半部执行函数
struct work_struct my_wq; /* 定义一个工做队列 */
void my_wq_func(struct work_struct *work); /* 定义一个处理函数 */
经过INIT_WORK()能够初始化这个工做队列并将工做队列与处理函数绑定:
INIT_WORK(&my_wq, my_wq_func);
/* 初始化工做队列并将其与处理函数绑定 */
与tasklet_schedule()对应的用于调度工做队列执行的函数为schedule_work(),如:
schedule_work(&my_wq); /* 调度工做队列执行 */
工做队列函数模版
/* 定义工做队列和关联函数 */
struct work_struct xxx_wq;
void xxx_do_work(struct work_struct *work);
/* 中断处理底半部 */
void xxx_do_work(struct work_struct *work)
...
/*中断处理顶半部*/
irqreturn_t xxx_interrupt(int irq, void *dev_id)
{
...
schedule_work(&xxx_wq);
...
return IRQ_HANDLED;
}
/* 设备驱动模块加载函数 */
int xxx_init(void)
{
...
/* 申请中断 */
result = request_irq(xxx_irq, xxx_interrupt,
0, "xxx", NULL);
...
/* 初始化工做队列 */
INIT_WORK(&xxx_wq, xxx_do_work);
...
}
/* 设备驱动模块卸载函数 */
void xxx_exit(void)
{
...
/* 释放中断 */
free_irq(xxx_irq, xxx_interrupt);
...
}
工做队列早期的实现是在每一个CPU核上建立一个worker内核线程,全部在这个核上调度的工做都在该worker线程中执行,其并发性显然差强人意。在Linux 2.6.36之后,转而实现“Concurrency-managedworkqueues”,简称cmwq,cmwq会自动维护工做队列的线程池以提升并发性,同时保持了API的向后兼容。
进程上下文和中断上下文
软中断和硬中断的区别
硬中断:
1. 硬中断是由硬件产生的,好比,像磁盘,网卡,键盘,时钟等。每一个设备或设备集都有它本身的IRQ(中断请求)。基于IRQ,CPU能够将相应的请求分发到对应的硬件驱动上(注:硬件驱动一般是内核中的一个子程序,而不是一个独立的进程)。
2. 处理中断的驱动是须要运行在CPU上的,所以,当中断产生的时候,CPU会中断当前正在运行的任务,来处理中断。在有多核心的系统上,一个中断一般只能中断一颗CPU(也有一种特殊的状况,就是在大型主机上是有硬件通道的,它能够在没有主CPU的支持下,能够同时处理多个中断。)。
3. 硬中断能够直接中断CPU。它会引发内核中相关的代码被触发。对于那些须要花费一些时间去处理的进程,中断代码自己也能够被其余的硬中断中断。
4. 对于时钟中断,内核调度代码会将当前正在运行的进程挂起,从而让其余的进程来运行。它的存在是为了让调度代码(或称为调度器)能够调度多任务。
软中断:
1. 软中断的处理很是像硬中断。然而,它们仅仅是由当前正在运行的进程所产生的。
2. 一般,软中断是一些对I/O的请求。这些请求会调用内核中能够调度I/O发生的程序。对于某些设备,I/O请求须要被当即处理,而磁盘I/O请求一般能够排队而且能够稍后处理。根据I/O模型的不一样,进程或许会被挂起直到I/O完成,此时内核调度器就会选择另外一个进程去运行。I/O能够在进程之间产生而且调度过程一般和磁盘I/O的方式是相同。
3. 软中断仅与内核相联系。而内核主要负责对须要运行的任何其余的进程进行调度。一些内核容许设备驱动的一些部分存在于用户空间,而且当须要的时候内核也会调度这个进程去运行。
4. 软中断并不会直接中断CPU。也只有当前正在运行的代码(或进程)才会产生软中断。这种中断是一种须要内核为正在运行的进程去作一些事情(一般为I/O)的请求。有一个特殊的软中断是Yield调用,它的做用是请求内核调度器去查看是否有一些其余的进程能够运行。
硬中断、软中断和信号的区别
硬中断是外部设备对CPU的中断,软中断是中断底半部的一种处理机制,而信号则是由内核(或其余进程)对某个进程的中断。在涉及系统调用的场合,人们也常说经过软中断(例如ARM为swi)陷入内核,此时软中断的概念是指由软件指令引起的中断,和咱们这个地方说的softirq是两个彻底不一样的概念,一个是software,一个是soft。
须要特别说明的是,软中断以及基于软中断的tasklet若是在某段时间内大量出现的话,内核会把后续软中断放入ksoftirqd内核线程中执行。总的来讲,中断优先级高于软中断,软中断又高于任何一个线程。软中断适度线程化,能够缓解高负载状况下系统的响应。
如遇到排版错乱的问题,能够经过如下连接访问个人CSDN。
**CSDN:[CSDN搜索“嵌入式与Linux那些事”]