首页 > 代码库 > 下半部和推后运行的工作

下半部和推后运行的工作

(一):下半部

下半部的任务就是运行与中断处理密切相关但中断处理程序本身不运行的工作.那么有一些提示能够借鉴哪些工作放在上半部中运行,哪些工作放在下半部运行.

1:假设一个任务对时间很敏感,将其放在中断处理程序中进行
2:假设一个任务与硬件相关,将其放在中断处理程序中进行
3:假设一个任务保证不被其它中断打断,将其放在中断处理程序中进行
4:其它全部任务,考虑放在下半部运行

1:为什么要用下半部

我们希望的是尽快降低中断处理程序须要完毕的工作量,由于他在运行的时候,当前中断线在全部处理器上都会被屏蔽.更糟糕的是,假设一个中断处理程序是IRQF_DISABLE的类型,他运行的时候会禁止全部本地中断(并且把本地中断线全局的屏蔽掉).而缩短中断被屏蔽的时间对系统的响应能力和性能都至关重要.再加上中断处理程序与其它程序异步运行,所以,我们必须尽力缩短中断处理程序的运行.

2:下半部的环境

和上半部仅仅能通过中断处理程序实现不同,下半部能够通过多种机制实现.这些用来实现下半部的机制分别由不同的接口和子系统组成.

1):”下半部”的起源

最早的linux仅仅提供”botton half”这样的机制用于实现下半部,也被称作”BH”.BH提供了一个静态创建,有32个bottom halves组成的链表.上半部通过一个32位整数中的一位来标识出哪个bottom half能够运行.每一个BH都在全局范围内进行同步.即使分属于不同的处理器,也不同意不论什么两个bottom half同一时候运行.这样的机制使用方便却不够灵活,简单却有性能瓶颈.

2):任务队列
后来,任务对来取代BH机制来实现下半部运行的工作.内核定义了一组队列,当中每一个队列都包括一个有等待调用的函数组成的链表,依据其所在的位置,这些函数会在某个时刻运行.驱动程序能够把他们自己的下半部注冊到合适的队列上.

3):软中断和tasklet

在2.3版本号中。内核引入了软中断和tasklet.软中断是一组静态定义的下半部接口,有32个。能够在全部处理器上同一时候运行--及时两个类型同样也能够.tasklet是一种基于软中断实现的灵活性强。动态创建的下半部实现机制,两个不同类型的tasklet能够在不同的处理器上同一时候运行,但类型同样的tasklet不能同一时候运行.tasklet事实上是一种在性能和易用性之间寻求平衡的产物.对于大部分下半部处理来说。用tasklet就够了.像网络这样对性能要求很高的情况才须要使用软中断.可是,使用软中断须要小心,由于两个同样的软中断有可能同一时候运行.同一时候,软中断必须在编译期间就进行静态注冊,可是tasklet能够通过代码动态注冊.

在中间的发展过程中。一直到2.6版本号。内核提供了三种不同形式的下半部实现机制:软中断,tasklet和工作队列.

注意。内核定时器也能够实现将工作推后运行.内核定时器把操作推迟到某个确定的时间段之后运行.在刚刚的三种机制中。也须要使用内核定时器来确定推迟的时间段.

(二)软中断

首先须要指明的是,软中断为于kernel/softirq.c

1:软中断的实现
软中断在编译期间是静态分配的.她不像tasklet那样能够动态的注冊或者是注销.软中断由softirq_action结构表示。这个结构定义在

struct softirq_action
{
    void (*action)(struct softirq_action *);
};

kernel/siftirq.c中定义了一个包括有32个该结构体的数组.

static struct softirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp;

每一个注冊的软中断都占领该数组的一项,因此。最多可能有32个软中断.在2.6.34中,这32项仅仅用到当中的9项.

1):软中断处理程序

软中断处理程序action的函数原型例如以下:

void softirq_handler(struct softirq_action*)

当内核运行一个软中断处理函数的时候,他就会运行这个action函数,其唯一的參数就是运行这个软中断的softirq_action结构体的指针.比如,假设my_softirq指向softirqvec数组中的某一项,则内核会调用例如以下方法来调用软中断处理程序中的函数.

my_softirq->action(my_softirq);

