1. kmalloc和vmalloc对比

以ARM64为例,不考虑高端内存。

1.1. 实现原理

kmallocvmalloc底层函数都是__alloc_pages

kmalloc会根据申请大小是否大于KMALLOC_MAX_CACHE_SIZE来决定使用slab还是__alloc_pages,而slab最终也是调用__alloc_pages

1.2. 初始化

默认情况下,kmallocvmalloc都不会对申请的内存做初始。 可以通过kzallocvzalloc申请内存并初始化为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虚拟地址转物理地址

vaddrvmalloc返回的地址区间内的地址。 由于物理地址不一定连续,使用get_phy_addrvaddrvaddr + 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需要在虚拟地址空间中找到足够大的连续区域,也需要调用kmallocvmalloc来申请额外的数据来管理分散的页面。在使用__alloc_pages分配后,还需要将页面映射到虚拟地址空间。