物理内存分区(32 位)
Linux 将物理内存分为三个 Zone:
| Zone | 范围 | 用途 |
|---|---|---|
ZONE_DMA | <16MB | DMA 方式数据缓冲 |
ZONE_NORMAL | 16MB~896MB | 内核虚拟地址映射(系统空间) |
ZONE_HIGHMEM | >896MB | 用户空间(进程映像) |
分配优先级:首选 HIGHMEM → 满时用 NORMAL → DMA 专用
重点 伙伴系统(Buddy System)
- 基于 2 的幂次分配内存块(1, 2, 4, 8, 16 页…)
- 分配:将大块拆分为两个 ” 伙伴 “,从大到小剖分
- 回收:相邻伙伴合并为更大块,逐级向上合并
- 伙伴定义:大小相同 + 物理地址连续 + 第一个页帧编号为 2 的倍数
分配示例(16 页初始 → 分配 2 页):
- 初始:空闲链表仅 16 页块 [0-15]
- 请求 2 页 → 剖分 16→[0-7]+[8-15],[8-15] 入 8 页链表
- [0-7] 剖分 → [0-3]+[4-7],[4-7] 入 4 页链表
- [0-3] 剖分 → [0-1]+[2-3],[0-1] 分配出去,[2-3] 入 2 页链表
回收:释放 [0-1] → 检查伙伴 [2-3] 是否空闲 → 合并为 [0-3] → 继续向上合并。
优点:保留大的连续空间,分配的内存尽量连续(提高 TLB 命中率),减少外部碎片。
Slab 分配器
- 在伙伴系统之上用于小对象分配(Buddy 以页为单位,4K 以下会造成内部碎片)
- 为常用的小对象预分配缓存(Cache),将多个同类型小对象打包成一个大的页面
- 充当 Buddy 与内核之间的中间代理
- 每种对象类型有自己的 Slab 缓存(如 int cache、inode cache 等)
- 通用预分配大小:32B、64B、128B、4K、128K 等
- 优点:减小内部碎片,管理局部化,减少与 Buddy 系统的直接交互
- 相关系统调用:
kmem_cache_create、kmalloc、kfree
进程虚拟地址空间(32 位)
+------------------+ 0xFFFFFFFF (4G-1)
| 内核空间 |
+------------------+ 0xC0000000 (3G)
| 用户空间 |
+------------------+ 0x00000000
- 用户虚空间通过页表映射到物理内存
- 内核空间通常对应固定物理内存区域
重点 两级页表(32 位 Linux)
32 位 Linux 虚拟地址:页目录 (10 位) + 页表项 (10 位) + 页内偏移 (12 位=4K)
线性地址 → 物理地址转换:
- CR3 寄存器指向进程的页目录
- 逻辑地址高 10 位索引页目录 → 获取页表
- 中间 10 位索引页表 → 获取物理页帧号
- 低 12 位页内偏移拼接 → 物理地址
页表项标志位:存在位、读写位、访问位、脏位
关键数据结构
mm_struct
- 每个进程的
task_struct中包含指向mm_struct的指针 - 描述进程的完整内存映像:
- 指向页目录的指针(PGD)
- 指向
vm_area_struct链表的指针 - 代码段/数据段/堆/栈的地址范围等
vm_area_struct
- 描述进程虚拟地址空间中的一个连续区域(如代码段、堆、栈、mmap 区域等)
- 组织成链表/红黑树
mem_map[]
- 数组大小为页帧数
- 页描述符:管理每个物理页的状态
Copy-on-Write(写时拷贝,COW)
fork()创建子进程时,父进程与子进程共享同一物理页面(页表项指向相同页帧,标记为只读),不立即复制- 写触发:子进程或父进程要写入共享页面时 → 触发缺页异常 → OS 分配新物理页帧 → 复制原内容 → 修改页表为可写 → 分别映射各自的副本
- 优点(Lazy 策略):
- 避免一次性拷贝所有数据,开销分散
- 只被写入的页面才真正复制,共享页面保留
- 提高进程创建效率
- 缺页异常处理:
do_page_fault函数 → 判断地址是否在有效区域 → 区分写缺失/页不在内存 → 执行对应处理
用户态与核心态系统调用
brk— 改变堆大小exit— 结束进程,释放空间mmap/munmap— 映射/取消映射文件或匿名内存shmget— 创建共享存储区