【Note】《Linux 内核深度解析:基于 ARM64 架构的 Linux 4.x 内核》 第三章:内存管理(Memory Management)

《Linux 内核深度解析:基于 ARM64 架构的 Linux 4.x 内核》 第三章:内存管理(Memory Management)

内存管理是 Linux 内核中最复杂、最核心的子系统之一,涵盖从系统启动到运行期间的各个方面。从启动时物理内存初始化讲起,深入分析 ARM64 架构下 Linux 4.x 的内存映射、页框管理、Slab 分配器等关键模块,并结合实际代码路径解释其设计原理与运作机制。


一、ARM64 内存架构概述

ARM64 使用 64 位虚拟地址空间,但实际支持的地址范围由 SoC 限制,常见配置如下:

配置 虚拟地址空间 页表级别 每页大小
4KB 页 48-bit VA 4 级页表 4KB
16KB 页 48-bit VA 3 级页表 16KB
64KB 页 42-bit VA 2 级页表 64KB

Linux 采用基于 页(Page) 的虚拟内存管理方式,每个页的大小通常为 4KB,通过页表完成虚拟地址到物理地址的映射。

  • 页表结构PGD → PUD → PMD → PTE,每一级页表映射更多地址。
  • 用户空间地址段:0x0000000000000000 ~ TASK_SIZE_64(0x0000ffff_ffffffff)
  • 内核空间地址段:0xffff0000_00000000 ~ 0xffff_ffff_ffff_ffff

ARM64 的内核地址空间以 PAGE_OFFSET 开始,一般为 0xffff_0000_0000_0000


二、早期物理内存管理:memblock

在内核初始化早期,还未启用 buddy 分配器时,内核依赖 memblock 来临时管理物理内存的分配。

2.1 memblock 数据结构

c 复制代码
struct memblock {
    struct memblock_type memory;   // 可用内存块数组
    struct memblock_type reserved; // 保留内存块数组
};

内核启动后会通过设备树 /memory 节点添加物理内存区域到 memblock.memory,同时将设备树、内核映像、自身页表等保留区域登记到 memblock.reserved 中。

2.2 常用 API

  • memblock_add():注册内存区域
  • memblock_reserve():标记保留区域(不能用于普通分配)
  • memblock_alloc():在 early_boot 阶段分配内存,通常用于页表、log buffer、initrd 等

三、页框管理与伙伴系统(Buddy System)

当内核初始化到 start_kernel() 调用 mm_init() 后,内存管理从 memblock 迁移到标准页框管理系统。

3.1 页描述符:struct page

内核用 struct page 数组来管理每一个物理页框,每个物理页框对应一个 page 结构体,定义于 include/linux/mm_types.h

c 复制代码
struct page {
    unsigned long flags;      // PG_locked、PG_dirty 等状态
    atomic_t _count;          // 引用计数
    atomic_t _mapcount;       // 映射次数
    union {
        struct list_head lru;    // 页面回收、LRU 用
        struct slab *slab;       // SLAB 分配器使用
    };
    struct address_space *mapping;
    pgoff_t index;
    void *virtual;            // 对应的虚拟地址(部分配置)
    ...
};

3.2 Buddy 分配器简介

Buddy System 是一种将物理内存按 2^n 分割成不同阶(order)的管理策略,常用于大页分配(如页表、大块 buffer 分配):

  • 系统将内存划分为若干"zone"(DMA、Normal、HighMem),每个 zone 再分为多个 order。
  • 每个 order 对应 2^order 个连续页框。
  • 若请求大小为 order = 2,系统会尝试从 order-2 的空闲列表中取出一块,如果没有则从更高 order 拆分。

常见函数:

  • __alloc_pages():根据 GFP 标志位选择 zone 和 order,分配页
  • __free_pages():释放页并进行合并

四、内核页表建立

setup_arch()paging_init()map_kernel_segment() 路径中,内核会为自己建立早期静态映射:

4.1 静态映射区域(Linear Mapping)

ARM64 内核将物理内存直接线性映射到内核虚拟地址空间,如:

复制代码
Virtual: 0xffff_0000_0000_0000
   |
   +-- RAM (0x0000_8000_0000, 512MB) 映射至 0xffff_0000_8000_0000

静态映射的建立依赖于页表初始化函数:

  • create_pgd_mapping():建立虚拟地址 → 物理地址的映射
  • alloc_init_pte(), alloc_init_pmd(), alloc_init_pud():分配页表项

