引言

Linux 内核的内存管理是操作系统的核心支柱之一。它不仅负责在有限物理内存条件下为每个进程提供巨大的虚拟地址空间,还要确保系统在高负载下依然保持稳定和高效。本文将深入剖析 Linux 内存管理的完整架构,从底层硬件抽象到上层分配算法,帮助读者建立系统性的理解。

一、内存管理架构总览

Linux 内存管理系统是一个分层的架构,从上到下依次是:用户空间接口层、内核虚拟内存管理层、页表管理层、物理内存分配层。每一层都有明确的职责和高效的实现策略。

1.1 地址空间布局

在 64 位 Linux 系统(x86_64)中,用户空间占用低 128TB(0x0000000000000000 到 0x00007FFFFFFFFFFF),内核空间占用高 128TB(0xFFFF800000000000 到 0xFFFFFFFFFFFFFFFF)。48 位虚拟地址的设计在可扩展性和性能间取得了良好平衡。

1.2 核心数据结构

内核中最关键的数据结构包括:task_struct(进程描述符)、mm_struct(内存描述符)、vm_area_struct(虚拟内存区域,也称为 VMA)、page(页描述符)、pgd_t/p4d_t/pud_t/pmd_t/pte_t(页表项)。每个进程通过 task_struct->mm 指向其 mm_struct,而 mm_struct 中的 VMA 链表和红黑树共同管理虚拟地址空间的各个区间。

二、物理内存管理

2.1 伙伴系统(Buddy System)

伙伴系统是 Linux 物理内存分配的基础算法。它将空闲页面组织为 11 个链表,分别管理 2^0 到 2^10(即 1 页到 1024 页,最大 4MB)大小的连续物理页块。当请求内存时,系统从合适大小的链表中取出一个页块,若不合适则递归拆分;释放时若相邻的"伙伴"页块也空闲,则递归合并以减少碎片。

2.2 页框分配器

页框分配器的入口函数是 alloc_pages() 和 。分配粒度按 2 的幂次对齐,常用 (可睡眠)、GFP_ATOMIC(不可睡眠,用于中断上下文)、GFP_DMA(DMA 可用内存)等标志。分配失败时会触发直接内存回收,甚至唤醒 OOM Killer。

2.3 页描述符(struct page)

系统中的每一页物理内存都对应一个 struct page 实例,全局数组 mem_map 或 维护了映射关系。关键字段包括:_refcount(引用计数)、_mapcount(映射计数)、flags(页状态标志 PG_locked、PG_dirty 等)、mapping(关联的地址空间)以及 index(在映射中的偏移)。

三、虚拟内存与页表

3.1 虚拟地址到物理地址的转换

x86_64 架构采用 4 级页表(PGD→PUD→PMD→PTE),将 48 位虚拟地址划分为多个索引位和 12 位页内偏移。每次内存访问都需要多次查表,这就是快表(TLB)存在的原因——TLB 是页表项的缓存,命中时几乎无额外开销。Linux 采用延迟分配策略:分配虚拟地址时不分配物理页,首次访问时触发缺页异常才真正完成映射。

3.2 多级页表的优势

平坦页表在 64 位空间下需要数十 GB 内存,而多级页表只在创建映射时才分配表项,极大节省内存。当一个 PMD 项指向一个大页(2MB 或 1GB)时,内核可以跳过最后一级,直接映射大页从而减少 TLB 缺失。

3.3 透明大页(Transparent Huge Pages, THP)

内核启用透明大页后,缺页异常处理时尝试分配 2MB 大页合并相邻的小页,可以使用更少 TLB 项覆盖相同内存,降低 TLB 缺失率。这大幅提升了数据库、虚拟化等密集型内存应用的性能。

四、内存分配器栈

4.1 SLAB 分配器家族

SLAB 是内核对象的专用缓存系统,由 Jeff Bonwick 在 Solaris 中首创并经 Linux 演化。它将相同类型的对象(如 task_struct、dentry、inode)分组到单个 SLAB 中,每个 SLAB 是一个或多个连续物理页。对象创建后不被立即释放,而是放入空闲链表供下次使用,从而避免频繁的页面级分配与初始化。

