linux 中断下半部 下半部 处理时间过长 怎么办

Linux内核——中断机制_Linux编程_Linux公社-Linux系统门户网站
你好,游客
Linux内核——中断机制
来源:Linux社区&
作者:walkerkalr
为什么需要中断?
如果让内核定期对设备进行轮询,以便处理设备,那会做很多无用功,因为外设的处理速度一般慢于CPU,而CPU不能一直等待外部事件。所以能让设备在需要内核时主动通知内核,会是一个聪明的方式,这便是中断。
中断处理程序
在响应一个特定中断时,内核会执行一个函数&&中断处理程序。中断处理程序与其他内核函数的区别在于,中断处理程序是被内核调用来响应中断的,而它们运行于我们称之为中断上下文的特殊上下文中。
中断处理程序就是普通的C代码。特别之处在于中断处理程序是在中断上下文中运行的,它的行为受到某些限制:1)不能向用户空间发送或接受数据2)不能使用可能引起阻塞的函数3)不能使用可能引起调度的函数
Linux基础篇之内存管理机制
Linux内核&&进程管理与调度
Linux内核&&内存管理
Linux内存管理之高端内存
Linux内存管理之分段机制
Linux内存管理伙伴算法
注册与释放中断
设备驱动程序利用request_irq()注册中断处理程序,并激活给定的中断线。返回0表示成功,或者返回一个错误码。
卸载设备驱动程序时,需要注销相应的中断处理程序,并释放中断线,这时需要调用free_irq&&如果在给定的中断线上没有中断处理程序,则注销响应的处理程序,并禁用其中断线。
上下半部机制
我们期望让中断处理程序运行得快,并想让它完成的工作量多,这两个目标相互制约,如何解决&&上下半部机制。
我们把中断处理切为两半。中断处理程序是上半部&&接受中断,他就立即开始执行,但只有做严格时限的工作。能够被允许稍后完成的工作会推迟到下半部去,此后,在合适的时机,下半部会被开终端执行。上半部简单快速,执行时禁止一些或者全部中断。下半部稍后执行,而且执行期间可以响应所有的中断。这种设计可以使系统处于中断屏蔽状态的时间尽可能的短,以此来提高系统的响应能力。上半部只有中断处理程序机制,而下半部的实现有软中断实现,tasklet实现和工作队列实现。
我们用网卡来解释一下这两半。当网卡接受到数据包时,通知内核,触发中断,所谓的上半部就是,及时读取数据包到内存,防止因为延迟导致丢失,这是很急迫的工作。读到内存后,对这些数据的处理不再紧迫,此时内核可以去执行中断前运行的程序,而对网络数据包的处理则交给下半部处理。
上下半部划分原则
1) 如果一个任务对时间非常敏感,将其放在中断处理程序中执行;
2) 如果一个任务和硬件有关,将其放在中断处理程序中执行;
3) 如果一个任务要保证不被其他中断打断,将其放在中断处理程序中执行;
4) 其他所有任务,考虑放置在下半部执行。
下半部实现机制之软中断
软中断作为下半部机制的代表,是随着SMP(share memoryprocessor)的出现应运而生的,它也是tasklet实现的基础(tasklet实际上只是在软中断的基础上添加了一定的机制)。软中断一般是&可延迟函数&的总称,有时候也包括了tasklet(请读者在遇到的时候根据上下文推断是否包含tasklet)。它的出现就是因为要满足上面所提出的上半部和下半部的区别,使得对时间不敏感的任务延后执行,软中断执行中断处理程序留给它去完成的剩余任务,而且可以在多个CPU上并行执行,使得总的系统效率可以更高。它的特性包括:
a)产生后并不是马上可以执行,必须要等待内核的调度才能执行。软中断不能被自己打断,只能被硬件中断打断(上半部)。
b)可以并发运行在多个CPU上(即使同一类型的也可以)。所以软中断必须设计为可重入的函数(允许多个CPU同时操作),因此也需要使用自旋锁来保护其数据结构。
下半部实现机制之tasklet
tasklet是通过软中断实现的,所以它本身也是软中断。
软中断用轮询的方式处理。假如正好是最后一种中断,则必须循环完所有的中断类型,才能最终执行对应的处理函数。显然当年开发人员为了保证轮询的效率,于是限制中断个数为32个。
为了提高中断处理数量,顺道改进处理效率,于是产生了tasklet机制。
Tasklet采用无差别的队列机制,有中断时才执行,免去了循环查表之苦。Tasklet作为一种新机制,显然可以承担更多的优点。正好这时候SMP越来越火了,因此又在tasklet中加入了SMP机制,保证同种中断只能在一个cpu上执行。在软中断时代,显然没有这种考虑。因此同一种软中断可以在两个cpu上同时执行,很可能造成冲突。
总结下tasklet的优点:
(1)无类型数量限制;
(2)效率高,无需循环查表;
(3)支持SMP机制;
它的特性如下:
1)一种特定类型的tasklet只能运行在一个CPU上,不能并行,只能串行执行。
2)多个不同类型的tasklet可以并行在多个CPU上。
3)软中断是静态分配的,在内核编译好之后,就不能改变。但tasklet就灵活许多,可以在运行时改变(比如添加模块时)。
下半部实现机制之工作队列(work queue)
上面我们介绍的可延迟函数运行在中断上下文中(软中断的一个检查点就是do_IRQ退出的时候),于是导致了一些问题:软中断不能睡眠、不能阻塞。由于中断上下文出于内核态,没有进程切换,所以如果软中断一旦睡眠或者阻塞,将无法退出这种状态,导致内核会整个僵死。但可阻塞函数不能用在中断上下文中实现,必须要运行在进程上下文中,例如访问磁盘数据块的函数。因此,可阻塞函数不能用软中断来实现。但是它们往往又具有可延迟的特性。
上面我们介绍的可延迟函数运行在中断上下文中,于是导致了一些问题,说明它们不可挂起,也就是说软中断不能睡眠、不能阻塞,原因是由于中断上下文出于内核态,没有进程切换,所以如果软中断一旦睡眠或者阻塞,将无法退出这种状态,导致内核会整个僵死。因此,可阻塞函数不能用软中断来实现。但是它们往往又具有可延迟的特性。而且由于是串行执行,因此只要有一个处理时间较长,则会导致其他中断响应的延迟。为了完成这些不可能完成的任务,于是出现了工作队列,它能够在不同的进程间切换,以完成不同的工作。
如果推后执行的任务需要睡眠,那么就选择工作队列,如果不需要睡眠,那么就选择软中断或tasklet。工作队列能运行在进程上下文,它将工作托付给一个内核线程。工作队列说白了就是一组内核线程,作为中断守护线程来使用。多个中断可以放在一个线程中,也可以每个中断分配一个线程。我们用结构体workqueue_struct表示工作者线程,工作者线程是用内核线程实现的。而工作者线程是如何执行被推后的工作&&有这样一个链表,它由结构体work_struct组成,而这个work_struct则描述了一个工作,一旦这个工作被执行完,相应的work_struct对象就从链表上移去,当链表上不再有对象时,工作者线程就会继续休眠。因为工作队列是线程,所以我们可以使用所有可以在线程中使用的方法。
Linux软中断和工作队列的作用是什么
Linux中的软中断和工作队列是中断上下部机制中的下半部实现机制。
1.软中断一般是&可延迟函数&的总称,它不能睡眠,不能阻塞,它处于中断上下文,不能进城切换,软中断不能被自己打断,只能被硬件中断打断(上半部),可以并发的运行在多个CPU上。所以软中断必须设计成可重入的函数,因此也需要自旋锁来保护其数据结构。
2.工作队列中的函数处在进程上下文中,它可以睡眠,也能被阻塞,能够在不同的进程间切换,以完成不同的工作。
可延迟函数和工作队列都不能访问用户的进程空间,可延时函数在执行时不可能有任何正在运行的进程,工作队列的函数有内核进程执行,他不能访问用户空间地址。
本文永久更新链接地址:
相关资讯 & & &
& (02月14日)
& (12/06/:56)
& (03月11日)
& (01月13日)
& (11/22/:13)
图片资讯 & & &
   同意评论声明
   发表