在这里传递整个结构体而不是传递数值有一些本身的长处。能够保证将来在结构体中增加新的域的时候。无须对全部的软中断处理程序都进行变动.假设须要,软中断处理程序能够方便的解析他们的參数。从数据成员中提取数值.

一个软中断不会抢占另外一个软中断,实际上,唯一能够抢占软中断的就是中断处理程序.只是。其它的软中断能够在其它处理器上同一时候运行.

2):运行软中断

一个注冊的软中断必须在被标记之后才会运行.这被称作出发软中断.通常,中断处理程序会在返回前标记他的软中断,使其在稍后运行.于是,在合适的时刻,软中断就会运行.在下列地方,待处理的软中断会被检查和运行.

?1:从一个硬件中断代码处返回时
?2:在ksoftirq内核线程中    ?
?3:在那些显示检查和运行待处理的软中断的代码中,如网络子系统中.无论是用什么办法唤起,软中断都要在do_softirq()中运行.这个函数比較简单,假设有带运行的软中断,do_softirq()会循环遍历每一个。调用他们的处理程序.

以下看一下do_softirq()函数的实现过程.

asmlinkage void __do_softirq(void)
{
    //软中断结构体
    struct softirq_action *h;
    //保存待处理的软中断的32位位图
    __u32 pending;
    int max_restart = MAX_SOFTIRQ_RESTART;
    int cpu;
    //获取当前待处理的软中断的32位位图
    pending = local_softirq_pending();
    account_system_vtime(current);
    __local_bh_disable((unsigned long)__builtin_return_address(0));
    lockdep_softirq_enter();
    cpu = smp_processor_id();
restart:
    /* 由于当前位图已经保存下来了,所以能够同意中断了*/
    /* 须要在同意中断之前,重设待处理的位图 */
    /* Reset the pending bitmask before enabling irqs */
    set_softirq_pending(0);
    //同意中断
    local_irq_enable();
    /* 使h指向数组的第一个 */
    h = softirq_vec;
    do {
        if (pending & 1) {
            int prev_count = preempt_count();
            kstat_incr_softirqs_this_cpu(h - softirq_vec);
            trace_softirq_entry(h, softirq_vec);
            //运行中断处理函数
            h->action(h);
            trace_softirq_exit(h, softirq_vec);
            if (unlikely(prev_count != preempt_count())) {
                printk(KERN_ERR "huh, entered softirq %td %s %p"
                       "with preempt_count %08x,"
                       " exited with %08x?

\n", h - softirq_vec, softirq_to_name[h - softirq_vec], h->action, prev_count, preempt_count()); preempt_count() = prev_count; } rcu_bh_qs(cpu); } //移动到下一个位置,接着进行推断 h++; //同一时候位图也要移动一个位置 pending >>= 1; //由于位图是32位的,数组长度也是32,所以他们是一一相应的. } while (pending); //禁止中断 local_irq_disable(); //获取当前待处理的软中断的32位位图 pending = local_softirq_pending(); if (pending && --max_restart) goto restart; if (pending) wakeup_softirqd(); lockdep_softirq_exit(); account_system_vtime(current); _local_bh_enable(); } #ifndef __ARCH_HAS_DO_SOFTIRQ asmlinkage void do_softirq(void) { __u32 pending; unsigned long flags; if (in_interrupt()) return; local_irq_save(flags); //获取当前待处理的软中断的32位位图 pending = local_softirq_pending(); //假设有待处理的软中断,也就是有被标记的软中断 //则进行软中断运行 if (pending) __do_softirq(); local_irq_restore(flags); } #endif

这两个函数检查并且运行软中断.详细要做的包括:

?1):用局部变量pending保存local_softirq_pending()宏的返回值.他是待处理的软中断的32位位图.假设第n位被设置为1,那么第n位相应类型的软中断等待处理.
?2):如今待处理的软中断位图已经被保存。能够将实际的软中断位图清零了
?3):将指针h指向softirq_vec的第一项
?4):假设pending的第一位被设置为1,则h->action(h)被调用
?5):指针加1。所以他如今指向softirq_vec数组的第二位
?6):位掩码pending右移一位.这样会丢弃第一位,然后让其它各位以此向右移动一个位置.于是原来在第二位如今就在第一个位置上了.
?7):如今指针h指向数组的第二项,pending位掩码的第二位如今也到了第一位上.反复上面的步骤.
?8):一直反复下去。知道pending变为0,这表明已经没有待处理的软中断了,我们的任务也就完毕了.

