《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_struct
与 vm_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
:内存管理入口结构