1.信号(Signal)
信号是 Linux 内核向进程发送的异步通知机制,用于告知进程发生了某个事件。
1.1.信号的产生途径
| 来源 |
示例 |
| 软件异常 |
除零(SIGFPE)、非法内存访问(SIGSEGV) |
| 终端输入 |
Ctrl+C → SIGINT,Ctrl+Z → SIGTSTP |
| 软件条件 |
定时器到期(SIGALRM)、管道断裂(SIGPIPE) |
| 系统调用 |
kill()、raise()、tkill() |
1.2.信号的传递与处理流程
产生信号 → 写入进程 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.中断的分类
中断
├── 外部中断(硬件中断)
│ ├── 可屏蔽中断(Maskable)
│ │ └── 通过 INTR 引脚触发,受 IF 标志位控制
│ │ └── 例如:网卡、磁盘、键盘、定时器(PIT/HPET/APIC Timer)
│ └── 不可屏蔽中断(NMI)
│ └── 通过 NMI 引脚触发,不受 IF 位影响
│ └── 例如:内存奇偶校验错误、严重硬件故障
│
└── 内部中断
├── 异常(Exception / 同步中断)------ 见下文
└── 软中断(Softirq)------ 见下文
2.2.x86 架构中断描述符表(IDT)
IDT 包含 256 个条目(0~255)
0~31:Intel 保留,用于异常和 NMI
32~47:通常分配给可屏蔽硬件中断(IRQ0~IRQ15)
128(0x80):传统系统调用入口(int 0x80)
- 其他:可由内核自定义分配
2.3.中断处理流程(Top Half)
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 的执行时机
- 硬中断返回时:如果当前
pending 的 softirq 存在,直接执行
ksoftirqd 内核线程:如果 softirq 过多,避免用户态进程饿死,由专门的内核线程处理
- 显式调用:如 local_bh_enable()、raise_softirq()
3.4.预定义的 Softirq 类型
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 中最常见、最重要的异常:
触发 #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.代码示例
// 方法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_uring、readv/writev、sendmmsg 减少上下文切换次数 |
6.整体关系图
用户进程
│
├── 信号(异步通知)←──────────┐
│ │
│ │
系统调用 ──→ 异常(Trap) │
│ 页错误(Fault) │
│ │
↓ │
内核态 ─────────────────────────┤
│ │
├── 硬中断(Top Half)────────┤
│ 快速、关中断 │
│ ↓ │
├── 软中断(Bottom Half)─────┘
│ 开中断、可延迟
│ ↓
└── 工作队列(进程上下文)
可睡眠