linux内存管理概述
分层管理:物理内存(伙伴系统 + Slab)与虚拟内存(页表 + VMA)分离,兼顾效率与抽象。地址隔离:每个进程独立的虚拟地址空间,通过 MMU 实现安全隔离。高效复用:多级缓存(页面缓存、Slab 缓存、页表缓存)减少磁盘 I/O 和内存分配开销。动态适应:内存紧张时通过 LRU 置换、Swap 交换、OOM 机制保证系统可用性。Linux 内存管理是 “灵活性” 与 “效率” 的平衡,既支持
Linux 内存管理是内核中最复杂的子系统之一,其设计目标是高效利用物理内存、支持多进程安全隔离、适配多样化内存需求(从 KB 级小对象到 GB 级大文件),并通过虚拟内存技术实现 “内存抽象”。其核心架构可分为物理内存管理、虚拟内存管理、内存分配与回收、缓存机制等模块,以下详细解析:
一、物理内存管理:管理 “真实的内存硬件”
物理内存(RAM)是系统运行的基础,Linux 以 “页框(Page Frame)” 为基本单位管理物理内存(通常页框大小为 4KB,可配置为 8KB、16KB 等)。核心目标是:高效分配 / 回收连续或非连续的页框,减少内存碎片。
1. 页框的表示:struct page
每个物理页框(无论是否被使用)都由一个 struct page 结构体描述,记录页框的核心信息:
struct page {
unsigned long flags; // 页框状态(如是否空闲、是否锁定、是否脏页等)
atomic_t _count; // 引用计数(0 表示空闲)
struct address_space *mapping; // 关联的地址空间(如文件系统)
pgoff_t index; // 在映射中的偏移量
struct list_head lru; // 用于 LRU 链表(页面置换)
// 其他字段:如页框所有者、页表项指针等
};
所有 struct page 组成一个全局数组 mem_map,内核通过该数组访问任意页框的元数据。
2. 伙伴系统(Buddy System):管理连续页框
伙伴系统是 Linux 管理物理页框的核心机制,用于分配连续的 2^n 个页框(n 为整数,如 1 页、2 页、4 页等),解决 “外部碎片” 问题(即零散的小空闲块无法满足大连续内存需求)。
-
核心思想:将空闲页框按 “2^n 个页框” 的大小分组(称为 “阶”,order),每个阶维护一个空闲链表。例如:
- order 0:1 个页框(4KB)
- order 1:2 个页框(8KB)
- order 2:4 个页框(16KB)
- ... 最大阶通常为 11(对应 2^11=2048 个页框,8MB)
-
分配过程:
当请求分配 2^k 个页框时,优先从 order=k 的链表中取一块;若为空,则从更高阶(order=k+1)的链表中取一块,分割为两个 “伙伴” 块(大小均为 2^k),一个分配出去,另一个加入 order=k 的链表。 -
释放过程:
释放 2^k 个页框时,检查其 “伙伴”(地址相邻且大小相同的空闲块)是否也空闲。若空闲,则合并为 2^(k+1) 个页框,递归向上合并,直到无法合并为止,最终加入对应阶的空闲链表。 -
优势:通过 “分割 - 合并” 机制,有效减少外部碎片,保证连续页框的分配效率。
3. Slab 分配器:管理内核小对象
伙伴系统擅长分配大的连续页框,但对于内核中频繁创建 / 销毁的小对象(如进程描述符 task_struct、inode、页表项等,通常几十到几百字节),直接使用伙伴系统会导致 “内部碎片”(如用 4KB 页框存储 100 字节对象,浪费 3996 字节)。
Slab 分配器基于伙伴系统,将页框划分为更小的 “槽(slot)”,专门缓存和复用小对象,核心特点是 “对象级缓存”。
-
核心结构:
- 缓存(Cache):每种内核对象对应一个缓存(如
task_struct_cache),缓存中包含多个 “slab”。 - Slab:由一个或多个连续页框组成的内存块,内部被划分为大小相同的 “槽”(每个槽存储一个对象)。
- 对象(Object):实际的内核数据结构(如
task_struct实例)。
- 缓存(Cache):每种内核对象对应一个缓存(如
-
工作流程:
- 分配对象时,从缓存的空闲槽中直接取一个(若有),无需分配新页框;若无空闲槽,从伙伴系统分配页框创建新 slab,再划分槽。
- 释放对象时,将其标记为空闲,保留在 slab 中供下次复用,不立即归还给伙伴系统。
-
变种:Linux 内核根据场景优化了 Slab 机制,如:
- Slub:简化 Slab 元数据,适合大内存系统;
- Slob:极简实现,适合嵌入式系统(内存紧张场景)。
二、虚拟内存管理:实现 “内存抽象”
Linux 依赖 MMU(内存管理单元) 实现虚拟内存,核心目标是:
- 为每个进程提供独立的虚拟地址空间(地址隔离,进程间互不干扰);
- 允许进程使用超过物理内存大小的地址空间(通过 Swap 分区暂存不常用数据);
- 实现按需加载(仅将当前需要的代码 / 数据加载到物理内存)。
1. 虚拟地址空间布局
每个进程的虚拟地址空间是一个连续的地址范围(32 位系统通常为 4GB,64 位系统为 2^48 或更大),分为用户空间和内核空间两部分:
-
32 位系统:
虚拟地址空间共 4GB,通常按 3:1 划分(3GB 用户空间 + 1GB 内核空间)。- 用户空间:从 0 到 0xBFFFFFFF,包含进程的代码段、数据段、堆、栈、动态链接库等。
- 内核空间:从 0xC0000000 到 0xFFFFFFFF,所有进程共享,映射物理内存的高端地址。
-
64 位系统:
虚拟地址空间通常支持 48 位(256TB),用户空间和内核空间各占 128TB,中间通过 “空洞” 隔离。
2. 虚拟地址到物理地址的映射
虚拟地址通过页表(Page Table) 映射到物理页框,映射过程由 MMU 硬件完成(内核负责维护页表)。Linux 采用多级页表(如 x86_64 为 4 级页表),减少页表本身的内存开销:
-
页表结构(以 4 级为例):
虚拟地址被分为 5 个部分:PGD 索引(1st 级)→ PUD 索引(2nd 级)→ PMD 索引(3rd 级)→ PTE 索引(4th 级)→ 页内偏移(12 位,对应 4KB 页框)。
每级页表项(PTE)指向更低一级的页表,最终 PTE 存储物理页框号(PFN)。 -
映射过程:
进程访问虚拟地址时,MMU 从 CR3 寄存器(存储页全局目录 PGD 基地址)开始,逐级查找页表,最终得到物理页框号,加上页内偏移得到物理地址。若页表项标记为 “未映射”(缺页),则触发缺页异常,内核负责加载数据到物理内存并更新页表。
3. 虚拟内存区域(VMA):管理地址空间
每个进程的虚拟地址空间被划分为多个虚拟内存区域(VMA),每个 VMA 是一段连续的、具有相同属性(如可读、可写、可执行)的虚拟地址。例如:代码段(.text)、数据段(.data)、堆(heap)、栈(stack)、mmap 映射区域等。
-
核心数据结构:
struct mm_struct:描述进程的整个地址空间,包含所有 VMA 的链表 / 树、页表指针、堆 / 栈边界等。struct vm_area_struct(VMA):描述单个虚拟内存区域,关键字段包括:struct vm_area_struct { unsigned long vm_start; // 区域起始虚拟地址 unsigned long vm_end; // 区域结束虚拟地址(不含) struct vm_area_struct *vm_next; // 下一个 VMA(链表) pgprot_t vm_page_prot; // 页保护属性(读写执行权限) struct file *vm_file; // 关联的文件(若为文件映射) void (*vm_ops)(...); // 区域操作函数(如缺页处理) };
-
作用:VMA 使内核能高效管理进程地址空间(如查找某地址所属区域、批量设置权限),避免对每个虚拟页单独管理。
三、内存分配接口:用户态与内核态
Linux 为用户态和内核态提供了不同的内存分配接口,满足不同场景的需求。
1. 用户态内存分配
用户程序通过标准库函数或系统调用分配内存,核心接口包括:
-
malloc(size_t size):
C 标准库函数,用于分配size字节的内存。底层通过两种方式实现:- 小内存(通常 < 128KB):调用
brk()系统调用,调整进程堆的边界(mm_struct->brk),扩大或缩小堆空间。 - 大内存(通常 ≥ 128KB):调用
mmap()系统调用,在文件映射区分配一块匿名内存(不关联文件)。
- 小内存(通常 < 128KB):调用
-
free(void *ptr):
释放malloc分配的内存。若释放的是堆内存,会合并相邻空闲块(减少碎片);若释放的是mmap内存,会调用munmap()解除映射。 -
mmap(void *addr, size_t len, int prot, int flags, int fd, off_t offset):
直接将文件或设备映射到进程虚拟地址空间,支持 “文件映射”(如加载动态库)和 “匿名映射”(如大内存分配)。优势是可共享内存(多进程映射同一文件),且释放时无需合并碎片。
2. 内核态内存分配
内核自身需要分配内存(如创建进程、管理设备等),核心接口包括:
-
kmalloc(size_t size, gfp_t flags):
分配物理连续的内存块(大小通常 ≤ 128KB),基于 Slab 分配器(小内存)或伙伴系统(大内存)。flags控制分配行为(如GFP_KERNEL允许睡眠等待内存,GFP_ATOMIC不允许睡眠,用于中断上下文)。 -
vmalloc(size_t size):
分配物理不连续但虚拟连续的内存块(适合大内存,如几 MB),通过页表将离散的物理页框映射到连续的虚拟地址。缺点是访问速度较慢(需多次页表查找),不适合高性能场景。 -
kmem_cache_alloc(struct kmem_cache *cache, gfp_t flags):
从指定的 Slab 缓存中分配对象(如task_struct),效率最高,适合频繁创建 / 销毁的小对象。
四、内存回收与页面置换:应对内存不足
当物理内存紧张时,Linux 会主动回收内存,核心机制包括页面缓存回收和Swap 交换。
1. 页面缓存(Page Cache)
页面缓存是 Linux 提高 I/O 性能的关键,用于缓存文件内容(磁盘文件、块设备)。大多数文件读写操作先通过页面缓存进行,仅当缓存未命中时才访问磁盘。页面缓存中的页称为 “缓存页”,分为:
- 干净页:与磁盘数据一致,可直接释放;
- 脏页:与磁盘数据不一致,需先写回磁盘(通过
pdflush进程异步写回)。
2. LRU 页面置换算法
当内存不足时,内核需选择 “最不常用” 的页换出(释放物理页框),采用LRU(最近最少使用) 算法的变种:
- 活跃 / 非活跃链表:将页分为
active_list(最近使用过)和inactive_list(长期未使用),优先回收inactive_list中的页。 - 访问标记:通过页表项的 “访问位(Accessed)” 跟踪页的使用情况,内核定期扫描并调整页在链表中的位置(如访问过的页从非活跃链表移到活跃链表)。
3. 内存回收进程
- kswapd:内核线程,当空闲内存低于 “低水位线” 时,后台异步回收内存(不阻塞用户进程)。
- 直接回收(Direct Reclaim):当进程分配内存时发现内存不足,主动触发回收(可能阻塞进程,直到有足够内存)。
- OOM Killer:若内存耗尽且无法回收,内核会启动 “Out Of Memory 杀手”,根据进程的 “oom_score”(内存使用、优先级等)选择一个进程杀死,释放其内存。
五、核心特点总结
- 分层管理:物理内存(伙伴系统 + Slab)与虚拟内存(页表 + VMA)分离,兼顾效率与抽象。
- 地址隔离:每个进程独立的虚拟地址空间,通过 MMU 实现安全隔离。
- 高效复用:多级缓存(页面缓存、Slab 缓存、页表缓存)减少磁盘 I/O 和内存分配开销。
- 动态适应:内存紧张时通过 LRU 置换、Swap 交换、OOM 机制保证系统可用性。
Linux 内存管理是 “灵活性” 与 “效率” 的平衡,既支持嵌入式设备的资源约束场景,也能满足服务器的大规模内存需求,是其跨平台能力的核心支撑。
openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。
更多推荐
所有评论(0)