第一百三十六章:中断下半部
合道期涉及内核源码:
一
林小源跟在那位中断武神身后,沿着山路继续前行。忽然,武神停下脚步,指向山腰处的一道裂缝。
裂缝将一块巨石劈成两半。上半部分悬在半空中,闪烁着紫色的电弧——那是硬中断的标志。下半部分沉在地底,散发着柔和的蓝光。
"中断上下半部。"武神说,"这是内核处理中断的核心设计。"
他指着上半部分:"上半部就是硬中断处理——CPU 暂停一切,跳转到处理函数,处理最紧急的事:读设备状态、清除中断标志、把数据拷到安全的地方。然后立刻退出,越快越好。"
"那耗时的工作呢?"林小源问。
"留给下半部。"武神指向地底那团蓝光,"下半部在中断处理结束后执行,处理那些不那么紧急但耗时的工作——数据处理、协议栈逻辑、内存分配。"
/*
* 中断下半部的机制:
*
* 1. softirq (软中断)
* 静态定义,编译时确定
* 类型有限(约 10 种)
* 性能最好
* 例: NET_RX_SOFTIRQ, TIMER_SOFTIRQ
*
* 2. tasklet
* 基于 softirq 实现
* 可以动态创建
* 使用更简单
* 同一 tasklet 不能并发
*
* 3. 工作队列
* 在进程上下文中执行
* 可以休眠
* 最灵活
*
* 选择:
* 需要最高性能 → softirq
* 简单延迟处理 → tasklet
* 需要休眠 → 工作队列
*/
printf("=== 中断下半部 — 延迟处理 ===\n\n");
printf("中断上下半部:\n\n");
printf("上半部 (硬中断):\n");
printf(" 处理紧急工作\n");
printf(" 读取设备状态\n");
printf(" 清除中断标志\n");
printf(" 调度下半部\n\n");
printf("下半部:\n");
printf(" 处理耗时工作\n");
printf(" 数据处理\n");
printf(" 协议栈逻辑\n\n");
printf("--- 下半部机制 ---\n\n");
printf("1. softirq:\n");
printf(" 静态定义\n");
printf(" 性能最好\n");
printf(" NET_RX_SOFTIRQ — 网络收包\n");
printf(" TIMER_SOFTIRQ — 定时器\n\n");
printf("2. tasklet:\n");
printf(" 基于 softirq\n");
printf(" 可以动态创建\n");
printf(" 同一 tasklet 不能并发\n\n");
printf("3. 工作队列:\n");
printf(" 在进程上下文中执行\n");
printf(" 可以休眠\n");
printf(" 最灵活\n\n");
printf("--- 选择 ---\n");
printf("需要最高性能 → softirq\n");
printf("简单延迟处理 → tasklet\n");
printf("需要休眠 → 工作队列\n\n");
printf("--- 网络中断示例 ---\n");
printf("上半部:\n");
printf(" 网卡中断\n");
printf(" 读取状态\n");
printf(" 调度 NET_RX_SOFTIRQ\n\n");
printf("下半部:\n");
printf(" 处理收到的数据包\n");
printf(" 传递给协议栈\n");#include <stdio.h>
/*
* 中断下半部的机制:
*
* 1. softirq (软中断)
* 静态定义,编译时确定
* 类型有限(约 10 种)
* 性能最好
* 例: NET_RX_SOFTIRQ, TIMER_SOFTIRQ
*
* 2. tasklet
* 基于 softirq 实现
* 可以动态创建
* 使用更简单
* 同一 tasklet 不能并发
*
* 3. 工作队列
* 在进程上下文中执行
* 可以休眠
* 最灵活
*
* 选择:
* 需要最高性能 → softirq
* 简单延迟处理 → tasklet
* 需要休眠 → 工作队列
*/
int main() {
printf("=== 中断下半部 — 延迟处理 ===\n\n");
printf("中断上下半部:\n\n");
printf("上半部 (硬中断):\n");
printf(" 处理紧急工作\n");
printf(" 读取设备状态\n");
printf(" 清除中断标志\n");
printf(" 调度下半部\n\n");
printf("下半部:\n");
printf(" 处理耗时工作\n");
printf(" 数据处理\n");
printf(" 协议栈逻辑\n\n");
printf("--- 下半部机制 ---\n\n");
printf("1. softirq:\n");
printf(" 静态定义\n");
printf(" 性能最好\n");
printf(" NET_RX_SOFTIRQ — 网络收包\n");
printf(" TIMER_SOFTIRQ — 定时器\n\n");
printf("2. tasklet:\n");
printf(" 基于 softirq\n");
printf(" 可以动态创建\n");
printf(" 同一 tasklet 不能并发\n\n");
printf("3. 工作队列:\n");
printf(" 在进程上下文中执行\n");
printf(" 可以休眠\n");
printf(" 最灵活\n\n");
printf("--- 选择 ---\n");
printf("需要最高性能 → softirq\n");
printf("简单延迟处理 → tasklet\n");
printf("需要休眠 → 工作队列\n\n");
printf("--- 网络中断示例 ---\n");
printf("上半部:\n");
printf(" 网卡中断\n");
printf(" 读取状态\n");
printf(" 调度 NET_RX_SOFTIRQ\n\n");
printf("下半部:\n");
printf(" 处理收到的数据包\n");
printf(" 传递给协议栈\n");
return 0;
}二
"下半部有三种实现方式。"武神蹲下身,在地上画了三个圆圈。
他指着第一个圆圈:"softirq——软中断。它是最底层的下半部机制,在中断上下文中执行。类型在编译时就确定了,总共只有十来种—— 处理网络收包, 处理定时器。因为是静态定义的,性能最好,没有运行时开销。"
"那它有什么限制?"
"类型太少,你不能随便加新的 softirq。"武神说,"而且它在中断上下文中执行,同样不能休眠。"
他又指着第二个圆圈:"tasklet——基于 softirq 实现,但更灵活。你可以动态创建任意数量的 tasklet。不过有个限制——同一个 tasklet 不能在多个 CPU 上同时执行。如果你有多个 CPU,同一个 tasklet 的不同实例会被串行化。"
"是为了避免并发问题?"
"对。tasklet 的设计目标就是简单安全——你不用考虑锁的问题,因为同一个 tasklet 保证不会并发。"
三
武神指着第三个圆圈,这个圆圈的颜色跟前两个不同——它散发着温暖的橙色光芒。
"工作队列。"武神说,"它跟前两个有本质区别——工作队列在进程上下文中执行,不是中断上下文。"
"进程上下文?"林小源眼睛一亮,"那就可以休眠了?"
"对。"武神点头,"工作队列中的函数可以休眠、可以获取 mutex、可以分配大块内存、可以做文件操作。它是最灵活的下半部机制。"
"那为什么不用工作队列处理所有下半部?"
"因为上下文切换有开销。"武神说,"softirq 和 tasklet 在中断返回时直接执行,不需要调度。工作队列需要唤醒内核线程,涉及上下文切换,延迟更高。所以——"
他站起身,拍了拍手上的泥土:"需要最高性能,用 softirq。需要简单延迟处理,用 tasklet。需要休眠或者做复杂操作,用工作队列。没有万能的选择,只有合适的权衡。"
林小源看着那三个圆圈,心中记下了这个设计:上半部处理紧急的事,下半部处理耗时的事;softirq 最快但最僵硬,工作队列最灵活但最慢,tasklet 居中。一切取决于场景。
道藏笔记
内核启示
中断下半部处理耗时的工作。
下半部机制:
- softirq — 静态定义,性能最好
- tasklet — 动态创建,使用简单
- 工作队列 — 进程上下文,可以休眠
选择原则:
- 最高性能 → softirq
- 简单延迟 → tasklet
- 需要休眠 → 工作队列
下半部是"延迟"的智慧——把耗时的工作留到后面。
中断下半部之试
中断下半部一章里,被用来把不紧急工作延后执行的传统机制是什么?