2009年(49)
分类: LINUX
2009-05-25 12:17:01
十五 softirqs and tasklets
linux2.6里面用两种功能来给非紧急中断使用,软中断和tasklet。都叫defferable functions
softirqs和tasklets是严格相关的,因为tasklet是建立在softirq之上的。
软中断和tasklet的不同
1软中断必须在编译的时候就被分配好,但是tasklet可以在运行的时候被初始化和分配。(比如在添加一个kernel module的时候)
2 softirq可以在不同的CPU上同时运行一个type的。但是tasklet是被串行化了的,不能同时在不同的CPU上运行同一个类型的。(tasklet不用解决可重入的问题,所以对device developer比较好用)
在defferable functions里面可以有的4中操作:
1 初始化:在kernel初始化的时候,或者module被加载的时候。
2 活动性(activation)
3 masking 掩码,用来屏蔽不需要被执行的defferable function
4 执行
十六 softirqs
表示softirqs的主要数据结构是softirq_vec[]数组。里面有32个softirq_action数据结构。
Softirq_action里面有个指向softirq function的指针,还有一个指向softirq function可能被用到的数据结构。
Open_softirq()函数用来初始化软中断。它有三个传递参数,index(优先权),指向softirq function的指针,和softirq function要用到的数据结构。
Raise_softirq()用来激活softirq。
do_softirq()函数。
这个函数做一些实现检查,然后调用__do_softirq()来轮询需要被操作的softirqs。
十七 tasklets
在I/O设备中,比较常用tasklet。Tasklet是建立在两个softirq的名字为HI_SOFTIRQ和TASKLET_SOFTIRQ之上的。
Tasklet是被存在taskllet_vec[]和tasklet_hi_vec[]上面的,每个tasklet有一个数据结构,tasklet_struct。
{
Next; state; count; func; data;
}
具体意义参加p178
一个使用tasklet写设备驱动的具体例子:1.分配task_struct,并用tasklet_init()初始化它。2.这个函数的参数有:tasklet descriptor; tasklet function的地址; 和一些可选择项
要激活一个tasklet,必须调用tasklet_schedule()或者tasklet_hi_schedule()。
Tasklet是怎样被执行的:通过do_softirq()来执行softirq function,这就可以调用tasklet
具体的过程参加p179
十八 工作队列 work queue
2.6中引入work queue来代替2.4里面的task queue。
Work queue和defferable function的不同:
就是后者运行在interrupt context下面,而前者运行在process context下面。这样后者就可以被堵塞(blocked),process switch不可能在interrupt context下面执行。
Work queue里面的函数是在内核线程(kernel thread)下面被执行的,所以没有USER MODE address space可以进入,这个是不是说明内核线程没有用户空间?
关于工作队列的具体内容参看p181
十九 在中断和异常中返回
两个返回在大体上差不多。
ret_from_exception() and ret_from_intr()
1有内核控制路径的嵌套么?(没有的话返回)2需要有其他工作被做么?(没有的话返回) 有的话将工作挂起。3需要sche么?4work_notifysig,发送信号。
具体的部分参看p185以后的部分。
这个部分的内容太多了。。。tasklet和irq的具体应用还是不怎么清楚。
某位仁兄的博客里面的技术文章引用:
http://man.lupaworld.com/content/develop/joyfire/kernel/2.html#I368
http://www.cnblogs.com/huqingyu/archive/
提出softirq的机制的目的和老版本的底半部分的目的是一致的,都是将某个中断处理的一部分任务延迟到后面去执行。
Linux内核中一共可以有32个softirq,每个softirq实际上就是指向一个函数。当内核执行softirq(do_softirq),就对这32个softirq进行轮询:
(1)是否该softirq被定义了,并且允许被执行?
(2)是否激活了(也就是以前有中断要求它执行)?
如果得到肯定的答复,那么就执行这个softirq指向的函数。
值得一提的是,无论有多少个CPU,内核一共只有32个公共的softirq,但是每个CPU可以执行不同的softirq,可以禁止/起用不同的softirq,可以激活不同的softirq,因此,可以说,所有CPU有相同的例程,但是
每个CPU却有自己完全独立的实例。
对(1)的判断是通过考察irq_stat[ cpu ].mask相应的位得到的。这里面的cpu指的是当前指令所在的cpu.在一开始,softirq被定义时,所有的cpu的掩码mask都是一样的。但是在实际运行中,每个cpu上运行的程序可以根据自己的需要调整。
对(2)的判断是通过考察irq_stat[ cpu ].active相应的位得到的.
虽然原则上可以任意定义每个softirq的函数,Linux内核为了进一步加强延迟中断功能,提出了tasklet的机制。tasklet实际上也就是一个函数。在第0个softirq的处理函数tasklet_hi_action中,我们可以看到,当执行这个函数的时候,会依次执行一个链表上所有的tasklet.
我们大致上可以把softirq的机制概括成:
内核依次对32个softirq轮询,如果遇到一个可以执行并且需要的softirq,就执行对应的函数,这些函数有可能又会执行一个函数队列。当执行完这个函数队列后,才会继续询问下一个softirq对应的函数。
挂上一个软中断
void open_softirq(int nr, void (*action)(struct softirq_action*), void *data)
{
unsigned long flags;
int i;
spin_lock_irqsave(&softirq_mask_lock, flags);
softirq_vec[nr].data = data;
softirq_vec[nr].action = action;
for (i=0; i
}
其中对每个CPU的softirq_mask都标注一下,表明这个softirq被定义了。
tasklet
在这个32个softirq中,有的softirq的函数会依次执行一个队列中的tasklet。
tasklet其实就是一个函数。它的结构如下:
struct tasklet_struct
{
struct tasklet_struct *next;
unsigned long state;
atomic_t count;
void (*func)(unsigned long);
unsigned long data;
};
next 用于将tasklet串成一个队列
state 表示一些状态,后面详细讨论
count 用来禁用(count = 1 )或者启用( count = 0 )这个tasklet.因为一旦一个tasklet被挂到队列里,如果没有这个机制,它就一定会被执行。 这个count算是一个事后补救措施,万一挂上了不想执行,就可以把它置1。
func 即为所要执行的函数。
data 由于可能多个tasklet调用公用函数,因此用data可以区分不同tasklet.
如何将一个tasklet挂上
首先要初始化一个tasklet,填上相应的参数
void tasklet_init(struct tasklet_struct *t,
void (*func)(unsigned long), unsigned long data)
{
t->func = func;
t->data = data;
t->state = 0;
atomic_set(&t->count, 0);
}
然后调用schedule函数,注意,下面的函数仅仅是将这个tasklet挂到 TASKLET_SOFTIRQ对应的软中断所执行的tasklet队列上去, 事实上,还有其它的软中断,比如HI_SOFTIRQ,会执行其它的tasklet队列,如果要挂上,那么就要调用tasklet_hi_schedule(). 如果你自己写的softirq执行一个tasklet队列,那么你需要自己写类似下面的函数。
static inline void tasklet_schedule(struct tasklet_struct *t)
{
if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state))
{
int cpu = smp_processor_id();
unsigned long flags;
local_irq_save(flags);
/**/ t->next = tasklet_vec[cpu].list;
/**/ tasklet_vec[cpu].list = t;
__cpu_raise_softirq(cpu, TASKLET_SOFTIRQ);
local_irq_restore(flags);
}
}
这个函数中/**/标注的句子用来挂接上tasklet,
__cpu_raise_softirq用来激活TASKLET_SOFTIRQ,这样,下次执行do_softirq就会执行这个TASKLET_SOFTIRQ软中断了
__cpu_raise_softirq定义如下:
static inline void __cpu_raise_softirq(int cpu, int nr)
{
softirq_active(cpu) |= (1<
tasklet的运行方式
我们以tasklet_action为例,来说明tasklet运行机制。事实上,还有一个函数tasklet_hi_action同样也运行tasklet队列。
首先值得注意的是,我们前面提到过,所有的cpu共用32个softirq,但是同一个softirq在不同的cpu上执行的数据是独立的,基于这个原则,tasklet_vec对每个cpu都有一个,每个cpu都运行自己的tasklet队列。
当执行一个tasklet队列时,内核将这个队列摘下来,以list为队列头,然后从list的下一个开始依次执行。这样做达到什么效果呢?在执行这个队列时,这个队列的结构是静止的,如果在运行期间,有中断产生,并且往这个队列里添加tasklet的话,将填加到tasklet_vec[cpu].list中, 注意这个时候,这个队列里的任何tasklet都不会被执行,被执行的是list接管的队列。
软中断调用时机
最直接的调用:
当硬中断执行完后,迅速调用do_softirq来执行软中断(见下面的代码),这样,被硬中断标注的软中断能得以迅速执行。当然,不是每次调用都成功的,见前面关于重入的帖子。
-----------------------------------------------------
asmlinkage unsigned int do_IRQ(struct pt_regs regs)
{
... ...
if (softirq_active(cpu) & softirq_mask(cpu))
do_softirq();
}
-----------------------------------------------------
还有,不是每个被标注的软中断都能在这次陷入内核的部分中完成,可能会延迟到下次中断。
其它地方的调用:
在entry.S中有一个调用点:
handle_softirq:
call SYMBOL_NAME(do_softirq)
jmp ret_from_intr
有两处调用它,一处是当系统调用处理完后:
ENTRY(ret_from_sys_call)
#ifdef CONFIG_SMP
movl processor(%ebx),%eax
shll $CONFIG_X86_L1_CACHE_SHIFT,%eax
movl SYMBOL_NAME(irq_stat)(,%eax),%ecx # softirq_active
testl SYMBOL_NAME(irq_stat)+4(,%eax),%ecx # softirq_mask
#else
movl SYMBOL_NAME(irq_stat),%ecx # softirq_active
testl SYMBOL_NAME(irq_stat)+4,%ecx # softirq_mask
#endif
jne handle_softirq
一处是当异常处理完后:
注意其中的irq_stat, irq_stat +4 对应的就是字段 active和mask
既然我们每次调用完硬中断后都马上调用软中断,为什么还要在这里调用呢?
原因可能都多方面的:
(1)在系统调用或者异常处理中同样可以标注软中断,这样它们在返回前就能得以迅速执行
(2)前面提到,有些软中断要延迟到下次陷入内核才能执行,系统调用和异常都陷入内核,所以可以尽早的把软中断处理掉
(3)如果在异常或者系统调用中发生中断,那么前面提到,可能还会有一些软中断没有处理,在这两个地方做一个补救工作,尽量避免到下次陷入内核才处理这些软中断。
另外,在切换前也调用。