linux 软中断和tasklet

1. 软中断是什么 ?

 软中断是一种延时机制,代码执行的优先级比进程要高,比硬中断要低。相比于硬件中断,软中段是在开中断的环境中执行的(长时间关中断对系统的开销太大), 代码是执行在中断/线程上下文的,是不能睡眠的,虽然每个cpu都有一个对应的ksoftirqd/n线程来执行软中断,但是do_softirq这个函数也还会在中断退出时调用到,因此不能睡眠(中断上下文不能睡眠的原因是由于调度系统是以进程为基本单位的,调度时会把当前进程的上下文保存在task_struct这个数据结构中,当进程被调度重新执行时会找到执行的断点,但是中断上下文是没有特定task_struct结构体的,当然现在有所谓的线程话中断,可以满足在中断处理函数执行阻塞操作,但是实时性可能会有问题。还有就是中断代表当前进程执行的概念,个人感觉有点扯淡,毕竟整个内核空间是由所有进程共享的,不存在代表的概念)

2. 软中段是怎么实现的?

一个模块或者子系统的实现都是数据结构+算法来实现的,算法一般由特定的函数来表征。数据结构一般会定义一些全局变量来支撑算法的实现。软中断牵涉到的数据结构主要是一个全局的向量数组,来映射一个软中断向量号和对应的处理函数:

static struct sotfirq_action softirq_vec[NR_SOFTIRQS];
struct softirq_action {
void (*action)(struct softirq_action *);
}

目前代码中有10种类型的软中断

enum  {
        HI_SOFTIRQ=0,
        TIMER_SOFTIRQ,
        NET_TX_SOFTIRQ,
        NET_RX_SOFTIRQ,
        BLOCK_SOFTIRQ,
        BLOCK_IOPOLL_SOFTIRQ,
        TASKLET_SOFTIRQ,
        SCHED_SOFTIRQ,
        HRTIMER_SOFTIRQ,
        RCU_SOFTIRQ,

        NR_SOFTIRQS
}

这里只关注和tasklet相关的软中断: HI_SOFTIRQ和TASKLET_SOFTIRQ

核心函数:

open_softirq(int nr, void (*action)(struct softirq_action *))  //一个软中断和对应的处理函数的绑定,
{
        softirq_vec[nr].action = action;
}
raise_softirq(unsingned int nr)   //表明一个软中断处于pending状态等待处理, 在do_softirq函数里会检查处于pending的软中断,然后调用对应的处理函数
{
        or_softirq_pending(1UL << nr);    // 主要置位per-cpu类型的irq_stat.__softirq_pending成员的第nr位
        if (!in_interrupt())   //如果不在中断上下文,则唤醒内核线程ksoftirqd/n来处理pending的软中断,如果在中断上下文的话,可能是由于软中断被禁止执行了
            wakeup_softirqd(); 
}

do_softirq(void)
{
        if(in_interrupt())    // 这里有两种情况:do_softirq已经在中断上下文被调用一次了,例如在执行软中断的时候又发生了硬件中断,在中断退出的时候又会去执行软中断,防止代码的重入,第二种情况是软中断系统被显示地禁止执行了(全局变量preempt_count的第二个字段softirq counter用来记录软中断被disable的次数)
             return;
        pending = local_softirq_pending();
        if (pending)
             __do_softirq();
                 -->__local_bh_disable(); // 进入软中断的处理前,会显示地disable掉软中断,防止重入
                 --> do {
                           if (pending & 1) {
                               softirq_vec[nr]->action(h); //调用对应的软中断处理函数
                           }
                           pending >>=1;  //优先级从0开始
                         }  while (pending);
}


3. tasklet的概念和实现

驱动程序里最常见的就是在中断处理函数里面调度一个tasklet来执行延时的任务,tasklet是在软中断的基础之上实现的,牵涉到HI_SOFTIRQ和TASKLET_SOFTIRQ两种软中断,两种软中断对应的处理函数tasklet_action/tasklet_hi_action会去tasklet_vec和tasklet_hi_vec数组(基于per cpu结构)中取所存在的tasklet,然后执行相应的tasklet处理函数。

static DEFINE_PER_CPU(struct tasklet_head, tasklet_vec);   //数组中的元素为一个链表头, 所有的tasklet是链接在一个链表上的
static DEFINE_PER_CPU(struct tasklet_head, tasklet_hi_vec);

struct tasket_head {
        struct tasklet_struct *head;
        struct tasklet_struct **tail;
}

struct tasklet_struct {
        struct tasklet_struct *next;
        unsigned long state;    //一个tasklet对象所处的状态,可以为TASKLET_STATE_SCHED或者TASKLET_STATE_RUN
        atomic_t count;     //不为0表示禁止执行tasklet
        void (*func)(unsigned long);
        unsigned data;
}

static inline void tasklet_schedule(struct tasklet_struct *t)
{
         if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state))  //同一个tasklet只能被调度一次,如果另一个CPU正在执行同一个tasklet,如果刚好清掉TASKLET_STATE_SCHED标志,这个tasklet可以被挂到这个CPU上的tasklet_vec链表上,但是不能被这个CPU被执行,因为在执行tasklet之前,会检查当前tasklet的状态是否为TASKLET_STATE_RUN, 如果被置位,则不会执行  
               __tasklet_schedule(t);  //将该tasklet链接到本地CPU对应的tasklet链表上去
                    -->raise_softirq_irqoff(TASKLET_SOFTIRQ); //通知软中断系统,TASKLET_SOFTIRQ类型的软中断处于pending状态
}

void tasklet_action(struct softirq_action *a)
{
          struct tasklet_struct *list = __this_cpu_read(tasklet_vec.head);

          while(list) {
                struct tasklet_struct *t = list;
                list = list->next;
                if (tasklet_trylock(t)) {   //测试TASKLET_STATE_RUN标志有没有置位,然后置位该标志位
                        if(!atomic_read(&t->count)) {   //该tasklet被disabled了吗?
                              if(!test_and_clear_bit(TASKLET_STATE_SCHED, &t->sched))
                                          BUG();
                        t->func(t->data);
                        tasklet_unlock(t);
                        continue;
                        }
                        tasklet_unlock(t);  //复位TASKLET_STATE_RUN标志位
                 }
           }

}


static inline void tasklet_disable(struct tasklet_struct *t)
{
          atomic_inc(&t->count);  
          tasklet_unlock_wait(t); //等待正在执行的tasklet执行完毕,确保调用该函数后,在调用tasklet_schedule后tasklet是不会被执行的
}

void tasklet_kill(struct tasklet_struct *t)  //感觉这个函数和tasklet_disable类似,只是等待当前正在执行的tasklet完成,下次调用tasklet_schedule应该还是会执行的,貌似和kill这个单词不匹配啊,还没tasklet_disable彻底
{
          while(test_and_set_bit(TASKLET_STATE_SCHED, &t->state)) {
                do {
                   yield();
                 } while(test_bit(TASKLET_STATE_SCHED, &t->sched));
          }
          tasklet_unlock_wait(t);
          clear_bit(TASKLET_STATE_SCHED, &t->state);
}

从上面的分析可以看出,tasklet的处理函数是不可重入的,换句话说,就是不可能在两个CPU上跑相同的代码,因为一个tasklet只能被调度一次,也就是挂接在一个链表里面,而且在哪个CPU上被调度,就会在哪个CPU被执行,因为是基于per cpu结构的, 但是软中断对应的处理函数是可重入的, 需要处理同步的问题,不过一般驱动里面还是用tasklet,简单方便,而且软中断是在编译时决定的,开销太大。

你可能感兴趣的:(linux)