Linux之中断子系统-软中断softirq 和小任务 tasklet 分析(6)
1.概述
硬件的中断处理函数处于中断上半部分,在CPU关中断的状态下执行,中断线程、软中断(softirq)及小任务(tasklet)属于中断的下半部分(bottom half),在CPU开中断的状态下执行。小任务基于软中断实现,实质是对软中断的进一步封装, 在实际使用中应尽量使用小任务 。软中断及小任务的执行时机通常是中断上半部分返回(中断服务函数还未完全退出)的时候,其执行的上下文环境也处于(软)中断当中,因此其调用的处理函数不允许睡眠。这里说的软中断是中断下半部分的一种处理机制,和执行指令(ARM架构的SWI、SVC指令)触发中断的软中断不是一个概念。
软中断和小任务如果在某段时间内大量出现的话,内核会把多余的软中断和小任务放入ksoftirqd内核线程中执行。中断优先级高于软中断,软中断优先级高于线程。软中断适度线程化,可以缓解高负载情况下系统的响应。
2.软中断数据结构
Linux内核的软中断类型有10种。按优先级划分,优先级从0-9,数字越小优先级越高,优先级越高的软中断能得到优先执行。tasklet使用优先级为6的软中断。
[include/linux/interrupt.h]enum{HI_SOFTIRQ=0, // 优先级为0,最高优先级的软中断TIMER_SOFTIRQ, // 优先级为1,Timer定时器软中断NET_TX_SOFTIRQ, // 优先级为2,发送网络数据包的软中断NET_RX_SOFTIRQ, // 优先级为3,接收网络数据包的软中断BLOCK_SOFTIRQ, // 优先级为4,用于块设备的软中断IRQ_POLL_SOFTIRQ, // 优先级为5,用于轮训中断的软中断TASKLET_SOFTIRQ, // 优先级为6,tasklet类型的软中断SCHED_SOFTIRQ, // 优先级为7,用于进程调度以及负载均衡的软中断HRTIMER_SOFTIRQ, // 优先级为8,用于高精度定时器的软中断RCU_SOFTIRQ, // 优先级为9,用于RCU的软中断NR_SOFTIRQS // 软中断数量为10};
软中断执行的回调函数被封装在softirq_action的结构体中,其内部保存了一个函数指针,注册软中断时需要设置此函数指针。内核为所有软中断定义了一个类型为softirq_action的softirq_vec数组,数组长度为NR_SOFTIRQS,正好每个软中断对应一个softirq_action,数组的索引为软中断的优先级。softirq_vec数组为所有CPU共享。内核还定义了一个类型为irq_cpustat_t的数组irq_stat,数组长度为系统CPU的个数,索引为CPU的编号,每个CPU对应一个数组元素,此数组用来标记某个CPU上的某个软中断的状态,软中断pending,则对应的bit置1,否则为0。
[include/linux/interrupt.h]struct softirq_action{void (*action)(struct softirq_action *);};[kernel/softirq.c]static struct softirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp;irq_cpustat_t irq_stat[NR_CPUS] ____cacheline_aligned; // 软中断状态标记[include/asm-generic/hardirq.h]typedef struct {unsigned int __softirq_pending;} ____cacheline_aligned irq_cpustat_t;
3.注册软中断
使用open_softirq注册软中断,nr为软中断的优先级,action为软中断执行的回调函数。
void open_softirq(int nr, void (*action)(struct softirq_action *)){softirq_vec[nr].action = action;}
4.触发及执行软中断
4.1.触发软中断
只有处于pending状态的软中断才会得到执行,可使用raise_softirq和raise_softirq_irqoff函数使某个软中断处于pending状态(即触发软中断),raise_softirq会关闭中断,raise_softirq_irqoff不会关闭中断。由于__softirq_pending变量为Per-CPU类型,因此在修改此变量时只需要关闭本地中断即可,不需要额外的同步措施。raise_softirq首先关闭本地中断,然后调用raise_softirq_irqoff设置__softirq_pending变量,如果不处于硬中断、软中断及不可屏蔽中断上下文,则会唤醒内核线程ksoftirqd执行软中断,最后打开中断。可以看出一个CPU上的软中断执行是串行的,不会发生软中断嵌套的现象
[kernel/softirq.c]void raise_softirq(unsigned int nr){unsigned long flags;local_irq_save(flags); // 保存cpsr,然后关闭中断raise_softirq_irqoff(nr);local_irq_restore(fla