前言
看完了伙伴系统的设计、slab分配器的设计、vmalloc、kmalloc这些内容对于内存管理的一些细节上好像比较清楚了,但是内核是如何使用内存的还是有一些混淆。也产生了一些疑问,在内核中内存的正确打开方式是什么呢?
内核地址空间划分
首先我们需要搞情况内核的地址空间,不同地址空间的内存的使用方式是不同。
flowchart LR A[直接映射] B[vmalloc] C[持久映射] D[固定映射] A -.- B -.- C -.- D内核的地址空间被划分为直接映射区、vmalloc区、持久映射区和固定映射区。
- 直接映射区: 提供了线性的地址映射关系,位于该区域的虚拟地址可以通过减去固定的地址偏移的到物理内存地址。虽然能够直接计算出地址,但是访存时还是需要通过页表+MMU实现地址转换,只是该部分虚拟地址空间的页表已经在内核初始化阶段就已经建立好了。
- vmalloc区: 通过
vmalloc
分配,在vmalloc区域中找到一片连续虚拟地址空间,然后分配一些物理页帧(优先分配高端内存),并在页表建立起映射关系,以实现分配逻辑上连续但是物理上不连续的大块内存。 - 持久映射区: 通过
kmap
建立起虚拟地址到物理页帧的映射,持久映射的作用就是满足访问高端内存的需要,持久映射区的虚拟地址就像一排插槽,每个插槽可以通过kmap
映射到一个高端内存页帧,当插槽用完时kmap
就会陷入睡眠等待可用插槽。那么,kmap
可以建立到低端内存的映射吗?答案是no,kmap
以page
指针为参数,如果page
指针对应的页帧属于低端内存则不会建立插槽到物理页帧的映射,而是直接通过线性地址映射返回虚拟地址。 - 固定映射区: 通过
kmap_atomic
建立,其原理和kmap
类似,但是在插槽的设计上固定映射区的插槽以per-cpu的形式被划分,并且每个cpu拥有的插槽的数目和用途也在编译时被指定,因此kmap_atomic
建立映射时需要指定具体的映射去覆盖,不会出现插槽数量不足导致睡眠。
内核中使用内存的正确方式
因此在内核中访问低端内存岂不是直接使用虚拟地址就可以了?不需要经过伙伴系统?似乎是这样的,但是这样的做法很不规范,访问和修改一个还在伙伴系统中的页帧内容或者他人申请的内存是有问题的。
NOTE: 不管使用什么虚拟地址,页表和MMU都是绕不开的
我个人理解访问内存还是依靠使用内核提供的一些接口。目前已知的有:
kmalloc
: 通过slab分配器分配小尺寸的内存对象,由slab分配器向伙伴系统申请内存进行细化的管理。这里似乎没有主动建立地址映射,这是因为slab分配器不会申请高端内存和DMA内存,只会使用NORAML内存,其地址映射在页表中早已存在,有的文章没有指出这一点只是说从伙伴系统系统分配了内存,容易产生这种疑惑。vmalloc
: 从伙伴系统中分配多个不连续的页帧,并建立其vmalloc区域地址到物理页帧的映射,需要注意的是vmalloc
虽然以字节为单位申请内存分配,但是实际上其分配的物理内存以页帧为单位,因此不要使用vmalloc
分配小块内存,会造成浪费。另外为了管理vmalloc
的地址空间,需要分配一些小的数据结构是通过kmalloc
完成的。alloc_pages_node
&kmap
: 还有一种方式则是通过主动从伙伴系统申请分配高端内存并使用kmap
建立页表映射的方式。alloc_pages_node
&kamp_atomic
: 用于特殊用途的高端内存映射 or 在中断上下文这种不允许睡眠的场景使用。才疏学浅,具体的使用场景还没研究过。