linux中断下半部机制

来源:互联网 发布:舆论监督与网络暴力 编辑:程序博客网 时间:2024/05/16 13:05

Linux内核可以有三种方法来实现中断下半部:sotfirqtasklet workqueue

1softirq

软中断一般很少用于实现中断下半部,但tasklet是通过软中断实现的,所以先介绍软中断。字面理解,软中断就是软件实现的异步中断,它的优先级比硬中断低,但比普通进程优先级高,同时,它和硬中断一样不能休眠

 

kernel/softirq.c文件中有这样一个数组

static structsoftirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp;

内核通过一个 softirq_action结构体数组来维护软中断,NR_SOFTIRQS是当前支持的软中断枚举类型中最后的一个成员。

 

/* 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 etal. should be converted to tasklets, not to softirqs.*/

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,  /* Preferable RCU should always be the last softirq */

 

   NR_SOFTIRQS

};

如果要想有一个新的软中断,可以在这个枚举结构体的NR_SOFTIRQS 上面添加。不过,从注释中我们发现内核并不建议我们添加新的软中断,毕竟softirq很少用于实现中断的下半部。

 

看一下softirq_action结构体

struct softirq_action

{

   void   (*action)(struct softirq_action *);

};

结构体里面就一个软中断函数,它的参数就是本身结构体的指针。之所以这样设计,是为了以后的拓展,如果在结构体中添加了新成员,也不需要修改函数接口。在2.4内核中,该结构体里面还有一个data的成员,用于传参,不过现在没有了。

 

使用softirq机制需要通过 open_softirq来注册软中断处理函数,使中断索引号与中断处理函数对应。该函数定义在 kernel/softirq.c文件中。

voidopen_softirq(int nr, void (*action)(struct softirq_action *))

{

   softirq_vec[nr].action = action;

}

该函数将软中断的中断处理函数指针赋值给相应的softirq_vec,当这个softirq被挂起后,内核会在某个时刻去执行这个中断处理函数。

 

在中断处理函数完成了紧急的硬件操作后,就应该调用 raise_softirq函数来触发软中断,让软中断来处理耗时的中断下半部操作。

voidraise_softirq(unsigned int nr)

{

   unsigned long flags;

local_irq_save(flags);

   raise_softirq_irqoff(nr);

   local_irq_restore(flags);

}

该函数挂起相应的软中断,并在没有中断时唤醒线程 ksoftirq,让内核在下次执行软中断的时候去执行这个软中断的处理函数。

 

注意:若在模块中直接使用open_softirqraise_softirq 这两个函数,模块加载时会报错,提醒这两个符号未声明。为了让模块正常加载,可以在kernel/softirq.c文件中将这两个符号导出。

EXPORT_SYMBOL(raise_softirq);

EXPORT_SYMBOL(open_softirq);

 

上面介绍,触发软中断函数raise_softirq并不会让软中断处理函数马上执行,它只是打了个标记,等到适合的时候再被执行。如在中断处理函数返回后,内核就会检查软中断是否被触发并执行触发的软中断。

软中断会在do_softirq中被执行,其中核心部分在do_softirq中调用的__do_softirq中:

asmlinkage void __do_softirq(void)

{

  .

  .

  .

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); //调用中断处理函数

               .

                .

                .

          }

 

          rcu_bh_qs(cpu);

      }

      h++; //下移,获取另一个软中断

      pending >>= 1;

   } while (pending); //直到所有被触发的软中断都执行完

      .

      .

      .

}

 

 

 

2tasklet

在实际的项目中我们一般使用tasklet实现中断的下半部。在介绍软中断索引号的时候,有两个用于实现tasklet的软中断索引号:HI_SOFTIRQTASKLET_SOFTIRQ。两个tasklet唯一的区别就是HI_SOFTIRQ优先级高些,一般使用TAKSLET_SOFTIRQ

 

内核中是通过tasklet_struct来维护一个tasklet,介绍一下tasklet_struct结构体里面的成员:

include/linux/interrupt.h

struct tasklet_struct

{

   struct tasklet_struct *next;

   unsigned long state;  

   atomic_t count;

   void (*func)(unsigned long);

   unsigned long data;

};

