一、软中断类型
在Linux内核中,中断处理分为上半部(硬中断)和下半部。上半部负责快速响应硬件事件,而下半部用于处理耗时任务,避免阻塞系统。下半部有三种机制:软中断(Softirq)、小任务(Tasklet)和工作队列(Workqueue)。它们的区别如下:
1. 软中断(Softirq)
特点:
不允许睡眠:在中断上下文中执行,不能调用可能引发睡眠的函数(如
kmalloc(GFP_KERNEL)
)。静态定义:类型在编译时确定(如网络、定时器等),无法动态添加新类型。
可重入与并发:同类型软中断可在多个CPU上并行执行,处理函数需支持可重入,需用锁保护临界区。
示例:
网络数据包处理:网卡硬中断将数据包存入队列后,触发
NET_RX_SOFTIRQ
软中断。多个CPU可同时调用处理函数net_rx_action()
,并行处理不同数据包。函数内部需使用自旋锁保护共享队列。
2. 小任务(Tasklet)
特点:
不允许睡眠:同样在中断上下文中运行。
动态调度:可在运行时动态注册和调度(如驱动初始化时)。
串行执行:同一Tasklet实例仅在一个CPU上执行,无需处理可重入问题。
示例:
USB设备中断处理:硬中断快速确认中断后,调度Tasklet处理数据传输。例如:
void usb_tasklet_func(unsigned long data) { /* 处理数据 */ } DECLARE_TASKLET(usb_tasklet, usb_tasklet_func, 0); // 硬中断中调用: tasklet_schedule(&usb_tasklet);
即使系统有多个CPU,
usb_tasklet
同一时刻只在一个CPU上运行。
3. 工作队列(Workqueue)
特点:
允许睡眠:在进程上下文中运行(内核线程),可使用阻塞函数(如
msleep()
、mutex_lock()
)。动态管理:可动态创建队列和任务。
线程执行:任务由内核线程按顺序处理。
示例:
磁盘写入操作:需要休眠等待I/O完成时,使用工作队列:
struct work_struct write_work; void write_work_func(struct work_struct *work) { // 调用可能阻塞的函数,如文件写入 filp->f_op->write(...); } // 初始化并提交任务: INIT_WORK(&write_work, write_work_func); schedule_work(&write_work);
工作队列处理函数可安全调用
write()
等可能阻塞的操作。
三者的对比总结
特性 软中断 小任务 工作队列 是否允许睡眠 否 否 是 动态添加/删除 否(静态编译时定义) 是 是(可动态创建队列) 并发性 多CPU并行,需可重入 单CPU串行,无需可重入 按线程调度,顺序执行
软中断(softirq)是中断处理程序在开启中断的情况下执行的部分,可以被硬中断抢占。内核定义了一张软中断向量表,每种软中断有一个唯一编号,对应一个softirq_action实例,内核源码如下:
软中断类型内核源码如下:
二、注册软中断以及触发软中断
函数open_softirq()来注册软中断处理函数,在软中断向量表中为指定的软中断编号设置处理函数,内核源码如下:
函数raise_softirq()用来触发软中断,参数是软中断编号:
三、执行软中断
在中断处理程序的后半部分执行软中断,对执行时间有限制:不能超过 2 毫秒,并且最多执行 10 次;每个处理器有一个软中断线程,调度策略是 SCHED_NORMAL,优先级是120。
中断处理程序执行软中断:
软中断线程:
以NIC网卡接收数据触发软中断为例,讲解这些函数工作流程:
1. open_softirq
- 功能:用于注册软中断处理函数。在内核中,软中断类型是预先定义好的 ,如
NET_RX_SOFTIRQ
(网卡接收数据软中断类型)。通过open_softirq(net_rx_softirq, net_rx_action)
,将net_rx_action
函数注册为NET_RX_SOFTIRQ
软中断的处理函数。这样当该软中断被触发时,内核知道要调用哪个函数来处理。- 流程:将传入的处理函数指针赋值给对应软中断向量表(
softirq_vec
数组 )中该软中断类型索引位置的action
成员。2. raise_softirq
- 功能:标记要触发的软中断,使其进入待处理状态。当 NIC 网卡接收到数据时,相关代码会调用
raise_softirq
来触发NET_RX_SOFTIRQ
软中断。- 流程:
- 首先调用
raise_softirq_irqoff
,该函数通过__raise_softirq_irqoff
设置当前 CPU 的软中断 pending 标志位(对应__softirq_pending
中NET_RX_SOFTIRQ
相关位) ,表示有软中断等待处理。- 然后检查当前是否不在中断上下文(通过
in_interrupt
判断) ,如果满足条件,就唤醒软中断守护进程ksoftirqd
(调用wakeup_softirqd
),准备处理软中断;若在中断上下文,则软中断会在中断退出阶段处理。3. irq_exit
- 功能:在硬中断处理完成后调用,用于退出中断上下文,并检查是否有需要处理的软中断。当 NIC 网卡硬中断处理完成后,会执行到
irq_exit
。- 流程:
- 执行一些统计和追踪相关操作,如
account_system_vtime(current)
、trace_hardirq_exit
。- 通过
sub_preempt_count(IRQ_EXIT_OFFSET)
标识 HARDIRQ 中断上下文结束。- 使用
in_interrupt
判断当前是否处于中断上下文,用local_softirq_pending
检查是否有等待处理的软中断。若不在中断上下文且有软中断等待处理,则调用invoke_softirq
。4. invoke_softirq
- 功能:实际触发软中断处理,它是
__do_softirq
的宏定义(#define invoke_softirq() __do_softirq()
),负责调用软中断处理函数。- 流程:直接调用
__do_softirq
,进入软中断处理核心流程。5. _do_softirq
- 功能:软中断处理核心函数,遍历并执行处于 pending 状态的软中断处理函数。
- 流程:
- 获取当前 CPU 软中断位图
pending = local_softirq_pending()
,得到哪些软中断被触发。- 禁止本地 CPU 的软中断(
__local_bh_disable
),防止处理过程中软中断被干扰。- 标记进入 softirq context(
lockdep_softirq_enter
),记录相关锁依赖信息。- 循环处理位图中的软中断:
- 对位图清零(
set_softirq_pending(0)
)。- 开启本地 CPU 硬中断(
local_irq_enable
),允许硬中断打断软中断处理(但软中断不可嵌套)。- 获取软中断描述(
h = softirq_vec
),根据软中断位图找到对应的软中断处理函数并执行(h->action(h)
) 。- 处理过程中检查是否超时(通过
jiffies
和设定的最大处理时间MAX_SOFTIRQ_TIME
比较 )和达到最大重启次数(max_restart
,默认 10 次) ,若满足条件且还有软中断未处理完,则唤醒ksoftirqd
继续处理剩余软中断。- 处理完成后恢复相关设置,如关闭本地 CPU 硬中断等。
6. run_softirqd
- 功能:
ksoftirqd
内核线程执行的函数,当软中断处理在irq_exit
中未处理完,或内核其他代码主动触发软中断且不在中断上下文时,ksoftirqd
会被唤醒执行run_softirqd
来处理软中断。- 流程:在内核关闭抢占的情况下调用
__do_softirq
,执行与上述__do_softirq
类似的软中断处理流程,遍历并执行等待处理的软中断处理函数。
https://github.com/0voice