Linux中内存初始化mem_init函数的实现
内存管理系统初始化mem_init
void __init mem_init(void)
{extern int ppro_with_ram_bug(void);int codesize, reservedpages, datasize, initsize;int tmp;int bad_ppro;#ifndef CONFIG_DISCONTIGMEMif (!mem_map)BUG();
#endifbad_ppro = ppro_with_ram_bug();#ifdef CONFIG_HIGHMEM/* check that fixmap and pkmap do not overlap */if (PKMAP_BASE+LAST_PKMAP*PAGE_SIZE >= FIXADDR_START) {printk(KERN_ERR "fixmap and kmap areas overlap - this will crash\n");printk(KERN_ERR "pkstart: %lxh pkend: %lxh fixstart %lxh\n",PKMAP_BASE, PKMAP_BASE+LAST_PKMAP*PAGE_SIZE, FIXADDR_START);BUG();}
#endifset_max_mapnr_init();#ifdef CONFIG_HIGHMEMhigh_memory = (void *) __va(highstart_pfn * PAGE_SIZE);
#elsehigh_memory = (void *) __va(max_low_pfn * PAGE_SIZE);
#endif/* this will put all low memory onto the freelists */totalram_pages += __free_all_bootmem();reservedpages = 0;for (tmp = 0; tmp < max_low_pfn; tmp++)/** Only count reserved RAM pages*/if (page_is_ram(tmp) && PageReserved(pfn_to_page(tmp)))reservedpages++;set_highmem_pages_init(bad_ppro);codesize = (unsigned long) &_etext - (unsigned long) &_text;datasize = (unsigned long) &_edata - (unsigned long) &_etext;initsize = (unsigned long) &__init_end - (unsigned long) &__init_begin;kclist_add(&kcore_mem, __va(0), max_low_pfn << PAGE_SHIFT);kclist_add(&kcore_vmalloc, (void *)VMALLOC_START,VMALLOC_END-VMALLOC_START);printk(KERN_INFO "Memory: %luk/%luk available (%dk kernel code, %dk reserved, %dk data, %dk init, %ldk highmem)\n",(unsigned long) nr_free_pages() << (PAGE_SHIFT-10),num_physpages << (PAGE_SHIFT-10),codesize >> 10,reservedpages << (PAGE_SHIFT-10),datasize >> 10,initsize >> 10,(unsigned long) (totalhigh_pages << (PAGE_SHIFT-10)));#ifdef CONFIG_X86_PAEif (!cpu_has_pae)panic("cannot execute a PAE-enabled kernel on a PAE-less CPU!");
#endifif (boot_cpu_data.wp_works_ok < 0)test_wp_bit();/** Subtle. SMP is doing it's boot stuff late (because it has to* fork idle threads) - but it also needs low mappings for the* protected-mode entry to work. We zap these entries only after* the WP-bit has been tested.*/
#ifndef CONFIG_SMPzap_low_mappings();
#endif
}
函数功能概述
mem_init
函数负责完成内核内存管理系统的最终初始化,包括释放启动内存、统计内存使用、设置高端内存、检查硬件兼容性等,是内存管理系统从启动阶段过渡到运行阶段的关键函数
代码逐段分析
1. 函数定义和变量声明
void __init mem_init(void)
{extern int ppro_with_ram_bug(void);int codesize, reservedpages, datasize, initsize;int tmp;int bad_ppro;
void __init
:无返回值,初始化阶段函数extern int ppro_with_ram_bug(void)
:声明外部函数,检查Pentium Pro内存bug- 变量用于统计各种内存区域大小
2. 内存映射检查(非离散内存)
#ifndef CONFIG_DISCONTIGMEMif (!mem_map)BUG();
#endif
#ifndef CONFIG_DISCONTIGMEM
:只在连续内存系统中检查if (!mem_map)
:检查全局内存映射数组是否已初始化BUG()
:如果未初始化,触发内核错误
3. 检查Pentium Pro内存bug
bad_ppro = ppro_with_ram_bug();
- 调用函数检测Pentium Pro处理器的特定内存缺陷
- 结果用于后续的高端内存初始化
4. 高端内存区域重叠检查
#ifdef CONFIG_HIGHMEM/* check that fixmap and pkmap do not overlap */if (PKMAP_BASE+LAST_PKMAP*PAGE_SIZE >= FIXADDR_START) {printk(KERN_ERR "fixmap and kmap areas overlap - this will crash\n");printk(KERN_ERR "pkstart: %lxh pkend: %lxh fixstart %lxh\n",PKMAP_BASE, PKMAP_BASE+LAST_PKMAP*PAGE_SIZE, FIXADDR_START);BUG();}
#endif
- 检查永久内核映射(
pkmap
)和固定映射(fixmap
)是否重叠 - 如果重叠会打印详细地址信息并触发错误
5. 设置最大内存页数
set_max_mapnr_init();
- 初始化全局变量
max_mapnr
,表示系统最大可映射页数
6. 设置高端内存指针
#ifdef CONFIG_HIGHMEMhigh_memory = (void *) __va(highstart_pfn * PAGE_SIZE);
#elsehigh_memory = (void *) __va(max_low_pfn * PAGE_SIZE);
#endif
high_memory
:指向物理内存顶部的虚拟地址- 有高端内存时使用
highstart_pfn
,否则使用max_low_pfn
7. 释放所有启动内存
/* this will put all low memory onto the freelists */totalram_pages += __free_all_bootmem();
__free_all_bootmem()
:释放启动期间使用的所有内存到伙伴系统totalram_pages
:累计总可用内存页数
8. 统计保留页面
reservedpages = 0;for (tmp = 0; tmp < max_low_pfn; tmp++)/** Only count reserved RAM pages*/if (page_is_ram(tmp) && PageReserved(pfn_to_page(tmp)))reservedpages++;
- 遍历所有低端内存页帧
- 统计既是RAM又是保留状态的页面
- 这些页面用于内核代码、数据等关键区域
9. 初始化高端内存页面
set_highmem_pages_init(bad_ppro);
- 初始化高端内存页面到伙伴系统
- 传入
bad_ppro
参数处理Pentium Pro兼容性问题
10. 计算内核各段大小
codesize = (unsigned long) &_etext - (unsigned long) &_text;datasize = (unsigned long) &_edata - (unsigned long) &_etext;initsize = (unsigned long) &__init_end - (unsigned long) &__init_begin;
codesize
:代码段大小(_text到_etext
)datasize
:数据段大小(_etext到_edata
)initsize
:初始化段大小(__init_begin到__init_end
)
11. 设置内核核心转储列表
kclist_add(&kcore_mem, __va(0), max_low_pfn << PAGE_SHIFT);kclist_add(&kcore_vmalloc, (void *)VMALLOC_START,VMALLOC_END-VMALLOC_START);
- 注册物理内存和
vmalloc
区域到内核核心转储系统 &kcore_mem
:指向kcore_list
结构的指针,用于存储物理内存区域信息__va(0)
:将物理地址 0 转换为虚拟地址max_low_pfn << PAGE_SHIFT
:计算低端内存的总大小(字节)&kcore_vmalloc
:指向vmalloc
区域信息的指针(void *)VMALLOC_START
:vmalloc
区域的起始虚拟地址VMALLOC_END-VMALLOC_START
:vmalloc
区域的总大小- 便于调试工具访问内核内存
12. 打印内存信息
printk(KERN_INFO "Memory: %luk/%luk available (%dk kernel code, %dk reserved, %dk data, %dk init, %ldk highmem)\n",(unsigned long) nr_free_pages() << (PAGE_SHIFT-10),num_physpages << (PAGE_SHIFT-10),codesize >> 10,reservedpages << (PAGE_SHIFT-10),datasize >> 10,initsize >> 10,(unsigned long) (totalhigh_pages << (PAGE_SHIFT-10)));
- 输出完整的内存使用统计信息
- 包括可用内存、总内存、内核各段大小等
13. PAE(物理地址扩展)检查
#ifdef CONFIG_X86_PAEif (!cpu_has_pae)panic("cannot execute a PAE-enabled kernel on a PAE-less CPU!");
#endif
- 检查CPU是否支持PAE(36位物理地址)
- 如果内核配置需要PAE但CPU不支持,触发恐慌
14. 写保护位测试
if (boot_cpu_data.wp_works_ok < 0)test_wp_bit();
- 测试CPU的写保护位是否正常工作
- 对内存保护至关重要
关键技术细节详解
1. 内存区域符号定义
内核链接布局:
+----------------+
| .text | ← _text
| (代码段) |
+----------------+ ← _etext
| .data |
| (数据段) |
+----------------+ ← _edata
| __init_begin |
| (初始化代码段) |
+----------------+ ← __init_end
2. 启动内存释放过程
// __free_all_bootmem() 的工作:
1. 遍历所有启动内存页面
2. 将非保留页面添加到伙伴系统
3. 更新 totalram_pages 计数
4. 返回释放的页面数量
3. 高端内存初始化
// set_highmem_pages_init() 处理:
1. 将高端内存页面添加到伙伴系统
2. 更新 totalhigh_pages 计数
3. 根据 bad_ppro 调整映射策略
系统状态转换
1. 内存管理阶段变化
启动阶段:
Bootmem分配器 → 简单、受限的内存管理mem_init()调用后:
伙伴系统(Buddy System) → 完整、高效的内存管理
2. 关键全局变量设置
totalram_pages // 总可用内存页数
totalhigh_pages // 高端内存页数
high_memory // 物理内存顶部虚拟地址
max_mapnr // 最大可映射页数
设置最大内存页数set_max_mapnr_init
void __init set_max_mapnr_init(void)
{
#ifdef CONFIG_HIGHMEMstruct zone *high0 = &NODE_DATA(0)->node_zones[ZONE_HIGHMEM];if (high0->spanned_pages > 0)highmem_start_page = high0->zone_mem_map;elsehighmem_start_page = pfn_to_page(max_low_pfn - 1) + 1;num_physpages = highend_pfn;
#elsenum_physpages = max_low_pfn;
#endif
}
函数功能概述
set_max_mapnr_init
函数负责初始化全局变量 num_physpages
,该变量表示系统中的总物理页面数,同时处理高端内存的特殊情况
代码逐段分析
1. 函数定义
void __init set_max_mapnr_init(void)
{
void __init
:无返回值,__init
表示只在初始化阶段使用- 函数名表明这是设置最大内存页数的初始化函数
2. 高端内存配置处理
#ifdef CONFIG_HIGHMEMstruct zone *high0 = &NODE_DATA(0)->node_zones[ZONE_HIGHMEM];
#ifdef CONFIG_HIGHMEM
:条件编译,只在配置了高端内存支持时包含struct zone *high0
:指向节点0的高端内存区域NODE_DATA(0)
:获取节点0的数据结构(在NUMA系统中)node_zones[ZONE_HIGHMEM]
:访问高端内存区域
3. 检查高端内存区域是否跨越页面
if (high0->spanned_pages > 0)highmem_start_page = high0->zone_mem_map;
high0->spanned_pages > 0
:检查高端内存区域是否包含任何页面highmem_start_page = high0->zone_mem_map
:如果存在高端内存,设置高端内存起始页面指针zone_mem_map
:指向该内存区域页面描述符数组的起始位置
4. 处理无高端内存的情况
elsehighmem_start_page = pfn_to_page(max_low_pfn - 1) + 1;
else
:如果高端内存区域没有实际页面pfn_to_page(max_low_pfn - 1)
:将最大低端内存页帧号转换为页面描述符+ 1
:指向下一个页面(即高端内存的理论起始位置)- 作用:即使没有实际高端内存,也设置一个理论上的起始位置
5. 设置总物理页面数(高端内存情况)
num_physpages = highend_pfn;
num_physpages
:全局变量,存储系统中的总物理页面数highend_pfn
:最高物理页帧号(包括高端内存)
6. 非高端内存配置处理
#elsenum_physpages = max_low_pfn;
#endif
}
#else
:没有配置高端内存支持的情况num_physpages = max_low_pfn
:总物理页面数等于最大低端内存页帧号#endif
:结束条件编译块
关键技术细节详解
1. 内存区域和页帧号关系
物理内存布局:
低端内存: 0x00000000 - 0x0FFFFFFF (页帧号: 0 到 max_low_pfn-1)
高端内存: 0x10000000 - 0xFFFFFFFF (页帧号: max_low_pfn 到 highend_pfn)页面描述符数组:
+---------+---------+-----+---------+---------+-----+---------+
| page0 | page1 | ... | pageN | pageN+1 | ... | pageM |
+---------+---------+-----+---------+---------+-----+---------+低端内存页面 高端内存页面↑ ↑mem_map highmem_start_page
在系统初始化中的位置
// 典型的内存初始化调用序列
start_kernel()→ setup_arch() // 架构特定设置→ setup_per_cpu_areas() // 每CPU区域设置 → build_all_zonelists() // 构建区域列表→ mem_init() // 内存初始化→ set_max_mapnr_init() // 我们分析的函数 ← 在这里→ __free_all_bootmem() // 释放启动内存
释放启动内存free_all_bootmem_core
#define __free_all_bootmem() free_all_bootmem()
unsigned long __init free_all_bootmem (void)
{return(free_all_bootmem_core(NODE_DATA(0)));
}
static unsigned long __init free_all_bootmem_core(pg_data_t *pgdat)
{struct page *page;bootmem_data_t *bdata = pgdat->bdata;unsigned long i, count, total = 0;unsigned long idx;unsigned long *map;int gofast = 0;BUG_ON(!bdata->node_bootmem_map);count = 0;/* first extant page of the node */page = virt_to_page(phys_to_virt(bdata->node_boot_start));idx = bdata->node_low_pfn - (bdata->node_boot_start >> PAGE_SHIFT);map = bdata->node_bootmem_map;/* Check physaddr is O(LOG2(BITS_PER_LONG)) page aligned */if (bdata->node_boot_start == 0 ||ffs(bdata->node_boot_start) - PAGE_SHIFT > ffs(BITS_PER_LONG))gofast = 1;for (i = 0; i < idx; ) {unsigned long v = ~map[i / BITS_PER_LONG];if (gofast && v == ~0UL) {int j;count += BITS_PER_LONG;__ClearPageReserved(page);set_page_count(page, 1);for (j = 1; j < BITS_PER_LONG; j++) {if (j + 16 < BITS_PER_LONG)prefetchw(page + j + 16);__ClearPageReserved(page + j);}__free_pages(page, ffs(BITS_PER_LONG)-1);i += BITS_PER_LONG;page += BITS_PER_LONG;} else if (v) {unsigned long m;for (m = 1; m && i < idx; m<<=1, page++, i++) {if (v & m) {count++;__ClearPageReserved(page);set_page_count(page, 1);__free_page(page);}}} else {i+=BITS_PER_LONG;page += BITS_PER_LONG;}}total += count;/** Now free the allocator bitmap itself, it's not* needed anymore:*/page = virt_to_page(bdata->node_bootmem_map);count = 0;for (i = 0; i < ((bdata->node_low_pfn-(bdata->node_boot_start >> PAGE_SHIFT))/8 + PAGE_SIZE-1)/PAGE_SIZE; i++,page++) {count++;__ClearPageReserved(page);set_page_count(page, 1);__free_page(page);}total += count;bdata->node_bootmem_map = NULL;return total;
}
函数功能概述
free_all_bootmem_core
函数负责将启动期间使用的内存释放到伙伴系统,完成从简单的启动内存管理到完整的内存管理系统的过渡
代码逐段分析
1. 函数定义和变量声明
static unsigned long __init free_all_bootmem_core(pg_data_t *pgdat)
{struct page *page;bootmem_data_t *bdata = pgdat->bdata;unsigned long i, count, total = 0;unsigned long idx;unsigned long *map;int gofast = 0;
static unsigned long __init
:静态函数,返回释放的页面数,只在初始化阶段使用pg_data_t *pgdat
:节点数据指针bootmem_data_t *bdata = pgdat->bdata
:获取启动内存数据- 变量用于循环计数和统计
2. 安全检查
BUG_ON(!bdata->node_bootmem_map);
- 检查启动内存位图是否存在
- 如果不存在,触发内核错误
3. 初始化变量
count = 0;/* first extant page of the node */page = virt_to_page(phys_to_virt(bdata->node_boot_start));idx = bdata->node_low_pfn - (bdata->node_boot_start >> PAGE_SHIFT);map = bdata->node_bootmem_map;
count = 0
:初始化计数器page = virt_to_page(phys_to_virt(bdata->node_boot_start))
:计算第一个页面的虚拟地址并转换为页面结构idx
:计算需要处理的页面数量map = bdata->node_bootmem_map
:获取启动内存位图指针
4. 快速路径检查
/* Check physaddr is O(LOG2(BITS_PER_LONG)) page aligned */if (bdata->node_boot_start == 0 ||ffs(bdata->node_boot_start) - PAGE_SHIFT > ffs(BITS_PER_LONG))gofast = 1;
-
检查是否可以使用快速路径
-
条件:启动地址为0或对齐方式允许快速处理
-
快速路径可以批量处理32个页面
-
ffs()
:找到第一个设置位的位置-
// ffs = Find First Set bit (找到第一个设置为1的位) // 返回从1开始计数的位置(最低位为1) ffs(0x00000001) = 1 // 第1位 ffs(0x00000010) = 5 // 第5位 ffs(0x80000000) = 32 // 第32位 ffs(0x00000000) = 0 // 没有设置位
-
5. 主释放循环
for (i = 0; i < idx; ) {unsigned long v = ~map[i / BITS_PER_LONG];
- 循环遍历所有页面
v = ~map[i / BITS_PER_LONG]
:取反位图值,1表示空闲页面
6. 快速路径处理
if (gofast && v == ~0UL) {int j;count += BITS_PER_LONG;__ClearPageReserved(page);set_page_count(page, 1);for (j = 1; j < BITS_PER_LONG; j++) {if (j + 16 < BITS_PER_LONG)prefetchw(page + j + 16);__ClearPageReserved(page + j);}__free_pages(page, ffs(BITS_PER_LONG)-1);i += BITS_PER_LONG;page += BITS_PER_LONG;
- 如果整个字(BITS_PER_LONG位)都是空闲页面
- 批量清除保留标志
- 使用预取优化性能
- 一次性释放多个页面到伙伴系统
7. 慢速路径处理(部分空闲)
} else if (v) {unsigned long m;for (m = 1; m && i < idx; m<<=1, page++, i++) {if (v & m) {count++;__ClearPageReserved(page);set_page_count(page, 1);__free_page(page);}}
-
逐位检查哪些页面是空闲的
-
for (m = 1; // 初始化:m从第0位开始m && i < idx; // 条件:m不为0且未超出页面范围m<<=1, page++, i++) // 递增:m左移,page和i增加 // 初始值:m = 1 (二进制 00000000 00000000 00000000 00000001) // 每次循环:m <<= 1 (左移1位) // 作用:用于逐个检查 v 中的每一个位
-
对每个空闲页面单独处理
-
清除保留标志并释放到伙伴系统
8. 跳过已使用页面
} else {i+=BITS_PER_LONG;page += BITS_PER_LONG;}}
- 如果整个字都是已使用页面,直接跳过
9. 统计已释放页面
total += count;
10. 释放启动内存位图本身
/** Now free the allocator bitmap itself, it's not* needed anymore:*/page = virt_to_page(bdata->node_bootmem_map);count = 0;for (i = 0; i < ((bdata->node_low_pfn-(bdata->node_boot_start >> PAGE_SHIFT))/8 + PAGE_SIZE-1)/PAGE_SIZE; i++,page++) {count++;__ClearPageReserved(page);set_page_count(page, 1);__free_page(page);}
-
计算启动内存位图本身占用多少页面
-
bdata->node_low_pfn // 节点低端内存的最大页帧号 bdata->node_boot_start // 启动内存起始物理地址 PAGE_SHIFT = 12 // 页大小移位 (4KB = 2¹²) PAGE_SIZE = 4096 // 页大小 (4KB) bdata->node_boot_start >> PAGE_SHIFT // 将物理地址转换为页帧号 bdata->node_low_pfn - (bdata->node_boot_start >> PAGE_SHIFT) // 得到需要管理的总页面数量 / 8 // 每个页面用1位表示,所以除以8得到字节数 + PAGE_SIZE-1)/PAGE_SIZE // 向上取整到页面边界
-
循环释放每个位图页面
-
清除保留标志并释放到伙伴系统
11. 清理和返回
total += count;bdata->node_bootmem_map = NULL;return total;
}
- 累加总释放页面数
- 清空位图指针
- 返回释放的总页面数
性能优化策略
1. 快速路径 (gofast)
- 一次性处理32个页面(在32位系统上)
- 减少循环次数和函数调用
- 使用预取指令优化缓存
2. 位操作优化
// 使用位运算批量检查页面状态
v = ~map[i / BITS_PER_LONG]; // 一次检查32个页面// 快速检查是否全空闲
if (v == ~0UL) { /* 快速路径 */ }// 逐位检查部分空闲
for (m = 1; m; m<<=1) { /* 慢速路径 */ }
连续高端内存页面初始化set_highmem_pages_init
#ifndef CONFIG_DISCONTIGMEM
void __init set_highmem_pages_init(int bad_ppro)
{int pfn;for (pfn = highstart_pfn; pfn < highend_pfn; pfn++)one_highpage_init(pfn_to_page(pfn), pfn, bad_ppro);totalram_pages += totalhigh_pages;
}
函数功能概述
这些函数负责初始化高端内存页面,处理 Pentium Pro 处理器的特定内存缺陷,并将可用的高端内存页面释放到伙伴系统中
代码逐段分析
1. 顶层初始化函数
#ifndef CONFIG_DISCONTIGMEM
void __init set_highmem_pages_init(int bad_ppro)
{int pfn;for (pfn = highstart_pfn; pfn < highend_pfn; pfn++)one_highpage_init(pfn_to_page(pfn), pfn, bad_ppro);totalram_pages += totalhigh_pages;
}
#ifndef CONFIG_DISCONTIGMEM
:只在连续内存系统中包含- 遍历从
highstart_pfn
到highend_pfn
的所有高端内存页帧 - 对每个页面调用
one_highpage_init
进行初始化 - 最后将高端内存页面数加到总内存页面数中
非连续高端内存页面初始化set_highmem_pages_init
void __init set_highmem_pages_init(int bad_ppro)
{
#ifdef CONFIG_HIGHMEMstruct zone *zone;for_each_zone(zone) {unsigned long node_pfn, node_high_size, zone_start_pfn;struct page * zone_mem_map;if (!is_highmem(zone))continue;printk("Initializing %s for node %d\n", zone->name,zone->zone_pgdat->node_id);node_high_size = zone->spanned_pages;zone_mem_map = zone->zone_mem_map;zone_start_pfn = zone->zone_start_pfn;for (node_pfn = 0; node_pfn < node_high_size; node_pfn++) {one_highpage_init((struct page *)(zone_mem_map + node_pfn),zone_start_pfn + node_pfn, bad_ppro);}}totalram_pages += totalhigh_pages;
#endif
}
函数功能概述
这个函数是支持非连续内存系统(CONFIG_DISCONTIGMEM)的高端内存初始化版本,它遍历所有内存节点的所有高端内存区域,初始化每个高端内存页面
代码逐段分析
1. 函数定义和条件编译
void __init set_highmem_pages_init(int bad_ppro)
{
#ifdef CONFIG_HIGHMEM
void __init
:无返回值,初始化阶段函数int bad_ppro
:Pentium Pro内存缺陷标志#ifdef CONFIG_HIGHMEM
:只在配置了高端内存支持时编译
2. 变量声明
struct zone *zone;
struct zone *zone
:内存区域指针,用于遍历所有内存区域
3. 遍历所有内存区域
for_each_zone(zone) {
for_each_zone(zone)
:宏,遍历系统中的所有内存区域- 包括:ZONE_DMA, ZONE_NORMAL, ZONE_HIGHMEM等
4. 跳过非高端内存区域
if (!is_highmem(zone))continue;
is_highmem(zone)
:检查区域是否为高端内存区域continue
:如果不是高端内存区域,跳过处理
5. 打印初始化信息
printk("Initializing %s for node %d\n", zone->name,zone->zone_pgdat->node_id);
- 输出正在初始化的区域名称和节点ID
- 示例输出:
"Initializing HighMem for node 0"
6. 获取区域内存信息
node_high_size = zone->spanned_pages;zone_mem_map = zone->zone_mem_map;zone_start_pfn = zone->zone_start_pfn;
node_high_size
:区域跨越的总页面数zone_mem_map
:指向该区域页面描述符数组的指针zone_start_pfn
:区域的起始页帧号
7. 遍历区域内的所有页面
for (node_pfn = 0; node_pfn < node_high_size; node_pfn++) {one_highpage_init((struct page *)(zone_mem_map + node_pfn),zone_start_pfn + node_pfn, bad_ppro);}
- 循环遍历区域内的每个页面
zone_mem_map + node_pfn
:计算页面描述符的地址zone_start_pfn + node_pfn
:计算实际的物理页帧号- 对每个页面调用
one_highpage_init
进行初始化
8. 更新总内存页面计数
}totalram_pages += totalhigh_pages;
#endif
}
- 结束区域循环
totalram_pages += totalhigh_pages
:将高端页面数加到总内存页面数中- 结束条件编译块和函数
关键技术细节详解
1. 内存区域遍历宏
// for_each_zone(zone) 宏的简化实现
#define for_each_zone(zone) \for (zone = pgdat_list->node_zones; zone; zone = next_zone(zone))
遍历所有节点的zone
2. 高端内存区域判断
// is_highmem(zone) 的实现
static inline int is_highmem(struct zone *zone)
{return (is_highmem_idx(zone - zone->zone_pgdat->node_zones));
}
static inline int is_highmem_idx(int idx)
{return (idx == ZONE_HIGHMEM);
}
3. 内存区域数据结构关系
系统内存组织:
pg_data_t (节点0) pg_data_t (节点1)↓ ↓
node_zones[] node_zones[]↓ ↓
ZONE_DMA ZONE_DMA
ZONE_NORMAL ZONE_NORMAL
ZONE_HIGHMEM ZONE_HIGHMEM
4. 页面地址计算
// 页面描述符地址计算
struct page *page = zone_mem_map + node_pfn;// 物理页帧号计算
unsigned long pfn = zone_start_pfn + node_pfn;// 这相当于:
page = &zone_mem_map[node_pfn];
单个高端页面初始化one_highpage_init
void __init one_highpage_init(struct page *page, int pfn, int bad_ppro)
{if (page_is_ram(pfn) && !(bad_ppro && page_kills_ppro(pfn))) {ClearPageReserved(page);set_bit(PG_highmem, &page->flags);set_page_count(page, 1);__free_page(page);totalhigh_pages++;} elseSetPageReserved(page);
}
static inline int page_kills_ppro(unsigned long pagenr)
{if (pagenr >= 0x70000 && pagenr <= 0x7003F)return 1;return 0;
}
static inline int page_is_ram(unsigned long pagenr)
{int i;unsigned long addr, end;if (efi_enabled) {efi_memory_desc_t *md;for (i = 0; i < memmap.nr_map; i++) {md = &memmap.map[i];if (!is_available_memory(md))continue;addr = (md->phys_addr+PAGE_SIZE-1) >> PAGE_SHIFT;end = (md->phys_addr + (md->num_pages << EFI_PAGE_SHIFT)) >> PAGE_SHIFT;if ((pagenr >= addr) && (pagenr < end))return 1;}return 0;}for (i = 0; i < e820.nr_map; i++) {if (e820.map[i].type != E820_RAM) /* not usable memory */continue;/** !!!FIXME!!! Some BIOSen report areas as RAM that* are not. Notably the 640->1Mb area. We need a sanity* check here.*/addr = (e820.map[i].addr+PAGE_SIZE-1) >> PAGE_SHIFT;end = (e820.map[i].addr+e820.map[i].size) >> PAGE_SHIFT;if ((pagenr >= addr) && (pagenr < end))return 1;}return 0;
}
1. 单个高端页面初始化
void __init one_highpage_init(struct page *page, int pfn, int bad_ppro)
{if (page_is_ram(pfn) && !(bad_ppro && page_kills_ppro(pfn))) {ClearPageReserved(page);set_bit(PG_highmem, &page->flags);set_page_count(page, 1);__free_page(page);totalhigh_pages++;} elseSetPageReserved(page);
}
条件检查:
page_is_ram(pfn)
:检查页面是否是真正的RAM!(bad_ppro && page_kills_ppro(pfn))
:检查是否受Pentium Pro bug影响
可用页面处理:
ClearPageReserved(page)
:清除保留标志set_bit(PG_highmem, &page->flags)
:标记为高端内存页面set_page_count(page, 1)
:设置引用计数__free_page(page)
:释放到伙伴系统totalhigh_pages++
:增加高端页面计数
不可用页面处理:
SetPageReserved(page)
:标记为保留,防止使用
2. Pentium Pro 缺陷页面检查
static inline int page_kills_ppro(unsigned long pagenr)
{if (pagenr >= 0x70000 && pagenr <= 0x7003F)return 1;return 0;
}
- 检查页面是否在受影响的地址范围:0x70000 到 0x7003F
- 这个范围内的页面在Pentium Pro上会导致系统崩溃
- 受影响的是64个页面(0x70000-0x7003F = 64个页面)
3. 页面RAM状态检查
static inline int page_is_ram(unsigned long pagenr)
{int i;unsigned long addr, end;if (efi_enabled) {efi_memory_desc_t *md;for (i = 0; i < memmap.nr_map; i++) {md = &memmap.map[i];if (!is_available_memory(md))continue;addr = (md->phys_addr+PAGE_SIZE-1) >> PAGE_SHIFT;end = (md->phys_addr + (md->num_pages << EFI_PAGE_SHIFT)) >> PAGE_SHIFT;if ((pagenr >= addr) && (pagenr < end))return 1;}return 0;}
EFI系统处理:
- 遍历EFI内存映射表
- 跳过不可用内存区域
- 计算每个内存区域的页帧范围
- 检查目标页面是否在可用RAM区域内
4. 传统BIOS(e820)处理
for (i = 0; i < e820.nr_map; i++) {if (e820.map[i].type != E820_RAM) /* not usable memory */continue;/** !!!FIXME!!! Some BIOSen report areas as RAM that* are not. Notably the 640->1Mb area. We need a sanity* check here.*/addr = (e820.map[i].addr+PAGE_SIZE-1) >> PAGE_SHIFT;end = (e820.map[i].addr+e820.map[i].size) >> PAGE_SHIFT;if ((pagenr >= addr) && (pagenr < end))return 1;}return 0;
}
传统BIOS处理:
- 遍历e820内存映射表
- 只处理
E820_RAM
类型的区域 - 有些BIOS错误报告RAM区域(如640KB-1MB区域)
- 计算每个区域的页帧范围并检查
写保护位测试test_wp_bit
void __init test_wp_bit(void)
{printk("Checking if this processor honours the WP bit even in supervisor mode... ");/* Any page-aligned address will do, the test is non-destructive */__set_fixmap(FIX_WP_TEST, __pa(&swapper_pg_dir), PAGE_READONLY);boot_cpu_data.wp_works_ok = do_test_wp_bit();clear_fixmap(FIX_WP_TEST);if (!boot_cpu_data.wp_works_ok) {printk("No.\n");
#ifdef CONFIG_X86_WP_WORKS_OKpanic("This kernel doesn't support CPU's with broken WP. Recompile it for a 386!");
#endif} else {printk("Ok.\n");}
}
static int noinline do_test_wp_bit(void)
{char tmp_reg;int flag;__asm__ __volatile__(" movb %0,%1 \n""1: movb %1,%0 \n"" xorl %2,%2 \n""2: \n"".section __ex_table,\"a\"\n"" .align 4 \n"" .long 1b,2b \n"".previous \n":"=m" (*(char *)fix_to_virt(FIX_WP_TEST)),"=q" (tmp_reg),"=r" (flag):"2" (1):"memory");return flag;
}
函数功能概述
这些函数用于测试 CPU 是否在超级用户模式下正确支持写保护位,这是内存保护机制的基础,对于系统的安全性至关重要
代码逐段分析
1. 顶层测试函数
void __init test_wp_bit(void)
{printk("Checking if this processor honours the WP bit even in supervisor mode... ");
void __init
:初始化阶段函数,完成后内存会被释放- 打印测试开始信息,说明测试的是"超级用户模式下的WP位"
2. 设置测试页面
/* Any page-aligned address will do, the test is non-destructive */__set_fixmap(FIX_WP_TEST, __pa(&swapper_pg_dir), PAGE_READONLY);
-
任何页面对齐的地址都可以,测试是非破坏性的
-
__set_fixmap(FIX_WP_TEST, __pa(&swapper_pg_dir), PAGE_READONLY)
:-
FIX_WP_TEST
:固定映射的测试槽位 -
__pa(&swapper_pg_dir)
:获取交换页目录的物理地址-
x86 分页层次: swapper_pg_dir (PGD) → 页中间目录(PMD) → 页表(PTE) → 物理页面↓CR3 寄存器指向这里
-
-
PAGE_READONLY
:设置为只读权限
-
3. 执行测试并记录结果
boot_cpu_data.wp_works_ok = do_test_wp_bit();clear_fixmap(FIX_WP_TEST);
- 调用实际的测试函数,将结果保存到启动CPU数据中
- 清除固定映射,恢复原状
4. 测试结果处理
if (!boot_cpu_data.wp_works_ok) {printk("No.\n");
#ifdef CONFIG_X86_WP_WORKS_OKpanic("This kernel doesn't support CPU's with broken WP. Recompile it for a 386!");
#endif} else {printk("Ok.\n");}
}
- 如果WP位不工作:打印"No",在特定配置下触发恐慌
- 如果WP位工作:打印"Ok"
内联汇编测试函数
5. 汇编测试函数定义
static int noinline do_test_wp_bit(void)
{char tmp_reg;int flag;__asm__ __volatile__(
noinline
:禁止内联,确保汇编代码位置固定tmp_reg
:临时寄存器变量flag
:测试结果标志__asm__ __volatile__
:内联汇编,防止编译器优化
6. 汇编代码主体
" movb %0,%1 \n""1: movb %1,%0 \n"" xorl %2,%2 \n""2: \n"
指令解析:
movb %0,%1
:从内存读取一个字节到临时寄存器1: movb %1,%0
:关键测试:尝试写回内存(应该触发页错误)xorl %2,%2
:清除标志位(如果执行到这里说明WP失败)2:
:页错误处理程序的标签
7. 异常处理表
".section __ex_table,\"a\"\n"" .align 4 \n"" .long 1b,2b \n"".previous \n"
- 创建异常处理表段
.long 1b,2b
:如果指令1(1b)发生异常,跳转到指令2(2b)- 这是内核的异常修复机制
8. 输入输出操作数
:"=m" (*(char *)fix_to_virt(FIX_WP_TEST)),"=q" (tmp_reg),"=r" (flag):"2" (1):"memory");
输出操作数:
%0
:内存操作数,指向测试页面%1
:字节寄存器(tmp_reg
)%2
:通用寄存器(flag)
输入操作数:
"2" (1)
:将1输入到%2寄存器(flag初始为1)
破坏描述:
"memory"
:告诉编译器内存被修改
9. 返回结果
return flag;
}
- 返回测试结果
- 写保护不工作,flag会清0,即
xorl %2,%2
- 写保护工作则flag仍然为1
测试原理详解
1. WP位工作的情况:
1. 页面设置为只读
2. 尝试写入 → 触发页错误
3. 异常处理跳转到标签2
4. flag保持初始值1
5. 返回1表示WP工作
2. WP位不工作的情况:
1. 页面设置为只读
2. 尝试写入 → 成功写入(不应该发生)
3. 执行xorl %2,%2清除flag
4. flag变为0
5. 返回0表示WP不工作