2:使用软中断

软中断保留给系统中对时间要求最严格以及最重要的下半部使用.眼下仅仅有两个子系统(网络和SCSI)直接使用软中断.此外tasklet和内核定时器都是建立在软中断上的.tasklet相对于软中断来说,能够动态生成,对加锁要求不高。性能也不错.

1):分配索引
在编译期间,通过在linux/interrupt.h中定义的一个枚举类型来静态的声明软中断.内核用这些从0開始的索引来表示一种相对优先级.索引號小的软中断在索引號大的软中断之前运行.

建立一个新的软中断必须在此枚举类型中增加新的项.而增加的时候,我们不能像在其它地方一样,简单的把新项加到列表的末尾.相反,你必须依据希望赋予它的优先级来决定增加的位置.习惯上,HI_SOFTIRQ作为第一项,RCU_SOFTIRQ作为第二项.
以下列出已有的tasklet类型.

/* PLEASE, avoid to allocate new softirqs, if you need not _really_ high
   frequency threaded job scheduling. For almost all the purposes
   tasklets are more than enough. F.e. all serial device BHs et
   al. should be converted to tasklets, not to softirqs.
 */
enum
{
    HI_SOFTIRQ=0,
    TIMER_SOFTIRQ,
    NET_TX_SOFTIRQ,
    NET_RX_SOFTIRQ,
    BLOCK_SOFTIRQ,
    TASKLET_SOFTIRQ,
SCHED_SOFTIRQ,
    HRTIMER_SOFTIRQ,
    RCU_SOFTIRQ,    /* Preferable RCU should always be the last softirq */
};

上面的凝视说的是假设不是对时间要求特别高的话,tasklet就足够满足我们的要求了.

技术分享

2):注冊你的处理程序

在运行的时候。通过调用open_softirq()注冊软中断处理程序。该函数有两个參数:软中断索引號和处理函数.比如网络子系统,在net/coreldev.c。通过以下方式注冊自己的软中断.

open_softirq(NET_TX_SOFTIRQ,net_tx_action);
open_softirq(NET_RX_SOFTIRQ,net_rx_action);

软中断处理程序运行的时候,同意响应中断.但他自己不能休眠.在一个处理程序运行的时候,当前处理器上的软中断被禁止.可是其它的处理器仍能够运行别的软中断.实际上,假设同一个软中断在他被运行的时候再次被触发了。那么另外一个处理器能够同一时候运行其处理程序.这意味着不论什么共享数据(甚至是仅在软中断处理程序内部使用的全局变量)都须要严格的锁保护.tasklet仅仅只是是同一个处理程序的多个实例不能在多个处理器上同一时候运行.

3):触发你的软中断
通过在枚举类型的列表中增加新项以及调用open_softirq()进行注冊以后,新的中断处理程序就能够运行.raise_softirq()函数能够将一个软中断设置为挂起状态,让他在下次调用do_softirq()函数的时候投入运行.比如。网络子系统可能会调用:

raise_softirq(NET_TX_SOFTIRQ);

这会触发NET_TX_SOFTIRQ软中断.他的处理程序net_tx_action()就会在内核下一次运行软中断的时候投入运行.该函数在触发一个软中断之前先要禁止中断,触发后再恢复到原来的状态.假设中断本来就已经禁止了,那么能够调用还有一个函数raise_softirq_irqoff()。这会带来一些优化效果.

/* 
 *  中断已经禁止
 */
raise_softirq_irqoff(NET_TX_SOFTIRQ);

在中断处理程序中触发软中断是最常见的形式.在这样的情况下,中断处理程序运行硬件设备的相关操作,然后触发相应的软中断。最后退出.内核在运行完中断处理程序之后,立即就会调用do_softirq()函数.于是软中断開始运行中断处理程序留给他去完毕的剩余任务.

<script type="text/javascript"> $(function () { $(‘pre.prettyprint code‘).each(function () { var lines = $(this).text().split(‘\n‘).length; var $numbering = $(‘
    ‘).addClass(‘pre-numbering‘).hide(); $(this).addClass(‘has-numbering‘).parent().append($numbering); for (i = 1; i <= lines; i++) { $numbering.append($(‘
  • ‘).text(i)); }; $numbering.fadeIn(1700); }); }); </script>

下半部和推后运行的工作