Linux中释放初始化内存到伙伴系统free_initmem函数的实现
核心函数功能总结
free_initmem - 主释放函数
- 功能:释放内核初始化段(
.init段)的内存 - 释放范围:
__init_begin到__init_end之间的所有页面 - 关键操作:
- 清除页面保留标志,使其可被重新分配
- 用
0xcc模式填充内存,检测后续非法访问 - 通过
free_page将页面返回伙伴系统 - 更新系统内存统计信息
内存释放调用链
free_page(addr) → free_pages(addr,0) → __free_pages(page,0) → free_hot_page(page) → free_hot_cold_page(page,0)
分层设计:
- 接口层:
free_page- 用户友好接口 - 验证层:
free_pages- 地址有效性检查 - 逻辑层:
__free_pages- 引用计数和保留状态检查 - 优化层:
free_hot_cold_page- 每CPU缓存管理
每CPU缓存机制
- 热页面:刚释放的页面,很可能在CPU缓存中
- 冷页面:较长时间未使用的页面
- 水位控制:当缓存达到高水位时批量释放到伙伴系统
- 性能优势:减少锁竞争,提高单页分配/释放效率
内存管理架构层次
1. 物理页面管理(底层)
- 伙伴系统:
__free_pages_ok- 处理多页释放 - 每CPU缓存:
free_hot_cold_page- 处理单页释放 - 内存域:
page_zone- NUMA架构支持
2. 页面状态管理(中层)
- 标志位:
PG_reserved,PG_dirty,PG_lru等 - 引用计数:
_count字段跟踪使用情况 - 映射关系:
mapping指针管理地址空间
3. 安全验证(高层)
- 地址验证:
virt_addr_valid检查虚拟地址有效性 - 状态验证:
free_pages_check确保释放条件满足 - 调试支持:填充模式和错误报告
释放初始化内存free_initmem
void free_initmem(void)
{unsigned long addr;addr = (unsigned long)(&__init_begin);for (; addr < (unsigned long)(&__init_end); addr += PAGE_SIZE) {ClearPageReserved(virt_to_page(addr));set_page_count(virt_to_page(addr), 1);memset((void *)addr, 0xcc, PAGE_SIZE);free_page(addr);totalram_pages++;}printk (KERN_INFO "Freeing unused kernel memory: %dk freed\n", (__init_end - __init_begin) >> 10);
}
函数功能概述
free_initmem 函数负责释放内核初始化阶段使用的内存,这些内存在系统启动完成后不再需要,可以返回给系统供正常使用
代码详细解析
函数声明和变量定义
void free_initmem(void)
{unsigned long addr;
函数声明:
void: 无返回值free_initmem(void): 函数名,明确表示释放初始化内存unsigned long addr: 用于遍历内存页的地址变量
初始化内存范围确定
addr = (unsigned long)(&__init_begin);
设置起始地址:
&__init_begin: 获取初始化内存段的开始地址(unsigned long): 强制转换为无符号长整型__init_begin: 链接器定义的符号,标记初始化代码和数据的开始位置
内存释放循环开始
for (; addr < (unsigned long)(&__init_end); addr += PAGE_SIZE) {
循环遍历所有初始化内存页:
- 没有初始表达式,直接使用
addr的当前值 addr < (unsigned long)(&__init_end): 循环条件,直到达到初始化内存段结束addr += PAGE_SIZE: 每次增加一页的大小- PAGE_SIZE: 系统页面大小,通常是4096字节(4KB)
__init_end: 链接器定义的符号,标记初始化段的结束位置
清除页面保留标志
ClearPageReserved(virt_to_page(addr));
取消页面的保留状态:
virt_to_page(addr): 将虚拟地址转换为对应的页面结构指针- 这个函数完成从虚拟地址到
struct page的映射
- 这个函数完成从虚拟地址到
ClearPageReserved(): 清除页面的保留标志位- 保留标志作用: 标记这些页面为内核初始化专用,防止被其他用途占用
- 清除必要性: 在释放前必须清除保留标志,否则页面无法被重新分配
设置页面引用计数
set_page_count(virt_to_page(addr), 1);
初始化页面引用计数:
-
virt_to_page(addr): 再次获取页面结构指针 -
set_page_count(..., 1): 实际上将页面引用计数设置为1 - 1 = 0 -
设置为0的原因: 准备将页面放入空闲列表,伙伴系统期望回收页面的计数是0
内存内容填充
memset((void *)addr, 0xcc, PAGE_SIZE);
用特定模式填充内存:
memset((void *)addr, 0xcc, PAGE_SIZE): 用0xcc字节模式填充整个页面- 0xcc的特定含义:
- 在x86架构中,0xcc对应
int 3指令(断点指令) - 如果错误地执行这些内存,会触发调试断点
- 用于检测对已释放内存的意外访问
- 在x86架构中,0xcc对应
释放页面到系统
free_page(addr);
核心释放操作:
free_page(addr): 将页面返回给伙伴系统(buddy system)- 内部操作:
- 将页面添加到对应的空闲链表
- 可能进行页面合并(与相邻空闲页面合并成更大的连续块)
- 更新内存管理器的统计信息
- 伙伴系统: Linux内核的物理内存管理器
更新系统内存统计
totalram_pages++;}
增加可用页面计数:
totalram_pages++: 增加系统总可用物理页面计数totalram_pages: 全局变量,记录系统中可用的物理页面总数- 重要性: 这个计数影响内存分配决策和系统统计信息
- 循环结束
输出释放信息
printk (KERN_INFO "Freeing unused kernel memory: %dk freed\n", (__init_end - __init_begin) >> 10);
}
记录释放操作:
printk(KERN_INFO ...): 输出内核信息级日志"Freeing unused kernel memory: %dk freed\n": 格式化字符串,显示释放了多少内存(__init_end - __init_begin) >> 10: 计算释放的内存大小__init_end - __init_begin: 初始化段的总字节数>> 10: 右移10位,相当于除以1024,转换为KB单位
- 函数结束
页面相关宏解释
#define set_page_count(p,v) atomic_set(&(p)->_count, v - 1)#define virt_addr_valid(kaddr) pfn_valid(__pa(kaddr) >> PAGE_SHIFT)
#define __pa(x) ((unsigned long)(x)-PAGE_OFFSET)
#define pfn_valid(pfn) ((pfn) < max_mapnr)#define virt_to_page(kaddr) pfn_to_page(__pa(kaddr) >> PAGE_SHIFT)
#define pfn_to_page(pfn) (mem_map + (pfn))#define put_page_testzero(p) \({ \BUG_ON(page_count(p) == 0); \atomic_add_negative(-1, &(p)->_count); \})
static __inline__ int atomic_add_negative(int i, atomic_t *v)
{unsigned char c;__asm__ __volatile__(LOCK "addl %2,%0; sets %1":"=m" (v->counter), "=qm" (c):"ir" (i), "m" (v->counter) : "memory");return c;
}
第一部分:页计数设置宏
#define set_page_count(p,v) atomic_set(&(p)->_count, v - 1)
功能:设置页面的引用计数值
p:指向page结构体的指针v:要设置的计数值- 关键点:实际设置的是
v - 1,这是因为内核中引用计数从-1开始(0表示空闲页)
第二部分:虚拟地址验证宏
#define virt_addr_valid(kaddr) pfn_valid(__pa(kaddr) >> PAGE_SHIFT)
#define __pa(x) ((unsigned long)(x)-PAGE_OFFSET)
#define pfn_valid(pfn) ((pfn) < max_mapnr)
-
__pa(x):将虚拟地址转换为物理地址PAGE_OFFSET:内核虚拟地址空间的起始偏移量(通常是0xC0000000)- 通过减法得到对应的物理地址
-
pfn_valid(pfn):验证页帧号是否有效pfn:页帧号(Page Frame Number)max_mapnr:系统支持的最大页帧号- 检查
pfn是否在有效范围内
-
virt_addr_valid(kaddr):综合验证虚拟地址是否有效- 先将虚拟地址转物理地址,再右移得到页帧号
- 最后验证该页帧号是否有效
第三部分:地址到页结构转换
#define virt_to_page(kaddr) pfn_to_page(__pa(kaddr) >> PAGE_SHIFT)
#define pfn_to_page(pfn) (mem_map + (pfn))
解析:
-
pfn_to_page(pfn):页帧号到page结构体指针的转换mem_map:全局page结构体数组的起始地址- 通过数组下标访问得到对应的page结构体
-
virt_to_page(kaddr):虚拟地址到page结构体指针的转换- 先将虚拟地址转为物理地址,再得到页帧号
- 最后通过页帧号找到对应的page结构体
第四部分:页引用计数减量测试
#define put_page_testzero(p) \({ \BUG_ON(page_count(p) == 0); \atomic_add_negative(-1, &(p)->_count); \})
解析:
({ ... }):GCC的语句表达式,允许将多个语句作为一个表达式atomic_add_negative(-1, &(p)->_count):原子地将计数减1并检查结果是否为负
第五部分:atomic_add_negative函数
static __inline__ int atomic_add_negative(int i, atomic_t *v)
{unsigned char c;__asm__ __volatile__(LOCK "addl %2,%0; sets %1":"=m" (v->counter), "=qm" (c):"ir" (i), "m" (v->counter) : "memory");return c;
}
内联汇编详解:
LOCK:总线锁前缀,确保操作的原子性addl %2,%0:将输入操作数2(i)加到操作数0(v->counter)sets %1:根据符号标志位(SF)设置操作数1©- 输出约束:
"=m" (v->counter):修改内存中的计数器"=qm" (c):结果存入字节寄存器或内存
- 输入约束:
"ir" (i):立即数或寄存器"m" (v->counter):内存中的原子变量
"memory":内存屏障,防止编译器重排序
整体功能总结
这些代码实现了Linux内核中的页引用计数管理:
- 引用计数语义:计数从-1开始,0表示最后一个引用被释放
- 原子操作:确保多核环境下的线程安全
- 地址转换:在虚拟地址、物理地址、页帧号、page结构体之间转换
- 引用跟踪:通过
put_page_testzero安全地减少引用计数并检测是否归零
页面释放的完整调用链free_page
#define free_page(addr) free_pages((addr),0)fastcall void free_pages(unsigned long addr, unsigned int order)
{if (addr != 0) {BUG_ON(!virt_addr_valid((void *)addr));__free_pages(virt_to_page((void *)addr), order);}
}
fastcall void __free_pages(struct page *page, unsigned int order)
{if (!PageReserved(page) && put_page_testzero(page)) {if (order == 0)free_hot_page(page);else__free_pages_ok(page, order);}
}
void fastcall free_hot_page(struct page *page)
{free_hot_cold_page(page, 0);
}
static void fastcall free_hot_cold_page(struct page *page, int cold)
{struct zone *zone = page_zone(page);struct per_cpu_pages *pcp;unsigned long flags;arch_free_page(page, 0);kernel_map_pages(page, 1, 0);inc_page_state(pgfree);if (PageAnon(page))page->mapping = NULL;free_pages_check(__FUNCTION__, page);pcp = &zone->pageset[get_cpu()].pcp[cold];local_irq_save(flags);if (pcp->count >= pcp->high)pcp->count -= free_pages_bulk(zone, pcp->batch, &pcp->list, 0);list_add(&page->lru, &pcp->list);pcp->count++;local_irq_restore(flags);put_cpu();
}
第一层:free_page 宏
#define free_page(addr) free_pages((addr),0)
功能:释放单个页面的宏封装
addr:要释放的内存虚拟地址- 转换为调用
free_pages,order=0表示释放1个页面(2^0 = 1页)
第二层:free_pages 函数
fastcall void free_pages(unsigned long addr, unsigned int order)
{if (addr != 0) {BUG_ON(!virt_addr_valid((void *)addr));__free_pages(virt_to_page((void *)addr), order);}
}
if (addr != 0):检查地址非空(NULL地址不需要释放)BUG_ON(!virt_addr_valid((void *)addr)):调试检查,确保虚拟地址有效virt_addr_valid:验证地址是否在有效的内核映射范围内- 如果地址无效,触发内核Oops
__free_pages(virt_to_page((void *)addr), order):virt_to_page((void *)addr):将虚拟地址转换为对应的page结构体指针- 调用下一级释放函数
第三层:__free_pages 函数
fastcall void __free_pages(struct page *page, unsigned int order)
{if (!PageReserved(page) && put_page_testzero(page)) {if (order == 0)free_hot_page(page);else__free_pages_ok(page, order);}
}
关键检查逻辑:
!PageReserved(page):检查页面是否不是保留页面- 保留页面(如内核代码、页表等)不能被释放
put_page_testzero(page):原子地减少引用计数并检查是否归零- 如果返回true,表示这是最后一个引用,可以释放
- 如果返回false,表示还有其他引用,不能释放
释放路径选择:
order == 0:单页释放,进入快速路径free_hot_pageorder > 0:多页释放(复合页),进入标准路径__free_pages_ok
第四层:free_hot_page 函数
void fastcall free_hot_page(struct page *page)
{free_hot_cold_page(page, 0);
}
功能:单页热释放的简单封装
0表示"热"页面(很可能在CPU缓存中)- 热页面更适合快速重用
第五层:free_hot_cold_page 函数
static void fastcall free_hot_cold_page(struct page *page, int cold)
{struct zone *zone = page_zone(page);struct per_cpu_pages *pcp;unsigned long flags;arch_free_page(page, 0);kernel_map_pages(page, 1, 0);inc_page_state(pgfree);if (PageAnon(page))page->mapping = NULL;free_pages_check(__FUNCTION__, page);
初始化阶段:
struct zone *zone = page_zone(page):获取页面所属的内存域struct per_cpu_pages *pcp:每CPU页面缓存指针unsigned long flags:保存中断状态
页面清理操作:
arch_free_page(page, 0):架构相关的页面清理- 可能清除缓存、TLB等架构特定状态
kernel_map_pages(page, 1, 0):重置页面的内核映射属性inc_page_state(pgfree):更新页面释放统计信息if (PageAnon(page)) page->mapping = NULL:如果是匿名页面,清除映射关系free_pages_check(__FUNCTION__, page):调试检查,验证页面状态正确
pcp = &zone->pageset[get_cpu()].pcp[cold];local_irq_save(flags);if (pcp->count >= pcp->high)pcp->count -= free_pages_bulk(zone, pcp->batch, &pcp->list, 0);list_add(&page->lru, &pcp->list);pcp->count++;local_irq_restore(flags);put_cpu();
}
每CPU缓存管理:
pcp = &zone->pageset[get_cpu()].pcp[cold]:获取当前CPU的热/冷页面缓存get_cpu():禁用内核抢占并返回当前CPU ID
local_irq_save(flags):保存中断状态并禁用本地中断- 防止中断处理程序干扰页面缓存操作
缓存水位控制:
if (pcp->count >= pcp->high):检查缓存是否达到高水位线pcp->count -= free_pages_bulk(zone, pcp->batch, &pcp->list, 0):- 如果缓存太满,批量释放部分页面回内存域
- 更新缓存计数
页面添加到缓存:
list_add(&page->lru, &pcp->list):将页面添加到每CPU缓存链表pcp->count++:增加缓存页面计数local_irq_restore(flags):恢复中断状态put_cpu():启用内核抢占
整体功能总结
这个调用链实现了Linux内核的单页面释放机制,具有以下特点:
核心功能
- 安全检查:地址验证、页面保留检查、引用计数检查
- 性能优化:热页面快速路径、每CPU缓存减少锁竞争
- 内存管理:水位线控制防止缓存过度增长
获取页面的内存区域page_zone
struct zone *zone_table[];
#define NODEZONE_SHIFT (sizeof(page_flags_t)*8 - MAX_NODES_SHIFT - MAX_ZONES_SHIFT)
/** with 32 bit page->flags field, we reserve 8 bits for node/zone info.* there are 3 zones (2 bits) and this leaves 8-2=6 bits for nodes.*/
#define MAX_NODES_SHIFT 6
/* There are currently 3 zones: DMA, Normal & Highmem, thus we need 2 bits */
#define MAX_ZONES_SHIFT 2
static inline struct zone *page_zone(struct page *page)
{return zone_table[page->flags >> NODEZONE_SHIFT];
}
static inline void set_page_zone(struct page *page, unsigned long nodezone_num)
{page->flags &= ~(~0UL << NODEZONE_SHIFT);page->flags |= nodezone_num << NODEZONE_SHIFT;
}
第一部分:常量和宏定义
struct zone *zone_table[];
#define NODEZONE_SHIFT (sizeof(page_flags_t)*8 - MAX_NODES_SHIFT - MAX_ZONES_SHIFT)
逐行解析:
-
struct zone *zone_table[];:声明一个全局的zone指针数组- 用于存储所有内存域(zone)的指针
- 通过索引快速查找zone结构
-
#define NODEZONE_SHIFT (sizeof(page_flags_t)*8 - MAX_NODES_SHIFT - MAX_ZONES_SHIFT):- 计算node和zone信息在page->flags中的起始位位置
sizeof(page_flags_t)*8:page->flags的总位数(通常是32位)- 减去节点和区域所需的位数,得到起始偏移
第二部分:位数分配定义
/** with 32 bit page->flags field, we reserve 8 bits for node/zone info.* there are 3 zones (2 bits) and this leaves 8-2=6 bits for nodes.*/
#define MAX_NODES_SHIFT 6
#define MAX_ZONES_SHIFT 2
- 32位的page->flags字段中,保留8位用于存储节点和区域信息
- 有3个内存区域(需要2位),剩下6位用于节点ID
常量定义:
MAX_NODES_SHIFT 6:节点ID占用的位数(2^6 = 64个节点)MAX_ZONES_SHIFT 2:区域类型占用的位数(2^2 = 4个区域)
实际位分配:
32位 page->flags 布局:
[31...24] [23...8] [7...0]未使用 未使用 节点区域信息
在最低8位中:
[7...2] [1...0]节点ID 区域ID
第三部分:page_zone 函数
static inline struct zone *page_zone(struct page *page)
{return zone_table[page->flags >> NODEZONE_SHIFT];
}
static inline:静态内联函数,减少函数调用开销struct zone *page_zone(struct page *page):获取页面所属的内存域page->flags >> NODEZONE_SHIFT:右移得到节点区域索引- 假设32位系统:
NODEZONE_SHIFT = 32 - 6 - 2 = 24 page->flags >> 24将节点区域信息移到最低8位
- 假设32位系统:
zone_table[...]:通过索引在全局表中查找对应的zone指针
第四部分:set_page_zone 函数
static inline void set_page_zone(struct page *page, unsigned long nodezone_num)
{page->flags &= ~(~0UL << NODEZONE_SHIFT);page->flags |= nodezone_num << NODEZONE_SHIFT;
}
-
page->flags &= ~(~0UL << NODEZONE_SHIFT):清除节点区域信息位~0UL:全1的掩码(32位系统:0xFFFFFFFF)~0UL << NODEZONE_SHIFT:左移得到高位的掩码- 32位系统:
0xFFFFFFFF << 24 = 0xFF000000
- 32位系统:
~(...):取反得到低位的掩码:0x00FFFFFF&=:用掩码清除高8位,保留低24位
-
page->flags |= nodezone_num << NODEZONE_SHIFT:设置新的节点区域信息nodezone_num << NODEZONE_SHIFT:将节点区域编号移到高位|=:设置到page->flags中
整体功能总结
核心功能
- 快速查找:通过page->flags中的编码快速找到页面所属的内存域
- 空间优化:在有限的page->flags中高效存储节点和区域信息
- 性能优化:使用位运算和数组查找,避免复杂的计算
设计优势
- 位字段压缩:在32位flags中只用8位存储节点+区域信息
- 快速转换:简单的移位和数组索引操作
- 扩展性:支持最多64个节点和4个内存区域
- 内存效率:不需要在page结构体中存储额外的指针
inc_page_state/PageAnon/free_pages_check
#define inc_page_state(member) mod_page_state(member, 1UL)
#define mod_page_state(member, delta) \do { \unsigned long flags; \local_irq_save(flags); \__get_cpu_var(page_states).member += (delta); \local_irq_restore(flags); \} while (0)
static inline int PageAnon(struct page *page)
{return ((unsigned long)page->mapping & PAGE_MAPPING_ANON) != 0;
}
static inline void free_pages_check(const char *function, struct page *page)
{if ( page_mapped(page) ||page->mapping != NULL ||page_count(page) != 0 ||(page->flags & (1 << PG_lru |1 << PG_private |1 << PG_locked |1 << PG_active |1 << PG_reclaim |1 << PG_slab |1 << PG_swapcache |1 << PG_writeback )))bad_page(function, page);if (PageDirty(page))ClearPageDirty(page);
}
第一部分:页面状态统计宏
#define inc_page_state(member) mod_page_state(member, 1UL)
功能:增加页面状态统计计数
解析:
inc_page_state(member):将指定的页面状态计数器增加1member:页面状态结构体中的成员名(如pgfree)1UL:无符号长整型1,确保类型正确
第二部分:页面状态修改宏
#define mod_page_state(member, delta) \do { \unsigned long flags; \local_irq_save(flags); \__get_cpu_var(page_states).member += (delta); \local_irq_restore(flags); \} while (0)
功能:原子地修改每CPU页面状态计数器
-
unsigned long flags;:保存中断状态的变量 -
local_irq_save(flags);:关键操作- 保存当前中断状态到flags
- 禁用本地CPU的中断
- 防止中断处理程序干扰统计操作
-
__get_cpu_var(page_states).member += (delta);:核心操作__get_cpu_var(page_states):获取当前CPU的page_states变量.member:访问特定的统计成员+= (delta):增加指定的增量值
-
local_irq_restore(flags);:恢复中断状态
第三部分:匿名页面检查函数
static inline int PageAnon(struct page *page)
{return ((unsigned long)page->mapping & PAGE_MAPPING_ANON) != 0;
}
功能:检查页面是否为匿名页面
-
(unsigned long)page->mapping:将mapping指针转换为数值page->mapping通常指向address_space结构体- 但对于匿名页面,这个字段被特殊编码
-
& PAGE_MAPPING_ANON:与匿名映射标志位进行位与操作PAGE_MAPPING_ANON通常是最低位(如0x1)
-
!= 0:检查结果是否非零- 如果设置了匿名标志,返回true(1)
- 否则返回false(0)
第四部分:页面释放检查函数
static inline void free_pages_check(const char *function, struct page *page)
{if ( page_mapped(page) ||page->mapping != NULL ||page_count(page) != 0 ||(page->flags & (1 << PG_lru |1 << PG_private |1 << PG_locked |1 << PG_active |1 << PG_reclaim |1 << PG_slab |1 << PG_swapcache |1 << PG_writeback )))bad_page(function, page);
功能:在释放页面前的完整性检查
1. 页面仍被映射
page_mapped(page)
- 检查页面是否还在页表中有映射
- 如果仍被映射,不能释放
2. 映射指针非空
page->mapping != NULL
- 对于要释放的页面,mapping应该为NULL
- 非空表示页面还在使用中
3. 引用计数非零
page_count(page) != 0
- 页面引用计数应该为0才能释放
- 非零表示还有其他引用
4. 非法标志位检查
(page->flags & (1 << PG_lru |1 << PG_private |1 << PG_locked |1 << PG_active |1 << PG_reclaim |1 << PG_slab |1 << PG_swapcache |1 << PG_writeback ))
检查以下不应该设置的标志位:
PG_lru:页面在LRU链表中(应该先移除)PG_private:页面有私有数据PG_locked:页面被锁定PG_active:页面在活跃链表中PG_reclaim:页面正在被回收PG_slab:页面属于slab分配器PG_swapcache:页面在交换缓存中PG_writeback:页面正在回写
错误处理
bad_page(function, page);
- 如果任何检查失败,调用坏页处理函数
- 记录调试信息,可能触发内核错误
第五部分:脏页清理
if (PageDirty(page))ClearPageDirty(page);
}
功能:清理页面的脏标志
-
PageDirty(page):检查页面是否被标记为脏- 脏页表示内容已被修改,需要写回存储设备
-
ClearPageDirty(page):清除脏标志- 即使页面实际上是脏的,在释放时也清除标志
- 防止后续错误检查
