当前位置: 首页 > news >正文

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_STARTvmalloc 区域的起始虚拟地址
  • VMALLOC_END-VMALLOC_STARTvmalloc 区域的总大小
  • 便于调试工具访问内核内存

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_pfnhighend_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不工作
http://www.dtcms.com/a/496615.html

相关文章:

  • 怎么做微信钓鱼网站吗有哪些设计好看的企业官网
  • html做的网页怎么变成网站seo分析网站
  • 沈阳网站建设技术公司怎么登陆自己的公司网站
  • nextjs前端工程如何打包部署(nginx)
  • 网站设计与建设的阿里巴巴代加工平台
  • 高性能物联网双轴倾角传感器及其可靠厂家选择指南
  • 一个网站如何做桌面快捷链接建设银行网站会员基本信息
  • 广州汽车网站建设国外做的比较好的购物网站
  • 深度学习2-损失函数-数值微分-随机梯度下降法(SGD)-反向传播算法
  • 濮阳网站建设熊掌号网站建设捌金手指花总五
  • 深度剖析:Feign 调用第三方接口 + Token 自动续期(24 小时有效期 + 1/4 时间触发)实战指南
  • AgentScope RAG 示例指南
  • 做网站学哪种代码好jquery 显示 wordpress
  • 做网站模板的网页名称是m开头swiper wordpress
  • 首京建设投资引导基金网站海淀重庆网站建设
  • NumPy random.choice() 函数详解
  • 网站手机端 怎么做东莞工业品网站建设
  • 广东网站建设网站前端一个页面多少钱
  • Redis分布式锁、Redisson及Redis红锁知识点总结
  • 企业网络建站动漫制作专业专升本大学
  • 东莞网站建设推广方案制作一个网站多少钱啊
  • Spark Shuffle 分区与 AQE 优化
  • 上海住建部网站wordpress下载按钮插件
  • 深度解析:电商API的核心功能与应用
  • 网站建设 定制移动端开发工具
  • html5网站开发费用什么是网络营销?网络营销有哪些功能
  • 衡石 HQL:以函数为基,构建AI时代的敏捷语义层
  • cms网站系统网站建设评审会总结发言
  • 倍数关系:最多能选出多少个数
  • 建设一个怎样的自己的网站首页苏州做网站优化的