原文地址: http://blog.csdn.net/droidphone/article/details/7518428
软件中断(softIRQ)是内核提供的一种延迟执行机制,它完全由软件触发,虽然说是延迟机制,实际上,在大多数情况下,它与普通进程相比,能得到更快的响应时间。软中断也是其他一些内核机制的基础,比如tasklet,高分辨率timer等。
声明:本博内容均由http://blog.csdn.net/droidphone原创,转载请注明出处,谢谢!
1. 软件中断的数据结构
1.1 struct softirq_action
- struct
softirq_action - {
-
void (*action)(struct softirq_action *); - };
interrupt.h中
- 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, -
-
NR_SOFTIRQS - };
- typedef
struct { -
unsigned int __softirq_pending; - }
____cacheline_aligned irq_cpustat_t; //hardirq.h
- irq_cpustat_t
irq_stat[NR_CPUS] ____cacheline_aligned;
1.3 软中断的守护进程ksoftirqd
- DEFINE_PER_CPU(struct
task_struct *, ksoftirqd);
irq_exit 是退出中断上下文
2. 触发软中断
- void
raise_softirq(unsigned int nr) - {
-
unsigned long flags; -
-
local_irq_save(flags); -
raise_softirq_irqoff(nr); -
local_irq_restore(flags); - }
- inline
void raise_softirq_irqoff(unsigned int nr) - {
-
__raise_softirq_irqoff(nr); -
-
...... -
if (!in_interrupt()) -
wakeup_softirqd(); - }
3. 软中断的执行
3.1 在irq_exit中执行
- void
irq_exit(void) - {
-
...... -
sub_preempt_count(IRQ_EXIT_OFFSET); -
if (!in_interrupt() && local_softirq_pending()) -
invoke_softirq(); -
...... - }
- asmlinkage
void __do_softirq(void) - {
-
...... -
pending = local_softirq_pending(); -
-
__local_bh_disable((unsigned long)__builtin_return_address(0), -
SOFTIRQ_OFFSET); - restart:
-
-
set_softirq_pending(0); -
-
local_irq_enable(); -
-
h = softirq_vec; -
-
do { -
if (pending & 1) { -
...... -
trace_softirq_entry(vec_nr); -
h->action(h); -
trace_softirq_exit(vec_nr); -
...... -
} -
h++; -
pending >>= 1; -
} while (pending); -
-
local_irq_disable(); -
-
pending = local_softirq_pending(); -
if (pending && --max_restart) -
goto restart; -
-
if (pending) -
wakeup_softirqd(); -
-
lockdep_softirq_exit(); -
-
__local_bh_enable(SOFTIRQ_OFFSET); - }
- 首先取出pending的状态;
- 禁止软中断,主要是为了防止和软中断守护进程发生竞争;
- 清除所有的软中断待决标志;
- 打开本地cpu中断;
- 循环执行待决软中断的回调函数;
- 如果循环完毕,发现新的软中断被触发,则重新启动循环,直到以下条件满足,才退出:
- 没有新的软中断等待执行;
- 循环已经达到最大的循环次数MAX_SOFTIRQ_RESTART,目前的设定值时10次;
- 如果经过MAX_SOFTIRQ_RESTART次循环后还未处理完,则激活守护进程,处理剩下的软中断;
- 退出前恢复软中断;
3.2 在ksoftirqd进程中执行
- 在irq_exit中执行软中断,但是在经过MAX_SOFTIRQ_RESTART次循环后,软中断还未处理完,这种情况虽然极少发生,但毕竟有可能;
- 内核的其它代码主动调用raise_softirq,而这时正好不是在中断上下文中,守护进程将被唤醒;
4. tasklet
4.1 tasklet_struct
在软中断的初始化函数softirq_init的最后,内核注册了TASKLET_SOFTIRQ和HI_SOFTIRQ这两个软中断:
- void
__init softirq_init(void) - {
-
...... -
open_softirq(TASKLET_SOFTIRQ, tasklet_action); -
open_softirq(HI_SOFTIRQ, tasklet_hi_action); - }
- struct
tasklet_struct - {
-
struct tasklet_struct *next; -
unsigned long state; -
atomic_t count; -
void (*func)(unsigned long); -
unsigned long data; - };
[cpp] view plaincopy
- enum
- {
-
TASKLET_STATE_SCHED, -
TASKLET_STATE_RUN - };
open_softirq 调用
的地方:
软中断通过open_softirq初始化,分别在
net_dev_init (dev.c) softirq_init(softirq.c) init_timers(timer.c) blk_softirq_init(blk-softirq.c)等地方调用
4.2 初始化一个tasklet
有两种办法初始化一个tasklet,第一种是静态初始化,使用以下两个宏,这两个宏定义一个tasklet_struct结构,并用相应的参数对结构中的字段进行初始化:
- DECLARE_TASKLET(name, func,data);定义名字为name的tasklet,默认为enable状态,也就是count字段等于0。
- DECLARE_TASKLET_DISABLED(name, func,data);定义名字为name的tasklet,默认为enable状态,也就是count字段等于1。
使能和禁止tasklet,使用以下函数:
- tasklet_disable()
通过给count字段加1来禁止一个tasklet,如果tasklet正在运行中,则等待运行完毕才返回(通过TASKLET_STATE_RUN标志)。
- tasklet_disable_nosync()
tasklet_disable的异步版本,它不会等待tasklet运行完毕。
- tasklet_enable()
使能tasklet,只是简单地给count字段减1。
- tasklet_schedule(struct tasklet_struct *t)
如果TASKLET_STATE_SCHED标志为0,则置位TASKLET_STATE_SCHED,然后把tasklet挂到该cpu等待执行的tasklet链表上,接着发出TASKLET_SOFTIRQ软件中断请求。
- tasklet_hi_schedule(struct tasklet_struct *t)
效果同上,区别是它发出的是HI_SOFTIRQ软件中断请求。
- tasklet_kill(struct tasklet_struct *t)
如果tasklet处于TASKLET_STATE_SCHED状态,或者tasklet正在执行,则会等待tasklet执行完毕,然后清除TASKLET_STATE_SCHED状态。
4.4 tasklet的内部执行机制
内核为每个cpu用定义了一个tasklet_head结
构
,用于
管理每个cpu上的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);
- 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) { -
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_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(); -
} - }
- 关闭本地中断的前提下,移出当前cpu的待处理tasklet链表到一个临时链表后,清除当前cpu的tasklet链表,之所以这样处理,是为了处理当前tasklet链表的时候,允许新的tasklet被调度进待处理链表中。
- 遍历临时链表,用tasklet_trylock判断当前tasklet是否已经在其他cpu上运行,而且tasklet没有被禁止:
- 如果没有运行,也没有禁止,则清除TASKLET_STATE_SCHED状态位,执行tasklet的回调函数。
- 如果已经在运行,或者被禁止,则把该tasklet重新添加会当前cpu的待处理tasklet链表上,然后触发TASKLET_SOFTIRQ软中断,等待下一次软中断时再次执行。
通过以上的分析,我们需要注意的是,tasklet有以下几个特征:
- 同一个tasklet只能同时在一个cpu上执行,但不同的tasklet可以同时在不同的cpu上执行;
- 一旦tasklet_schedule被调用,内核会保证tasklet一定会在某个cpu上执行一次;
- 如果tasklet_schedule被调用时,tasklet不是出于正在执行状态,则它只会执行一次;
- 如果tasklet_schedule被调用时,tasklet已经正在执行,则它会在稍后被调度再次被执行;
- 两个tasklet之间如果有资源冲突,应该要用自旋锁进行同步保护;