free_reserved_area()是内核启动后期用于将保留内存(reserved memory)释放回伙伴系统的核心函数。它通常服务于free_initmem()、free_initrd_mem()等场景,把初始化代码段、initrd 等不再需要的物理内存归还给分配器。
核心作用与设计意图
它的设计目标很明确:将一段物理地址范围内的“保留页”标记为可分配,并归还给伙伴系统。这些页在启动早期被memblock标记为保留,不参与常规分配;当相关用途(如内核初始化、initrd 挂载)结束后,通过这个函数将它们“解禁”。
参数解析
根据内核源码注释,函数的语义如下:
/* * Free reserved pages within range [PAGE_ALIGN(start), end & PAGE_MASK) * into the buddy system. The freed pages will be poisoned with pattern * "poison" if it's within range [0, UCHAR_MAX]. * Return pages freed into the buddy system. */ unsigned long free_reserved_area(void *start, void *end, int poison, const char *s)
| 参数 | 含义 |
|---|---|
start | 起始地址(会被向上对齐到页边界) |
end | 结束地址(会被向下对齐到页边界) |
poison | 毒化填充值:在[0, UCHAR_MAX]范围内时,用该值填充每页;否则不毒化 |
s | 描述字符串,用于打印日志(如"unused kernel") |
返回值:释放到伙伴系统的页数。
核心逻辑
1. 地址对齐
start = (void *)PAGE_ALIGN((unsigned long)start); end = (void *)((unsigned long)end & PAGE_MASK);
将start向上对齐、end向下对齐,确保操作以页为单位。
2. 遍历释放每一页
for (pos = start; pos < end; pos += PAGE_SIZE, pages++) { struct page *page = virt_to_page(pos); void *direct_map_addr; direct_map_addr = page_address(page); if ((unsigned int)poison <= 0xFF) memset(direct_map_addr, poison, PAGE_SIZE); free_reserved_page(page); }这里有一个重要的历史修复:早期代码直接对pos做memset(),但如果pos是内核镜像的别名映射(alias mapping)且落在只读区域,memset会失败。修复后改为先通过page_address(page)获取直接映射地址,再对该地址毒化,确保总是可写的。
3. 打印日志
if (pages && s) pr_info("Freeing %s memory: %ldK\n", s, pages << (PAGE_SHIFT - 10));释放成功后,打印释放的内存大小(KB)。
典型调用场景
1. 释放内核初始化代码段
// include/linux/mm.h static inline unsigned long free_initmem_default(int poison) { extern char __init_begin[], __init_end[]; return free_reserved_area(&__init_begin, &__init_end, poison, "unused kernel"); }内核启动完成后,__init段(标记为__init的函数和数据)不再需要,通过这个函数释放回伙伴系统。
2. 释放 initrd 内存
// arch/powerpc/mm/mem.c void __init free_initrd_mem(unsigned long start, unsigned long end) { free_reserved_area((void *)start, (void *)end, -1, "initrd"); }initrd 内容被解压/挂载后,其占用的内存被释放。注意poison = -1表示不毒化(因为-1不在[0, 255]范围内)。
3. 毒化模式选择
poison = 0:用 0 填充,用于某些需要清零的场景。poison = -1:不毒化,直接释放。具体毒化值(如
POISON_FREE_INITMEM)由平台定义。
历史演进
2013 年:Jiang Liu 的补丁增强了
free_reserved_area(),使其支持用 0 毒化内存,并修复了 s390 上 initmem 毒化被误禁的问题。2018 年:补丁修复了“非直接映射地址”调用失败的问题,通过
page_address()获取可写别名。2026 年:重构提案将
free_reserved_area()从page_alloc.c移至memblock.c,并与memblock_free_late()合并共性逻辑。
总结
free_reserved_area()是保留内存的释放阀门:它将[start, end)范围内按页对齐的保留页逐页毒化(可选)并通过free_reserved_page()归还伙伴系统,返回释放的页数。它服务于free_initmem()(释放内核初始化段)、free_initrd_mem()(释放 initrd)等启动后期清理场景,是内核把“启动专用内存”转化为“通用可分配内存”的关键一步。