深入理解Linux内核--信号,中断,软中断,异常,性能优化

1.信号(Signal)

信号是 Linux 内核向进程发送的异步通知机制,用于告知进程发生了某个事件。

1.1.信号的产生途径

来源 示例
软件异常 除零(SIGFPE)、非法内存访问(SIGSEGV
终端输入 Ctrl+CSIGINTCtrl+ZSIGTSTP
软件条件 定时器到期(SIGALRM)、管道断裂(SIGPIPE
系统调用 kill()raise()tkill()

1.2.信号的传递与处理流程

cpp 复制代码
产生信号 → 写入进程 task_struct 的 pending 位图
   ↓
信号递达(Delivery):内核在进程从内核态返回用户态时检查 pending
   ↓
执行信号处理函数(若注册了)/ 默认动作(终止/忽略/暂停/继续)

1.3.关键数据结构

  • task_struct->signal:指向共享的信号结构(线程组共享)
  • task_struct->blocked:信号屏蔽字
  • sigpending:挂起信号队列

1.4.可靠信号 vs 不可靠信号

  • 不可靠信号(1~31):标准信号,不排队,可能丢失
  • 可靠信号(32~64):实时信号,支持排队,保证不丢失

2.中断(Interrupt)

中断是硬件或软件向 CPU 发出的异步事件通知,迫使 CPU 暂停当前执行流,转去执行中断处理程序。

2.1.中断的分类

c 复制代码
中断
├── 外部中断(硬件中断)
│   ├── 可屏蔽中断(Maskable)
│   │   └── 通过 INTR 引脚触发,受 IF 标志位控制
│   │   └── 例如:网卡、磁盘、键盘、定时器(PIT/HPET/APIC Timer)
│   └── 不可屏蔽中断(NMI)
│       └── 通过 NMI 引脚触发,不受 IF 位影响
│       └── 例如:内存奇偶校验错误、严重硬件故障
│
└── 内部中断
    ├── 异常(Exception / 同步中断)------ 见下文
    └── 软中断(Softirq)------ 见下文

2.2.x86 架构中断描述符表(IDT)

  • IDT 包含 256 个条目(0~255
  • 0~31Intel 保留,用于异常和 NMI
  • 32~47:通常分配给可屏蔽硬件中断(IRQ0~IRQ15
  • 128(0x80):传统系统调用入口(int 0x80
  • 其他:可由内核自定义分配

2.3.中断处理流程(Top Half)

cpp 复制代码
1. CPU 收到中断请求
2. 保存现场(寄存器、EFLAGS、返回地址压栈)
3. 根据中断向量号查询 IDT,找到中断处理程序
4. 执行 do_IRQ() → 调用注册的中断处理函数(handler)
5. 发送 EOI(End of Interrupt)给中断控制器
6. 恢复现场,iret 返回

2.4.中断控制器演进

控制器 特点
8259A PIC 传统可编程中断控制器,级联支持 15 个 IRQ
IO-APIC 多处理器时代,支持中断分发到指定 CPU
MSI/MSI-X 消息信号中断,PCIe 设备直接通过内存写触发,无需专用中断线

3.软中断(Softirq / Bottom Half)

软中断是中断处理的后半部分机制,用于将非紧急、耗时的工作从中断上下文(硬中断)推迟到软中断上下文执行。

3.1.为什么需要 Bottom Half?

  • 硬中断上下文(Top Half)要求快速执行,因为中断是关闭的(或优先级很高),长时间执行会丢失其他中断
  • Bottom Half 在开中断环境下运行,可以被硬中断打断,适合耗时操作

3.2.Linux 的三种 Bottom Half 机制

机制 执行上下文 特点 使用场景
Softirq 中断上下文(ksoftirqd) 静态分配(最多 32 个),同一类型可在多个 CPU 并行 网络收发、块设备、定时器、SCSI
Tasklet 中断上下文 基于 softirq 实现,同一 tasklet 不会并行执行 大多数驱动程序
Workqueue 进程上下文 可以睡眠,延迟较大 需要睡眠的操作(如 I/O、内存分配)

3.3.Softirq 的执行时机

  • 硬中断返回时:如果当前 pendingsoftirq 存在,直接执行
  • ksoftirqd 内核线程:如果 softirq 过多,避免用户态进程饿死,由专门的内核线程处理
  • 显式调用:如 local_bh_enable()、raise_softirq()

3.4.预定义的 Softirq 类型

c 复制代码
enum {
    HI_SOFTIRQ = 0,      // 高优先级 tasklet
    TIMER_SOFTIRQ,       // 定时器
    NET_TX_SOFTIRQ,      // 网络发送
    NET_RX_SOFTIRQ,      // 网络接收
    BLOCK_SOFTIRQ,       // 块设备
    IRQ_POLL_SOFTIRQ,    // 中断轮询
    TASKLET_SOFTIRQ,     // 普通 tasklet
    SCHED_SOFTIRQ,       // 调度器
    HRTIMER_SOFTIRQ,     // 高精度定时器
    RCU_SOFTIRQ,         // RCU
    NR_SOFTIRQS
};

4.异常(Exception)

异常是 CPU 在执行指令过程中同步检测到的错误或特殊条件,与当前指令直接相关。

4.1.异常的分类(x86 架构)

类型 名称 特点 示例
Fault 故障 可纠正,保存的 EIP 指向触发指令,处理后可重新执行 页错误(Page Fault #PF)、段不存在、除零错误
Trap 陷阱 有意的同步事件,保存的 EIP 指向下一条指令 断点(INT3)、调试(#DB)、系统调用(SYSCALL
Abort 中止 严重错误,无法精确定位,通常终止进程 双重故障(Double Fault)、机器检查(Machine Check)

4.2.x86 常见异常向量

向量号 名称 类型 说明
0 #DE Divide Error Fault 除零或结果溢出
1 #DB Debug Trap/Fault 调试异常
3 #BP Breakpoint Trap 断点(int3
4 #OF Overflow Trap into 指令溢出
6 #UD Invalid Opcode Fault 无效操作码
8 #DF Double Fault Abort 处理异常时又发生异常
13 #GP General Protection Fault 通用保护违规(权限、段错误等)
14 #PF Page Fault Fault 页错误,缺页或权限不足
18 #MC Machine Check Abort 硬件错误(由 CPU 报告)

4.3.页错误(Page Fault)详解

页错误是 Linux 中最常见、最重要的异常:

cpp 复制代码
触发 #PF 的情况:
├── 合法缺页(Minor Fault)
│   ├── 匿名页首次访问 → 分配物理页并清零
│   ├── 文件映射页首次访问 → 从磁盘读取(或共享零页)
│   └── Copy-on-Write → 复制物理页
│
└── 非法访问(Major Fault / SIGSEGV)
    ├── 访问未映射地址 → 发送 SIGSEGV
    └── 权限不足(写只读页等)→ SIGSEGV

4.3.1.Minor Fault与Major Fault

维度 Minor Fault Major Fault
是否需要磁盘 I/O ❌ 否 ✅ 是
耗时 ~1-10 μs ~1-10 ms(磁盘延迟主导)
是否阻塞进程 极短,几乎无感知 显著阻塞
常见原因 首次访问、COW、栈增长 磁盘读取、swap-in
优化方向 预分配、预触摸 预读、加内存、锁内存、关 swap

核心原则: Major Fault 是性能杀手,Minor Fault 是正常开销。优化目标是消除 Major Fault,控制 Minor Fault 的总量和频率。

4.3.2.代码示例

c 复制代码
// 方法1:mmap + MAP_POPULATE(预填充,减少运行时缺页)
void *addr = mmap(NULL, size, PROT_READ|PROT_WRITE,
                  MAP_PRIVATE|MAP_ANONYMOUS|MAP_POPULATE, -1, 0);

// 方法2:madvise 顺序预读(适合大文件顺序访问)
madvise(addr, size, MADV_SEQUENTIAL);
madvise(addr, size, MADV_WILLNEED);  // 提示内核预读

// 方法3:mlock 锁定关键内存(防止 swap out,但需谨慎使用)
mlock(addr, size);

5.性能优化

5.1.中断相关优化

优化方向 具体手段
中断亲和性(IRQ Affinity) 将中断绑定到特定 CPU,避免缓存失效:/proc/irq/<n>/smp_affinity
中断合并(Interrupt Coalescing) 网卡等硬件批量产生中断,减少中断次数
NAPI(New API) 网络接收混合轮询+中断,高负载时关闭中断纯轮询
MSI-X 多队列 网卡多队列中断,每个队列绑定不同 CPU,实现并行处理
硬中断轻量化 Top Half 只做最少工作(如禁用设备中断、标记状态),其余放 Softirq/Workqueue

5.2.Softirq 优化

优化方向 具体手段
ksoftirqd 调优 监控 /proc/softirqs,若某 CPU 的 ksoftirqd 占用过高,考虑分散中断
RPS/RFS(Receive Packet Steering/Flow Steering) 软件层面将网络包处理分散到多个 CPU
避免 Softirq 饥饿 限制单次 softirq 处理数量,防止用户态进程无法运行

5.3.信号优化

优化方向 具体手段
使用实时信号 对可靠性要求高的场景使用 SIGRTMIN~SIGRTMAX
信号处理函数精简 信号处理函数中只设置标志位,主循环中处理逻辑(类似 self-pipe trick)
signalfd 使用 signalfd 将信号转为文件描述符事件,与 epoll 统一处理,避免传统信号处理的竞态

5.4.页错误优化

优化方向 具体手段
减少 Major Fault 预读(madvise(MADV_SEQUENTIAL))、mlock 锁定关键内存
大页(Hugepage) 使用 2MB/1GB 大页减少 TLB Miss 和页表遍历
THP(Transparent Hugepage) 内核自动合并小页为大页
减少 Minor Fault 预分配内存并触摸(touch),避免运行时缺页

5.5.系统调用优化

优化方向 具体手段
vDSO 部分系统调用(如 gettimeofday)通过用户态共享库直接执行,避免陷入内核
批量系统调用 使用 io_uringreadv/writevsendmmsg 减少上下文切换次数

6.整体关系图

c 复制代码
用户进程
   │
   ├── 信号(异步通知)←──────────┐
   │                           │
   │                           │
系统调用 ──→ 异常(Trap)         │
   │         页错误(Fault)     │
   │                           │
   ↓                           │
内核态 ─────────────────────────┤
   │                           │
   ├── 硬中断(Top Half)────────┤
   │      快速、关中断           │
   │         ↓                 │
   ├── 软中断(Bottom Half)─────┘
   │      开中断、可延迟
   │         ↓
   └── 工作队列(进程上下文)
          可睡眠
相关推荐
mayaairi15 分钟前
JS DOM节点操作完全指南:增删改查与性能优化
开发语言·javascript·性能优化
qq_452396232 小时前
第十一篇:《前端性能优化体系:从加载到交互的全链路》
前端·性能优化·交互
晓晓_za89866814 小时前
开源 GEO 优化源码二次开发:贴牌改造与业务模块扩展实践
java·运维·服务器·开发语言·性能优化·开源
systemPro20 小时前
前端发布后偶发白屏?从 Vite Hash、Nginx 缓存到 ChunkLoadError 的完整排查
性能优化
天空之城--1 天前
Android全链路性能优化:从原理到实践的终极指南
android·性能优化
Meteors.1 天前
Android 性能优化:05.CPU优化
android·性能优化
见山是山-见水是水1 天前
网络请求性能优化落地指南:让原生界面在模拟器里稳定运行
网络协议·华为·性能优化·harmonyos
_ZHOURUI_H_1 天前
Unity EasyECS:并不是所有字段都适合 SoA,Unity 项目中应该怎样拆数据
游戏·unity·性能优化·架构·游戏引擎
Android打工仔1 天前
一次 Android 拍照后卡顿的 Perfetto 定位与优化实践
android·性能优化