尊重网上道德,遵守中华人民共和国的各项有关法律法规
承担一切因您的行为而直接或间接导致的民事或刑事法律责任
本站管理人员有权保留或删除其管辖留言中的任意内容
本站有权在网站内转载或引用您的评论
参与本评论即表明您已经阅读并接受上述条款linux中断--中断下半部机制的使用&中断编程
中断程序一般会包含在某个设备的驱动程序中,因此,中断处理程序本质上还是一个内核模块。在上篇文章中也看到了一个简单的中断处理流程和内核模板的写法非常相似。但是那个中断是最简单的中断,没有用到中断处理的下半部的处理机制,在别的文章中也讲述了下半部的处理机制,这里简单的使用介绍下!
上文中我们通过一个简单的例子分析了一个中断程序的基本结构。可以看到,中断处理程序在处理中断时起到了关键作用,也是一个中断程序必不可少的部分。不过,现如今的中断处理流程都会分为两部分:上半部分(top half)和下半部分(bottom half)。为什么要将一个中断分为如此两部分?下面的几个经典原因可以很好的诠释这个问题。
1.中断可以随时的打断处理机对其他程序的执行,如果被打断的代码对系统很重要,那么此时中断处理程序的执行时间应该是越短越好。
2.通过上文我们知道,中断处理程序正在执行时,会屏蔽同条中断线上的中断请求;而更严重的是,如果设置了IRQF_DISABLED,那么该中断服务程序执行是会屏蔽所有其他的中断请求。那么此时应该让中断处理程序执行的越快越好。
上面的几个例子都要求中断服务程序的执行时间越短越好。一般的,中断处理程序会在上半部分执行。而事实上,几乎所有的情况,上半部分就只执行中断处理程序。因此,我们可以这样认为:一个完整的中断处理流程是由中断处理程序和下半部分共同完成的。
这样划分是有一定原因的,因为我们必须有一个快速、异步而且简单的处理程序专门来负责对硬件的中断请求做出快速响应,与此同时也要完成那些对时间要求很严格的操作。而那些对时间要求相对宽松,其他的剩余工作则会在稍候的任意时间执行,也就是在所谓的下半部分去执行。
总之,这样划分一个中断处理过程主要是希望减少中断处理程序的工作量(当然了,理想情况是将全部工作都抛给下半段。但是中断处理程序至少应该完成对中断请求的相应。),因为在它运行期间至少会使得同级的中断请求被屏蔽,这些都直接关系到整个系统的响应能力和性能。而在下半段执行期间,则会允许响应所有的中断。
和上半段只能通过中断处理程序实现不同的是,下半部可以通过多种机制来完成:小任务(tasklet),工作队列,软中断。在本博客后续的文章当中你会看到,不管是那种机制,它们均为下半部提供了一种执行机制,比上半部灵活多了。至于何时执行,则由内核负责。
以上是上下部分划分的基本概述,通过tasklet和工作队列机制,你可以更深刻的理解下部分的执行。
tasklet的实现
tasklet(小任务)机制是中断处理下半部分最常用的一种方法,其使用也是非常简单的。正如在前文中你所知道的那样,一个使用tasklet的中断程序首先会通过执行中断处理程序来快速完成上半部分的工作,接着通过调用tasklet使得下半部分的工作得以完成。可以看到,下半部分被上半部分所调用,至于下半部分何时执行则属于内核的工作。对应到我们此刻所说的tasklet就是,在中断处理程序中,除了完成对中断的响应等工作,还要调用tasklet,如下图示。
tasklet由tasklet_struct结构体来表示,每一个这样的结构体就表示一个tasklet。在&linux/interrupt.h&中可以看到如下的定义:
tasklet_struct
structtasklet_struct *
unsigned long
void(*func)(unsignedlong);
unsigned long
在这个结构体中,第一个成员代表链表中的下一个tasklet。第二个变量代表此刻tasklet的状态,一般为TASKLET_STATE_SCHED,表示此tasklet已被调度且正准备运行;此变量还可取TASKLET_STATE_RUN,表示正在运行,但只用在多处理器的情况下。count成员是一个引用计数器,只有当其值为0时候,tasklet才会被激活;否则被禁止,不能被执行。而接下来的func变量很明显是一个函数指针,它指向tasklet处理函数,这个处理函数的唯一参数为data。
使用tasklet
在使用tasklet前,必须首先创建一个tasklet_struct类型的变量。通常有两种方法:静态创建和动态创建。这样官方的说法仍然使我们不能理解这两种创建到底是怎么一回事。不够透过来分析倒是可以搞明白。
在&linux/interrupt.h&中的两个宏:
464#define DECLARE_TASKLET(name, func, data) \
465struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(0), func, data }
467#define DECLARE_TASKLET_DISABLED(name, func, data) \
468struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(1), func, data }
就是我们进行静态创建tasklet的两种方法。通过第一个宏创建的tasklet处于激活状态,再通过调度函数被挂起尽而被内核执行;而通过第二个宏创建的tasklet处于禁止状态。从两个宏的定义可以看到,所谓的静态创建就是直接定义个一个名为name的tasklet_struct类型的变量,并将宏中各个参数相应的赋值给这个name变量的各个成员。注意,两个宏在功能上差异就在于对name变量count成员的赋值上,具体原因在第一部分已经说明。也许你对ATOMIC_INIT这样的初始化方式感到疑惑,那么看完定义后,你就会一目了然:
15#define ATOMIC_INIT(i) { (i) }
190typedef struct{
192} atomic_t;
与静态创建相对的是动态创建,通过给tasklet_init函数传递一个事先定义的指针,来动态创建一个tasklet。这个函数源码如下。
470void tasklet_init(structtasklet_struct *t,
471 void(*func)(unsignedlong), unsignedlongdata)
473 t-&next = NULL;
474 t-&state = 0;
475 atomic_set(&t-&count, 0);
476 t-&func =
477 t-&data =
相信你在上面的代码是基本上没有什么难以理解的地方,不过这里还是要特别说明一下atomic_set函数:
35static inlinevoidatomic_set(atomic_t *v,inti)
37 v-&counter =
首先tasklet_init当中,将&t-&count传递给了此函数。也就是说将atomic_t类型的成员count的地址传递给了atomic_set函数。而我们在此函数中却要为count变量中的成员counter赋值。如果说我们当前要使用i,那么应该是如下的引用方法:t-》count.i。明白了吗?
ok,通过上述两种方法就可以创建一个tasklet了。同时,你应该注意到不管是上述那种创建方式都有func参数。透过上述分析的源码,我们可以看到func参数是一个函数指针,它指向的是这样的一个函数:
void tasklet_handler(unsignedlongdata);
如同上半部分的中断处理程序一样,这个函数需要我们自己来实现。
创建好之后,我们还要通过如下的方法对tasklet进行调度:
tasklet_schedule(&my_tasklet)
通过此函数的调用,我们的tasklet就会被挂起,等待机会被执行
在此只分析上下两部分的调用关系,完整代码在这里查看。
static struct tasklet_
static void mytasklet_handler(unsigned longdata)
printk(&This is tasklet handler..\n&);
static irqreturn_t myirq_handler(int irq,void* dev)
staticintcount=0;
if(count&10)
printk(&-----------%d start--------------------------\n&,count+1);
printk(&The interrupt handeler is working..\n&);
printk(&The most of interrupt work will be done by following tasklet..\n&);
tasklet_init(&mytasklet,mytasklet_handler,0);
tasklet_schedule(&mytasklet);
printk(&The top half has been done and bottom half will be processed..\n&);
returnIRQ_HANDLED;
从代码中可以看到,在上半部中通过调用tasklet,使得对时间要求宽松的那部分中断程序推后执行。
为什么还需要工作队列?
工作队列(work queue)是另外一种将中断的部分工作推后的一种方式,它可以实现一些tasklet不能实现的工作,比如工作队列机制可以睡眠。这种差异的本质原因是,在工作队列机制中,将推后的工作交给一个称之为工作者线程(worker thread)的内核线程去完成(单核下一般会交给默认的线程events/0)。因此,在该机制中,当内核在执行中断的剩余工作时就处在进程上下文(process context)中。也就是说由工作队列所执行的中断代码会表现出进程的一些特性,最典型的就是可以重新调度甚至睡眠。
对于tasklet机制(中断处理程序也是如此),内核在执行时处于中断上下文(interrupt context)中。而中断上下文与进程毫无瓜葛,所以在中断上下文中就不能睡眠。
因此,选择tasklet还是工作队列来完成下半部分应该不难选择。当推后的那部分中断程序需要睡眠时,工作队列毫无疑问是你的最佳选择;否则,还是用tasklet吧。
中断上下文
在了解中断上下文时,先来回顾另一个熟悉概念:进程上下文(这个中文翻译真的不是很好理解,用&环境&比它好很多)。一般的进程运行在用户态,如果这个进程进行了调用,那么此时用户空间中的程序就进入了内核空间,并且称内核代表该进程运行于内核空间中。由于用户空间和内核空间具有不同的地址映射,并且用户空间的进程要传递很多变量、参数给内核,内核也要保存用户进程的一些寄存器、变量等,以便系统调用结束后回到用户空间继续执行。这样就产生了进程上下文。
所谓的进程上下文,就是一个进程在执行的时候,CPU的所有寄存器中的值、进程的状态以及堆栈中的内容。当内核需要切换到另一个进程时(上下文切换),它需要保存当前进程的所有状态,即保存当前进程的进程上下文,以便再次执行该进程时,能够恢复切换时的状态继续执行。上述所说的工作队列所要做的工作都交给工作者线程来处理,因此它可以表现出进程的一些特性,比如说可以睡眠等。
对于中断而言,是硬件通过触发信号,导致内核调用中断处理程序,进入内核空间。这个过程中,硬件的一些变量和参数也要传递给内核,内核通过这些参数进行中断处理,中断上下文就可以理解为硬件传递过来的这些参数和内核需要保存的一些环境,主要是被中断的进程的环境。因此处于中断上下文的tasklet不会有睡眠这样的特性。
工作队列的使用
内核中通过下述结构体来表示一个具体的工作:
struct work_struct
unsigned long
structlist_
void(*func)(void*);
structtimer_
而这些工作(结构体)链接成的链表就是所谓的工作队列。工作者线程会在被唤醒时执行链表上的所有工作,当一个工作被执行完毕后,相应的work_struct结构体也会被删除。当这个工作链表上没有工作时,工作线程就会休眠。
通过如下宏可以创建一个要推后的完成的工作:
DECLARE_WORK(name,void(*func)(void*),void*data);
也可以通过下述宏动态创建一个工作:
INIT_WORK(structwork_struct *work,void(*func)(void*),void*data);
与tasklet类似,每个工作都有具体的工作队列处理函数,原型如下:
void work_handler(void*data)
将工作队列机制对应到具体的中断程序中,即那些被推后的工作将会在func所指向的那个工作队列处理函数中被执行。
实现了工作队列处理函数后,就需要schedule_work函数对这个工作进行调度,就像这样:
schedule_work(&work);
这样work会马上就被调度,一旦工作线程被唤醒,这个工作就会被执行(因为其所在工作队列会被执行)。
(PS;在前面很多篇文章中从理论的角度分析了中断机制的处理流程,分为上下两部完成中断处理,其实就是为了满足各个条件才分两步的。用实际的例子讲述怎么使用下半部 /kernel-book/ch03/3.3.3.htm)
您对本文章有什么意见或着疑问吗?请到您的关注和建议是我们前行的参考和动力&&
您的浏览器不支持嵌入式框架,或者当前配置为不显示嵌入式框架。设备的中断会打断内核中进程的正常调度和运行,系统对更高吞吐率的追求势必
要求中断服务程序尽可能地短小精悍。但是,这个良好的愿望往往与现实并不吻合。
在大多数真实的系统中,当中断到来时,要完成的工作往往并不会是短小的,它可能
要进行较大量的耗时处理。&
为了在中断执行时间尽可能短和中断处理需完成大量工作之间找到一个平衡点,Linux&将中断
处理程序分解为两个半部:顶半部(top&&half)和底半部(bottom&half)。&
顶半部完成尽可能少的比较紧急的功能,它往往只是简单地读取寄存器中的中断状态并清除
中断标志后就进行“登记中断”的工作。“登记中断”意味着将底半部处理程序挂到该设备的底半
部执行队列中去。这样,顶半部执行的速度就会很快,可以服务更多的中断请求。
现在,中断处理工作的重心就落在了底半部的头上,它来完成中断事件的绝大多数任务。底半部
几乎做了中断处理程序所有的事情,而且可以被新的中断打断,这也是底半部和顶半部的最大不同,
因为顶半部往往被设计成不可中断。底半部则相对来说并不是非常紧急的,而且相对比较耗时,不在
硬件中断服务程序中执行。&
尽管顶半部、底半部的结合能够改善系统的响应能力,但是,僵化地认为&Linux
设备驱动中的中断处理一定要分两个半部则是不对的。如果中断要处理的工作本身很
少,则完全可以直接在顶半部全部完成。&
其他操作系统中对中断的处理也采用了类似于Linux系统的方法,真正的硬件中断服务程序都应该尽可能短。
因此,许多操作系统都提供了中断上下文和非中断上下文相结合的机制,将中断的耗时工作保留到非中断上下文去执行。
Linux 系统实现底半部的机制主要有tasklet,工作队列和软中断。&
Linux 的中断处理分为两个半部,顶半部处理紧急的硬件操作,底半部处理不紧
急的耗时操作。tasklet 和工作队列都是调度中断底半部的良好机制,tasklet 基于软中
断实现。内核定时器也依靠软中断实现。
内核中的延时是忙等待或者睡眠等待,为了充分利用CPU资源,使系统有更好的
吞吐性能,在对延迟时间的要求并不是很精确的情况下,睡眠等待通常是值得推荐的。
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:276300次
积分:3260
积分:3260
排名:第4847名
原创:13篇
转载:243篇
评论:11条
(1)(1)(3)(11)(1)(1)(2)(4)(12)(4)(12)(9)(10)(15)(9)(7)(2)(13)(7)(5)(10)(9)(24)(10)(18)(18)(17)(5)(4)(12)linux 中断 - CSDN博客
研究linux系统,不管是做驱动、协议栈还是进程调度等等,都离不开中断。这说明,要想编写正确的linux代码,不了解中断是不行的。
话说曾几何时,在大学的课堂里,老师滔滔不绝的讲解中断,说中断可以嵌套,说中断有优先级,那么linux操作系统是不是中断嵌套?是不是按优先级嵌套?
其实大家应该可以猜到了,并不完全是的。因为老师讲的是理论,linux是现实,这两者是很难相同的。就像小时候要当科学家,结果长大了发现自己天天在搬砖。
现在回到正轨上来,通过下面几个问题的讲解,大家就可以对linux的中断有个整体上的了解。
硬件平台:x86
操作系统版本:linux-2.6.24
1.什么时硬中断,什么是软中断?
硬中断:是由与系统相连的外设(比如:网卡、硬盘)自动产生的。主要是用来通知操作系统外设状态的变化。比如当网卡收到数据包的时候,就会发出一个中断。
软中断:我们知道,为了满足实时系统的要求,中断处理应该是越快越好。linux为了实现这个特点,当中断发生的时候,硬中断处理那些短时间就可以完成的工作,而将那些处理时间比较长的工作,放到中断之后来完成,也就是软中断中来完成。
2.不同的硬中断是否可以嵌套?相同的硬中断是否可以嵌套,以及是否按优先级嵌套?硬中断最多可能嵌套几级?
Linux下硬中断是可以嵌套的,但是没有优先级的概念,也就是说任何一个新的中断都可以打断正在执行的中断,但是同种中断不会打断同种中断的执行。
但是并不是所有的中断都是可以被打断的,这需要看注册的中断函数是否设置了IRQF_DISABLED,如果设置了IRQF_DISABLED,那么硬中断处理的时候是不允许被打断的,否则是允许被打断的。Peter Zijlstra在2009.3的一个讨论中关于IRQF_DISABLED的使用问题(详见http://lwn.net/Articles/321663/)。
从代码的角度上来说中断嵌套发生的位置:
硬件中断--&do_IRQ--&handle_IRQ_event--&handler。 在硬件中断到handle_event_irq之间,由于发生中断的时候CPU会自动屏蔽中断,所以在这中间是不会发生中断嵌套的,但是在handle_event_irq中,可能会重新开启中断,也就是说在handler中是可以发生中断嵌套的。
同种中断不会嵌套的实现:
linux通过一个标志位IRQ_INPROGRESS来实现。当中断类型A的一个中断A1处理的时候,linux会在do_IRQ中,handle_IRQ_event之前,置位A类型中断的IRQ_INPROGRESS位。当A1中断在handle_IRQ_event中被同种类型的中断A2到达,会调用do_IRQ,然后发现A类型中断的IRQ_INPROGRESS,就会置位IRQ_PENDING后返回,不会嵌套执行。
由于同种类型的中断不会嵌套,所以最多可能的嵌套级数,就是未设置IRQF_DISABLED中断类型的个数。(是否还有其他的限制,没有详细的研究)
3.不同的软中断是否可以嵌套?相同的软中断是否可以嵌套?
软中断的调用是通过do_softirq()来激活的。
同种类型的软中断,不可以嵌套执行。但是不同的CPU上,可以同时运行相同类型的软中断。
4.软中断在什么时间点被调度?
(1)内核显示的允许软中断的时候 local_bh_enable
(2)irq_exit()的时候
(3)ksoftirqd进程被唤醒的时候
(4)其他可能的地方(这里没有详细的追究)
Linux中断下半部处理有三种方式:软中断、tasklet、工作队列。
曾经有人问我为什么要分这几种,该怎么用。当时用书上的东西蒙混了过去,但是自己明白自己实际上是不懂的。最近有时间了,于是试着整理一下linux的中断处理机制,目的是起码从原理上能够说得通。
一、最简单的中断机制
最简单的中断机制就是像芯片手册上讲的那样,在中断向量表中填入跳转到对应处理函数的指令,然后在处理函数中实现需要的功能。类似下图:
这种方式在原来的单片机课程中常常用到,一些简单的单片机系统也是这样用。
它的好处很明显,简单,直接。
二、下半部
中断处理函数所作的第一件事情是什么?答案是屏蔽中断(或者是什么都不做,因为常常是如果不清除IF位,就等于屏蔽中断了),当然只屏蔽同一种中断。之所以要屏蔽中断,是因为新的中断会再次调用中断处理函数,导致原来中断处理现场的破坏。即,破坏了&interrupt context。
随着系统的不断复杂,中断处理函数要做的事情也越来越多,多到都来不及接收新的中断了。于是发生了中断丢失,这显然不行,于是产生了新的机制:分离中断接收与中断处理过程。中断接收在屏蔽中断的情况下完成;中断处理在时能中断的情况下完成,这部分被称为中断下半部。
从上图中看,只看int0的处理。Func0为中断接收函数。中断只能简单的触发func0,而func0则能做更多的事情,它与funcA之间可以使用队列等缓存机制。当又有中断发生时,func0被触发,然后发送一个中断请求到缓存队列,然后让funcA去处理。
由于func0做的事情是很简单的,所以不会影响int0的再次接收。而且在func0返回时就会使能int0,因此funcA执行时间再长也不会影响int0的接收。
三、软中断
下面看看linux中断处理。作为一个操作系统显然不能任由每个中断都各自为政,统一管理是必须的。
我们不可中断部分的共同部分放在函数do_IRQ中,需要添加中断处理函数时,通过request_irq实现。下半部放在do_softirq中,也就是软中断,通过open_softirq添加对应的处理函数。
四、tasklet
旧事物跟不上历史的发展时,总会有新事物出现。
随着中断数的不停增加,软中断不够用了,于是下半部又做了进化。
软中断用轮询的方式处理。假如正好是最后一种中断,则必须循环完所有的中断类型,才能最终执行对应的处理函数。显然当年开发人员为了保证轮询的效率,于是限制中断个数为32个。
为了提高中断处理数量,顺道改进处理效率,于是产生了tasklet机制。
Tasklet采用无差别的队列机制,有中断时才执行,免去了循环查表之苦。
CDMA因为频谱重叠问题,必须降功率。而功率降低后,又发现原来功率低了有助于环保。
Tasklet作为一种新机制,显然可以承担更多的优点。正好这时候SMP越来越火了,因此又在tasklet中加入了SMP机制,保证同种中断只能在一个cpu上执行。在软中断时代,显然没有这种考虑。因此同一种中断可以在两个cpu上同时执行,很可能造成冲突。
总结下tasklet的优点:
(1)无类型数量限制;
(2)效率高,无需循环查表;
(3)支持SMP机制;
五、工作队列
前面的机制不论如何折腾,有一点是不会变的。它们都在中断上下文中。什么意思?说明它们不可挂起。而且由于是串行执行,因此只要有一个处理时间较长,则会导致其他中断响应的延迟。为了完成这些不可能完成的任务,于是出现了工作队列。工作队列说白了就是一组内核线程,作为中断守护线程来使用。多个中断可以放在一个线程中,也可以每个中断分配一个线程。
工作队列对线程作了封装,使用起来更方便。
因为工作队列是线程,所以我们可以使用所有可以在线程中使用的方法。
Tasklet其实也不一定是在中断上下文中执行,它也有可能在线程中执行。
假如中断数量很多,而且这些中断都是自启动型的(中断处理函数会导致新的中断产生),则有可能cpu一直在这里执行中断处理函数,会导致用户进程永远得不到调度时间。
为了避免这种情况,linux发现中断数量过多时,会把多余的中断处理放到一个单独的线程中去做,就是ksoftirqd线程。这样又保证了中断不多时的响应速度,又保证了中断过多时不会把用户进程饿死。
问题是我们不能保证我们的tasklet或软中断处理函数一定会在线程中执行,所以还是不能使用进程才能用的一些方法,如放弃调度、长延时等。
六、使用方式总结
Request_irq挂的中断函数要尽量简单,只做必须在屏蔽中断情况下要做的事情。
中断的其他部分都在下半部中完成。
软中断的使用原则很简单,永远不用。它甚至都不算是一种正是的中断处理机制,而只是tasklet的实现基础。
工作队列也要少用,如果不是必须要用到线程才能用的某些机制,就不要使用工作队列。其实对于中断来说,只是对中断进行简单的处理,大部分工作是在驱动程序中完成的。所以有什么必要非使用工作队列呢?
除了上述情况,就要使用tasklet。
即使是下半部,也只是作必须在中断中要做的事情,如保存数据等,其他都交给驱动程序去做。

我要回帖

更多关于 悬雍垂过长怎么办 的文章

 

随机推荐