graph TB
A["kmem_cache_alloc"] --> B["1. per-CPU 缓存\n无锁,最快"]
B --> C{"有空闲对象?"}
C -->|"是"| D["返回对象\n无锁"]
C -->|"否"| E["2. partial 链表"]
E --> F{"partial 有?"}
F -->|"是"| G["取 slab\n更新 per-CPU"]
F -->|"否"| H["3. 伙伴系统\n分配新 slab"]
H --> I["返回对象"]
style A fill:#C7CEEA,stroke:#9FA8DA,color:#333
style B fill:#B5EAD7,stroke:#80CBC4,color:#333
style C fill:#FFF9C4,stroke:#F9A825,color:#333
style D fill:#B5EAD7,stroke:#80CBC4,color:#333
style E fill:#FFDAB9,stroke:#FFAB76,color:#333
style F fill:#FFF9C4,stroke:#F9A825,color:#333
style G fill:#FFDAB9,stroke:#FFAB76,color:#333
style H fill:#FFB3C6,stroke:#F48FB1,color:#333
style I fill:#B5EAD7,stroke:#80CBC4,color:#333
关键点:
per-CPU 缓存——无锁分配,最快路径
partial 链表——多核争用,效率高
伙伴系统——慢路径,分配新页
1.4 为什么 per-CPU 缓存快?
graph TB
A["CPU 0\nper-CPU 缓存"] -->|"独占"| B["对象 X"]
C["CPU 1\nper-CPU 缓存"] -->|"独占"| D["对象 Y"]
A -.-|"无锁"| E["无竞争"]
C -.-|"无锁"| E
style A fill:#B5EAD7,stroke:#80CBC4,color:#333
style B fill:#FFF9C4,stroke:#F9A825,color:#333
style C fill:#B5EAD7,stroke:#80CBC4,color:#333
style D fill:#FFF9C4,stroke:#F9A825,color:#333
style E fill:#FFDAB9,stroke:#FFAB76,color:#333
优势:
无锁(避免 cache bouncing)
CPU 局部性好(对象常被同一 CPU 用)
分配延迟可预测
1.5 kmalloc 与 slab 的关系
graph TB
A["kmalloc(64)"] --> B["kmalloc-64\n(slab 缓存)"]
C["kmalloc(128)"] --> D["kmalloc-128\n(slab 缓存)"]
E["kmalloc(256)"] --> F["kmalloc-256\n(slab 缓存)"]
G["kmalloc(8192)"] --> H["伙伴系统\n(2 个 order-1 页)"]
style A fill:#C7CEEA,stroke:#9FA8DA,color:#333
style B fill:#B5EAD7,stroke:#80CBC4,color:#333
style C fill:#C7CEEA,stroke:#9FA8DA,color:#333
style D fill:#B5EAD7,stroke:#80CBC4,color:#333
style E fill:#C7CEEA,stroke:#9FA8DA,color:#333
style F fill:#B5EAD7,stroke:#80CBC4,color:#333
style G fill:#C7CEEA,stroke:#9FA8DA,color:#333
style H fill:#FFDAB9,stroke:#FFAB76,color:#333
graph TB
A["内存紧张"] --> B["释放一些页"]
B --> C["释放的来源"]
C --> D["page cache"]
C --> E["用户进程页"]
C --> F["swap"]
style A fill:#FFB3C6,stroke:#F48FB1,color:#333
style B fill:#FFDAB9,stroke:#FFAB76,color:#333
style C fill:#C7CEEA,stroke:#9FA8DA,color:#333
style D fill:#B5EAD7,stroke:#80CBC4,color:#333
style E fill:#E8D5F5,stroke:#CE93D8,color:#333
style F fill:#FFF9C4,stroke:#F9A825,color:#333
3.2 LRU(最近最少使用)
graph TB
A["LRU 链表"] --> B["active_list\n活跃 LRU"]
A --> C["inactive_list\n不活跃 LRU"]
B --> B1["刚被访问的页"]
B --> B2["会保留在内存"]
C --> C1["可能被回收"]
C --> C2["swap / drop"]
style A fill:#C7CEEA,stroke:#9FA8DA,color:#333
style B fill:#B5EAD7,stroke:#80CBC4,color:#333
style C fill:#FFDAB9,stroke:#FFAB76,color:#333
style B1 fill:#FFF9C4,stroke:#F9A825,color:#333
style B2 fill:#FFF9C4,stroke:#F9A825,color:#333
style C1 fill:#FFF9C4,stroke:#F9A825,color:#333
style C2 fill:#FFF9C4,stroke:#F9A825,color:#333
3.3 Linux 4.8+:多代 LRU(Multi-Gen LRU)
graph LR
A["Gen 0\n(新)"] --> B["Gen 1"]
B --> C["Gen 2"]
C --> D["Gen MAX"]
D --> E["回收"]
style A fill:#B5EAD7,stroke:#80CBC4,color:#333
style B fill:#B5EAD7,stroke:#80CBC4,color:#333
style C fill:#FFDAB9,stroke:#FFAB76,color:#333
style D fill:#FFB3C6,stroke:#F48FB1,color:#333
style E fill:#F48FB1,stroke:#FFB3C6,color:#333
graph TB
A["kswapd 内核线程\n每 NUMA 节点一个"] --> B["监控水位"]
B --> C{"水位 < LOW?"}
C -->|"是"| D["开始回收"]
C -->|"否"| B
D --> E["回收匿名页 → swap"]
D --> F["回收文件页 → drop"]
style A fill:#C7CEEA,stroke:#9FA8DA,color:#333
style B fill:#FFDAB9,stroke:#FFAB76,color:#333
style C fill:#FFF9C4,stroke:#F9A825,color:#333
style D fill:#FFB3C6,stroke:#F48FB1,color:#333
style E fill:#B5EAD7,stroke:#80CBC4,color:#333
style F fill:#B5EAD7,stroke:#80CBC4,color:#333
4.2 kswapd 回收流程
graph TB
A["kswapd wakeup"] --> B["shrink_lruvec"]
B --> C["扫描 inactive LRU"]
C --> D{"页可回收?"}
D -->|"匿名页"| E["swap out"]
D -->|"文件页"| F["drop 或 writeback"]
E --> G["页加入 free list"]
F --> G
G --> H{"水位 > HIGH?"}
H -->|"否"| C
H -->|"是"| I["kswapd sleep"]
style A fill:#C7CEEA,stroke:#9FA8DA,color:#333
style B fill:#E8D5F5,stroke:#CE93D8,color:#333
style C fill:#FFDAB9,stroke:#FFAB76,color:#333
style D fill:#FFF9C4,stroke:#F9A825,color:#333
style E fill:#FFB3C6,stroke:#F48FB1,color:#333
style F fill:#FFB3C6,stroke:#F48FB1,color:#333
style G fill:#B5EAD7,stroke:#80CBC4,color:#333
style H fill:#FFF9C4,stroke:#F9A825,color:#333
style I fill:#C7CEEA,stroke:#9FA8DA,color:#333
4.3 直接回收(Direct Reclaim)
sequenceDiagram
participant App as 应用进程
participant Alloc as 分配路径
participant Page as 页面分配
participant Kswapd as kswapd
App->>Alloc: kmalloc(...)
Alloc->>Page: __alloc_pages
Page->>Page: 检查高水位
Page-->>Alloc: ❌ 低水位
Alloc->>Page: try_to_free_pages
Page->>Alloc: 同步回收
Alloc-->>App: 返回内存
两种回收模式对比:
维度
kswapd
直接回收
触发
异步(水位低)
同步(分配时)
性能影响
后台
阻塞进程
优先级
低
高
4.4 关键启示
kswapd = 后台回收——提前回收到高水位
直接回收 = 同步——分配路径上同步回收
NUMA 每节点一个——并行回收
OOM 是最后手段——回收不出来就杀进程
五、Swap 机制
5.1 Swap 是什么?
graph LR
A["物理内存"] -->|"swap out"| B["Swap 设备/文件\n(磁盘)"]
B -->|"swap in"| A
style A fill:#C7CEEA,stroke:#9FA8DA,color:#333
style B fill:#FFDAB9,stroke:#FFAB76,color:#333
Swap = 把不活跃的匿名页换出到磁盘。
5.2 Swap 设备 vs Swap 文件
维度
Swap 设备
Swap 文件
性能
更快(连续)
稍慢(碎片)
灵活性
低(独立分区)
高(任意文件)
空间管理
简单
复杂
5.3 Swap 触发条件
graph TB
A["触发 swap"] --> B["匿名页不被访问"]
B --> C["回收时优先选择"]
C --> D["写入 swap"]
A --> E["系统内存压力大"]
E --> F["主动换出"]
F --> D
style A fill:#C7CEEA,stroke:#9FA8DA,color:#333
style B fill:#FFDAB9,stroke:#FFAB76,color:#333
style C fill:#FFF9C4,stroke:#F9A825,color:#333
style D fill:#FFB3C6,stroke:#F48FB1,color:#333
style E fill:#FFDAB9,stroke:#FFAB76,color:#333
style F fill:#FFF9C4,stroke:#F9A825,color:#333
5.4 Swap 算法
1 2 3
// mm/swap.c // Linux 4.0+:基于时钟的 swap 算法 // 不再用纯 LRU——避免"循环颠簸"
graph TB
A["Swap 入口"] --> B["Clock 算法"]
B --> C{"页 A_bit == 0?"}
C -->|"是"| D["换出"]
C -->|"否"| E["清 A_bit"]
E --> F["下一轮"]
style A fill:#C7CEEA,stroke:#9FA8DA,color:#333
style B fill:#E8D5F5,stroke:#CE93D8,color:#333
style C fill:#FFF9C4,stroke:#F9A825,color:#333
style D fill:#FFB3C6,stroke:#F48FB1,color:#333
style E fill:#B5EAD7,stroke:#80CBC4,color:#333
style F fill:#B5EAD7,stroke:#80CBC4,color:#333
5.5 关键启示
Swap = 内存的”延展”——磁盘当内存用
匿名页 swap——文件页 drop/writeback
Swap 不是越多越好——过度 swap 会颠簸
现代建议:禁用 swap 或小 swap
六、内存回收的优先级
6.1 4 大回收源
graph TB
A["内存回收"] --> B["1. slab\n对象缓存"]
A --> C["2. page cache\n文件缓存"]
A --> D["3. 匿名页\n→ swap"]
A --> E["4. 不可回收\nmlock"]
style A fill:#C7CEEA,stroke:#9FA8DA,color:#333
style B fill:#E8D5F5,stroke:#CE93D8,color:#333
style C fill:#FFDAB9,stroke:#FFAB76,color:#333
style D fill:#FFB3C6,stroke:#F48FB1,color:#333
style E fill:#F48FB1,stroke:#FFB3C6,color:#333
graph TB
A["内存严重不足"] --> B["尝试回收"]
B --> C["回收不出来"]
C --> D["OOM Killer"]
D --> E["选择进程杀掉"]
style A fill:#FFB3C6,stroke:#F48FB1,color:#333
style B fill:#FFDAB9,stroke:#FFAB76,color:#333
style C fill:#FFB3C6,stroke:#F48FB1,color:#333
style D fill:#F48FB1,stroke:#FFB3C6,color:#333
style E fill:#F48FB1,stroke:#FFB3C6,color:#333
# vmstat 1 procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu----- r b swpd free buff cache si so bi bo in cs us sy id wa st 0 0 0 2048000 512000 4096000 0 0 0 0 100 200 5 3 92 0 0
graph TB
A["内存分配器与回收核心"] --> B["Slab 分配器\nper-CPU 缓存"]
A --> C["专用缓存\nkmem_cache"]
A --> D["LRU 链表\n多代 LRU"]
A --> E["kswapd\n异步回收"]
A --> F["Swap + OOM\n最后防线"]
style A fill:#C7CEEA,stroke:#9FA8DA,color:#333
style B fill:#E8D5F5,stroke:#CE93D8,color:#333
style C fill:#FFDAB9,stroke:#FFAB76,color:#333
style D fill:#B5EAD7,stroke:#80CBC4,color:#333
style E fill:#FFB3C6,stroke:#F48FB1,color:#333
style F fill:#F48FB1,stroke:#FFB3C6,color:#333