目录
VMALLOC 分配器原理
VMALLOC 内存分配器称为 “Virtual Memory Allocator”, 从定义可以知道 VMALLOC
内存的主要任务就是分配虚拟内存。在 Linux 内核中,划分了一块虚拟内存区域给
VMALLOC 内存分配器进行管理, VMALLOC 分配器提供相应的函数将这段虚拟内存分配
给内核其他子系统. 在不同的体系结构中 VMALLOC 内存管理器管理的虚拟内存区域
可能不同,但可以通过 VMALLOC_START 和 VMALLOC_END 进行确认.
VMALLOC 内存分配器分配虚拟内存之后,虚拟内存还没有与物理内存进行映射,因此
VMALLOC 内存需要和物理页进行映射之后才算真正的从 VMALLOC 分配器中分配可用
的虚拟内存。物理页可用通过 Buddy 内存管理器分配,也可以通过 PCP 内存管理器
进行分配,物理内存可以来自 ZONE_HIGHMEM,也可以来自非 ZONE_HIGHMEM 的区间。
由于 VMALLOC 分配器可以分配连续的虚拟地址,但与其映射的物理地址可能不是连续
的物理页块,也可能是连续的物理页块,因此通俗称 VMALLOC 内存分配器分配的内存
特点是 “虚拟地址连续,物理地址不一定连续的内存”。
VMALLOC 分配器的历史上采用过多种数据结构管理 VMALLOC 虚拟区虚拟地址的分配
和回收,比如最新版本采用红黑树管理 VMALLOC 虚拟区域的分配和回收,而在较早
的内核中,只是采用简单的遍历来查找 VMALLOC 虚拟区域内可用的虚拟内存块和回收
虚拟内存区块. 当无论采用什么方法管理 VMALLOc 虚拟区域,VMALLOC 内存管理器
总要保证能从 VMALLOC 区域内进行虚拟地址的分配和释放。
当 VMALLOC 内存分配器从 VMALLOC 虚拟区域内找到一块可用的虚拟区域之后,都会
在指定虚拟内存之后加上一个 PAGE_SIZE 的虚拟内存,这块虚拟内存称为 VMALLOC
的 GUARD 虚拟内存块,主要是用于隔离其他已经分配的虚拟内存区块. 虚拟地址分配
完毕之后,VMALLOC 内存管理器就从 Buddy 或者 PCP 内存管理器中分配对应数量的
物理页,然后将虚拟内存和物理内存建立页表。VMALLOC 分配器建立的页表可以是三级
页表,也可以高达五级页表。以上操作完毕之后,VMALLOC 分配器将虚拟地址传递给
调用者,调用者就可以使用这段虚拟内存区域. 由于 VMALLOC 按 PAGE_SIZE 为单位
从 Buddy/PCP 内存管理器中分配物理内存,并且 VMALLOC 分配虚拟地址时按 PAGE_SIZE
大小进行对齐,因此 VMALLOC 内存分配器分配以 PAGE_SIZE 为单位的虚拟内存块。
VMALLOC 内存分配器提供的分配接口如下:
当使用者使用完 VMALLOC 内存分配器分配的内存之后,通过 VMALLOC 释放函数将虚拟
地址传递给 VMALLOC 分配器,VMALLOC 分配器执行与分配相反的动作,首先将虚拟地址
与物理地址关联的页表清除,然后释放对应的物理页块给 Buddy 内存管理器或者 PCP
内存管理器, 执行完毕之后 VMALLOC 释放才完成. VMALLOC 内存分配器提供的释放
接口如下:
VMALLOC 页表
当从 VMALLOC 分配器获得一段 VMALLOc 虚拟内存块之后,接下来是分配一段物理内存,
并建立 VMALLOC 虚拟地址与物理地址之间的映射关系,即建立页表. VMALLOC 内存
分配器可以支持多级页表,例如 linux 2.6 版本的 VMALLOC 分配器支持如下页表:
VMALLOC 分配器通过建立 4 级页表最终将物理内存和 VMALLOC 虚拟内存建立映射
关系. Linux 5.x 版本的 VMALLOC 分配器支持如下页表:
Linux 2.6 版本的 VMALLOC
在 Linux 2.6 版本上,VMALLOC 使用 struct vm_struct 结构维护一段 VMALLOC
虚拟内存块,定义如下:
VMALLOC 内存管理器通过建立一个 struct vm_struct 结构的 vmlist 链表。当 VMALLOC
从 VMALLOC 虚拟内存区域找一块可用内存时,遍历 vmlist 链表,以此确认找到
的虚拟内存区块未使用,然后将其加入到 vmlist 里。当释放 VMALLOC 虚拟块的
时候,VMALLOC 分配器将其对应的 struct vm_struct 从 vmlist 链表中移除.
Linux 5.x 版本的 VMALLOC
在 Linux 5.x 版本上,VMALLOC 使用一颗红黑树管理着所有的 VMALLOC 虚拟内存。
当 VMALLOC 管理器需要分配虚拟内存时,VMALLOc 从红黑树上查找一块可用的虚拟
内存。当释放时,VMALLOC 分配器再将虚拟地址插入到红黑树中.
VMALLOC 的优点
当系统运行一段时间后,线性映射区已经很难找到连续的虚拟内存区域了,这时可以
从 VMALLOC 区域获得连续的物理内存。
VMALLOC 的缺点
由于要动态建立页表,分配虚拟内存的效率远低于 Slab/Slub 等线性区分配器.
VMALLOC 分配器使用
基础用法介绍
VMALLOC 分配器提供了用于分配和释放虚拟内存的相关函数接口:
VMALLOC 分配
VMALLOC 释放
具体函数解析说明,请查看:
VMALLOC 分配虚拟内存
从 VMALLOC 区域内存分配、使用和释放虚拟内存,开发者可以参考如下代码:
VMALLOC 分配器实践
实践准备
本实践是基于 BiscuitOS Linux 5.0 ARM32 环境进行搭建,因此开发者首先
准备实践环境,请查看如下文档进行搭建:
实践部署
准备好基础开发环境之后,开发者接下来部署项目所需的开发环境。由于项目
支持多个版本的 VMALLOC,开发者可以根据需求进行选择,本文以 linux 2.6.12
版本的 VMALLOC 进行讲解。开发者使用如下命令:
选择并进入 “[*] Package —>” 目录。
选择并进入 “[*] Memory Development History —>” 目录。
选择并进入 “[*] VMALLOC Allocator —>” 目录。
选择 “[*] VMALLOC on linux 2.6.12 —>” 目录,保存并退出。接着执行如下命令:
成功之后将出现上图的内容,接下来开发者执行如下命令以便切换到项目的路径:
至此源码已经下载完成,开发者可以使用 tree 等工具查看源码:
arch 目录下包含内存初始化早期,与体系结构相关的处理部分。mm 目录下面包含
了与各个内存分配器和内存管理行为相关的代码。init 目录下是整个模块的初始化
入口流程。modules 目录下包含了内存分配器的使用例程和测试代码. fs 目录下
包含了内存管理信息输出到文件系统的相关实现。入口函数是 init/main.c 的
start_kernel()。
如果你是第一次使用这个项目,需要修改 DTS 的内容。如果不是可以跳到下一节。
开发者参考源码目录里面的 “BiscuitOS.dts” 文件,将文件中描述的内容添加
到系统的 DTS 里面,”BiscuitOS.dts” 里的内容用来从系统中预留 100MB 的物理
内存供项目使用,具体如下:
开发者将 “BiscuitOS.dts” 的内容添加到:
添加完毕之后,使用如下命令更新 DTS:
实践执行
环境部署完毕之后,开发者可以向通用模块一样对源码进行编译和安装使用,使用
如下命令:
以上就是模块成功编译,接下来将 ko 模块安装到 BiscuitOS 中,使用如下命令:
以上准备完毕之后,最后就是在 BiscuitOS 运行这个模块了,使用如下命令:
在 BiscuitOS 中插入了模块 “BiscuitOS_VMALLOC-2.6.12.ko”,打印如上信息,那么
BiscuitOS Memory Manager Unit History 项目的内存管理子系统已经可以使用。
测试建议
BiscuitOS Memory Manager Unit History 项目提供了大量的测试用例用于测试
不同内存分配器的功能。结合项目提供的 initcall 机制,项目将测试用例分作
两类,第一类类似于内核源码树内编译,也就是同 MMU 子系统一同编译的源码。
第二类类似于模块编译,是在 MMU 模块加载之后独立加载的模块。以上两种方案
皆在最大程度的测试内存管理器的功能。
要在项目中使用以上两种测试代码,开发者可以通过项目提供的 Makefile 进行
配置。以 linux 2.6.12 为例, Makefile 的位置如下:
Makefile 内提供了两种方案的编译开关,例如需要使用打开 buddy 内存管理器的
源码树内部调试功能,需要保证 Makefile 内下面语句不被注释:
如果要关闭 buddy 内存管理器的源码树内部调试功能,可以将其注释:
同理,需要打开 buddy 模块测试功能,可以参照下面的代码:
如果不需要 buddy 模块测试功能,可以参考下面代码, 将其注释:
在上面的例子中,例如打开了 buddy 的模块调试功能,重新编译模块并在 BiscuitOS
上运行,如下图,可以在 “lib/module/5.0.0/extra/” 目录下看到两个模块:
然后先向 BiscuitOS 中插入 “BiscuitOS_VMALLOC-2.6.12.ko” 模块,然后再插入
“BiscuitOS_VMALLOC-2.6.12-buddy.ko” 模块。如下:
以上便是测试代码的使用办法。开发者如果想在源码中启用或关闭某些宏,可以
修改 Makefile 中内容:
从上图可以知道,如果要启用某些宏,可以在 ccflags-y 中添加 “-D” 选项进行
启用,源码的编译参数也可以添加到 ccflags-y 中去。开发者除了使用上面的办法
进行测试之外,也可以使用项目提供的 initcall 机制进行调试,具体请参考:
Initcall 机制提供了以下函数用于 VMALLOC 调试:
从项目的 Initcall 机制可以知道,vmalloc_initcall_bs() 调用的函数将
在 VMALLOC 分配器初始化完毕之后自动调用。VMALLOC 相关的测试代码位于:
在 Makefile 中打开调试开关:
VMALLOC 测试代码也包含模块测试,在 Makefile 中打开调试开关:
VMALLOC 模块测试结果如下:
VMALLOC 历史补丁
VMALLOC Linux 2.6.12
Linux 2.6.12 采用 VMALLOC 分配器管理 VMALLOC 虚拟内存区域。
VMALLOC 分配
VMALLOC 释放
具体函数解析说明,请查看:
与项目相关
VMALLOC 内存分配器与本项目相关的 vmalloc() 调用顺序如下:
VMALLOC 内存分配器与本项目相关的 vfree() 调用顺序如下:
项目中虚拟内存布局如下:
在项目中,VMALLOC 虚拟内存的管理的范围是: 0x9440A000 到 0x95E0A000.
补丁
对于 Linux 2.6.12 的补丁,Linus 将 Linux 内核源码树加入到 git 中来,因此
这个版本的代码均不产生补丁。更多补丁的使用请参考:
VMALLOC Linux 2.6.12.1
Linux 2.6.12.1 采用 VMALLOC 分配器管理 VMALLOC 虚拟内存区域。
VMALLOC 分配
VMALLOC 释放
具体函数解析说明,请查看:
与项目相关
VMALLOC 内存分配器与本项目相关的 vmalloc() 调用顺序如下:
VMALLOC 内存分配器与本项目相关的 vfree() 调用顺序如下:
项目中虚拟内存布局如下:
在项目中,VMALLOC 虚拟内存的管理的范围是: 0x9440A000 到 0x95E0A000.
补丁
与 Linux 2.6.12 的相比,VMALLOC 并为产生补丁。更多补丁的使用请参考:
VMALLOC Linux 2.6.12.2
Linux 2.6.12.2 采用 VMALLOC 分配器管理 VMALLOC 虚拟内存区域。
VMALLOC 分配
VMALLOC 释放
具体函数解析说明,请查看:
与项目相关
VMALLOC 内存分配器与本项目相关的 vmalloc() 调用顺序如下:
VMALLOC 内存分配器与本项目相关的 vfree() 调用顺序如下:
项目中虚拟内存布局如下:
在项目中,VMALLOC 虚拟内存的管理的范围是: 0x9440A000 到 0x95E0A000.
补丁
与 Linux 2.6.12.1 的相比,VMALLOC 并为产生补丁。更多补丁的使用请参考:
VMALLOC Linux 2.6.12.3
Linux 2.6.12.3 采用 VMALLOC 分配器管理 VMALLOC 虚拟内存区域。
VMALLOC 分配
VMALLOC 释放
具体函数解析说明,请查看:
与项目相关
VMALLOC 内存分配器与本项目相关的 vmalloc() 调用顺序如下:
VMALLOC 内存分配器与本项目相关的 vfree() 调用顺序如下:
项目中虚拟内存布局如下:
在项目中,VMALLOC 虚拟内存的管理的范围是: 0x9440A000 到 0x95E0A000.
补丁
与 Linux 2.6.12.2 的相比,VMALLOC 并为产生补丁。更多补丁的使用请参考:
VMALLOC Linux 2.6.12.4
Linux 2.6.12.4 采用 VMALLOC 分配器管理 VMALLOC 虚拟内存区域。
VMALLOC 分配
VMALLOC 释放
具体函数解析说明,请查看:
与项目相关
VMALLOC 内存分配器与本项目相关的 vmalloc() 调用顺序如下:
VMALLOC 内存分配器与本项目相关的 vfree() 调用顺序如下:
项目中虚拟内存布局如下:
在项目中,VMALLOC 虚拟内存的管理的范围是: 0x9440A000 到 0x95E0A000.
补丁
与 Linux 2.6.12.3 的相比,VMALLOC 并为产生补丁。更多补丁的使用请参考:
VMALLOC Linux 2.6.12.5
Linux 2.6.12.5 采用 VMALLOC 分配器管理 VMALLOC 虚拟内存区域。
VMALLOC 分配
VMALLOC 释放
具体函数解析说明,请查看:
与项目相关
VMALLOC 内存分配器与本项目相关的 vmalloc() 调用顺序如下:
VMALLOC 内存分配器与本项目相关的 vfree() 调用顺序如下:
项目中虚拟内存布局如下:
在项目中,VMALLOC 虚拟内存的管理的范围是: 0x9440A000 到 0x95E0A000.
补丁
与 Linux 2.6.12.4 的相比,VMALLOC 并为产生补丁。更多补丁的使用请参考:
VMALLOC Linux 2.6.12.6
Linux 2.6.12.6 采用 VMALLOC 分配器管理 VMALLOC 虚拟内存区域。
VMALLOC 分配
VMALLOC 释放
具体函数解析说明,请查看:
与项目相关
VMALLOC 内存分配器与本项目相关的 vmalloc() 调用顺序如下:
VMALLOC 内存分配器与本项目相关的 vfree() 调用顺序如下:
项目中虚拟内存布局如下:
在项目中,VMALLOC 虚拟内存的管理的范围是: 0x9440A000 到 0x95E0A000.
补丁
与 Linux 2.6.12.5 的相比,VMALLOC 并为产生补丁。更多补丁的使用请参考:
VMALLOC Linux 2.6.13
Linux 2.6.13 采用 VMALLOC 分配器管理 VMALLOC 虚拟内存区域。
VMALLOC 分配
VMALLOC 释放
具体函数解析说明,请查看:
与项目相关
VMALLOC 内存分配器与本项目相关的 vmalloc() 调用顺序如下:
VMALLOC 内存分配器与本项目相关的 vfree() 调用顺序如下:
项目中虚拟内存布局如下:
在项目中,VMALLOC 虚拟内存的管理的范围是: 0x9440A000 到 0x95E0A000.
补丁
相对上一个版本 linux 2.6.12.6,VMALLOC 内存分配器增加了多个补丁,如下:
该补丁新增了页表的权限 PAGE_KERNEL_EXEC, 使分配的虚拟内存拥有执行和读写权限.
该补丁新增了 __remove_vm_area() 函数并更新了 remove_vm_area() 函数实现.
更多补丁使用方法请参考下面文章:
VMALLOC Linux 2.6.13.1
Linux 2.6.13.1 采用 VMALLOC 分配器管理 VMALLOC 虚拟内存区域。
VMALLOC 分配
VMALLOC 释放
具体函数解析说明,请查看:
与项目相关
VMALLOC 内存分配器与本项目相关的 vmalloc() 调用顺序如下:
VMALLOC 内存分配器与本项目相关的 vfree() 调用顺序如下:
项目中虚拟内存布局如下:
在项目中,VMALLOC 虚拟内存的管理的范围是: 0x9440A000 到 0x95E0A000.
补丁
与 Linux 2.6.13 的相比,VMALLOC 并为产生补丁。更多补丁的使用请参考:
VMALLOC Linux 2.6.14
Linux 2.6.14 采用 VMALLOC 分配器管理 VMALLOC 虚拟内存区域。
VMALLOC 分配
VMALLOC 释放
具体函数解析说明,请查看:
与项目相关
VMALLOC 内存分配器与本项目相关的 vmalloc() 调用顺序如下:
VMALLOC 内存分配器与本项目相关的 vfree() 调用顺序如下:
项目中虚拟内存布局如下:
在项目中,VMALLOC 虚拟内存的管理的范围是: 0x9440A000 到 0x95E0A000.
补丁
相对上一个版本 linux 2.6.13.1,VMALLOC 内存分配器增加了多个补丁,如下:
该补丁将 IOREMAP_MAX_ORDER 宏移到 vmalloc.h.
该补丁修改了相关的注释.
该补丁将 VMALLOC 分配器中 gfp 标志的类型替换成 gfp_t. 更多不行请参考下文:
VMALLOC Linux 2.6.15
Linux 2.6.15 采用 VMALLOC 分配器管理 VMALLOC 虚拟内存区域。
VMALLOC 分配
VMALLOC 释放
具体函数解析说明,请查看:
与项目相关
VMALLOC 内存分配器与本项目相关的 vmalloc() 调用顺序如下:
VMALLOC 内存分配器与本项目相关的 vfree() 调用顺序如下:
项目中虚拟内存布局如下:
在项目中,VMALLOC 虚拟内存的管理的范围是: 0x9440A000 到 0x95E0A000.
补丁
相对上一个版本 linux 2.6.14,VMALLOC 内存分配器增加了多个补丁,如下:
新增 vmalloc_node() 和 __vmalloc_node() 函数, 支持从指定的 NODE 上分配
VMALLOC 内存.
该补丁用于在 VMALLOC 建立 PTE 页表时移除了 page_table_lock 锁.
该补丁在 VMALLOC 分配器中添加了 node 相关的注释. 更多补丁使用请参考下文:
VMALLOC 历史时间轴
VMALLOC API
__get_vm_area
get_vm_area
__get_vm_area_node
get_vm_area_node
map_vm_area
unmap_vm_area
__remove_vm_area
remove_vm_area
vfree
__vmalloc
vmalloc
vmalloc_32
__vmalloc_area
__vmalloc_area_node
vmalloc_exec
__vmalloc_node
vmalloc_node
vmap
vmap_pmd_range
vmap_pte_range
vmap_pud_range
__vunmap
vunmap
vunmap_pmd_range
vunmap_pte_range
vunmap_pud_range
VMALLOC 进阶研究
VMALLOC 内存分配器调试
附录
BiscuitOS Home
BiscuitOS Driver
Linux Kernel
Bootlin: Elixir Cross Referencer
捐赠一下吧 🙂