前面谈到了硬中断,都是通过int指令发出,基本上都会关中断,更不可能存进程调度,如果ISR运行时间过长,则会造成明显的卡顿,所以规定中断中不能存在sleep、阻塞,也会尽量减少资源占用,但这还不够,linux将中断中的一部分逻辑推后执行,这就是软中断,软中断分多钟类型,执行时允许被中断,甚至有些软中断(work queue)可以允许被用户进程抢占。
软中断类型
软中断的实现难机制很多,比如bottom half(下半部)、task queue,不过这两者已经被废弃,下面三种实现机制是现在kernel还支持的:
软中断(softirq):内核2.3引入,是最基本、最优先的软中断处理形式,为了避免名字冲突,本文中将这种子类型的软中断叫softirq。
tasklet:其底层使用softirq机制实现,提供了一种用户方便使用的软中方式,为软中断提供了很好的扩展性。
work queue:前两种软中断执行时是禁止抢占的(softirq的ksoftirq除外),对于用户进程不友好。如果在softirq执行时间过长,会继续推后到work queue中执行,work queue执行处于进程上下文,其可被抢占,也可以被调度,如果软中断需要执行睡眠、阻塞,直接选择work queue。
软中断的数据结构
/* 用于描述一个软中断 */
struct softirq_action
{
/* 软中断的处理函数 */
void (*action)(struct softirq_action *);
};
/* 6个软中断描述符都保存在此数组 */
static struct softirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp;
kernel2.6支持以下6个软中断,其优先级和在softirq_vec的下标相等。其中HI_SOFTIRQ和TASKLET_SOFTIRQ就是用于实现tasklet机制,其他的基本都是外设专用的软中断。
__softirq_pending:每个cpu的irq_cpustat_t中有__softirq_pending,是32位的位图,如果第n位为1,则代表对应类型的软中断待处理,通过local_softirq_pending()宏获取,下面软中断执行流程中会用到。
preemt_count:该字段在task_struct -> thread_info -> preemt_count,其每位表示的意义如下。这个字段在在宏in_interrupt宏中用到,用于避免软中断嵌套的硬中断(软中断时允许硬中断)中再调用软中断。如果preemt_couont软中断和硬中断计数器有一个不为0,in_interrupt则返回true,表示程序处于中断中。
软中断的触发时机
1、irq_exit:在硬中断退出时,会检查local_softirq_pending和preemt_count,如果都符合条件,则执行软中断。
if (!in_interrupt() && local_softirq_pending())
invoke_softirq();
2、local_bh_enable:使用此函数开启软中断时,会检查local_softirq_pending,如果都符合条件,则执行软中断。调用链为local_bh_enable()->__local_bh_enable()->do_softirq()。
3、raise_softirq:主动唤起一个软中断,会首先设置__softirq_pending对应的软中断位为挂起,然后检查in_interrupt,如果不在中断中,则唤起ksoftirq线程执行软中断(ksoftirq是softirq的一种执行机制,在软中的运行流程中会提到)。
inline void raise_softirq_irqoff(unsigned int nr)
{
__raise_softirq_irqoff(nr);
if (!in_interrupt())
wakeup_softirqd(); // 如果不处于中断上下文中,则尽快执行软中断处理。
}
软中断的运行流程
open_softirq:注册软中断,nr是softirq_vec的下标,action是软中断处理函数入口。
/* 开启软中断 */
void open_softirq(int nr, void (*action)(struct softirq_action *))
{
softirq_vec[nr].action = action;
}
open_softirq(TASKLET_SOFTIRQ, tasklet_action); // 开启tasklet软中断
open_softirq(TIMER_SOFTIRQ, run_timer_softirq); // 开启时钟相关软中断
__do_softirq:执行软中断,其调用链分别为irq_exit -> invoke_softirq -> __do_softirq和local_bh_enable()->__local_bh_enable()-> do_softirq -> __do_softirq,执行流程如下:
ksoftirq:软中断处理进程,上图没有画完整的是在时间到期或者执行完10次后还有未执行的软中断时,会唤醒ksoftirq后台线程进行执行(ps:这不是work queue机制),其目的是为了处理剩余的软中断,避免cpu长时间不能被抢占的情况。ksoftirq就是一个普通进程,优先级是120,所以其是可以被抢占的。其处理函数是run_ksoftirqd()。所以单纯说softirq是否可以被抢占都不对,softirq在irq_exit中是不能被抢占的,但在ksoftirq中是可以被抢占的。
由上图可见HI_SOFTIRQ和TASKLET_SOFTIRQ是一个链条,这就是tasklet,其依赖于softirq机制实现,其保证了软中断机制的开放性和扩展性(由于其他软中断基本是专用类型的),使得用户可以自行丰富其所需要的软中断。
tasklet
softirq_init:软中断初始化,会将每个CPU的tasklet_vec和tasklet_hi_vec两个链表,分别代表HI_SOFTIRQ和TASKLET_SOFTIRQ。
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);
tasklet_struct:tasklet链表中的元素,其中func就是执行该tasklet的方法。
struct tasklet_struct
{
struct tasklet_struct *next; /* 指向链表下一个tasklet */
unsigned long state; /* tasklet状态 TASKLET_STATE_SCHED表示处于链表中,TASKLET*/
/* _STATE_RUN表示正在运行*/
atomic_t count; /* 禁止计数器,调用tasklet_disable()会增加此数,tasklet*/
/* enable()减少此数 */
void (*func)(unsigned long); /* 处理函数 */
unsigned long data; /* 处理函数使用的数据 */
};
tasklet_action:就是tasklet的处理函数,其会遍历tasklet_vec和tasklet_hi_vec链表,然后执行。
工作队列
感觉实现不太重要,请参加《深入Linux内核》182页。