本篇从我自己的角度来写对物理内存管理的理解。由于 Linux 引入了虚拟内存的概念,应用程序对物理内存的访问都是由内核模块来接管的,因此带着以下问题,逐步揭开相关的细节:
- 内核是使用什么地址访问物理内存的?
- 物理内存为何需要分区?
- 伙伴系统和 SLAB 系统 有何区别?
页框管理
想要管理内存,首先要知道有哪些内存,并且把内存状态记录下来。物理内存默认以 4k 分割为一个个的单元,每个单元被称为页框(page frame)。内核使用 struct page
数组跟踪内存中每个页框的当前状态。数组的每个元素对应于物理内存中的一个页框,数组定义如下:
// `struct page` 定义在 `linux/mm_types.h` struct page *mem_map;
例如,mem_map[0] 包含内存中第一个页框的信息
名词说明:
- 页框: 存储数据的内存块
- 页:存放在页框内的数据块
如此,内核就通过页框数组把所有的内存使用索引了起来,并且知道每个页的情况,例如:是否空闲、拥有者是谁。
为什么分区?
然而对于内核来说仅有分页是不够的,内核也没办法 完全 直接访问内存,是什么原因呢?
具体还是要从内存分配过程聊起来。进程申请内存的时候,会调用 malloc() 和 mmap() 等内存分配函数,最终会发起系统调用陷入内核态进行内存分配。但是,内存分配过程分配的只是虚拟地址空间,并没有给虚拟内存分配对应的物理内存。当进程访问没有建立映射关系的虚拟内存时,将触发一个缺页中断。当一个进程发生缺页中断的时候,进程会再次陷入内核态,查找/分配一个页框,建立映射关系(虚拟地址到物理地址)
可以看到进程在分配内的时候两次进入内核态,然而两次却完全不同。要理解这一点首先要熟悉两个概念 “进程上下文” vs “中断上下文”
在 Linux 实现中,处理器在执行过程中总是处于以下三种状态:
(1)内核态,运行于进程上下文,内核代表进程运行于内核空间。
(2)内核态,运行于中断上下文,内核代表硬件运行于内核空间。
(3)用户态,运行于用户空间。
内核的地址空间不仅仅要支持硬件访问,同时还需要映射到进程的虚拟地址空间,成为进程上下文的一部分。
当然,单独从实现来看,对于(1)、(2)两种情况,内核的上下文如果完完全全从进程上下文独立开也是可行的,甚至更为简单。但是从性能来看,当前的方案才是更优的。详情参考:《User Space on Top of Kernel Space Versus Separated Address Spaces》
分区地址映射
32位系统中,内核模块的地址空间只有1G。但是,内核又要访问所有的 4G 内存。但内核访问物理内存与进程访问虚拟内存不同,虚实映射既消耗空间也消耗性能(详见:地址映射),且在内核场景下,内存移动与内存换出的需求并不高,也没有多进程隔离的需求(详见:内存共享),映射的收益不大。
因此,内核把页框分组,划分为不同的区(ZONE)。内核空间的前 896MB(不仅是内核代码,还有它的数据)被“直接”映射到物理内存。虚拟内核空间的最后 128MB 部分被映射到物理“高内存”(> 896MB)的一些部分。物理内存的直接映射允许物理页面分配器的直接访问获得的页面,而无需任何映射操作。获取物理页的虚拟地址所需的唯一操作是添加固定偏移量。
通过以上方式,既实现 4G 内存的访问,也保证了内核访问的性能。最终,物理内存的页框就被组织成了以下的形式
从内核地址空间虚实转换的视角来看,如下:
内存分配器
对于空闲内存的分配管理是交给内存分配器进行的。内核中有两种内存分配器,即伙伴系统分配器 和 SLAB 分配器。前者是页框分配器,后者是对象分配器。
伙伴系统的引入为内核提供了一种用于分配一组连续的页而建立的一种高效的分配策略。避免因频繁地申请和释放不同大小的连续页框,导致在已分配页框的内存块中分散了许多小块的空闲页框,而其他需要分配连续页框的请求无法得到满足。
SLAB 工作是针对一些经常分配并释放的对象,如进程描述符等内核中常见的小对象。如果直接采用伙伴系统来进行分配和释放,不仅会造成大量的内碎片,而且处理速度也太慢。而 SLAB 分配器是基于对象进行管理的,相同类型的对象归为一类(如进程描述符),每当要申请这样一个对象,就从一个 SLAB 列表中分配同样大小的内存,而当要释放时,将其重新保存在该列表中。
伙伴系统解决了内存外部碎片问题,而 SLAB 解决了内存的内部碎片问题。所谓外部碎片是指由于频繁地申请和释放页框而导致的某些小的连续页框,而内部碎片就是指被分配出去但是不能被利用的内存。
两个系统的细节暂时按下,后续详聊。
本文作者 : cyningsun
本文地址 : https://www.cyningsun.com/06-15-2021/memory-management-physical-memory.html
版权声明 :本博客所有文章除特别声明外,均采用 CC BY-NC-ND 3.0 CN 许可协议。转载请注明出处!