Linux内存管理系统性总结
内存管理核心架构图
进程虚拟地址空间 页表机制 物理内存分配 SLAB分配器 内核对象 用户空间 虚拟内存区域 VMA 文件映射 匿名映射 页面回收 反向映射 交换机制
详细分析
1. 页表机制(多级页表转换)
核心功能:虚拟地址到物理地址的转换和内存保护
四级页表结构(x86_64):
c
// arch/x86/include/asm/pgtable_types.h
typedef struct { pgdval_t pgd; } pgd_t; // 页全局目录
typedef struct { p4dval_t p4d; } p4d_t; // 页四级目录
typedef struct { pudval_t pud; } pud_t; // 页上级目录
typedef struct { pmdval_t pmd; } pmd_t; // 页中间目录
typedef struct { pteval_t pte; } pte_t; // 页表项
地址转换流程:
CPU虚拟地址 PML4表 PDP表 PD表 PT表 物理内存 使用bits 47-39索引 返回PDP基址 使用bits 38-30索引 使用bits 29-21索引 使用bits 20-12索引 物理地址 + 偏移(bits 11-0) CPU虚拟地址 PML4表 PDP表 PD表 PT表 物理内存
关键机制:
-
大页支持:
- 2MB大页:PMD直接指向物理页
- 1GB大页:PUD直接指向物理页
- 减少TLB miss,提升性能
-
写时复制(CoW):
c// mm/memory.c static vm_fault_t do_wp_page(struct vm_fault *vmf) { // 1. 检查是否共享页 // 2. 分配新物理页 // 3. 复制内容 // 4. 更新页表项 }
-
缺页处理:
c// arch/x86/mm/fault.c void do_page_fault(struct pt_regs *regs, unsigned long error_code) { // 调用链: // handle_mm_fault() -> __handle_mm_fault() -> handle_pte_fault() }
-
TLB :
4.1 定位:CPU内部的硬件缓存(通常为SRAM)
4.2 作用:缓存最近使用的页表条目(PTE)
4.3 性能价值:
(1) 页表访问需4次内存访问(4级页表)
(2) TLB访问仅需1-3个CPU周期(比内存快100倍+)
2. 伙伴系统(Buddy System)
核心功能:管理物理页帧的分配与释放,解决外部碎片
数据结构:
c
// mmzone.h
struct zone {
struct free_area free_area[MAX_ORDER]; // 11个阶(0-10)
};
struct free_area {
struct list_head free_list[MIGRATE_TYPES];
unsigned long nr_free;
};
分配流程:
是 否 是 否 alloc_pages 所需阶是否有空闲 从对应链表分配 向上查找更高阶 找到空闲块? 分裂块并分配 尝试回收或OOM
迁移类型:
MIGRATE_UNMOVABLE
:内核核心数据结构MIGRATE_MOVABLE
:用户空间页面MIGRATE_RECLAIMABLE
:可回收页面MIGRATE_PCPTYPES
:per-CPU页面
关键操作:
c
// mm/page_alloc.c
static struct page *__rmqueue(struct zone *zone, unsigned int order,
int migratetype)
{
// 1. 尝试首选迁移类型
// 2. 失败时尝试fallback类型
}
3. SLAB分配器
核心功能:高效分配内核小对象,减少内存碎片
三级缓存结构:
kmem_cache SLAB列表 满SLAB 部分空SLAB 空SLAB Per-CPU缓存
关键数据结构:
c
// mm/slab.h
struct kmem_cache {
unsigned int size; // 对象实际大小
unsigned int object_size; // 对象原始大小
struct kmem_cache_node **node; // Per-NODE缓存
struct array_cache __percpu *cpu_cache; // Per-CPU缓存
};
struct slab {
struct list_head list; // SLAB链表
void *s_mem; // 第一个对象地址
unsigned int active; // 活跃对象数
};
分配流程:
kmalloc Per-CPU缓存 Partial SLAB 伙伴系统 请求对象 立即返回对象 获取一批对象 提供对象 分配新SLAB页 返回新页 初始化对象 alt [Partial SLAB可用] 返回对象 alt [CPU缓存中有空闲对象- ] kmalloc Per-CPU缓存 Partial SLAB 伙伴系统
4. 虚拟内存区域(VMA)
核心功能:管理进程的虚拟地址空间布局
数据结构:
c
// mm_types.h
struct vm_area_struct {
unsigned long vm_start; // 起始地址
unsigned long vm_end; // 结束地址
struct mm_struct *vm_mm; // 所属内存结构
pgprot_t vm_page_prot; // 访问权限
unsigned long vm_flags; // 标志位
struct rb_node vm_rb; // 红黑树节点
const struct vm_operations_struct *vm_ops; // 操作函数集
struct file *vm_file; // 映射的文件
};
VMA类型:
-
文件映射:
- 映射文件到内存(mmap)
- 缺页时从文件系统读取(filemap_fault)
-
匿名映射:
- 堆/栈空间
- 缺页时分配零页(do_anonymous_page)
VMA管理:
进程内存描述符 mm_struct VMA红黑树 VMA链表 快速查找 顺序遍历
反向映射:
c
// mm/rmap.c
struct anon_vma {
struct rw_semaphore rwsem;
struct rb_root rb_root; // 关联的VMA
};
5. 内存回收机制
核心组件:
- kswapd守护进程:后台回收
- 直接回收:分配时触发
- OOM Killer:内存耗尽处理
LRU算法:
c
// mmzone.h
enum lru_list {
LRU_INACTIVE_ANON = 0,
LRU_ACTIVE_ANON = 1,
LRU_INACTIVE_FILE = 2,
LRU_ACTIVE_FILE = 3,
LRU_UNEVICTABLE = 4,
NR_LRU_LISTS
};
回收流程:
文件页 是 否 匿名页 页面回收触发 页面类型 是否脏页 写回磁盘 直接回收 交换到swap 释放页面
系统级协作流程
用户进程 内核 页表 伙伴系统 SLAB VMA管理 malloc(申请内存) kmalloc 返回对象 alloc_pages 返回页面 alt [小对象] [大对象] 首次访问内存 触发缺页异常 查找VMA区域 从文件读取 分配物理页 alt [文件映射] [匿名映射] 建立映射 用户进程 内核 页表 伙伴系统 SLAB VMA管理
关键源码路径
-
页表机制:
arch/x86/mm/fault.c
(缺页处理)mm/memory.c
(页表操作)
-
伙伴系统:
mm/page_alloc.c
(核心分配逻辑)
-
SLAB分配器:
mm/slub.c
(SLUB实现)mm/slab_common.c
(通用接口)
-
虚拟内存管理:
mm/mmap.c
(VMA创建/删除)mm/vmalloc.c
(非连续内存分配)
-
内存回收:
mm/vmscan.c
(页面回收)mm/swap_state.c
(交换管理)
性能优化技术
- Per-CPU缓存:SLAB和伙伴系统均实现
- 预读机制:文件映射缺页时预加载
- 透明大页(THP):自动合并小页
- 内存压缩(zswap):减少交换I/O
- cgroup内存控制:容器级隔离
内存利用率优化技术
这是内存管理艺术的核心,通过各种技术减少浪费(碎片),让有限物理内存服务更多进程和数据:
1,分页:
1.1 基础: 允许物理内存以页为单位非连续分配,减少外部碎片。
1.2 按需分配: 进程申请大量内存时,操作系统通常只建立虚拟地址空间的映射,并不立即分配物理页。直到进程首次访问该页面触发页面故障时,才分配物理页帧并建立映射。这避免了分配但未使用的内存浪费。
2,虚拟内存:
2.1 核心思想: 利用磁盘空间(交换区/页文件)扩展物理内存。
2.2 换页: 当物理内存不足时,操作系统选择一些"不活跃"的内存页(页面置换算法如LRU, Clock, LFU等决定),将其内容写入磁盘,释放其物理页帧供其他进程使用。
2.3 效果: 使得进程的虚拟地址空间可以远大于物理内存,允许多个大型进程同时运行(尽管部分在磁盘上)。极大地提高了物理内存的利用率。
3,写时复制:
3.1 场景: fork()系统调用创建子进程,或共享内存映射。
3.2 机制: 初始时,父子进程共享相同的物理页帧,并将这些页标记为只读。当任何一方试图写入共享页时,触发页面故障。内核捕获此故障,为该进程复制一个新物理页帧,复制原内容,并更新该进程的页表映射为可写。然后恢复进程执行写入操作。
3.3 优势: 避免了在fork()后立即复制整个进程地址空间的巨大开销,大量共享的只读页(如代码段)无需复制。只有真正被修改的页才需要物理复制。显著节省内存。
4,内存压缩:
机制: 在物理内存紧张但尚未达到需要换页的程度时,内核将多个"可压缩"的、最近未使用的页内容压缩存储在一个物理页帧中,释放出空闲页帧。
优势: 比换页到磁盘快得多(磁盘I/O慢),能缓解内存压力,推迟甚至避免昂贵的换页操作,提高响应速度。
5,高效的内存分配器:
目标: 管理堆内存,减少内部碎片和外部碎片,提高分配/释放速度。
策略:
Slab分配器: 针对内核对象(如task_struct, inode等频繁创建销毁的小对象),预分配不同大小的Slab缓存,对象从Slab中快速分配释放,避免反复向通用分配器申请,减少碎片。
伙伴系统: 管理物理页帧分配,按2的幂次大小组织空闲链表,易于合并相邻空闲块,减少外部碎片。
用户空间分配器: 如glibc的ptmalloc, jemalloc, tcmalloc,采用不同策略(如线程本地缓存、大小分级、惰性合并)来优化多线程下的性能和碎片。
6,共享内存:
机制: 允许多个进程将同一块物理内存映射到各自的虚拟地址空间。
优势: 进程间通信最高效的方式,避免了数据复制。节省物理内存(一份数据供多个进程访问)。常用于数据库、科学计算等需要大量数据共享的场景。
7, 内存过量使用:
机制: 允许系统承诺分配的内存总量超过实际物理内存+交换空间的总和。依赖于程序不会同时访问所有已分配内存的特性(局部性原理)和高效的换页机制。
风险与平衡: 如果所有进程同时大量访问"承诺"的内存,会导致严重的抖动(频繁换页),性能急剧下降。操作系统需要谨慎监控和策略来管理风险。
总结表格
组件 | 核心功能 | 数据结构 | 优化技术 |
---|---|---|---|
页表机制 | 地址转换与保护 | pgd_t, pte_t | TLB, 大页 |
伙伴系统 | 物理页帧管理 | zone, free_area | 迁移类型, per-CPU缓存 |
SLAB分配器 | 小对象高效分配 | kmem_cache, slab | CPU缓存, 对象重用 |
VMA管理 | 虚拟地址空间布局 | vm_area_struct | 红黑树, 反向映射 |
内存回收 | 内存资源回收 | lruvec, anon_vma | LRU算法, 交换压缩 |
此架构实现了从进程虚拟地址空间到物理内存的完整映射,通过多级缓存和智能回收策略,在保证隔离性和安全性的同时,最大化内存利用率和访问性能。