Linux内核(内存管理篇)
内存管理
页
页表项(PTE)

拆解后分两部分:
(1) PFN(物理页号)
就是哪个物理页,存放在 PTE 的高 52 位(细节依赖 CPU)。
物理地址 = PFN << 12
(因为每页 4KB)
(2) flag bits(权限/状态位)
常见的标志位包括:
| 位 | 名称 | 含义 |
|---|---|---|
| P | Present | 是否在内存中 |
| R/W | 可写 | 是否允许写 |
| U/S | 用户态/内核态 | 是否允许用户态访问 |
| NX | Non-Execute | 是否禁止执行 |
| A | Accessed | 是否被访问 |
| D | Dirty | 是否被写过 |
| PAT | caching mode | 缓存策略 |
| Global | 全局页 | TLB 不会在切换 CR3 时刷新 |
| COW | Linux软件层标志 | 写时复制,内核组织在更高层 meta 信息 |
这些位让 CPU 知道如何处理对该页的内存访问。
例:
PTE 中 R/W=0 → 页只读
PTE 中 NX=1 → 页不可执行
PTE 中 Present=0 → 页不在内存,引发 page fault,从 swap/load-in 再填充页表
反向映射(RMAP)技术
基本概念
-
正向映射
当进程分配内存并发生写操作时,会分配虚拟地址并产生缺页,进而分配物理内存并建立虚拟地址到物理地址的映射关系, 这个叫正向映射。
-
反向映射
反过来, 通过物理页面找到映射它的所有虚拟页面叫反向映射(reverse-mapping, RMAP)。
为什么需要反向映射? 主要是为了内存回收。在操作系统中,多个进程的虚拟地址可能映射到同一个物理页(例如共享内存、写时复制等)。当内核需要回收一个物理页时,它必须修改所有映射了该页的页表项,使其无效或指向其他位置。如果没有反向映射,内核将不得不遍历所有进程的页表来寻找映射,这是极其低效的。
Linux2.4没有反向映射
在linux2.4内核中没有反向映射的这个概念,当时为了找到一个物理页面对应的页表项就需要遍历系统中所有的mm组成的链表,然后再遍历每一个mm的每一个vma看这个vma是否映射了这页,这个过程相当低效,最坏情况不得不遍历完所有的mm然后才能找映射到这个页的所有pte。
伙伴系统和slab分配器
在现代操作系统中,内存管理是支撑系统运行的核心支柱之一。它不仅决定了资源利用的效率,还直接影响性能、稳定性和应用程序的体验。在 Linux 内核中,内存管理通过分层设计实现了高效与灵活的平衡,其中 伙伴系统(Buddy System) 是物理内存分配的基础机制。无论是用户态程序通过 malloc() 请求内存,还是内核态创建进程描述符、分配文件缓冲区,背后都离不开伙伴系统的支持。
伙伴系统
Linux内核中使用伙伴系统(buddy system)来分配以页为单位的内存。
首先我们来说一下使用伙伴系统的原因,也就是解释一下内存分配时的内存碎片问题。内存碎片即“碎片的内存”,它分为外碎片和内碎片,内存碎片描述一个系统中所有不可用的空闲内存,这些碎片之所以不能被使用,是因为负责动态分配内存的分配算法使得这些空闲的内存无法使用,这一问题的发生,原因在于这些空闲内存小且以不连续方式出现在不同的位置。因此这个问题的或大或小取决于内存管理算法的实现上。
外碎片
外部碎片指的是还没有被分配出去(不属于任何进程),但由于太小了无法分配给申请内存空间的新进程的内存空闲区域。

内碎片
内部碎片就是已经被分配出去(能明确指出属于哪个进程)却不能被进程利用的内存空间。也就是分配给进程的内存空间过大,进程原本不需要这么多内存。

slab分配器
内存回收
内存回收的方式
需要注意的是,我们这里提到的内存回收指的物理内存管理的逻辑。
1.直接内存回收:在编程语言或操作系统级别手动回收不再使用的内存资源。在编程语言中,就像用户程序通过free函数释放曾经通过malloc函数分配的内存一样。在操作系统层面,直接内存回收通常由垃圾回收器(Garbage Collector)来完成。垃圾回收器会自动检测并回收不再使用的对象所占用的内存空间,以减少内存泄漏和资源浪费。
2.定期扫描回收(kswapd):定期扫描回收(kswapd)是Linux内核中的一个守护进程,用于管理系统内存和交换空间。它负责检查当前内存使用情况,并在需要时进行页面回收或交换页面到磁盘上的交换分区。
3.OOM机制(out of memory):OOM机制是在内存不足时,内核使用了kswapd回收内存后仍然不够分配内存,就会触发OOM机制强制杀死占用内存最多的程序
定期扫描回收(kswapd)
kswapd 是一个内核线程,在内存不足时负责在后台进行内存回收,这个过程发生在后台,因此是异步发生,不会阻塞进程。
内核对内存的容量设置了三个阈值:
- 页最小阈值(pages_min)
- 页低阈值(pages_low)
- 页高阈值(pages_high)
当内存大于 pages_high 时,表示此时系统内存足够,不会进行内存回收。
当内存小于 pages_low 时,表示此时内存存在压力,会触发 kswapd0 进行后台内存回收,直到 pages_high 为止。
当内存小于 pages_min 时,表示此时用户内存耗尽,会触发直接内存回收,进程被阻塞。