持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第24天,点击查看活动详情
kmalloc()、vmalloc()和malloc()这3个常用的接口函数非常重要,三者看上去很相似,但在实现上大有讲究。kmalloc()基于slab分配器,slab缓冲区建立在一个物理地址连续的大内存块之上,所以其缓存对象也是物理地址连续的。如果在内核中不需要连续的物理地址,而仅仅需要内核空间的虚拟地址是连续的内存块,该如何处理呢?这时vmalloc()就派上用场了。 vmalloc()函数的声明如下。
<mm/vmalloc.c>
void *vmalloc(unsigned long size)
{
return __vmalloc_node_flags(size, NUMA_NO_NODE,
GFP_KERNEL);
}
在Linux 4.0内核中,vmalloc()使用的分配掩码是“GFP_KERNEL | __GFP_HIGHMEM”,这说明会优先使用高端内存。而在Linux 5.0内核中只有“GFP_KERNEL”,这是不是说明vmalloc()不再优先使用高端内存了呢?其实不是,而是在Linux 4.12内核中把这个接口函数变得更加简单了,因为有些驱动喜欢使用下面这个接口函数。
<mm/vmalloc.c>
void *__vmalloc(unsigned long size, gfp_t gfp_mask, pgprot_t prot)
__vmalloc()接口函数包含了3个参数。
- size:分配的内存大小。
- gfp_mask:页面分配器使用的分配掩码。
- prot:分配内存的内存属性。
drivers/block/drbd/drbd_bitmap.c驱动文件中就直接使用__vmalloc()接口函数来分配内存,这样做的好处是可以指定gfp_mask分配掩码。在Linux 4.12内核之前的版本中,在调用这个接口函数时都要为gfp_mask分配掩码指定__GFP_HIGHMEM。因此,为了使这个接口函数更简单,把__GFP_HIGHMEM标志位的指定安排在内部实现的函数__vmalloc_area_node()中。
<drivers/block/drbd/drbd_bitmap.c>
static struct page **bm_realloc_pages(struct drbd_bitmap *b, unsigned long want)
{
new_pages = kzalloc(bytes, GFP_NOIO | __GFP_NOWARN);
if (!new_pages) {
new_pages = __vmalloc(bytes,
GFP_NOIO | __GFP_ZERO,
PAGE_KERNEL);
}
vmalloc()函数的核心实现主要是调用__vmalloc_node_range()函数来实现的。
<mm/vmalloc.c>
static void *__vmalloc_node(unsigned long size, unsigned long align,
gfp_t gfp_mask, pgprot_t prot,
int node, const void *caller)
{
return __vmalloc_node_range(size, align, VMALLOC_START, VMALLOC_END,
gfp_mask, prot, 0, node, caller);
}
这里的VMALLOC_START和VMALLOC_END是vmalloc()中很重要的宏,这两个宏定义在arch/arm64/include/asm/pgtable.h头文件中。VMALLOC_START是vmalloc区域的开始地址,它以内核模块区域的结束地址(MODULES_END)为起始点。
<arch/arm64/include/asm/memory.h >
#define MODULES_END (MODULES_VADDR + MODULES_VSIZE)
<arch/arm64/include/asm/pgtable.h>
#define VMALLOC_START (MODULES_END)
#define VMALLOC_END (PAGE_OFFSET - PUD_SIZE - VMEMMAP_SIZE - SZ_64K)
在ARM64系统中,VMALLOC_START宏的值为0xFFFF 0000 1000 0000,VMALLOC_END宏的值为0xFFFF 7DFF BFFF 0000,整个vmalloc区域的大小为129022GB。读者可以自行计算这些宏的值。