tags: [source-summary] type: source source: "Linux内核设计与实现-原理与本质" author: "AI助手" date: 2026-09-19
锚点:CFS需要一个数据结构支持O(log n)的插入删除 + 快速获取vruntime最小的进程。红黑树通过rb_root_cached缓存最左节点,实现O(1)获取最小值。
graph TD
A[红黑树性质] --> B[每个节点是红色或黑色]
A --> C[根节点是黑色]
A --> D[叶子节点NIL是黑色]
A --> E[红色节点的子节点必须是黑色]
A --> F[从任意节点到叶子的所有路径黑色节点数相同]
这些约束保证:最长路径不超过最短路径的2倍,查找/插入/删除都是O(log n)。
| 数据结构 | 查找最小值 | 插入 | 删除 | 问题 |
|---|---|---|---|---|
| 有序数组 | O(1) | O(n) | O(n) | 插入删除太慢 |
| 链表 | O(n) | O(1) | O(1) | 查找最小值太慢 |
| 二叉搜索树 | O(log n) | O(log n) | O(log n) | 可能退化成链表O(n) |
| 红黑树 | O(1)(cached) | O(log n) | O(log n) | 平衡方案 |
| 哈希表 | 不适用 | O(1) | O(1) | 无法按顺序获取最小值 |
早期Linux(<4.14):每次获取最小vruntime需要从根遍历到最左节点(O(log n))。
优化后:rb_root_cached把rb_root和rb_node指针打包在一起,插入/删除时同步更新最左节点指针,获取最小值变成O(1)。
默认调度周期约6ms(HZ=250时)。每次时钟中断触发scheduler_tick():
来源:Linux内核04-进程调度与中断管理
锚点:伙伴系统按2的幂次管理空闲页,释放时检查伙伴是否也空闲,是则合并。这是解决外部碎片的经典方案。
内存总空闲量够用,但没有连续的大块,分配大内存失败。就像停车场有10个空位但分散在各处,停不进一辆大巴。
graph TD
A[空闲页总数16页] --> B[分配4页]
B --> C[16页拆成8+8]
C --> D[8页拆成4+4]
D --> E[返回4页给请求者]
E --> F[释放4页]
F --> G{伙伴也空闲?}
G -->|是| H[合并成8页]
G -->|否| I[保持4页空闲]
| order | 页数 | 大小(4KB页) | 用途 |
|---|---|---|---|
| 0 | 1 | 4KB | 最小分配 |
| 1 | 2 | 8KB | 小对象 |
| 2 | 4 | 16KB | 常见分配 |
| ... | ... | ... | ... |
| 10 | 1024 | 4MB | 大块分配 |
来源:Linux内核04-进程调度与中断管理
锚点:slab = 对象缓存池。频繁分配/释放小对象会导致页级内存碎片化,slab预分配一批对象,避免碎片+加速分配。
假设频繁kmalloc(sizeof(struct task_struct))(几百字节):
graph TD
A[slab缓存] --> B[对象池预分配]
B --> C[kmalloc取一个: O1]
C --> D[kfree归还: O1]
D --> B
A --> E[不够时向伙伴系统申请一整页]
E --> B
来源:Linux内核04-进程调度与中断管理
锚点:spin_lock_irqsave()在获取自旋锁的同时禁止本地中断并保存之前的中断状态。因为无条件开关中断可能破坏中断状态。
local_irq_disable(); // 中断已关闭
spin_lock_irq(&lock); // 内部会 local_irq_enable() -> 错误地打开了中断!
// 临界区执行(中断竟然是开的)
spin_unlock_irq(&lock); // 中断状态被破坏
spin_lock_irq()无条件打开中断,如果之前中断已经被禁止,释放锁时会错误地改变中断状态。
unsigned long flags;
spin_lock_irqsave(&lock, flags); // 禁止中断 + 保存状态到flags
// 临界区(中断关闭,安全)
spin_unlock_irqrestore(&lock, flags); // 恢复之前保存的中断状态
flags记录了进入前中断是开还是关,退出时恢复原状,不破坏任何状态。
| 函数 | 禁止什么 | 保存状态 | 适用场景 |
|---|---|---|---|
| spin_lock | 无 | 无 | 只需防止任务抢占 |
| spin_lock_bh | 下半部(softirq) | 无 | 进程上下文与下半部竞态 |
| spin_lock_irq | 本地中断 | 无 | 确定中断之前是开的 |
| spin_lock_irqsave | 本地中断 | 是 | 不确定中断之前的状态 |
来源:Linux驱动03-并发同步与原子操作
锚点:互斥体(mutex)和信号量(semaphore)都能做互斥,但互斥体支持优先级继承,信号量不支持。这是两者最关键的区别。
| 特性 | 互斥体 | 信号量 |
|---|---|---|
| 计数 | 只能为1(互斥) | 可以大于1(计数) |
| 优先级继承 | 支持 | 不支持 |
| 持有者跟踪 | 记录持有者TCB | 不记录 |
| 递归获取 | 不可以(会死锁) | 不可以 |
| ISR中使用 | 不可以 | 可以Give |
| 临界区可以睡眠 | 可以 | 可以 |
场景:TaskL(优先级2)持有互斥锁,TaskH(优先级4)等待锁,TaskM(优先级3)抢占TaskL,TaskH被间接阻塞(优先级翻转)。
互斥体的解决方案:记录持锁者TCB,当高优先级任务等待时,临时提升持锁任务优先级,TaskM无法抢占,TaskL尽快释放锁。
信号量没有这个机制,所以优先级翻转问题不解决。
graph TD
A{需要互斥?} -->|否| B[不需要锁/per-CPU变量]
A -->|是| C{临界区能睡眠?}
C -->|否| D[自旋锁/原子操作]
C -->|是| E{需要优先级继承?}
E -->|是| F[互斥体mutex]
E -->|否| G[信号量semaphore]
来源:Linux驱动03-并发同步与原子操作
锚点:中断处理分为上半部(硬中断,快速响应硬件)和下半部(延迟处理,做耗时工作)。下半部有五种机制,核心区别是:能否睡眠、并发约束、开销大小。
| 机制 | 执行上下文 | 能否睡眠 | 同一实例能并发? | 开销 |
|---|---|---|---|---|
| softirq | 中断上下文 | 不能 | 可以 | 最低 |
| tasklet | 中断上下文 | 不能 | 不能(同tasklet串行) | 低 |
| workqueue | 进程上下文 | 可以 | 可以 | 中 |
| threaded_irq | 内核线程 | 可以 | 可以 | 中 |
关键约束:同一个tasklet只能在一个CPU上运行,不同tasklet可以并行。同一tasklet内部不需要加锁保护共享数据。
适用:需要等待硬件响应(如I2C传输完成、DMA完成)、需要分配大量内存、需要获取互斥锁。
graph TD
A[中断发生] --> B{需要睡眠?}
B -->|是| C{需要复杂处理?}
C -->|是| D[threaded_irq]
C -->|否| E[workqueue]
B -->|否| F{同一tasklet不能并发?}
F -->|需要串行| G[tasklet]
F -->|不需要| H[softirq]
来源:Linux驱动03-中断下半部处理
| 设计选择 | 原因 |
|---|---|
| CFS用红黑树 | O(log n)插入删除 + O(1)获取最小值 |
| 伙伴系统管理页 | 2的幂次分配+合并,减少外部碎片 |
| slab管理小对象 | O(1)分配释放,零碎片,cache友好 |
| 自旋锁irqsave | 不破坏中断状态,安全保护临界区 |
| 互斥体有优先级继承 | 解决优先级翻转,保证实时性 |
| 中断下半部分层 | 上半部快(关中断),下半部慢(可睡眠) |