第一篇技术博客,之前基础的东西网上大牛写的很多,这一部分在看第二遍的时候,结合项目上的东西有了点小小的领会,觉得有必要记录下来。
中断下半部的实现方式有三种:软中断、tasklet 和工作队列(work queue),这里先介绍前两个,基本概念省略。
软中断共有32个,但是只用到了9个,且均为系统分配好的,一般我们不是用软中断作为下半部的处理机制。以下是kernel分配的软中断,尽量避免去分配自己的软中断!
/* 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, //hi tasklet
TIMER_SOFTIRQ,
NET_TX_SOFTIRQ,
NET_RX_SOFTIRQ,
BLOCK_SOFTIRQ,
BLOCK_IOPOLL_SOFTIRQ,
TASKLET_SOFTIRQ, //普通tasklet
SCHED_SOFTIRQ,
HRTIMER_SOFTIRQ,
RCU_SOFTIRQ, /* Preferable RCU should always be the last softirq */
NR_SOFTIRQS
};
a.软中断结构体
struct softirq_action
{
void (*action)(struct softirq_action *);
};
softirq_action结构体定义了kernel的软中断:
static struct softirq_action softirq_vec[NR_SOFTIRQS]
每个变量下面都会有相应的软中断处理函数赋值给(*action)();
void open_softirq(int nr, void (*action)(struct softirq_action *))
{
softirq_vec[nr].action = action;
}
注册软中断例子:open_softirq(NET_TX_SOFTIRQ, net_tx_action);
c. raise_softirq()触发软中断或者 raise_softirq_irqoff()
把相应的软中断位置位(如上面列举的9个bit)
tasklet的调度函数__tasklet_schedule和__tasklet_hi_schedule中就有一个步骤是
raise_softirq_irqoff(TASKLET_SOFTIRQ);和raise_softirq_irqoff(HI_SOFTIRQ);
d. do_softirq()
不管软中断如何被唤醒,最终都是在do_softirq()函数中被执行。如下:
asmlinkage void __do_softirq(void)
{
……
do {
if (pending & 1) {
unsigned int vec_nr = h - softirq_vec;
int prev_count = preempt_count();
kstat_incr_softirqs_this_cpu(vec_nr);
trace_softirq_entry(vec_nr);
h->action(h); //这里执行的是注册好的action函数,tasklet也在此执行(tasklet_action和tasklet_hi_aciton函数)
trace_softirq_exit(vec_nr);
if (unlikely(prev_count != preempt_count())) {
printk(KERN_ERR "huh, entered softirq %u %s %p"
"with preempt_count %08x,"
" exited with %08x?\n", vec_nr,
softirq_to_name[vec_nr], h->action,
prev_count, preempt_count());
preempt_count() = prev_count;
}
rcu_bh_qs(cpu);
}
h++;
pending >>= 1;
} while (pending);
……
}
二、tasklet
tasklet基于软中断实现,可以将tasklet看作内核定义的32个(实际9个)软中断中的一个,只是功能上可能多了一些,下面以普通级别的tasklet为例,hi的类似。
tasklet使用了软中断的一个bit,即
TASKLET_SOFTIRQ,tasklet执行肯定跟软中断一样,需要一个action函数。这个函数就是tasklet_action,它的注册实在start_kernel时,softirq_init函数注册的:
void __init softirq_init(void)
{
int cpu;
for_each_possible_cpu(cpu) {
int i;
per_cpu(tasklet_vec, cpu).tail =
&per_cpu(tasklet_vec, cpu).head;
per_cpu(tasklet_hi_vec, cpu).tail =
&per_cpu(tasklet_hi_vec, cpu).head;
for (i = 0; i < NR_SOFTIRQS; i++)
INIT_LIST_HEAD(&per_cpu(softirq_work_list[i], cpu));
}
register_hotcpu_notifier(&remote_softirq_cpu_notifier);
open_softirq(TASKLET_SOFTIRQ, tasklet_action);
open_softirq(HI_SOFTIRQ, tasklet_hi_action);
}
也就是说,在start_kernel结束以后,基于软中断的tasklet已经注册好了自己的处理函数,走完了上面的步骤b,那么我们在使用tasklet的时候实际就是要raise_softirq_irqoff这个软中断就完成了对tasklet的使用,那么下面看看kernel如何走到将这个软中断唤醒这一步的。
先看看tasklet比单纯的软中断多的数据结构:
struct tasklet_struct
{
struct tasklet_struct *next;
unsigned long state;
atomic_t count;
void (*func)(unsigned long);
unsigned long data;
};
变量介绍:
struct tasklet_struct *next;
可以看出结构体中有个tasklet_struct的指针,内核就是用此指针,将多个tasklet串在一起,然后挂在一个软中断线上,处理的时候依次处理挂上去的tasklet。
struct tasklet_head
{
struct tasklet_struct *head;
struct tasklet_struct **tail;
};
static DEFINE_PER_CPU(struct tasklet_head, tasklet_vec);
static DEFINE_PER_CPU(struct tasklet_head, tasklet_hi_vec);
tasklet_vec和tasklet_hi_vec就是tasklet的链表,处理时可以根据这个变量遍历所有tasklet。
取值:0、TASKLET_STATE_SCHED 、TASKLET_STATE_RUN
TASKLET_STATE_SCHED:被调度,准备投入运行
atomic_t count;
tasklet引用次数,为0则tasklet被激活,可以被挂起并执行,非0则tasklet被禁止。
void (*func)(unsigned long);
tasklet被调度到时的处理函数,是需要自己定义的,会在tasklet_action函数中被依次执行
声明tasklet时的参数,在调用上面的函数时会被当作参数传入。
再看tasklet是如何使用的:
a.声明
静态声明:
DECLEAR_TASKLET(name, func, data);
DECLEAR_TASKLET_DISABLED(name, func, data); //声明的tasklet是禁止的,tasklet_enable来激活
// tasklet_disable()和tasklet_enable()来禁止和激活指定的tasklet
动态声明:
tasklet_init(tsklet, tsklet_handler, dev);
tasklet_kill可以删除一个在挂起队列中的tasklet,该函数可能导致睡眠,不能在中断上下文中使用
b.编写自己的tasklet处理函数 tsklet_handler
c.调度自己的tasklet tasklet_schedule()
static inline void tasklet_schedule(struct tasklet_struct *t)
{
if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state)) //判断是否被调度
__tasklet_schedule(t); //未被调度,则
}
void __tasklet_schedule(struct tasklet_struct *t)
{
unsigned long flags;
local_irq_save(flags);
t->next = NULL;
*__this_cpu_read(tasklet_vec.tail) = t; //这两行把新的tasklet加入到链表中
__this_cpu_write(tasklet_vec.tail, &(t->next));
raise_softirq_irqoff(TASKLET_SOFTIRQ); //唤醒了这个软中断
local_irq_restore(flags);
}
可以看出,在我们的驱动中初始化一个tasklet,在中断中调用tasklet_schedule()后,就可以等着处理函数被调用了,do_softirq会去调用start_kernel中注册的tasklet_action函数,该函数如下:
static void tasklet_action(struct softirq_action *a)
{
struct tasklet_struct *list;
local_irq_disable();
list = __this_cpu_read(tasklet_vec.head);
__this_cpu_write(tasklet_vec.head, NULL);
__this_cpu_write(tasklet_vec.tail, &__get_cpu_var(tasklet_vec).head);
local_irq_enable();
while (list) { //遍历所有的tasklet
struct tasklet_struct *t = list;
list = list->next;
if (tasklet_trylock(t)) {
if (!atomic_read(&t->count)) {
if (!test_and_clear_bit(TASKLET_STATE_SCHED, &t->state))
BUG();
t->func(t->data); //依次调用tasklet中我们自己定义的handler函数,并且data是传入参数
tasklet_unlock(t);
continue;
}
tasklet_unlock(t);
}
local_irq_disable();
t->next = NULL;
*__this_cpu_read(tasklet_vec.tail) = t;
__this_cpu_write(tasklet_vec.tail, &(t->next));
__raise_softirq_irqoff(TASKLET_SOFTIRQ);
local_irq_enable();
}
}
至此就完成了基于tasklet的下半部处理方式。流程概括为:编写好tasklet的处理函数,然后声明tasklet,再在中断函数中调度,就可以坐等中断下半部执行了。
阅读(1691) | 评论(0) | 转发(0) |