ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Linux内存规整机制深度剖析:伙伴系统应对高阶外部碎片的核心算法

Linux内存规整机制深度剖析:伙伴系统应对高阶外部碎片的核心算法 Linux内存规整机制深度剖析伙伴系统应对高阶外部碎片的核心算法在 Linux 运维或高负载服务开发中经常能见到一种令人费解的现象通过free -m观察系统可用内存Available Memory还有数个吉字节GB但网卡驱动在尝试分配巨型帧Jumbo Frame连续 DMA 缓冲区时或者内核透明大页THP, Transparent Huge Pages尝试分配 2MB 连续物理内存时却频频报错-ENOMEM甚至直接引发进程长时间卡顿在内核直接内存回收状态。造成这一诡异现象的元凶正是物理内存外部碎片External Fragmentation。伙伴系统Buddy System将物理页按照 2 的幂次方组织为 Order 0 到 Order 104KB 到 4MB的空闲链表。当系统长期高频分配释放不同生命周期的内存后连续的大块物理页被严重打碎。为了在不杀进程、不重启系统的代价下重新拼凑出高阶连续内存Linux 内核设计了精妙的内存规整机制Memory Compaction。碎片根源与迁移类型隔离Migrate Types如果所有的物理内存页都可以随意搬移碎片整理将是一件水到渠成的事。但内核本身的静态数据结构、设备驱动的直接映射页表、硬件 DMA 锁定的页是绝对**不可移动Unmovable**的。一旦一个 4KB 的不可移动页恰好落在了某个 2MB 物理块的正中间整个 2MB 块就永久性地失去了合并为高阶大页的能力。为了从根本上遏制这种污染内核将物理页按迁移类型migratetype实行严格的物理隔离MIGRATE_UNMOVABLE分配给内核核心、中断栈、页全局目录等生命周期内物理地址不可变动。MIGRATE_RECLAIMABLE分配给文件系统元数据缓存如 Dentry、Inode Cache不可直接移动但在内存吃紧时可以通过收缩器Shrinker主动丢弃释放。MIGRATE_MOVABLE分配给用户态进程的匿名页堆、栈以及 Page Cache。由于用户态访问全部基于虚拟内存内核只要在底层将物理内容拷贝到新页面并原子更新进程的页表项PTE上层应用对其物理地址的漂移完全无感。内存规整的主战场正是针对MIGRATE_MOVABLE区域内散落的物理页进行空间重组。规整算法核心双指针相向扫描Dual Scanner内存规整的精髓在于其极其克制且高效的双指针扫描算法。规整并不在全局胡乱调换页面而是针对单个内存管理区Zone如 Zone Normal启动两个扫描器空闲扫描器Free Scanner从 Zone 的物理起始地址Zone Start PFN自底向上单向步进专门寻找处于未分配状态的空闲物理页将其作为迁入目标Target。迁移扫描器Migrate Scanner从 Zone 的物理末尾地址Zone End PFN自顶向下单向倒退专门寻找处于使用中且允许搬移的物理页将其作为迁出源Source。规整的工作流水线如下迁移扫描器从顶部圈定一批可移动页调用isolate_migratepages()将它们从活跃 LRU 链表上暂时剥离防止并发访问空闲扫描器从底部圈定等量大小的空闲页块内核调用底层架构的拷贝函数copy_highpage完成物理内容的完整复制利用反向映射RMAP机制遍历所有映射了源页面的进程页表更新其 PTE 并刷新 TLB 缓存原先位于顶部的源页被彻底释放并回归伙伴系统两个扫描器继续沿着相反方向推进直到两个指针在 Zone 中间相遇宣告一轮规整周期结束。通过这种“把尾部的零碎数据搬运到头部空闲洞中”的策略Zone 尾部的高阶物理块被大面积腾空。伙伴系统的伙伴合并机制Buddy Merge随即被激活相邻的阶为 $k$ 的页面自动熔合成阶为 $k1$ 的超大连续物理内存块。异步守护进程与同步直接规整的权衡规整并非全无代价频繁的内存拷贝和 TLB 刷表会带来显著的 CPU 抖动。内核通过双轨机制来平衡吞吐与延迟异步后台规整kcompactd每个 NUMA 节点绑定一个kcompactd内核线程。当高阶水位线High Watermark出现碎片预警时该线程在后台以低优先级慢慢整理避免业务线程感知。同步直接规整Direct Compaction当用户线程在分配关键高阶内存失败且 Direct Reclaim 依然凑不齐连续页时分配路径会被迫就地挂起进入深度的直接规整。此时若系统存在大量脏页往往会诱发严重的毫秒级甚至秒级“毛刺”Latency Spike。C23 实现高阶碎片指数解析与主动规整触发器在关键实时业务启动前工程上通常需要主动探测当前系统的碎片指数并在必要时显式向内核发起全局规整。以下是一段纯 C23 实现的碎片探测与干预工具。#include stdio.h #include stdlib.h #include string.h #include fcntl.h #include unistd.h constexpr const char *BUDDYINFO_PATH /proc/buddyinfo; constexpr const char *COMPACT_TRIGGER_PATH /proc/sys/vm/compact_memory; // 读取并打印伙伴系统中特定 Zone 的高阶Order 9 / 2MB空闲页数量 void analyze_zone_fragmentation(void) { FILE *fp fopen(BUDDYINFO_PATH, re); if (!fp) { perror(Failed to read buddyinfo); return; } char line[512]; printf(%-20s %-10s %-12s\n, Node/Zone, Order 0(4K), Order 9(2M)); printf(--------------------------------------------\n); while (fgets(line, sizeof(line), fp)) { char node[32], zone[32]; int orders[11]; // 解析 buddyinfo 格式: Node 0, zone Normal 120 45 12 ... int matches sscanf(line, %*s %s %*s %s %d %d %d %d %d %d %d %d %d %d %d, node, zone, orders[0], orders[1], orders[2], orders[3], orders[4], orders[5], orders[6], orders[7], orders[8], orders[9], orders[10]); if (matches 13) { char zone_label[64]; snprintf(zone_label, sizeof(zone_label), Node %s %s, node, zone); printf(%-20s %-10d %-12d\n, zone_label, orders[0], orders[9]); } } fclose(fp); } // 主动向内核下发强制规整指令 void trigger_proactive_compaction(void) { int fd open(COMPACT_TRIGGER_PATH, O_WRONLY | O_CLOEXEC); if (fd 0) { perror(Failed to open compact_memory (root privileges required)); return; } constexpr const char trigger_val 1; if (write(fd, trigger_val, 1) 0) { perror(Write to compact_memory failed); } else { printf([SUCCESS] Global memory compaction successfully requested.\n); } close(fd); } int main(void) { printf( Pre-Compaction Buddyinfo Snapshot \n); analyze_zone_fragmentation(); printf(\nTriggering Manual Memory Compaction...\n); trigger_proactive_compaction(); // 稍微休眠等待后台扫描器收敛 sleep(1); printf(\n Post-Compaction Buddyinfo Snapshot \n); analyze_zone_fragmentation(); return EXIT_SUCCESS; }生产环境参数调优指南对于延迟极度敏感的高并发存储与网络节点应当防患于未然调整vm.extfrag_threshold该内核参数控制内核倾向于分配规整还是直接回收取值范围在 0 到 1000 之间。将其设为 500 可以让内核在碎片初露端倪时便更早介入整理避免雪崩。警惕transparent_hugepagealways的陷阱在随机小写密集的数据库如 Redis、RocksDB中无脑开启总是分配巨页会在碎片严重时引发大面积的直接规整阻塞。生产上更稳健的做法是将 THP 设定为madvise仅在预先分配好连续大块内存的关键通道中通过显式系统调用享受大页 TLB 加速以此换取系统全局运行时的确定性。
返回列表