各成员的含义如下:
1next指针:指向下一个tasklet的指针。
2state:定义了这个tasklet的当前状态。这一个32位的无符号长整数,当前只使用
bit1]和bit0]两个状态位。其中,bit1]=1表示这个tasklet当前正在某个
CPU
上被执行,它仅对SMP系统才有意义,其作用就是为了防止多个CPU同时执行一个
tasklet
的情形出现;bit0]=1表示这个tasklet已经被调度去等待执行了。对这两个
状态位的宏定义如下所示(interrupt.h):
enum
{
TASKLET_STATE_SCHED, /* Tasklet is scheduled for execution */
TASKLET_STATE_RUN /* Tasklet is running (SMP only) */
};
3)原子计数count:对这个tasklet的引用计数值。只有当count等于0时,
tasklet
代码段才能执行,即此时tasklet是被使能的;如果count非零,则这个
tasklet
是被禁止的。任何想要执行一个tasklet代码段的人都首先必须先检查其count
员是否为0
4)函数指针func:指向以函数形式表现的可执行tasklet代码段。
5data:函数func的参数。这是一个32位的无符号整数,其具体含义可供func函数自
行解释,比如将其解释成一个指向某个用户自定义数据结构的地址值。

 

在模块加载函数,或者open函数中可以使用下面两种方式初始化tasklet

DECLARE_TASKLET(name, func, data)或者

void tasklet_init(struct tasklet_struct *t,  void (*func)(unsigned long), unsigned long data)

然后在中断函数返回前调用

static inline void tasklet_schedule(struct tasklet_struct *t)

触发tasklet软中断。

static inline void tasklet_schedule(struct tasklet_struct *t)
{
 if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state))
  __tasklet_schedule(t);
}

函数__tasklet_schedule得到当前CPUtasklet_vec链表,并执行TASKLET_SOFTIRQ软中断。

void fastcall __tasklet_schedule(struct tasklet_struct *t)
{
 unsigned long flags;

 local_irq_save(flags);
 t->next = __get_cpu_var(tasklet_vec).list;
 __get_cpu_var(tasklet_vec).list = t;
 raise_softirq_irqoff(TASKLET_SOFTIRQ);
 local_irq_restore(flags);
}

函数raise_softirq_irqoff设置软中断nr为挂起状态,并在没有中断时唤醒线程ksoftirqd。函数raise_softirq_irqoff必须在关中断情况下运行。

 

 

 

3workqueue

内核使用work_struct结构体来维护一个加入工作队列的任务:

structwork_struct {

   atomic_long_t data;

#define WORK_STRUCT_PENDING 0     /* T if work item pending execution */

#define WORK_STRUCT_STATIC 1      /* static initializer (debugobjects) */

#define WORK_STRUCT_FLAG_MASK (3UL)

#define WORK_STRUCT_WQ_DATA_MASK (~WORK_STRUCT_FLAG_MASK)

   struct list_head entry;

   work_func_t func; //下半部实现的处理函数指针

#ifdef CONFIG_LOCKDEP

   struct lockdep_map lockdep_map;

#endif

};

 

要使用工作队列来实现中断下半部,首先需要定义一个 work_struct 结构体变量和一个处理函数。

struct work_struct my_wq; //定义一个工作队列

void xxxx_func(struct work_struct * xx) //处理函数

 

在模块加载函数或者open函数中通过INIT_WORK()初始化这个工作队列并与处理函数绑定,在中断处理函数返回前调用schedule_work(struct work_struct *work)。同样的,调度并不代表处理函数能够马上执行,这由内核进程调度决定。

 

 

总结:

软中断支持SMP,同一个softirq可以在不同的CPU上同时运行,softirq是可重入的,如果在软中断的处理函数中操作共享数据,则需要对共享数据保护机制。软中断是在编译期间静态分配的,它不像tasklet那样能被动态的注册或去除。

引入tasklet,最主要的是考虑支持SMP,提高SMP多个cpu的利用率;不同的tasklet可以在不同的cpu上运行。某一段tasklet代码在某个时刻只能在一个CPU上运行,但不同的tasklet代码在同一时刻可以在多个CPU上并发地执行。

软中断和tasklet优先级较高,性能较好,调度快,但不能睡眠。而工作队列是内核的进程调度,相对来说较慢,但能睡眠。所以,如果你的下半部需要睡眠,那只能选择动作队列。否则最好用tasklet

 

 

 

 

 

 

 

 

 

 

 

 

原创粉丝点击