软中断(SoftIRQ)是内核提供的一种基于中断的延时机制, Linux内核定义的软中断有以下几种:
enum
{
HI_SOFTIRQ=0, /*高优先级的tasklet*/
TIMER_SOFTIRQ,
NET_TX_SOFTIRQ,
NET_RX_SOFTIRQ,
BLOCK_SOFTIRQ,
TASKLET_SOFTIRQ, /*普通tasklet*/
SCHED_SOFTIRQ,
#ifdef CONFIG_HIGH_RES_TIMERS
HRTIMER_SOFTIRQ,
#endif
};
此外内核维持一个全局的IRQ数组,用来记录不同的软中断的详细信息:
static struct softirq_action softirq_vec[32] __cacheline_aligned_in_smp;
该全局变量的类型为struct softirq_action, 它所有IRQ中断处理操作的函数原型. 也就是说系统维持的全局变量记录的是每一种IRQ的中断处理函数信息, 但它的结构非常简单:
struct softirq_action
{
void (*action)(struct softirq_action *); /*中断处理函数指针*/
void *data; /*传递的参数*/
};
由于内核开发者们不建议我们添加自定义软中断, 为此专门提供了tasklet的机制,我们可以通过tasklet来实现我们自己的中断处理任务。tasklet是内核提供开发者的、基于软中断的任务延时机制。tasklet只是内核定义的几种softirq中的一种,设备驱动程序往往通过tasklet来实现某些延时操作。在软中断中tasklet分为两类:一类为高优先级的HI_SOFTIRQ; 另一类为TASKLET_SOFTIRQ。下面详细介绍tasklet的相关信息:
struct tasklet_struct
{
struct tasklet_struct *next;
unsigned long state;
atomic_t count;
void (*func)(unsigned long);
unsigned long data;
};
序号
变量名
作用
1
next
用来连接系统中的tasklet对象,构成链表
2
state
记录tasklet的状态: TASKLET_STATE_SCHED表示tasklet已经被提交。TASKLET_STATE_RUN表示tasklet正在执行(只用于SMP)
3
count
0:enable, 可以被调度执行。 1: disable, 不可执行 ;
4
func
tasklet执行体,由开发者实现
5
data
给func传递的参数
Linux系统在初始化的过程中,通过调用softirq_init函数来为HI_SOFTIRQ和TASKLET_SOFTIRQ安装上函数执行体,分别为tasklet_hi_action和tasklet_action:
void __init softirq_init(void)
{
open_softirq(TASKLET_SOFTIRQ, tasklet_action, NULL);
open_softirq(HI_SOFTIRQ, tasklet_hi_action, NULL);
}
所谓安装执行函数,就是在IRQ全局变量上,将这两个中断对应的成员参数(一个回调函数指针、一个传递的参数)进行初始化,而这部分是通过open_softirq()来实现的。open_softirq()函数实现十分简单:
static struct softirq_action softirq_vec[32] __cacheline_aligned_in_smp;
void open_softirq(int nr, void (*action)(struct softirq_action*), void *data)
{
softirq_vec[nr].data = data;
softirq_vec[nr].action = action;
}
通过上述两个函数的执行,便完成了tasklet机制的初始化任务,即:
softirq_vec[TASKLET_SOFTIRQ].action = tasklet_hi_action;
softirq_vec[HI_SOFTIRQ].action = tasklet_action;
tasklet对象的初始化方法有两种,一种为静态方式,一种为动态方式:
tasklet对象的声明由两个函数,声明对象时需要将tasklet的对象名name, 延时操作函数func, 以及需要传递的参数进行实现。
#define DECLARE_TASKLET(name, func, data) \ /*可以被调度执行*/
struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(0), func, data }
#define DECLARE_TASKLET_DISABLED(name, func, data) \ /*不可以被调度执行*/
struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(1), func, data }
使用DECLARE_TASKLET声明的对象处于enable状态,使用DECLARE_TASKLET_DISABLED声明的对象处于disable状态。
动态方式初始化则是直接定义一个tasklet_struct 变量,然后使用tasklet_init()函数完成初始化操作。
void tasklet_init(struct tasklet_struct *t,
void (*func)(unsigned long), unsigned long data)
{
t->next = NULL;
t->state = 0;
atomic_set(&t->count, 0);
t->func = func;
t->data = data;
}
所谓tasklet对象提交,实际上就是将声明的tasket加入到tasklet_vec管理的链表中, tasklet_vec是一个per-CPU类型的变量,用来将系统中所有通过tasklet_schedule函数提交的tasklet对象构成链表。(linux-2.6.22)
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);
}
static inline void tasklet_schedule(struct tasklet_struct *t)
{
if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state))/*调度位为0,再提交;如果为1,则证明已经提交,无需再提交*/
__tasklet_schedule(t);
}
解析如下:
tasklet任务被tasklet_schedule提交到系统相应的链表上后,系统会在遍历该链表然后依次执行tasklet任务。由于HI_SOFTIRQ和TASKLET_SOFTIRQ两个中断只是优先级的不同,其他在处理上基本一致。这里只是介绍TASKLET_SOFTIRQ的执行体tasklet_action。
static void tasklet_action(struct softirq_action *a)
{
struct tasklet_struct *list;
local_irq_disable();
list = __get_cpu_var(tasklet_vec).list; /*从tasklet_vec上取下链表,实际是链表的头指针*/
__get_cpu_var(tasklet_vec).list = NULL; /*清空tasklet_vec链表,实际只是清list指针*/
local_irq_enable();
while (list) {
struct tasklet_struct *t = list;
list = list->next;
if (tasklet_trylock(t)) {
if (!atomic_read(&t->count)) {/*count=0 使能*/
if (!test_and_clear_bit(TASKLET_STATE_SCHED, &t->state))/*清空调度位,返回原来值*/
BUG();/*如果原来为0,属异常情况,应结束退出*/
t->func(t->data);
tasklet_unlock(t);
continue;
}
tasklet_unlock(t);
}
local_irq_disable();
t->next = __get_cpu_var(tasklet_vec).list;/*重新插入到tasklet_var中*/
__get_cpu_var(tasklet_vec).list = t;
__raise_softirq_irqoff(TASKLET_SOFTIRQ);
local_irq_enable();
}
}
在关闭本地中断的前提下,将当前cpu上待处理的tasklet_vec 链表头指针移到临时变量list记录下来,然后将tasklet链表清空,之后重新使能本地中断。重新本地中断的目的是为了可以使系统重新调度新的tasklet任务到tasklet_vec 链表中;
遍历该链表
使用tasklet_trylock来判断当前的tasklet任务是否在其他cpu上进行处理;
如果tasklet_trylock的返回值为0,表示该任务可能在其他cpu上进行处理,那么需要重新将该tasklet任务重新提交到tasklet_vec链表上,然后触发软中断等待下次软中断重新执行;
从上述代码中可以知道,一个tasklet任务被执行完之前,它的的state上的TASKLET_STATE_SCHED被清空,这就是说除非这个tasklet被重新提交,否则下次软中断是不会再次执行该tasklet任务的。这是一种one-shot特性:提交一次,调度一次,运行完后,从CPU的tasklet_var链表上删除,除非该tasklet任务再次被提交。
在执行tasklet时,中断是被重新使能的,这是软中断设计时的初衷。如果执行期间,有其他软中段到来,且新的tasklet也是在该CPU上,那么新的tasklet会被调度到tasklet_vec , 此时并不会影响正在执行的list链表,已经执行的tasklet任务的TASKLET_STATE_SCHED位被清空,而新的tasklet任务的TASKLET_STATE_SCHED被使能,新的tasklet会在下一次IRQ调度中被执行
tasklet_disable 和tasklet_disable_nosync 可以使tasklet任务无法被调度后运行,实际上最关键的操作为atomic_inc(&t->count); 因为count=0时(enable),tasklet才可以被调用运行。
static inline void tasklet_disable_nosync(struct tasklet_struct *t)
{
atomic_inc(&t->count);
smp_mb__after_atomic_inc();
}
static inline void tasklet_disable(struct tasklet_struct *t)
{
tasklet_disable_nosync(t);
tasklet_unlock_wait(t);
smp_mb();
}
tasklet_enable 是通过将tasklet对象中的count-1来实现tasklet可以被调度运行的。
注:一个处于disable状态的tasklet可以被提交到tasklet_vec中,但不会被调度执行
static inline void tasklet_enable(struct tasklet_struct *t)
{
smp_mb__before_atomic_dec();
atomic_dec(&t->count);
}
该函数用来清除一个tasklet对象上的TASKLET_STATE_SCHED的状态位,使IRQ不再能够调度运行此任务。如果当前的tasklet任务正在被运行,那么tasklet_kill将忙等直到该任务执行完毕;如果一个对象已经被提交到系统但尚未调度运行,那么tasklet_kill将会睡眠直到tasklet从tasklet_vec链表中删除。因此该函数是一个可能被阻塞的函数。
由此可以看出,一个tasklet被调度后,一定会被执行一次。
void tasklet_kill(struct tasklet_struct *t)
{
if (in_interrupt())
printk("Attempt to kill tasklet from interrupt\n");
while (test_and_set_bit(TASKLET_STATE_SCHED, &t->state)) {
do
yield();
while (test_bit(TASKLET_STATE_SCHED, &t->state));
}
tasklet_unlock_wait(t);
clear_bit(TASKLET_STATE_SCHED, &t->state);
}
手机扫一扫
移动阅读更方便
你可能感兴趣的文章