1. kmalloc和vmalloc对比
以ARM64为例,不考虑高端内存。
1.1. 实现原理
kmalloc
和vmalloc
底层函数都是__alloc_pages
。
kmalloc
会根据申请大小是否大于KMALLOC_MAX_CACHE_SIZE
来决定使用slab
还是__alloc_pages
,而slab
最终也是调用__alloc_pages
。
1.2. 初始化
默认情况下,kmalloc
和vmalloc
都不会对申请的内存做初始。
可以通过kzalloc
或vzalloc
申请内存并初始化为0。
1.3. 对齐
kmalloc
按块分配,通常为2^n
对齐,每次分配的大小也是2^n
。根据实现,可能也支持96
或者192
字节。
vmalloc
按页面大小对齐,分配大小为PAGE_SIZE
的整数倍。
1.4. 分配大小
kmalloc
分配大小与实现有关,常见最小值为32、64或128字节。最大值为伙伴系统所管理的最大内存块大小,通常为1024个页面,以PAGE_SIZE
等于4k为例,最大值也就是4M。
vmalloc
可以分配的可以分配G级甚至T级的内存区域。以ARM64为例,vmalloc
区域超过100T。
1.5. 地址空间
kmalloc
返回地址在线性映射区域,物理地址连续。
vmalloc
返回地址在[VMALLOC_START, VMALLOC_END]
。
vmalloc
是通过将一个多个页面映射到一个连续的虚拟地址空间实现的,物理地址可能不连续。
被vmalloc
分配的物理页面,至少映射两次,一次被映射到线性映射区域,另一部分被映射到vmalloc
区域。
1.5.1. 验证代码示例
1size = 4096;
2data = vmalloc(size * 4);
3pr_debug("vmalloc: %px\n", data);
4
5data = kzalloc(size, GFP_KERNEL);
6pr_debug("kzalloc: %px\n", data);
7
8vmalloc: ffff800081f02000
9kzalloc: ffff00000334a000
1.6. 虚拟地址和物理地址转换
kmalloc
返回的地址位于线性映射区域,可以使用__pa
和__va
直接进行转换。
vmalloc
返回的地址位于vmalloc
区域,不能进行直接转换。
对于vmalloc
虚拟地址转物理地址,可以采用手动解析页表,或者如下方式。
1.6.1. vmalloc/ioremap虚拟地址转物理地址
vaddr
是vmalloc
返回的地址区间内的地址。
由于物理地址不一定连续,使用get_phy_addr
对vaddr
和vaddr + PAGE_SIZE
转换得到的地址差值不一定为PAGE_SIZE
。
1unsigned long get_phy_addr(const void *vaddr)
2{
3 unsigned long paddr;
4
5 paddr = (unsigned long)__pfn_to_phys(vmalloc_to_pfn(vaddr));
6 paddr += (unsigned long)vaddr & (PAGE_SIZE - 1);
7
8 return paddr;
9}
1.7. 使用
kmalloc
支持传入flags
,以适应不同的场景,如在中断上下文或不可睡眠的情况下使用GFP_ATOMIC
。
vmalloc
仅支持size
,分配过程可能导致睡眠,不能在中断上下文使用。
但是vfree
可以在中断上下文使用,此时会使用vfree_atomic
唤醒wq
来释放内存。
1.8. 性能和开销
kmalloc
分配速度相对较快,kmalloc
在分配小内存时,使用了slab
来进行分配。即使是分配大内存,__alloc_pages
进行分配后,也就可以直接使用了。
vmalloc
分配和释放内存的速度相对较慢。vmalloc
需要在虚拟地址空间中找到足够大的连续区域,也需要调用kmalloc
或vmalloc
来申请额外的数据来管理分散的页面。在使用__alloc_pages
分配后,还需要将页面映射到虚拟地址空间。