目录
[1.3 总结](#1.3 总结)
[2.4、 e820__end_of_ram_pfn](#2.4、 e820__end_of_ram_pfn)
1、内存探测
1.1、e820__memory_setup
e820__memory_setup
--> e820__memory_setup_default
--> append_e820_table // 将BIOS提供的原始E820内存映射表复制到内核安全区域
--> e820__update_table(e820_table); //更新内核的E820内存映射表
--> memcpy(e820_table_kexec, e820_table, sizeof(*e820_table_kexec));
memcpy(e820_table_firmware, e820_table, sizeof(*e820_table_firmware));
--> e820__print_table
该流程主要内容:
(1)从 BIOS 获取原始 E820 表
(2)构建三份内存表:
- e820_table:主表,供内核内存管理使用。
- e820_table_kexec:为 kexec 热重启保留,确保新内核看到一致的物理布局。
- e820_table_firmware:备份原始 BIOS 数据,用于调试或回退。
(3)打印内存布局
[ 0.000000] BIOS-provided physical RAM map:
[ 0.000000] BIOS-e820: [mem 0x0000000000000000-0x000000000009fbff] usable
[ 0.000000] BIOS-e820: [mem 0x000000000009fc00-0x000000000009ffff] reserved
[ 0.000000] BIOS-e820: [mem 0x00000000000f0000-0x00000000000fffff] reserved
[ 0.000000] BIOS-e820: [mem 0x0000000000100000-0x000000001ffdffff] usable
[ 0.000000] BIOS-e820: [mem 0x000000001ffe0000-0x000000001fffffff] reserved
[ 0.000000] BIOS-e820: [mem 0x00000000fffc0000-0x00000000ffffffff] reserved
1.2、parse_setup_data
parse_setup_data
--> e820__memory_setup_extended
--> early_memremap // 映射物理地址到内核虚拟地址空间
--> __append_e820_table // 将扩展条目追加到内核 E820 表
--> e820__update_table // 合并/优化内存表(处理重叠或相邻区域)
--> memcpy(e820_table_kexec, e820_table, sizeof(*e820_table_kexec)); // 备份更新后的内存表
memcpy(e820_table_firmware, e820_table, sizeof(*e820_table_firmware));
--> early_memunmap // 解除早期内存映射
--> e820__print_table("extended"); // 打印扩展后的内存布局
主要内容:
(1)在早期启动阶段(页表未完全初始化时),将物理地址 phys_addr 临时映射到内核虚拟地址空间,以访问扩展的 E820 数据
(2)将扩展条目追加到内核主表 e820_table,不进行合并(保留原始信息)
(3)合并相邻/重叠区域,解决冲突(如高优先级类型覆盖低优先级类型)
(4)释放临时映射,避免内存泄漏
(5)打印扩展后的完整内存布局,前缀为 "extended",示例:
[ 0.000000] extended physical RAM map:
[ 0.000000] extended: [mem 0x00000000-0x0009ffff] usable
[ 0.000000] extended: [mem 0x00100000-0x3fffffff] reserved
1.3 总结
第一部分整体调用流程:
start_kernel()
--> setup_arch()
--> e820__memory_setup() // 处理前 128 条目
--> parse_setup_data() // 遍历 setup_data 链表
--> e820__memory_setup_extended() // 处理扩展条目
目的:解决BIOS/引导加载程序struct boot_params 中 e820_table 仅能容纳 128 个内存条目的问题
解决方案:超出部分通过 SETUP_E820_EXT 节点(struct setup_data 链表)传递,传递后将数据合并到e820_table,经e820__update_table整理后更新到e820_table_kexec/firmware
2、物理内存整理
2.1、parse_early_param
parse_early_param()
--> parse_early_options(cmdline)
--> parse_args(..., do_early_param)
--> 对每个参数调用 parse_one()
--> 若匹配 early 标记,调用 do_early_param()
--> 执行 p->setup_func(val)(如 early_mem("512M"))
整个流程是解析早期启动参数(early param)的过程,涉及内核命令行参数(cmdline)的解析和相应处理函数的执行
(1)在 do_early_param() 中,内核通过以下步骤匹配并执行回调:
- 遍历注册表:扫描 __setup_start 到 __setup_end 之间的所有参数(由链接脚本生成)。
- 匹配参数名:比较 cmdline 中的参数(如 mem=512M)与注册的 str 字段。
- 执行回调:若匹配且标记为 early,则调用 setup_func(val),传入参数值(如 512M)。
(2)linux使用early_param 宏(定义于 include/linux/init.h),将参数名与处理函数绑定,并标记为 EARLY 属性,例如:
early_param("mem", early_mem); // 注册 "mem=" 参数,回调函数为 early_mem
展开后生成一个 struct obs_kernel_param 结构体,存入 .init.setup 段(通过链接脚本聚合),即__setup_start 到 __setup_end 之间的段
(3)因此,针对下面的代码:
early_param("mem", parse_memopt);
若命令行中有 'mem= ' 字段,便会调用parse_memopt回调函数,进行内存大小重新调整
2.2、e820__reserve_setup_data
e820__reserve_setup_data
--> e820__range_update
--> e820__update_table
--> e820__print_table
e820__finish_early_params
--> e820__update_table
--> e820__print_table
保留bootloader传递的setup_data内存区域,并更新内存映射表
在完成早期启动参数(如 mem=xxx)解析后,对内存映射表进行最终规范化,并打印调试信息
2.3、e820_add_kernel_range
e820_add_kernel_range
--> e820__mapped_all
--> e820__range_remove
--> e820__range_add
static void __init e820_add_kernel_range(void)
{
// 计算内核的物理地址范围(从 _text 到 _end)
u64 start = __pa_symbol(_text); // 内核代码起始物理地址
u64 size = __pa_symbol(_end) - start; // 内核占用的总大小
// 检查是否已被正确标记为 RAM
if (e820__mapped_all(start, start + size, E820_TYPE_RAM))
return; // 如果已经是 RAM,直接返回
// 报警:内核区域未被标记为 RAM
pr_warn(".text .data .bss are not marked as E820_TYPE_RAM!\n");
// 修复步骤:
// 1. 移除该范围内的所有现有内存条目(无论类型)
e820__range_remove(start, size, E820_TYPE_RAM, 0);
// 2. 重新添加为 E820_TYPE_RAM
e820__range_add(start, size, E820_TYPE_RAM);
}
- 背景:BIOS 或 bootloader 提供的 E820 内存映射表可能不准确,有时会错误地将内核代码和数据所在的内存区域标记为"保留"或"不可用"(如 E820_TYPE_RESERVED)。例如:用户通过 memmap=exactmap 或 memmap=xxM$yyM 手动排除内核所在的内存区域。
- 目的:强制将内核的代码段(.text)、数据段(.data)和未初始化数据段(.bss)标记为 E820_TYPE_RAM,避免内核因运行在"非 RAM"区域而崩溃。
2.4、 e820__end_of_ram_pfn
这段代码主要用于 确定系统物理内存的边界(以页帧号 pfn 表示)
e820__end_of_ram_pfn // 计算系统中可用物理内存(E820_TYPE_RAM)的最后一页的页帧号(last_pfn)。
--> e820_end_pfn
函数主要功能:
(1)确定物理内存边界:通过遍历 E820 表,计算可用 RAM 的最大页帧号(last_pfn)。
(2)区分低端/高端内存(32/64 位):
-
32 位:直接调用 find_low_pfn_range()。
-
64 位:根据物理内存是否超过 4GB 动态设置 max_low_pfn,last_pfn 和 max_low_pfn 是内核内存管理的关键参数,用于初始化页表、伙伴系统等。
MAX_ARCH_PFN (架构支持最大值) │ ▼ e820_end_pfn() → last_pfn │ ┌───────┴───────┐ ▼ ▼
max_low_pfn max_pfn
(低端内存边界) (实际物理内存上限)
│ │
▼ ▼
32位: find_low_pfn_range() 64位: high_memory = __va(max_pfn)物理地址空间
┌───────────────────────┐
│ Low Memory │ ◄─max_low_pfn (直接映射区)
│ (0x00000000-3FFFFFFF)│
├───────────────────────┤
│ High Memory │ ◄─动态映射
│ (>=0x40000000) │
└───────────────────────┘物理地址空间
┌───────────────────────┐
│ Low Memory (<4GB) │ ◄─max_low_pfn
├───────────────────────┤
│ High Memory (≥4GB) │ ◄─high_memory = __va(max_pfn)
└───────────────────────┘