Linux内核软中断线程对于通用内核线程的启示


Linux 2.6.11版本的内核软中断线程ksoftirqd代码如下,下面框架可以看出对于,吞吐量与处理延迟两者之间的权衡。

软中断线程处理概括:

1、如果没有活干(没有软中断等待处理)就 schedule()切出,并从运行队列里面删除(由于任务状态已经变成TASK_INTERRUPTIBLE)

2、如果有活儿干,就把当前pending的软中断处理完,处理过程中检查如果本线程运行时间过长,则 schedule()切出(cond_resched()完成这一切),避免其他线程饿死。但切出时任务状态是TASK_RUNNING,不会移出运行队列(线程还有活儿没干完,得赶紧回来),由于移出前,关闭了内核抢占,很快就会重新调度到本线程的。

3、系统调度让其他进程运行一段时间后,本进程重新切换回来运行,重复第2步,一直到该处理得软中断处理完

4、一批软中断处理完后,设置任务状态是TASK_INTERRUPTIBLE,为无软新的软中断过来时切出去并移出运行队列准备。

假如:一次来了8个软中断要处理,需要连续处理完8个软中断,期间可以会切出;如果处理完这组最后一个软中断后正好切出(调用 cond_resched()时里面检查时间片到),然后一段时间后切换回来,发现切出的这段时间有新的软中断等待处理,则有继续处理。

static int ksoftirqd (void * __bind_cpu)
{
    set_user_nice(current, 19);             <---软中断线程按照优先级最低运行
    current->flags |= PF_NOFREEZE;

    set_current_state(TASK_INTERRUPTIBLE);   <---进入主循环前,先设置任务状态为TASK_INTERRUPTIBLE

    while (!kthread_should_stop()) {               <---这个while循环内部处理时,线程可以切出,但仍处与运行队列
        if (!local_softirq_pending())                     <---如果不存在需要处理的软中断,则线程切出并移出运行队列
            schedule();

        __set_current_state(TASK_RUNNING);   <---如果有活儿干,则恢复线程的状态为TASK_RUNNING

        while (local_softirq_pending()) {            <---这个循环是干活的主循环
            preempt_disable();                              <---关闭内核抢占,目的是为了保证能连续处理完pending的软中断
            if (cpu_is_offline((long)__bind_cpu))
                goto wait_to_die;
            do_softirq();                                       <---处理软中断(转发、报文收发处理)
            preempt_enable();
            cond_resched();                                 <---运行“指定时间片”后本线程强行切出,切出时任务状态还是TASK_RUNNING
        }

        set_current_state(TASK_INTERRUPTIBLE);   <---设置任务状态为TASK_INTERRUPTIBLE,为重新循环开始时,发现如果没活儿干任务切出,并移出运行队列准备
    }
    __set_current_state(TASK_RUNNING);
    return 0;

wait_to_die:   <---出错的时候才会走入这个处理
    preempt_enable();
    set_current_state(TASK_INTERRUPTIBLE);
    while (!kthread_should_stop()) {
        schedule();
        set_current_state(TASK_INTERRUPTIBLE);
    }
    __set_current_state(TASK_RUNNING);
    return 0;
}

int __sched cond_resched (void)
{
    if (need_resched()) {
        __cond_resched();
        return 1;
    }
    return 0;
}

static inline void __cond_resched (void)
{
    do {
        add_preempt_count(PREEMPT_ACTIVE);   <---禁止内核(抢占计数++),目的是为了本线程切出后能尽快切回来,因为本次是本线程因为运行时间长了主动释放,后面还有软中断等待处理呢,系统不能老把我抢占了,耽误我的处理延迟
        schedule();
        sub_preempt_count(PREEMPT_ACTIVE);
    } while (need_resched());
}

static inline int need_resched (void)
{
    return unlikely(test_thread_flag(TIF_NEED_RESCHED));  <--测试TIF_NEED_RESCHED标志是否设置,主要是时钟中断调用的scheduler_tick()函数设置本线程的时间片是否到(本线程运行时间是否过长),如果到了,说明线程运行的时间足够长了,那就歇会儿吧,不要把系统其他线程饿死了,毕竟本线程是禁止了内核抢占的
}

上述软中断线程处理得经典框架可以作为有类似处理内核线程的通用框架 。

static int kmythread (...)
{
    ...
    set_current_state(TASK_INTERRUPTIBLE);
    while (!kthread_should_stop()) {
        if (!local_softirq_pending())    /* 这段代码对于通用线程可以删除 */
            schedule();

        __set_current_state(TASK_RUNNING);
        while (/* 有自己的数据处理,如:自己的队列里面有报文需要处理等 */) {
            preempt_disable();
            /* 干活... */
            preempt_enable();
            cond_resched();
        }
        set_current_state(TASK_INTERRUPTIBLE);
    }
    __set_current_state(TASK_RUNNING);
    return 0;

}

相关内容