Linux中断下半部分处理原理,linux中断下半部机制

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

1、softirq

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

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

static structsoftirq_actionsoftirq_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上面添加。不过,从注释中我们发现内核并不建议我们添加新的软中断,毕竟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_softirq和raise_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); //直到所有被触发的软中断都执行完

.

.

.

}

2、tasklet

在实际的项目中我们一般使用tasklet实现中断的下半部。在介绍软中断索引号的时候,有两个用于实现tasklet的软中断索引号:HI_SOFTIRQ和TASKLET_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;

};

各成员的含义如下:(1)next指针:指向下一个tasklet的指针。(2)state:定义了这个tasklet的当前状态。这一个32位的无符号长整数,当前只使用了bit[1]和bit[0]两个状态位。其中,bit[1]=1表示这个tasklet当前正在某个CPU上被执行,它仅对SMP系统才有意义,其作用就是为了防止多个CPU同时执行一个tasklet的情形出现;bit[0]=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代码段。(5)data:函数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软中断。

{

if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state))

__tasklet_schedule(t);

}

函数__tasklet_schedule得到当前CPU的tasklet_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必须在关中断情况下运行。

3、workqueue

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

atomic_long_t data;

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

#define WORK_STRUCT_STATIC1/* 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_structmy_wq; //定义一个工作队列

voidxxxx_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。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值