/*
本版块文章以后只接受原创。
hjlin.cublog.cn
参考资料:ulk3,http://blog.chinaunix.net/u1/51562/index.html
*/
本文目标:介绍软中断和tasklet的工作流程。
什么是软中断
软中断就是可延迟函数,包括了软中断和tasklet(通过软中断实现的)。irq中断处理程序在关中断的情况下执行一些紧迫可以迅速完成的任务,也可能有部分任务是在开中断的情况下执行的,但是总体上这些任务都是紧迫的,需要立即执行的。软中断就是将irq中断处理程序的哪些不紧急的任务在某些特定的时间执行的一种机制。
软中断和irq中断的关系:
同类型的软中断可以在不通cpu上同时执行。但是同类型的irq中断不可以在不同cpu上同时执行。
软中断是开中断的。irq中断基本是关中断的。
软中断和irq中断过程共同表示中断上下文。因此都不允许阻塞之类的操作发生。
软中断的数据结构:
struct softirq_action
{
void (*action)(struct softirq_action *);
void *data;
} softirq_vec[32];
linux用到了数组的前六项,元素下标越小优先级越高。第一项和第六项被用作实现tasklet机制。
软中断的执行时机:
1)从一个硬件中断代码处返回时。irq_exit()
2)在ksoftirqd内核线程中
3)在那些显式检查和执行待处理的软中断的代码中,如网络子系统
软中断处理函数:
asmlinkage void do_softirq(void)
{
__u32 pending;
unsigned long flags;
//当前正在硬中断或者软中断过程中。两种情况软中断直接返回
//第一个硬中断,被第二个硬中断中断。第二个硬中断返回的时候执行。
//第一个硬中断的软中断正在执行,被第二个硬中断中断。第二个硬中断返回的时候执行。
if (in_interrupt())
return;
local_irq_save(flags);
pending = local_softirq_pending();
if (pending)
__do_softirq();
local_irq_restore(flags);
}
asmlinkage void __do_softirq(void)
{
struct softirq_action *h;
__u32 pending;
//最多执行10个软中断过程,剩下的软中断给ksoftirqd。避免该进程长期处于run状态,但是进程本身的任务不能得到立即执行。从而影响整体系统反应。
int max_restart = MAX_SOFTIRQ_RESTART;
int cpu;
pending = local_softirq_pending();
account_system_vtime(current);
//软中断计数加1, preemt_count。(说明正在执行软中断,保证该软中断不会被其他路径的软中断嵌套)
__local_bh_disable((unsigned long)__builtin_return_address(0));
trace_softirq_enter();
cpu = smp_processor_id();
restart:
/* Reset the pending bitmask before enabling irqs */
//把挂上去的软中断清除掉,因为我们在这里会全部处理完
set_softirq_pending(0);
local_irq_enable(); //开中断
//下面这段代码是软中断处理的核心部分:
h = softirq_vec;
do {
if (pending & 1) {
h->action(h);
rcu_bh_qsctr_inc(cpu);
}
h++;
pending >>= 1;
} while (pending);
local_irq_disable(); //关中断
pending = local_softirq_pending();
if (pending && --max_restart)
goto restart;
//激活ksoftirqd
if (pending)
wakeup_softirqd();
trace_softirq_exit();
account_system_vtime(current);
//软中断计数减1, preemt_count
_local_bh_enable();
}
local_bh_enable()与__local_bh_enable()作用是不相同的:前者不仅会清除SOFTIRQ_OFFSET,还会调用do_softirq(),进行软中断的处理。
什么是tasklet:
tasklet是通过软中断实现的一种特殊软中断。tasklet是io驱动实现可延迟函数的首选方法。
tasklet和软中断的联系:
tasklet可以在运行时分配和初始化(模块编程)。软中断则要在内核编译时确定。
同一个tasklet在多cpu上时串行执行的。同一时刻多个cpu上不可能运行同一个tasklet。因此tasklet函数不要求可重入,简化了编程。而软中断同一时刻多个cpu可能同时运行同一个软中断。因此需要额外的保护。
tasklet数据结构:
struct tasklet_head
{
struct tasklet_struct *head;
struct tasklet_struct **tail;
};
struct tasklet_struct
{
struct tasklet_struct *next; //下个描述符指针
unsigned long state; //状态
atomic_t count; //锁计数器
void (*func)(unsigned long); //tasklet函数指针
unsigned long data; //可以由tasklet函数来使用
};
struct tasklet_head tasklet_vec[NR_CPUS];
struct tasklet_head tasklet_hi_vec[NR_CPUS];
tasklet处理函数:
//tasklet实际上内核编译时注册的两个软中断。
void __init softirq_init(void)
{
//普通优先级
open_softirq(TASKLET_SOFTIRQ, tasklet_action, NULL);
//高优先级
open_softirq(HI_SOFTIRQ, tasklet_hi_action, NULL);
}
//具体的处理函数
static void tasklet_action(struct softirq_action *a)
{
struct tasklet_struct *list;
//禁止本地中断
local_irq_disable();
//per_cpu变量
list = __get_cpu_var(tasklet_vec).list;
//链表置空
__get_cpu_var(tasklet_vec).list = NULL;
//恢复本地中断
local_irq_enable();
//接下来要遍历链表了
while (list) {
struct tasklet_struct *t = list;
list = list->next;
//多个cpu执行同一tasklet只能一个cpu获得执行,另外一个continue
if (tasklet_trylock(t)) {
//t->count为零才会调用task_struct里的函数
if (!atomic_read(&t->count)) {
//t->count 为1。但又没有置调度标志。系统BUG
if (!test_and_clear_bit(TASKLET_STATE_SCHED, &t->state))
BUG();
//调用tasklet函数
t->func(t->data);
tasklet_unlock(t);
continue;
}
tasklet_unlock(t);
}
//注意 :所有运行过的tasklet全被continue过去了,只有没有运行的tasklet才会重新加入到链表里面
//禁本地中断
local_irq_disable();
//把t放入队列头,准备下一次接收调度
t->next = __get_cpu_var(tasklet_vec).list;
__get_cpu_var(tasklet_vec).list = t;
//置软中断调用标志。下次运行到do_softirq的时候,可以继续被调用
__raise_softirq_irqoff(TASKLET_SOFTIRQ);
//启用本地中断
local_irq_enable();
}
}
高优先级tasklet的处理其实与上面分析的函数是一样的,只是per_cpu变量不同而已。