make_alloc_exact()是alloc_pages_exact()的核心实现函数,用于将伙伴系统分配的高阶页块中多余的部分释放掉,只保留调用方实际需要的大小。
核心作用与设计动机
alloc_pages_exact(size)的目标是:分配至少size字节的内存,但尽量不浪费——如果size只需要 3KB,它不会返回一个完整的 4KB 页,而是先分配一个足够大的高阶块,再把多余部分释放回伙伴系统。
这与__get_free_pages(gfp, order)不同:后者按 2 的幂次分配,可能浪费大量内存(如需要 5KB 却分配 8KB)。alloc_pages_exact()通过“先多分、再退还”来减少浪费。
核心实现
static void *make_alloc_exact(unsigned long addr, unsigned int order, size_t size) { if (addr) { unsigned long alloc_end = addr + (PAGE_SIZE << order); unsigned long used = addr + PAGE_ALIGN(size); split_page(virt_to_page((void *)addr), order); while (used < alloc_end) { free_page(used); used += PAGE_SIZE; } } return (void *)addr; }逐步说明
1.addr检查
if (addr) {如果分配失败(addr == 0),直接返回 0,不做任何处理。
2. 计算边界
unsigned long alloc_end = addr + (PAGE_SIZE << order); unsigned long used = addr + PAGE_ALIGN(size);
alloc_end:整个高阶块的结束地址(addr + 2^order * PAGE_SIZE)。used:实际需要的结束地址(addr + 向上取整到页边界的 size)。PAGE_ALIGN(size)确保used按页对齐,因为释放必须以页为单位。
3. 分裂页块
split_page(virt_to_page((void *)addr), order);
前面讲过的split_page():把一个非复合高阶页拆成2^order个独立的单页,每个页引用计数设为 1。这是关键步骤——只有拆成独立单页,才能单独释放其中一部分。
4. 释放多余页
while (used < alloc_end) { free_page(used); used += PAGE_SIZE; }从
used开始,逐页释放到alloc_end。每次释放一页(
free_page()是free_pages(addr, 0)的简写)。保留
[addr, used)范围内的页给调用方。
5. 返回起始地址
return (void *)addr;
返回分配到的内存起始虚拟地址。
在分配路径中的位置
它被alloc_pages_exact()调用:
void *alloc_pages_exact(size_t size, gfp_t gfp_mask) { unsigned int order = get_order(size); unsigned long addr; addr = __get_free_pages(gfp_mask, order); return make_alloc_exact(addr, order, size); } EXPORT_SYMBOL(alloc_pages_exact);流程:
get_order(size):计算满足size所需的最小阶数。__get_free_pages():分配该阶数的页块,返回虚拟地址。make_alloc_exact():把多余部分释放,只保留实际需要的。
关键约束
1. 必须用free_pages_exact()释放
因为make_alloc_exact()调用了split_page(),页块已被拆成独立单页,不能再作为一个整体释放。必须用配套的free_pages_exact():
void free_pages_exact(void *virt, size_t size) { unsigned long addr = (unsigned long)virt; unsigned long end = addr + PAGE_ALIGN(size); while (addr < end) { free_page(addr); addr += PAGE_SIZE; } } EXPORT_SYMBOL(free_pages_exact);它逐页释放[virt, virt + PAGE_ALIGN(size))范围。
2. 不能用于复合页
split_page()要求页是非复合的。alloc_pages_exact()内部用__get_free_pages()分配,不带__GFP_COMP,所以页是非复合的,可以安全split_page()。
3. 只适用于低阶
split_page()对高阶页有效,但alloc_pages_exact()通常用于较小的分配(几 KB 到几十 KB)。极大分配(如 MB 级)用alloc_pages_exact()会频繁分裂/释放,效率不高。
总结
make_alloc_exact()是alloc_pages_exact()的精确分配核心:它接收一个高阶页块的虚拟地址,计算实际需要的页数,通过split_page()把页块拆成独立单页,然后逐页释放多余部分,只保留调用方实际需要的内存。它通过“先多分、再退还”的策略减少内存浪费,但代价是必须用配套的free_pages_exact()逐页释放。