4.2 KASLR 与 VA 随机化

若启用内核地址随机化(CONFIG_RANDOMIZE_BASE),内核会在 early_init() 中调用 kaslr_offset() 函数动态计算内核加载地址偏移量。


五、Slab 分配器:内核对象分配机制

Slab 是内核中用于分配小对象(如 task_struct, inode, dentry)的核心机制。主要三种实现:

  • SLAB:最初版本,基于每 CPU 缓存
  • SLUB(默认):优化内存碎片,代码清晰,可跟踪对象状态
  • SLOB:最小系统中使用,适用于小内存设备

5.1 基本流程

  • 使用 kmem_cache_create() 创建对象缓存池
  • kmalloc()kmem_cache_alloc() 分配对象
  • kfree()kmem_cache_free() 释放对象

Slab 依赖伙伴系统进行实际内存页分配,每个 slab 区域由多个页组成,并通过 bitmap 管理对象使用情况。


六、用户空间地址映射与内存区域

6.1 mm_structvm_area_struct

  • 每个进程的地址空间由一个 mm_struct 管理
  • 其内部使用红黑树或链表组织多个 vm_area_struct,每个表示一段连续的虚拟地址(如代码段、堆、栈)
c 复制代码
struct mm_struct {
    pgd_t *pgd;
    struct vm_area_struct *mmap;   // 链表或红黑树
    struct rb_root mm_rb;
    unsigned long start_code, end_code;
    unsigned long start_brk, brk;
    unsigned long start_stack;
    ...
};
  • 当用户调用 mmap() 时,内核会为其创建新的 vm_area_struct 插入 mm_struct
  • 页面未映射时触发 page fault,通过 do_page_fault()handle_mm_fault() 分配实际页面。

七、页面回收机制(简述)

  • 内核通过 shrinker / kswapd / direct reclaim 回收页
  • 当内存不足时,调用 try_to_free_pages(),遍历所有 zone
  • 利用 LRU、active/inactive lists 回收冷页

总结

模块 作用与亮点
memblock 引导阶段的物理内存管理器,负责早期分配
struct page 每个物理页的抽象单位,用于追踪引用、状态、LRU、映射等
Buddy System 伙伴系统分配大块内存,按 order 管理物理页框
内核页表 早期建立内核虚拟地址映射,支持静态映射、KASLR、模块空间等
Slab 分配器 支持小对象快速分配,利用缓存提高性能
mm_struct 用户空间地址管理的核心,支持 mmap、page fault 等

内存管理贯穿整个内核生命周期,是内核调度、进程、文件系统、IO 的基础。下一章将深入调度器与上下文切换的核心机制,包括完全公平调度器(CFS)、实时调度、Preemption 与 load balance 等内容。


参考如下源码路径:

  • mm/memblock.c:memblock 相关代码
  • mm/page_alloc.c:页分配核心
  • mm/slub.c:SLUB 分配器实现
  • arch/arm64/mm/mmu.c:页表映射
  • include/linux/mm.h, mm/init-mm.c:内存管理入口结构
相关推荐
YC运维1 小时前
网络配置综合实验全攻略(对之前学习的总结)
linux·服务器·网络
平凡灵感码头2 小时前
什么是 Bootloader?怎么把它移植到 STM32 上?
linux·soc
Codebee2 小时前
OneCode3.0低代码引擎核心技术:常用动作事件速查手册及注解驱动开发详解
人工智能·架构
前端付豪2 小时前
15、前端可配置化系统设计:从硬编码到可视化配置
前端·javascript·架构
Codebee2 小时前
OneCode3.0 VFS分布式文件管理API速查手册
后端·架构·开源
用户0595661192092 小时前
Java 8 + 特性与 spring Boot 及 hibernate 等最新技术实操内容全解析
java·架构·设计
无敌的牛2 小时前
Linux基础开发工具
linux·运维·服务器
Edingbrugh.南空2 小时前
实战指南:用pmap+gdb排查Linux进程内存问题
linux·运维·服务器
亚马逊云开发者3 小时前
将 Go 应用从 x86 平台迁移至 Amazon Graviton:场景剖析与最佳实践
linux·数据库·golang
大叔是90后大叔3 小时前
Linux/Ubuntu安装go
linux·ubuntu·golang