=0, TIMER_SOFTIRQ, NET_TX_SOFTIRQ, NET_RX_SOFTIRQ, BLOCK_SOFTIRQ, BLOCK_IOPOLL_SOFTIRQ, TASKLET_SOFTIRQ BLOCK_SOFTIRQ和BLOCK_IOPOLL_SOFTIRQ用于块设置操作,SCHED_SOFTIRQ用于调度方面的操作,RCU_SOFTIRQ用于rcu方面的操作。 Softirq的注册 linux系统通过open_softirq注册softirq的回调函数,如下: void open_softirq(int nr, void (*action)(struct softirq_action *)) { softirq_vec[nr].action = action; } 比如注册TIMER_SOFTIRQ的回调函数如下: open_softirq(TIMER_SOFTIRQ, run_timer_softirq 处理Softirq 上述说了触发一个Softirq, 那就必须来处理此Softirq, 也就是最终调用到该Softirq的action回调函数中。
softirqs 是在 Linux 内核编译时就确定好的,例如网络收包对应的 NET_RX_SOFTIRQ 软中断。因此是一种静态机制。 如果想加一种新 softirq 类型,就需要修改并重新编译内核。 (NET_TX_SOFTIRQ, net_tx_action); open_softirq(NET_RX_SOFTIRQ, net_rx_action); static int __init net_dev_init (NET_TX_SOFTIRQ, net_tx_action); open_softirq(NET_RX_SOFTIRQ, net_rx_action); rc = cpuhp_setup_state_nocalls SoftIRQ 运行时间不够长,传入数据的速率可能会超过内核的速率 足够快地耗尽缓冲区的能力。
软中断由softirq_action结构体表示: struct softirq_action { void (*action) (struct softirq_action *); /* 软中断的处理函数 */ }; 目前已注册的软中断有10种,定义为一个全局数组: static struct softirq_action softirq_vec[NR_SOFTIRQS]; enum { HI_SOFTIRQ , /* 接收网络数据包 */ BLOCK_SOFTIRQ, /* BLOCK装置 */ BLOCK_IOPOLL_SOFTIRQ, TASKLET_SOFTIRQ, /* 正常优先级的 tasklets */ SCHED_SOFTIRQ, /* 调度程序 */ HRTIMER_SOFTIRQ, /* 高分辨率定时器 */ RCU_SOFTIRQ, /* RCU锁定 (NET_TX_SOFTIRQ, net_tx_action); open_softirq(NET_RX_SOFTIRQ, net_rx_action); (3) 触发软中断 调用raise_softirq
NET_TX_SOFTIRQ 2 给网卡发送数据 NET_RX_SOFTIRQ 3 从网卡接收数据 SCSI_SOFTIRQ 4 SCSI命令的后中断处理 TASKLET_SOFTIRQ 5 处理常规 2.2 处理软中断 软中断的初始化使用open_softirq()函数完成,函数原型如下所示: void open_softirq(int nr, void (*action)(struct softirq_action 2.3 do_softirq函数 如果某个时间点,检测到挂起的软中断(local_softirq_pending()非零),内核调用do_softirq()处理它们。 除了do_softirq()执行HI_SOFTIRQ的tasklet优先于 TASKLET_SOFTIRQ之外,这两种软中断没有实质上的差异。 调用raise_softirq_irqoff()激活TASKLET_SOFTIRQ或HI_SOFTIRQ软中断。 调用local_irq_restore恢复中断标志IF。
*); void *data; }; softirq_vec 数组是 softirq 机制的核心,softirq_vec 数组每个元素代表一种softirq。 但在Linux中只定义了四种softirq,如下: enum { HI_SOFTIRQ=0, NET_TX_SOFTIRQ, NET_RX_SOFTIRQ, TASKLET_SOFTIRQ 类型有32个位,每一个位代表一种softirq),而 __softirq_mask 字段表示哪种softirq被屏蔽了。 softirq,然后就通过对比 __softirq_active 字段的各个位来判断是否要执行该类型的softirq。 (cpu, HI_SOFTIRQ) 来告诉softirq需要执行 HI_SOFTIRQ 类型的softirq,我们来看看 __cpu_raise_softirq() 函数的实现: static inline
软中断由softirq_action结构体实现: struct softirq_action { void (*action) (struct softirq_action *); /* 软中断的处理函数 , /* 接收网络数据包 */ BLOCK_SOFTIRQ, /* BLOCK装置 */ BLOCK_IOPOLL_SOFTIRQ, TASKLET_SOFTIRQ, /* 正常优先级的 tasklets */ SCHED_SOFTIRQ, /* 调度程序 */ HRTIMER_SOFTIRQ, /* 高分辨率定时器 */ RCU_SOFTIRQ, /* RCU锁定 (int nr, void (*action) (struct softirq_action *)) { softirq_vec[nr].action = action; } 例如: open_softirq (NET_TX_SOFTIRQ, net_tx_action); open_softirq(NET_RX_SOFTIRQ, net_rx_action); (3)触发软中断 调用raise_softirq
softirq_action *); void *data; }; softirq_vec 数组是 softirq 机制的核心,softirq_vec 数组每个元素代表一种软中断。 open_softirq(TASKLET_SOFTIRQ, tasklet_action, NULL); open_softirq(HI_SOFTIRQ, tasklet_hi_action, 类型有32个位,每一个位代表一种softirq),而 __softirq_mask 字段表示哪种softirq被屏蔽了。 softirq,然后就通过对比 __softirq_active 字段的各个位来判断是否要执行该类型的softirq。 (cpu, HI_SOFTIRQ) 来告诉softirq需要执行 HI_SOFTIRQ 类型的softirq,我们来看看 __cpu_raise_softirq() 函数的实现: static inline
(__IRQ_MASK(SOFTIRQ_BITS) << SOFTIRQ_SHIFT) #define HARDIRQ_MASK (__IRQ_MASK(HARDIRQ_BITS) << HARDIRQ_SHIFT * in_softirq - Are we currently processing softirq or have bh disabled? * in_serving_softirq - Are we currently processing softirq? */ #define in_irq() (hardirq_count()) #define in_softirq() (softirq_count()) #define in_interrupt() (irq_count()) #define in_serving_softirq() (softirq_count() & SOFTIRQ_OFFSET) 其中in_irq用于判断当前进程是否在硬中断中
enum { HI_SOFTIRQ=0, /* 最高优先级软中断 */ TIMER_SOFTIRQ, /* Timer定时器软中断 */ NET_TX_SOFTIRQ, /* 发送网络数据包软中断 */ NET_RX_SOFTIRQ, /* 接收网络数据包软中断 */ BLOCK_SOFTIRQ, /* 块设备软中断 */ IRQ_POLL_SOFTIRQ Linux 在系统初始化时注册了两种 softirq 处理函数,分别为 TASKLET_SOFTIRQ 和 HI_SOFTIRQ. void __init softirq_init() { .. open_softirq(TASKLET_SOFTIRQ, tasklet_action, NULL); open_softirq(HI_SOFTIRQ, tasklet_hi_action, NULL); } open_softirq 函数如下所示: void open_softirq(int nr, void (*action)(struct softirq_action *)) {
在Softirq中说过了,Tasklet的实现是基于Softirq的。也就是说Tasklet是Softirq中的一种。 根据优先级不同,Linux将Tasklet分为两类如下: 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 }; 其中HI_SOFTIRQ和TASKLET_SOFTIRQ C: 这时候就需要触发softirq,在softirq小节有讲到。 D: 恢复中断。
, /*用于网络层发包*/ NET_RX_SOFTIRQ, /*用于网络层收报*/ BLOCK_SOFTIRQ, BLOCK_IOPOLL_SOFTIRQ , TASKLET_SOFTIRQ, /*用于低优先级的tasklet*/ SCHED_SOFTIRQ, HRTIMER_SOFTIRQ, 注冊软中断 void open_softirq(int nr, void (*action)(struct softirq_action *)) 即注冊相应类型的处理函数到全局数组softirq_vec 软中断运行函数 do_softirq-->__do_softirq 运行软中断处理函数__do_softirq前首先要满足两个条件: (1)不在中断中(硬中断、软中断和NMI) 。 trace_softirq_entry(h, softirq_vec); h->action(h); trace_softirq_exit(h, softirq_vec
软IRQ初始化 在内核初始化期间,softirq_init 会以两个通用软IRQ对软IRQ 层做初始化。 tasklet_action 和 tasklet_hi_action (分别与TASKLET_SOFTIRQ 以及 HI_SOFTIRQ 相关联) void __init softirq_init() { open_softirq(TASKLET_SOFTIRQ, tasklet_action, NULL); open_softirq(HI_SOFTIRQ, tasklet_hi_action , NULL); } 这两个由网络代码NET_RX_SOFTIRQ 和NET_TX_SOFTIRQ 所用的软IRQ 是在net_dev_init 中初始化的。
open_softirq(NET_TX_SOFTIRQ, net_tx_action); open_softirq(NET_RX_SOFTIRQ, net_rx_action); } subsys_initcall 然后 open_softirq 为每一种软中断都注册一个处理函数。NET_TX_SOFTIRQ的处理函数为net_tx_action,NET_RX_SOFTIRQ的为net_rx_action。 //kernel/softirq.c void open_softirq(int nr, void (*action)(struct softirq_action *)){ softirq_vec [nr].action = action; } open_softirq 会把不同的 action 记录在softirq_vec变量里的。 紧接着 __raise_softirq_irqoff 触发了一个软中断 NET_RX_SOFTIRQ。 注意:当RingBuffer满的时候,新来的数据包将给丢弃。
在上一阶段的中断处理函数中, 调用raise_softirq设置了对应的软中断, 到了这里, 软中断对应的处理函数就会被调用(处理函数由open_softirq函数来注册). 后面的工作由HI_SOFTIRQ/TASKLET_SOFTIRQ对应的软中断处理程序去处理被标记的tasklet(每个tasklet在其初始化时都设置了处理函数). 看上去, tasklet只不过是在softirq的基础上多了一层调用, 其作用是什么呢? 前面说过, softirq是与CPU相对应的, 每个CPU处理自己的softirq. 然而, softirq毕竟还是要比tasklet少绕点弯路, 所以少数实时性要求相对较高的处理过程还是在精心设计之后, 直接使用softirq了. 虽然softirq可能被推后到ksoftirqd内核线程去处理, 但是还是不能在softirq处理过程中睡眠, 因为不能保证softirq一定在ksoftirqd内核线程中被处理.
本地锁“softirq_ctrl.lock”的定义如下。 ; 5 6 static DEFINE_PER_CPU(struct softirq_ctrl, softirq_ctrl) = { 7 .lock = INIT_LOCAL_LOCK(softirq_ctrl.lock 第29行,把当前处理器上禁止软中断的计数值加上cnt,cnt的值是SOFTIRQ_DISABLE_OFFSET(它的值是SOFTIRQ_OFFSET的2倍)。 to SOFTIRQ_OFFSET which makes 41 * in_serving_softirq() become true. 42 */ 43 cnt = SOFTIRQ_OFFSET (3)第44行,调用函数__local_bh_enable(),把计数值减去SOFTIRQ_OFFSET(它的值是SOFTIRQ_DISABLE_OFFSET的一半),并且不释放本地锁。
对应NET_RX_SOFTIRQ这个软中断,软中断的类型如下: enum { HI_SOFTIRQ=0, TIMER_SOFTIRQ, NET_TX_SOFTIRQ , NET_RX_SOFTIRQ, BLOCK_SOFTIRQ, IRQ_POLL_SOFTIRQ, TASKLET_SOFTIRQ, 注册软中断 内核初始化期间,softirq_init会注册TASKLET_SOFTIRQ以及HI_SOFTIRQ相关联的处理函数。 open_softirq(TASKLET_SOFTIRQ, tasklet_action); open_softirq(HI_SOFTIRQ, tasklet_hi_action); } 网络子系统分两种 其中open_softirq实现为: void open_softirq(int nr, void (*action)(struct softirq_action *)) { softirq_vec
//file: include/linux/interrupt.h enum{ HI_SOFTIRQ=0, TIMER_SOFTIRQ, NET_TX_SOFTIRQ, NET_RX_SOFTIRQ, BLOCK_SOFTIRQ, BLOCK_IOPOLL_SOFTIRQ, TASKLET_SOFTIRQ, SCHED_SOFTIRQ, open_softirq(NET_TX_SOFTIRQ, net_tx_action); open_softirq(NET_RX_SOFTIRQ, net_rx_action); } subsys_initcall 另外open_softirq注册了每一种软中断都注册一个处理函数。NET_TX_SOFTIRQ的处理函数为net_tx_action,NET_RX_SOFTIRQ的为net_rx_action。 //file: kernel/softirq.c void open_softirq(int nr, void (*action)(struct softirq_action *)){ softirq_vec
open_softirq(NET_TX_SOFTIRQ, net_tx_action); open_softirq(NET_RX_SOFTIRQ, net_rx_action); } subsys_initcall 然后 open_softirq 为每一种软中断都注册一个处理函数。NET_TX_SOFTIRQ的处理函数为net_tx_action,NET_RX_SOFTIRQ的为net_rx_action。 //kernel/softirq.c void open_softirq(int nr, void (*action)(struct softirq_action *)){ softirq_vec [nr].action = action; } open_softirq 会把不同的 action 记录在softirq_vec变量里的。 紧接着 __raise_softirq_irqoff 触发了一个软中断 NET_RX_SOFTIRQ。 注意:当RingBuffer满的时候,新来的数据包将给丢弃。
, NET_TX_SOFTIRQ, NET_RX_SOFTIRQ, BLOCK_SOFTIRQ, BLOCK_IOPOLL_SOFTIRQ, TASKLET_SOFTIRQ open_softirq(NET_TX_SOFTIRQ, net_tx_action); open_softirq(NET_RX_SOFTIRQ, net_rx_action); } subsys_initcall 另外open_softirq注册了每一种软中断都注册一个处理函数。NET_TX_SOFTIRQ的处理函数为net_tx_action,NET_RX_SOFTIRQ的为net_rx_action。 //file: kernel/softirq.c void open_softirq(int nr, void(*action)(struct softirq_action *)){ softirq_vec void __raise_softirq_irqoff(unsigned int nr){ trace_softirq_raise(nr); or_softirq_pending(1UL
sy:system time,表示 CPU 在内核运行的时间,包括 IRQ 和 softirq。系统 CPU 占用越高,表明系统某部分存在瓶颈。通常这个值越低越好。 如果当前处于软中断执行上下文, 那么统计到 softirq 字段中 // 3. ()) cpustat->softirq = cputime64_add(cpustat->softirq, tmp); else cpustat->system 如果当前处于软中断执行上下文,那么增加到 CPU 统计结构的 softirq 字段中。 否则增加到 CPU 统计结构的 system 字段中。 下面说说这些数据的意义,从第一个数值开始分别代表:user ,nice,system,idle,iowait, irq,softirq,steal。