现代 Linux 默认使用 SLUB(Unqueued SLAB),它在 NUMA 系统上扩展性更好,减少了 per-CPU 队列的复杂度,却保持了 SLAB 的核心思想。SLOB 则是为极度受限环境设计的轻量替代品。

4.2 kmalloc/vmalloc

kmalloc() 基于 SLUB 分配器返回物理连续的虚拟地址,上限通常受碎片影响(几百 KB 到 128KB),适合设备 DMA。vmalloc() 返回虚拟连续、物理不一定连续的内存,上限受 vmalloc 区域大小限制(通常 4GB~数十 GB),虽然需要修改页表而略慢,但可分配大块内存。

4.3 用户空间内存分配

用户进程使用 malloc() 分配内存,底层通过 brk()/sbrk() 扩展数据段(适用于小块)或 mmap() 建立匿名映射(适用于大块,通常 128KB 以上)。CPU 通过 TLB 缓存加速访问。

五、内存回收与页面置换

5.1 LRU 算法

内核使用 LRU(最近最少使用)算法的变型——双链表 LRU 来分类页。每个页被放入活跃(active)或非活跃(inactive)链表。访问时硬件设置 Access 位引发缺页异常,内核将页从非活跃链表提升到活跃链表;内核定期扫描未访问的页,若仍未被重访,则降级并最终被回收。

5.2 页面回收机制

当空闲内存低于阈值时,内核唤醒 kswapd 后台进程回收内存。回收分为:干净页可直接丢弃;脏文件页写回文件;匿名页通过页面交换(swap)写入磁盘。如果内存紧急,direct_reclaim 会同步阻塞回收。

5.3 OOM Killer

当所有回收策略用尽,系统仍严重缺页时,OOM Killer 被触发。它根据 oom_score(与内存占用、运行时间、优先级、子进程数相关)选择进程杀死,释放其全部内存。管理员可以通过 /proc/[pid]/oom_score_adj 调整进程的 OOM 避免优先级。

六、实战工具与调优

6.1 诊断工具

  • free / vmstat / sar:查看内存总量、空闲、缓存、swap 使用情况
  • /proc/meminfo:详细内存统计(MemTotal、Buffers、Cached、SwapTotal、Active/Inactive 等)
  • top / htop / atop
  • /proc/[pid]/maps、smaps:查看单个进程 VMA 映射和每段详细内存
  • slabtop: 实时查看 slab 缓存占用,排查内核内存泄漏
  • perf probe + dtrace:追踪缺页异常、分配路径、内存抖动

6.2 关键参数调优

  • vm.swappiness(默认 60):控制系统匿名页与文件缓存的回收倾向,值越低越倾向于回收文件缓存而非 swap
  • vm.dirty_ratio / dirty_background_ratio:脏页比例阈值,影响 writeback 频率
  • vm.overcommit_memory:内存分配策略(0=启发式,1=总允许,2=禁止超过 CommitLimit)
  • kernel.sysrq:启用魔术键进行紧急内存诊断和强制 flush
  • transparent_hugepage=always|madvise|never:透明大页策略,数据库场景建议 madvise

七、总结

Linux 内存管理是整个操作系统中最复杂也最精妙的子系统之一。伙伴系统高效地解决了物理内存分配与碎片问题,SLUB/SLAB 提供了类型安全且低开销的内核对象分配,LRU 与按需分页实现了虚拟内存到物理内存的平滑映射与回收,而 OOM Killer 则为极端场景提供了最后的应急响应。理解这些机制不仅有助于编写高性能的应用程序,更是系统调优、内存泄漏排查以及内核开发的基石。随着持久内存(PMEM)和 CXL 技术的发展,Linux 内存层次持续演进,但核心的伙伴系统/LRU/分页思想依然是这一切的根基。

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部