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分配后,还需要将页面映射到虚拟地址空间。