中断与软中断简要分析

中断与软中断简要分析

在分析linux内核的中断,软中断时,先应该明确这样一个派生关系:

irq ==> softirq ==> tasklet ==> bottom half ==> task queue------------------------|==> timer

中断是最初的原动力。分时系统依赖于时钟中断来定时重新调度可以运行的程序。外设通过中断来通知cpu处理相关的任务。中断处理程序是内核中一段特殊的,独立的,可运行实体。这个实体,某种程度上,是和进程或线程类似的。

由于中断需要快速处理,因此派生出来软中断softirq来处理中断没有处理完的事情。比如在网卡的驱动程序里,在中断环境里,只是把包放到一个队列里,然后由软中断来把包传递给进程,或者转发包等。

linuxsoftirq是不可重入的,因此,在单cpu的系统上,一次只能有一个软中断在运行。而在多cpu系统上,可以同时有多个softirq在运行。softirq可以处理更多的任务,一般如协议栈,文件系统等,都可以放到软中断里面处理。

tasklet是在softirq上的一个扩展。它是一段可以执行的代码片断,但是,一个tasklet同时只能在一个cpu上执行(一个tasklet只能有一个活动实体,而一个softirq可以有多个活动实体,但是softirq的活动实体是不可重入的)。

bottom halflinux 2.2.x以前的系统所采用的软中断机制,由于在smp上扩展性不好,现在已经不用了。bh也是一个可执行的代码片断,但是全局只能有一个bh在运行,多个bh之间是线性执行的,所以在smp系统上,浪费比较严重。

task queue是在bh机制上的一个扩展。也是一个可执行的代码片断。其目的是突破bh的数目限制(只有32个)。但是它的执行也是线性的,因此在smp上,也没什么优势。

timer也是bh上的一个扩展。每个timer都是一个可执行的片断,但是它更灵活,所以,如果有优先级高的任务,可以考虑使用timer 

在编写设备驱动时, tasklet 机制是一种比较常见的机制,通常用于减少中断处理的时间,将本应该是在中断服务程序中完成的任务转化成软中断完成。

为了最大程度的避免中断处理时间过长而导致中断丢失,有时候我们需要把一些在中断处理中不是非常紧急的任务放在后面执行,而让中断处理程序尽快返回。在老版本的 linux 中通常将中断处理分为 top half handler bottom half handler 。利用 top half handler 处理中断必须处理的任务,而 bottom half handler 处理不是太紧急的任务。

但是 linux2.6 以后的 linux 采取了另外一种机制,就是软中断来代替 bottom half handler 的处理。而 tasklet 机制正是利用软中断来完成对驱动 bottom half 的处理。 Linux2.6 中软中断通常只有固定的几种: HI_SOFTIRQ( 高优先级的 tasklet ,一种特殊的 tasklet) TIMER_SOFTIRQ (定时器)、 NET_TX_SOFTIRQ (网口发送)、 NET_RX_SOFTIRQ (网口接收) 、 BLOCK_SOFTIRQ (块设备)、 TASKLET_SOFTIRQ (普通 tasklet )。当然也可以通过直接修改内核自己加入自己的软中断,但是一般来说这是不合理的,软中断的优先级比较高,如果不是在内核处理频繁的任务不建议使用。通常驱动用户使用 tasklet 足够了。

软中断和 tasklet 的关系如下图:

中断与软中断简要分析_第1张图片

上图可以看出, ksoftirqd 是一个后台运行的内核线程,它会周期的遍历软中断的向量列表,如果发现哪个软中断向量被挂起了( pend ),就执行对应的处理函数,对于 tasklet 来说,此处理函数就是 tasklet_action ,这个处理函数在系统启动时初始化软中断的就挂接了。

Tasklet_action 函数,遍历一个全局的 tasklet_vec 链表(此链表对于 SMP 系统是每个 CPU 都有一个),此链表中的元素为 tasklet_struct 。此结构如下 :

struct tasklet_struct

{

       struct tasklet_struct *next;

       unsigned long state;

       atomic_t count;

       void (*func)(unsigned long);

       unsigned long data;

};

每个结构一个函数指针,指向你自己定义的函数。当我们要使用 tasklet ,首先新定义一个 tasklet_struct 结构,并初始化好要执行函数指针,然后将它挂接到 task_vec 链表中,并发一个软中断就可以等着被执行了。

原理大概如此,对于 linux 驱动的作者其实不需要关心这些,关键是我们如何去使用 tasklet 这种机制。

Linux 中提供了如下接口:

DECLARE_TASKLET(name,function,data) :此接口初始化一个 tasklet ;其中 name tasklet 的名字, function 是执行 tasklet 的函数; data unsigned long 类型的 function 参数。

static inline void tasklet_schedule(struct tasklet_struct *t) :此接口将定义后的 tasklet 挂接到 cpu tasklet_vec 链表,具体是哪个 cpu tasklet_vec 链表,是根据当前线程是运行在哪个 cpu 来决定的。此函数不仅会挂接 tasklet ,而且会起一个软 tasklet 的软中断 , 既把 tasklet 对应的中断向量挂起 (pend)

两个工作完成后,基本上可以了, tasklet 机制并不复杂,很容易的使程序尽快的响应中断,避免造成中断丢失。

你可能感兴趣的:(中断与软中断简要分析)