Linux 内存管理模块

Linux内存管理是内核中最复杂、最关键的部分之一。它负责在有限的物理内存资源下,为进程提供安全、高效且易用的虚拟内存环境。

以下是Linux内存管理模块的深度解析,从核心概念到具体实现机制:

1. 核心概念:虚拟内存

这是理解一切的基础。

  • 为什么需要虚拟内存?

    • 隔离性: 每个进程拥有独立的地址空间,不会互相干扰,增强了系统的稳定性和安全性。

    • 抽象: 进程看到的是一块连续的巨大内存(例如 64位系统下的 128TB),而不必关心物理内存的实际大小和碎片情况。

    • 共享: 可以方便地将共享库的同一份物理内存映射到不同进程的虚拟地址空间中。

  • MMU(内存管理单元): CPU 内部的硬件单元,负责在指令执行时,将虚拟地址实时转换为物理地址。Linux 内核负责建立和维护这个转换规则表------页表

2. 地址空间布局

在 Linux 中,虚拟地址空间分为两部分:

  • 用户空间: 每个进程独有。包含代码段、数据段、堆(向高地址生长)、内存映射段(文件映射、共享库)、栈(向低地址生长)。

  • 内核空间: 所有进程共享。存放内核代码、内核数据结构、内核栈等。

    • 重要区别: 在 32位系统中,通常采用 3:1 分割(3G 用户/1G 内核)。在 64位系统中,空间巨大,分割比例不同。

3. 物理内存管理

内核如何管理有限的物理内存?

  • 伙伴系统

    • 作用: 解决物理内存分配中的外部碎片问题。

    • 原理: 将内存按 2 的幂次方(2^order)划分成块(Page Block)。分配时,系统会从满足大小的最小块中分配。释放时,系统会检查其伙伴块是否也空闲,如果是,则合并成更大的块。

    • 单位: 最小单位是 页框(Page Frame),通常大小为 4KB。

  • Slab 分配器

    • 作用: 解决内部碎片 问题,并针对内核中频繁分配的小对象(如 task_structinode)进行优化。

    • 原理: 基于伙伴系统分配的连续大块内存,将其划分为多个相同大小的对象缓存。当内核需要某个对象时,直接从缓存队列中取一个初始化好的对象,用完后放回,避免频繁的初始化。

  • NUMA 支持

    • 在多处理器系统中,内存访问时间可能因内存和 CPU 的物理距离而异。Linux 将内存划分为多个节点(Node),每个节点又分为多个管理区(Zone),力求让 CPU 优先访问本地内存。

4. 内存分配与回收

  • 缺页异常: 虚拟内存只有在真正被访问时才分配物理内存。

    • 请求调页: 代码段或数据段尚未加载到物理内存时,触发缺页,内核从磁盘(swap 分区或文件系统)加载相应页。

    • 写时拷贝: 进程通过 fork() 创建子进程时,父子进程共享物理内存页,并将页标记为只读。当其中一方尝试写入时,触发缺页异常,内核复制一份新的物理页给该进程,从而节省内存。

  • 页换出与交换:

    • 当物理内存不足时,内核的 kswapd 守护进程会被唤醒。

    • 它通过**LRU(最近最少使用)**链表扫描内存页,将不活跃的匿名内存页写入 Swap 分区,将不活跃的文件映射页(如果未被修改)直接丢弃(需要时再从磁盘读回),从而释放物理内存。

  • OOM Killer:

    • 如果内存极度紧缺,回收无法满足需求,内核会调用 Out-Of-Memory Killer

    • 它会通过一套评分机制(考虑内存占用、进程重要性等),强制杀死得分最高的进程,以释放内存,保证系统不至于崩溃。

5. 关键数据结构

  • struct page 代表一个物理页框,是物理内存管理的基石。它包含了页的状态(空闲、使用中)、引用计数、映射关系等。

  • struct vm_area_struct 代表进程地址空间中的一段连续区域(如代码段、栈、mmap 区域)。它描述了该区域的权限(读/写/执行)和操作函数。

  • 页表: 用于存储虚拟地址到物理地址的映射关系。现代 Linux 使用多级页表(如 4级或5级页表),以节省大地址空间下页表本身占用的内存。

6. 系统调用接口

内核通过以下系统调用为用户空间提供内存操作接口:

  • brk / sbrk 改变堆的结束地址,用于控制 Heap 区域。

  • mmap / munmap 创建/销毁内存映射。既可以用于映射文件到内存,也可以用于分配匿名内存(MAP_ANONYMOUS)。这是现代 malloc 实现的重要基础。

  • remap_pfn_range 设备驱动中常用,将设备内存映射到用户空间。

总结流程图

bash 复制代码
[用户进程 malloc()]
        |
        V
[glibc ptmalloc/tcmalloc/jemalloc]  <-- 用户态分配器
        |
        V
[系统调用 brk 或 mmap]
        |
        V
[内核处理]
        |
        +----> [检查 VMA]
        |
        +----> [触发缺页异常]
        |       |
        |       V
        |   [伙伴系统] <--> [Slab 分配器]  <-- 分配物理页
        |       |
        |       V
        |   [建立页表映射]
        |
        V
[进程继续执行]

Linux 内存管理的设计哲学是通过延迟分配 (写时拷贝、请求调页)和层次化管理 (Zone、Node、Page),在性能、并发和资源利用率之间寻找平衡。