Linux 内核内存管理深度实战:从 Buddy 分配器到 Slab 与缺页处理全链路
物理内存是操作系统最稀缺的资源之一。Linux 内核如何在多任务、多核环境下高效分配和回收内存?本文从 Buddy 分配器、Slab 分配器、缺页异常、OOM Killer 到 NUMA 优化,带你走完内存管理的全链路。
一、为什么需要深入理解内核内存管理
用户态开发者通常通过 malloc/free 或编程语言内置的 GC 管理内存,很少关心底层细节。但作为系统程序员或内核开发者,理解内核内存分配机制至关重要:
性能调优:了解 Slab 分配器如何减少内部碎片,如何调整 Slab 缓存以优化高频小对象分配场景(如 dentry、inode)。
问题诊断:当系统出现内存泄漏、OOM 或内存碎片导致的分配失败时,理解 Buddy 分配器的阶(order)机制和页面迁移类型是排障的关键。
驱动开发:DMA 分配、一致性映射(Coherent Mapping)等操作直接依赖内核内存子系统的底层 API。
NUMA 优化:多核服务器上,本地内存与远程内存访问延迟可差 2-3 倍,NUMA 感知的分配策略直接影响应用吞吐。
二、物理内存模型:节点、区域、页面
2.1 NUMA 节点 (pg_data_t)
Linux 采用 NUMA(Non-Uniform Memory Access)模型组织物理内存。每个 NUMA 节点由一个 pg_data_t 结构体描述,包含该节点的所有内存区域和分配器状态:
struct pglist_data {
struct zone node_zones[MAX_NR_ZONES]; // 内存区域数组
struct zonelist node_zonelists[MAX_ZONELISTS]; // 分配回退列表
int nr_zones; // 节点中区域的数量
struct page *node_mem_map; // 页面描述符数组
unsigned long node_start_pfn; // 起始物理页帧号
...
};
2.2 内存区域 (Zone)
每个节点内部进一步划分为多个区域,不同区域服务于不同的分配需求:
ZONE_DMA:0-16MB,用于老式 ISA 设备的 DMA 传输,物理地址必须在 24 位地址空间内。
ZONE_DMA32:16MB-4GB,用于 32 位 DMA 设备(如早期 PCI 网卡、声卡),仅在 64 位系统上存在。
ZONE_NORMAL:直接映射区(在 x86_64 上通常是 1GB-4GB 范围内),内核通过固定的线性偏移(__PAGE_OFFSET)直接访问这些页面,无需建页表映射。
ZONE_HIGHMEM:高端内存(32 位系统中 >896MB 部分),内核需临时建立映射才能访问。64 位系统因地址空间充裕已基本弃用此区域。
ZONE_MOVABLE:可移动区域,专门用于可迁移页面(用户态页面、文件缓存),便于内存热插拔和碎片整理。
2.3 页面 (struct page)
物理内存的基本单位是页面(通常 4KB)。每个物理页面对应一个 struct page 描述符(约 64 字节)。node_mem_map 是一个巨大的 struct page 数组,覆盖节点内的所有物理页面:
struct page {
unsigned long flags; // 页面状态标志 (PG_locked, PG_dirty, PG_slab...)
atomic_t _refcount; // 引用计数
atomic_t _mapcount; // 映射计数(同时映射此页的进程数)
struct { union { unsigned long private; ... }; };
struct address_space *mapping; // FILE / ANON
pgoff_t index; // 在 mapping 内的偏移
struct list_head lru; // LRU 链表节点
void *virtual; // 虚拟地址(仅 LOWMEM 直接映射区有效)
...
};
关键概念:_refcount 记录页面的使用者数量(映射进程 + 缓存 + 内核引用等),为 0 时页面可被回收。_mapcount 记录页表映射数量(负值表示 exclusively mapped),直接影响 KSM 和 THP 决策。
三、Buddy 分配器:物理页面的分配引擎
3.1 设计思想
Buddy 分配器的核心目标只有两个:减少外部碎片和快速分配。它维护 11 个空闲链表(order 0~10),每个链表管理大小为 2^order 个连续页面的内存块:
// mm/page_alloc.c
struct zone {
struct free_area free_area[MAX_ORDER]; // 11 个空闲区域
// order 0: 1 页(4KB), order 1: 2 页(8KB), ...
// order 10: 1024 页(4MB)
} free_area[MAX_ORDER];
struct free_area {
struct list_head free_list[MIGRATE_TYPES]; // 按迁移类型分链
unsigned long nr_free; // 空闲块数量
};
3.2 分配流程:上拆 (Split)
当请求 N 个页面(向上取整到最近的 2 的幂)时,Buddy 分配器按如下步骤工作:
1. 计算所需阶:order = ceil(log2(n))
2. 从需求阶开始向上查找,若找到非空链表,执行"上拆":将大块对半拆分为两个 Order-1 的 "Buddy"(伙伴)。一半放入低阶链表,另一半继续拆分或返回。
3. 遍历所有区域(Zone)和回退列表(Reclaim、Remote Node),直到成功或失败。
分配 3 个页面 (order=2, 4页):
请求 order=2 → 链表空
找 order=3 → 找到 8 页块
拆分为 2 个 4 页块 (order=2)
一个放入 free_area[2]
一个返回给调用者 ✓
拆分过程:
8 页块 [P0|P1|P2|P3|P4|P5|P6|P7]
拆为 [P0|P1|P2|P3] + [P4|P5|P6|P7]
↑ Buddy A ↑ Buddy B
3.3 释放流程:下合 (Coalesce)
释放页面时,Buddy 分配器尝试与原配对的 Buddy 块合并(向上合并成更大的块),这一过程可递归进行:
释放 order=2 的 4 页块 [P0|P1|P2|P3]:
1. 计算 Buddy 的页帧号: bfn = pfn ^ (1 << order)
若 order=2, pfn=8: bfn = 8 ^ 4 = 12 → 块 [P4|P5|P6|P7]
2. 检查 Buddy 是否在 free_area[order] 链表中且同样空闲
3. 若 Buddy 空闲 → 合并为 order=3 的 8 页块
4. 继续向上尝试合并直到 MAX_ORDER-1
3.4 迁移类型:对抗碎片的核心武器
Linux 2.6.24 引入的页面迁移类型(Page Migration Types)是应对内存碎片化的关键设计。每个空闲链表中的页面按迁移类型分为 6 组:
MIGRATE_UNMOVABLE:不可移动页(内核数据结构、页表、内核栈等),无法被迁移或回收。
MIGRATE_MOVABLE:可移动页(用户态匿名页),可以通过页面迁移移动到任意位置。
MIGRATE_RECLAIMABLE:可回收页(文件页缓存、共享内存映射),无法迁移但可直接回收。
MIGRATE_PCP:每-CPU 热页缓存(Page Cache Pool),加速单页分配的 per-cpu 页面池。
MIGRATE_ISOLATE:隔离用,用于内存热插拔和 CMA。
MIGRATE_CMA:连续内存分配器(Contiguous Memory Allocator),为需要大物理连续块的设备预留。
核心思想:将容易回收/移动的页面和难以移动的页面分到不同的区域,使得相同类型的页面聚集在一起,降低碎片化概率。这也是 ZONE_MOVABLE 存在的原因——MOVABLE 类型的页面集中管理,避免 UNMOVABLE 页面散布其中阻碍大块合并。
3.5 水位线机制 (Watermarks)
每个 Zone 有三个水位线,控制内存回收和分配阻塞行为:
High ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
| 充裕区:kswapd 正常回收
Low ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
| 紧张区:直接回收 (Direct Reclaim)
Min ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
| 危险区:所有分配器阻塞,唤醒紧急回收
0 ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
当空闲页面低于 pages_min 时,分配器进入同步回收模式,延迟分配至内存足够;低于 pages_low 时唤醒 kswapd 后台回收线程;高于 pages_high 时 kswapd 进入休眠。这些值可根据 min_free_kbytes 内核参数自动比例计算。
四、Slab 分配器:小对象分配的高速公路
4.1 为什么需要 Slab
Buddy 分配器以页面(4KB)为粒度分配,但内核经常需要分配远小于页面大小的对象(典型如 task_struct ~1.7KB、dentry ~300B、inode ~600B)。直接在 Buddy 分配器上操作会产生大量内部碎片(一个 task_struct 占用 4KB 页面,浪费近一半空间),且频繁映射页表项(TLB 压力)。
Slab 分配器在 Buddy 分配器之上构建对象缓存层,将预分配的页面"切分"为固定大小的对象槽(slot),实现了接近 O(1) 的对象分配/释放。
4.2 历史沿革:SLAB → SLUB → SLOB
SLAB:Solaris 操作系统引入的经典实现,Linux 早期版本采用。每个缓存包含完整链表(完全不空闲的 slab)、部分 slab 链表、空闲 slab 链表。问题:多核扩展性差(全局锁竞争)、管理复杂。
SLUB(默认):简化版 SLAB,去掉了复杂的链表管理,所有 CPU 共享同一个 kmem_cache。每个 slab 维护内嵌式空闲链表,支持 per-CPU 部分页面缓存减少锁竞争。目前 Linux 默认配置。
SLOB:面向嵌入式系统的超精简实现,用首次适应算法管理,仅适合极小内存设备。
4.3 SLUB 内部结构
struct kmem_cache {
struct kmem_cache_cpu *cpu_slab; // per-CPU 快速路径
unsigned long flags; // 对象对齐等标志
unsigned int size; // 对象实际大小
unsigned int object_size; // 用户请求大小
unsigned int offset; // 空闲指针偏移
struct kmem_cache_node *node[MAX_NUMNODES]; // 每 NUMA 节点慢速路径
struct kmem_cache_order_objects oo; // 每次从 Buddy 获取 2^order 页,分为 oo 个对象
const char *name; // 缓存名称
struct list_head list; // 全局缓存链表
int refcount;
void (*ctor)(void *); // 构造器
...
};
struct kmem_cache_cpu {
void **freelist; // 空闲对象链表头
unsigned long tid; // 全局事务 ID(防止 ABA 问题)
struct page *page; // 当前内存页
struct page *partial; // per-CPU 部分空闲页
};
struct kmem_cache_node {
spinlock_t list_lock;
unsigned long nr_partial; // 部分空闲 slab 数量
struct list_head partial; // 部分空闲 slab 链表
};
4.4 分配路径
快速路径(无锁):直接检查 cpu_slab->freelist,取出链表头对象,当前 slab 无空闲对象时回退到慢速路径。
慢速路径(per-node 锁):从 cpu_slab->partial 链表中分配一个部分空闲页;若为空,从 node 的 partial 链表取;若都为空,向 Buddy 分配器请求一个新页面作为 slab。
释放路径:将对象重新链入 freelist(链表头插入)。若 slab 全部空闲后页面未被及时释放(延迟释放策略),最终被返回给 Buddy 分配器。
4.5 关键 Slab 缓存
内核中几个最重要的自定义缓存及其典型对象大小:
| 缓存名称 | 对象 | 典型大小 | 说明 |
|---|---|---|---|
| task_struct_cachep | 进程描述符 | ~1.7KB (x86_64) | 每个进程/task 一个 |
| mm_cachep | 内存描述符 | ~1KB | 每个进程地址空间一个(线程共享) |
| vm_area_struct_cachep | VMA | ~200B | 每个 mmap 区域一个 |
| dentry_cache | 目录项 | ~300B | 路径查找缓存 |
| inode_cachep | 索引节点 | ~600B | 文件元数据 |
| filp_cachep | 文件对象 | ~256B | 每个 open() 调用一个 |
| names_cachep | 路径名 | ~256B | 路径组件缓存 |
| cred_jar | 进程凭据 | ~192B | 安全凭证(uid/gid/cap) |
使用 slabtop 命令查看系统中各 Slab 缓存的实时使用状态:
$ slabtop -o
Active / Total Objects (% used) : 1423582 / 1457411 (97.7%)
Active / Total Slabs (% used) : 42042 / 42042 (100.0%)
Active / Total Caches (% used) : 142 / 142 (...)
Active / Total Size (% used) : 456284.55K / 469876.47K (97.1%)
Minimum / Average / Maximum Object : 0.01K / 0.32K / 8.00K
OBJS ACTIVE USE OBJ SIZE SLABS OBJ/SLAB CACHE SIZE NAME
542910 542910 100% 0.10K 13927 39 55708K dentry
397152 397152 100% 0.04K 3879 102 15516K kmalloc-32
175488 175488 100% 0.19K 8356 21 33424K vm_area_struct
98800 98800 100% 0.66K 12350 8 98800K inode_cache
79920 79920 100% 0.19K 3804 21 15216K filp
五、虚拟内存缺页处理
5.1 缺页类型
当 CPU 访问的虚拟地址尚未建立有效页表映射时,触发缺页异常(Page Fault)。Linux 内核根据具体情况将其分为:
Minor Fault(次缺页):所需页面在内核的物理页缓存(Page Cache)中,仅需建立页表映射。耗时约微秒级。
Major Fault(主缺页):所需页面不在内存中,需从磁盘/交换区读取。耗时约毫秒级(SSD ~0.1ms,HDD ~10ms),对延迟极其敏感。
Invalid Fault(非法访问):访问未映射的地址空间,触发 SIGSEGV 信号或内核 oops。
5.2 缺页处理流程
do_page_fault(regs, error_code)
→ find_vma(addr) // 查找包含 addr 的 VMA
| 未找到 → bad_area → SIGSEGV
→ handle_mm_fault()
| 匿名映射 (Anonymous):
│ do_anonymous_page()
│ → alloc_zeroed_user_highpage() // 分配零页面
│ → mk_pte() // PTE 设置
│ → set_pte_at() // 写入页表
│
| 文件映射 (File-backed):
│ filemap_fault()
│ → pagecache_get_page() // 查找/创建缓存页
│ → 若在 Page Cache: lock + map (Minor Fault)
│ → 若不在:
│ → page_cache_alloc() 分配新页面
│ → mapping->a_ops->readpage() 从磁盘读取 (Major Fault)
│ → mk_pte + set_pte_at
│
| COW (写时复制):
│ do_wp_page()
│ → pte_write() 判断可写权限
│ → page_mapcount > 1: 分配新页 + 复制内容
│ → 否则: 提升写权限 (Reuse)
│
| Special (设备/大页):
│ vmf_insert_pfn() / hugetlb_fault()
5.3 反向映射 (Reverse Mapping)
内核回收页面时需将映射该页面的所有 PTE 项清除或更新。如果每次回收都扫描所有进程的页表,开销极为巨大。反向映射(Reverse Mapping, rmap)维持了从物理页面到所有映射其的虚拟地址(VMA)的反向索引:
物理页面 X 被映射到:
进程 A 的 VMA_1 → VA 0x7f1000
进程 B 的 VMA_2 → VA 0x7f2000 (共享库)
进程 C 的 VMA_3 → VA 0x7f3000
rmap 链表: page X → [anon_vma A, vaddr 0x7f1000]
→ [anon_vma B, vaddr 0x7f2000]
→ [anon_vma C, vaddr 0x7f3000]
对于匿名页面(用户态堆/bss),通过 anon_vma 链表维护;对于文件页面(Page Cache),通过 address_space 的 radix tree 查找。这使得回收和页面迁移操作无需扫描全系统进程。
六、页面回收与 swap
6.1 LRU 算法
Linux 内核使用近似 LRU(Least Recently Used)算法决定哪些页面应该被回收。每个 Zone 维护两个 LRU 链表:
Active 链表:最近访问过的页面(PG_referenced 标志位被置位),优先保留。
Inactive 链表:长期未访问的页面,回收候选。
页面从 Active 尾部转移到 Inactive 尾部(deactivate),在 Inactive 链表尾部的页面最先被回收。当页面在 Inactive 状态被再次访问时,提升到 Active 头部(promote)。这就是"二次机会法"。
6.2 Swap 机制
当系统内存不足且可回收页面不够时,内核将匿名页面写入 Swap 分区/文件:
1. 选定 Inactive 匿名链表尾部的页面
2. 通过反向映射清除所有 PTE 映射(PTE 变为 swap_entry 编码)
3. 分配 swap slot,写入磁盘
4. 页面返回 Buddy 分配器
当进程再次访问被 swap out 的页面时,触发缺页异常,从 swap 读回内存,重新建立映射。
关键参数 /proc/sys/vm/swappiness(0-200):
swappiness=60(默认):匿名页面和文件页面回收权重平衡。
swappiness=0:尽可能不 swap,优先回收 Page Cache(数据库服务推荐)。
swappiness=100:匿名页面和文件页面同等权重回收。
swappiness=200:过度 swap 匿名页面(极少使用)。
七、OOM Killer:内存耗尽的最后防线
7.1 触发条件
当系统所有 Zone 的水位线全部降到最低、直接回收和后台回收都已用尽、kswapd 持续失败时,OOM Killer(Out-Of-Memory Killer)被触发,选择一个"最该死"的进程强制杀除以释放内存。
7.2 选择算法 (badness score)
内核通过 oom_badness() 函数为每个进程计算一个分数(0~1000),分数越高越可能被选中:
score = (进程总占用内存 / 总物理内存) * 1000
调整因子:
+ CAP_SYS_ADMIN 进程: score *= 8 (更不容易被选中)
+ oom_score_adj: -1000(免疫) ~ +1000(优先) 用户可调整
+ mlock 内存: 不参与计算
+ 运行时间 < 1s: 一次性进程偏好
+ 短时峰值: 指数移动平均平滑
实操中,可以通过 /proc/[pid]/oom_score 和 /proc/[pid]/oom_score_adj 控制进程的 OOM 优先级。 systemd 保护关键服务的最佳实践是在 .service 文件中设置 OOMScoreAdjust=-900
7.3 现代内核的 OOM 行为变化
Linux 4.6+:引入 oom_reaper,异步收割已选中的进程页面,无需等待进程退出。
Linux 4.16+:cgroup v2 支持 memory.oom.group,OOM 可以作用于整个 cgroup。
Linux 6.7+:引入 PSI(Pressure Stall Information)、用户态 OOM killer(systemd-oomd)作为替代方案。
八、NUMA 感知分配
8.1 NUMA 架构简述
NUMA 服务器上,每个 CPU 节点附近的内存(本地节点)访问延迟最低,访问其他节点的内存(远程节点)延迟更高。典型 2 路服务器上:
Node 0 (CPU 0-31): 128GB DRAM, 本地延迟 ~80ns
Node 1 (CPU 32-63): 128GB DRAM, 本地延迟 ~80ns
跨 Node 1 访问 Node 0 内存: ~140ns (延迟差 1.75x)
8.2 NUMA 分配策略
本地优先 (Local Allocation):默认策略,进程从运行所在的 CPU 所属节点分配内存。
交错分配 (Interleaving):通过 MPOL_INTERLEAVE 策略,内存交替分配到不同节点,最大化内存带宽。适用于大内存并行计算(HPC、科学仿真)。
首选节点 (Preferred):MPOL_PREFERRED,优先分配指定节点但不强制绑定。
绑定 (Bind):MPOL_BIND,强制从指定节点列表分配,若全部节点不足则触发 OOM。
最佳实践应用 NUMA 策略的常见场景:
数据库服务(如 PostgreSQL、Redis)使用 numactl --interleave=all 启动,确保共享 buffer pool 跨节点分布均匀。
高吞吐网络转发(DPDK + KNI)绑定到网卡所在节点,确保网卡 DMA 从本地节点读写。
九、CMA 连续内存分配器
部分设备(GPU、视频编解码器、DMA 控制器)需要大块物理连续内存(通常数 MB 到数十 MB)。传统预分配方案浪费内存,CMA 允许这些大块区域在平时被可迁移页面占用,设备需要时回收后再分配。
CMA 区域初始化:
1. Boot 时通过 "cma=64M@0-4G" 保留大小,但标记为 MOVABLE
2. 非 CMA 进程可使用此区域(Buddy 分配器允许分配 MOVABLE)
3. 设备驱动请求时:
a. 调用 cma_alloc()
b. 触发迁移/回收已使用的 MOVABLE 页面
c. 返回连续物理内存块
十、实战调试
10.1 查看 Buddy 分配器状态
$ cat /proc/buddyinfo
Node 0, zone DMA 1 0 0 1 1 0 1 1 1 1 3
Node 0, zone DMA32 1234 567 234 123 45 23 12 5 2 1 0
Node 0, zone Normal 8765 4321 2109 876 345 123 67 34 12 3 1
# 可以看到 Normal zone 最高 order 页面数(order 10 = 4MB 块仅 1 个)
10.2 查看 Zone 水位线
$ cat /proc/zoneinfo | grep -E "Node|zone|low|min|high|free"
Node 0, zone Normal
pages free 2867550
min 131072
low 163840
high 196608
...
nr_free_pages 2867550
nr_zone_inactive_anon 123456
nr_zone_active_anon 234567
nr_zone_inactive_file 345678
nr_zone_active_file 456789
nr_kernel_misc_reclaimable 1024
# free 远高于 high → 内存充裕
# free 接近 min → 触顶危险
10.3 监控 Major Fault
$ ps -o pid,min_flt,maj_flt,cmd -p $(pgrep mysqld)
PID MINFLT MAJFLT CMD
12345 5829301 82341 /usr/sbin/mysqld
# MAJFLT 持续增长 → 内存不足,频繁从磁盘加载
# 使用 perf 追踪缺页延迟:
$ perf stat -e page-faults,dTLB-load-misses -p 12345
10.4 诊断 Slab 泄漏
# 观察 Slab 持续增长但 ACTIVE 对象数稳定的异常
$ watch -n 1 'cat /proc/slabinfo | sort -k3 -rn | head -5'
# 使用 slabtop 实时追踪
$ slabtop -s c # 按缓存大小排序
# 若怀疑 Leak,使用 kmemleak(需编译时开启 CONFIG_DEBUG_KMEMLEAK)
$ echo scan > /sys/kernel/debug/kmemleak
$ cat /sys/kernel/debug/kmemleak # 查看未释放对象的调用栈
10.5 OOM 事件分析
# 查看 OOM 触发历史
$ dmesg | grep -i "out of memory"
[50234.567] Out of memory: Killed process 6789 (java)
total-vm:8388608kB, anon-rss:4194304kB, file-rss:...
# OOM 核心决策日志
$ dmesg | grep "oom-kill\|oom_reaper\|Out of memory"
[50234.568] oom-kill: memory limit: 409600kB, task: java, pid: 6789
[50234.568] oom_reaper: reaped process 6789 (java)
# 使用 vm.stat 监控
$ grep -E "oom_kill|compact_|scan$" /proc/vmstat
oom_kill 3 # 累计 OOM kill 次数
compact_stall 45 # 内存紧凑 stall 次数
compact_fail 12 # 内存紧凑失败次数 → 碎片化严重
十一、性能选型决策树
你的内存分配场景是什么?
│
├─ 需要≤4KB 物理连续 (DMA/PAGETABLE)
│ ├─ GFP_DMA: 使用 ZONE_DMA 分配器
│ ├─ dma_alloc_coherent(): CMA + 一致性映射
│ └── kmalloc(): 底层走 Slab + Buddy,≤4MB 物理连续
│
├─ 仅需虚拟连续 (vmalloc 区域)
│ ├── vmalloc(): 虚拟连续但物理不一定连续
│ ├── vmap(): 映射已有页面集合成虚拟连续区
│ └── kvmalloc(): 优先 kmalloc,失败回退 vmalloc
│
├─ 小对象高频分配 (size ≤4KB)
│ ├── kmem_cache_alloc(): 自定义缓存,O(1) 分配
│ └── kmalloc(): 通用 Slab 缓存,最常用
│
└─ 大页/巨页场景
├── alloc_pages(): 直接 Buddy 分配,返回 struct page *
├── alloc_page(): 2^0 = 1 页
└── HugeTLB/THP: 绕过 Buddy,虚拟透明大页
十二、总结
Linux 内核内存管理是一个精密的多层系统:
底层:Buddy 分配器管理物理页面,通过上拆下合减少外部碎片,迁移类型对抗碎裂,水位线触发回收。
中层:Slab/SLUB 分配器在 Buddy 之上构建对象缓存,以 O(1) 速度满足小对象分配需求,per-CPU 设计提升多核扩展性。
上层:缺页异常处理虚拟地址映射,反向映射加速页面回收。LRU 算法决定谁留下谁离开,OOM Killer 是最后的防线。
拓扑:NUMA 感知分配策略让内存贴近 CPU,CMA 服务大连续块设备需求。
理解这些机制,你就能在面临内存紧张、延迟抖动、OOM 灾难时,从"祈祷式重启"变为"精准诊断"。内核不会自动完成一切,但提供了完整的观察接口(/proc/buddyinfo、/proc/zoneinfo、/proc/slabinfo、PSI)调优参数(min_free_kbytes、swappiness、vm.min_unmapped_ratio),让系统程序员完全掌控。

发表评论 取消回复