<1>软中断运行在中断上下文,它是静态分配的,内核编译时就已经确定,不能动态注册或删除。这限制了它们的灵活性,但提高了性能。软中断可以在多个CPU上并行运行,包括同一种类型的软中断,所以需要处理好同步问题,比如使用自旋锁。不过,软中断的代码必须是可以重入的,这增加了实现的复杂性。常见的应用例子是网络和块设备的数据处理。
<2> Tasklet也是在中断上下文中运行,它是在软中断的基础上构建的。比如,tasklet是通过软中断中的一个类型(比如HI_SOFTIRQ或TASKLET_SOFTIRQ)来调度的。Tasklet是动态创建和销毁的,更灵活。不过,同一个tasklet不能在多个CPU上同时运行,也就是说它们被串行化了,这简化了同步问题。每个tasklet都有一个函数指针,执行时就是调用这个函数。Tasklet适用于需要延迟处理但不需要并行执行的任务,比如某些硬件中断的后处理。
<3>工作队列不同之处在于它是在进程上下文中执行的,也就是说可以睡眠和阻塞。这是因为它由内核线程来执行,所以可以调度、可以长时间运行。工作队列的任务会被加入到一个队列中,由工作线程逐个处理。这样适用于需要睡眠或者耗时较长的操作,比如文件系统操作或模块初始化。工作队列的实现可能涉及到创建内核线程,管理任务队列,以及处理任务的调度。
中断程序是不参与进程调度的,一旦中断程序中进入睡眠交出cpu,是无法被调度接着执行中断处理程序的。在一个中断处理程序中,信号量不可以出现,因为中断处理程序不参与进程调度。一旦运行,必须运行完,在这个过程中是不能交出CPU的,然而信号量本身是可以睡眠的。
在中断的上半部与下半部可以使用自旋锁来保护共享资源,但是在中断上下文中获取自旋锁之前要先禁用本地中断(拥有自旋锁的代码必须不能睡眠,所以不能被其他中断打断)。上半部需要快速完成,使用自旋锁较为常见,因为他们在持有锁的时候不会睡眠。
下半部是指中断处理的延迟部分,通常在中断处理完成后执行,用于处理较复杂的任务,避免中断处理时间过长。下半部可以通过软中断、工作队列,tasklet等方式实现。在下半部中,也可以使用自旋锁、信号量等,下半部的执行是可以睡眠的,因此在这里使用的锁机制可以是更灵活的选择,比如信号量和互斥锁(mutexes)。
下半部在运行时是允许中断的!!!通常下半部分在中断处理程序一返回就会马上运行
软中断
内核可以注册32个软中断,而当前内核版本只注册了9个。软中断产生后并不是马上可以执行,必须要等待内核的调度才能执行。软中断不能被自己打断(即单个cpu上软中断不能嵌套执行),只能被硬件中断打断(上半部)。软中断可以并发运行在多个CPU上(即使同一类型的也可以)。所以软中断必须设计为可重入的函数(允许多个CPU同时操作),因此也需要使用自旋锁来保其数据结果。软中断不会抢占另外一个软中断,唯一可以抢占软中断的是中断处理程序。
Tasklet与软中断运行于中断上下文,不允许阻塞 、休眠
a)一种特定类型的tasklet只能运行在一个CPU上,不能并行,只能串行执行。
b)多个不同类型的tasklet可以并行在多个CPU上。
c)软中断是静态分配的,在内核编译好之后,就不能改变。但tasklet就灵活许多,可以在运行时改变(比如添加模块时)。
它的实现方法是将中断下半部,放入到内核线程的一个软中断链表(SOFTIRQD)中,内核线程会自动执行链表的方法。注意的是这个线程是运行在中断上下文中的,只要涉及到中断,就不可以使用休眠或延时等阻塞函数。中断不允许有这样的操作。
workqueue(工作队列)运行在进程上下文,可以休眠和阻塞。
从上面的介绍看以看出,软中断运行在中断上下文中,因此不能阻塞和睡眠,而tasklet使用软中断实现,当然也不能阻塞和睡眠。但如果某延迟处理函数需要睡眠或者阻塞呢?没关系工作队列就可以如您所愿了。
内核定义了一组队列,其中每个队列都包含一个由等待调用的函数组成的链表。根据其所处队列的位置,这些函数会在某个时刻执行。驱动程序可以把它们的下半部注册到合适的队列上去。缺点是,对于一些性能要求较高的子系统(例如定时器、网络部分),它则不能胜任。把推后执行的任务叫做工作(work),描述它的数据结构为work_struct ,这些工作以队列结构组织成工作队列(workqueue),其数据结构为workqueue_struct ,而工作线程就是负责执行工作队列中的工作。系统默认的工作者线程为events。工作队列(work queue)是另外一种将工作推后执行的形式。工作队列可以把工作推后,交由一个内核线程去执行—这个下半部分总是会在进程上下文执行,但由于是内核线程,其不能访问用户空间。最重要特点的就是工作队列允许重新调度甚至是睡眠。
为什么要区分上半部和下半部?
中断服务程序异步执行,可能会中断其他的重要代码,包括其他中断服务程序。因此,为了避免被
中断的代码延迟太长的时间,中断服务程序需要尽快运行,而且执行的时间越短越好,所以中断程序只
作必须的工作,其他工作推迟到以后处理。所以Linux把中断处理切为两个部分:上半部和下半部。上半部就是中断处理程序,它需要完成的工作越少越好,执行得越快越好,一旦接收到一个中断,它就立即开始执行。像对时间敏感、与硬件相关、要求保证不被其他中断打断的任务往往放在中断处理程序中执行;而剩下的与中断有相关性但是可以延后的任务,如对数据的操作处理,则推迟一点由下半部完成。
下半部分延后执行且执行期间可以相应所有中断,这样可使系统处于中断屏蔽状态的时间尽可能的短,
提高了系统的响应能力。实现了程序运行快同时完成的工作量多的目标。
中断上下半部处理原则
(1)必须立即进行紧急处理的极少量任务放入在中断的顶半部中,此时屏蔽了与自己同类型的中断,由于任务量少,所以可以迅速不受打扰地处理完紧急任务。
(2)需要较少时间的中等数量的急迫任务放在tasklet中。此时不会屏蔽任何中断(包括与自己的顶半部同类型的中断),所以不影响顶半部对紧急事务的处理;同时又不会进行用户进程调度(说明tasklet下半部也是中断上下文),从而保证了自己急迫任务得以迅速完成。
(3)需要较多时间且并不急迫(允许被操作系统剥夺运行权)的大量任务放在workqueue中。此时操作系统会尽量快速处理完这个任务,但如果任务量太大,期间操作系统也会有机会调度别的用户进程运行,从而保证不会因为这个任务需要运行时间将其它用户进程无法进行。
(4)可能引起睡眠的任务放在workqueue中。因为在workqueue中睡眠是安全的。在需要获得大量的内存时、在需要获取信号量时,在需要执行阻塞式的I/O操作时(也会有睡眠),用workqueue很合适。