初始化页面地址映射page_address_init
c
void __init page_address_init(void)
{
int i;
INIT_LIST_HEAD(&page_address_pool);
for (i = 0; i < ARRAY_SIZE(page_address_maps); i++)
list_add(&page_address_maps[i].list, &page_address_pool);
for (i = 0; i < ARRAY_SIZE(page_address_htable); i++) {
INIT_LIST_HEAD(&page_address_htable[i].lh);
spin_lock_init(&page_address_htable[i].lock);
}
spin_lock_init(&pool_lock);
}
1. 初始化空闲链表池
INIT_LIST_HEAD(&page_address_pool)
初始化一个全局的空闲链表头page_address_pool
,用于管理可用的page_address_map
结构体- 通过循环将
page_address_maps
数组中的所有元素(预分配的映射结构体)添加到page_address_pool
链表中,作为初始空闲资源
2. 初始化哈希表
page_address_htable
是一个哈希表数组,用于快速查找页面地址映射- 对每个哈希桶
page_address_htable[i]
INIT_LIST_HEAD(&page_address_htable[i].lh)
初始化链表头,用于存储映射条目spin_lock_init(&page_address_htable[i].lock)
初始化自旋锁,保证多核环境下对哈希桶的并发访问安全
3. 初始化全局锁
spin_lock_init(&pool_lock)
初始化pool_lock
,用于保护空闲池page_address_pool
的并发访问
4. 背景知识
- 用途 :该函数为内核的 高端内存(
HighMem
) 映射机制提供基础支持。在 32 位系统中,内核虚拟地址空间有限,无法直接映射所有物理内存,需要通过动态映射(如kmap()
)访问高端内存页。page_address_map
结构体用于记录页面虚拟地址与物理页面的映射关系 - 数据结构
page_address_pool
:空闲的page_address_map
结构体池,避免频繁内存分配page_address_htable
:哈希表,通过页面指针快速查找对应的映射信息pool_lock
:保护空闲池的自旋锁
架构相关的初始化setup_arch
c
void __init setup_arch(char **cmdline_p)
{
unsigned long max_low_pfn;
memcpy(&boot_cpu_data, &new_cpu_data, sizeof(new_cpu_data));
pre_setup_arch_hook();
early_cpu_init();
/*
* FIXME: This isn't an official loader_type right
* now but does currently work with elilo.
* If we were configured as an EFI kernel, check to make
* sure that we were loaded correctly from elilo and that
* the system table is valid. If not, then initialize normally.
*/
#ifdef CONFIG_EFI
if ((LOADER_TYPE == 0x50) && EFI_SYSTAB)
efi_enabled = 1;
#endif
ROOT_DEV = old_decode_dev(ORIG_ROOT_DEV);
drive_info = DRIVE_INFO;
screen_info = SCREEN_INFO;
edid_info = EDID_INFO;
apm_info.bios = APM_BIOS_INFO;
ist_info = IST_INFO;
saved_videomode = VIDEO_MODE;
if( SYS_DESC_TABLE.length != 0 ) {
MCA_bus = SYS_DESC_TABLE.table[3] &0x2;
machine_id = SYS_DESC_TABLE.table[0];
machine_submodel_id = SYS_DESC_TABLE.table[1];
BIOS_revision = SYS_DESC_TABLE.table[2];
}
aux_device_present = AUX_DEVICE_INFO;
#ifdef CONFIG_BLK_DEV_RAM
rd_image_start = RAMDISK_FLAGS & RAMDISK_IMAGE_START_MASK;
rd_prompt = ((RAMDISK_FLAGS & RAMDISK_PROMPT_FLAG) != 0);
rd_doload = ((RAMDISK_FLAGS & RAMDISK_LOAD_FLAG) != 0);
#endif
ARCH_SETUP
if (efi_enabled)
efi_init();
else {
printk(KERN_INFO "BIOS-provided physical RAM map:\n");
print_memory_map(machine_specific_memory_setup());
}
copy_edd();
if (!MOUNT_ROOT_RDONLY)
root_mountflags &= ~MS_RDONLY;
init_mm.start_code = (unsigned long) _text;
init_mm.end_code = (unsigned long) _etext;
init_mm.end_data = (unsigned long) _edata;
init_mm.brk = init_pg_tables_end + PAGE_OFFSET;
code_resource.start = virt_to_phys(_text);
code_resource.end = virt_to_phys(_etext)-1;
data_resource.start = virt_to_phys(_etext);
data_resource.end = virt_to_phys(_edata)-1;
parse_cmdline_early(cmdline_p);
max_low_pfn = setup_memory();
/*
* NOTE: before this point _nobody_ is allowed to allocate
* any memory using the bootmem allocator. Although the
* alloctor is now initialised only the first 8Mb of the kernel
* virtual address space has been mapped. All allocations before
* paging_init() has completed must use the alloc_bootmem_low_pages()
* variant (which allocates DMA'able memory) and care must be taken
* not to exceed the 8Mb limit.
*/
#ifdef CONFIG_SMP
smp_alloc_memory(); /* AP processor realmode stacks in low memory*/
#endif
paging_init();
/*
* NOTE: at this point the bootmem allocator is fully available.
*/
#ifdef CONFIG_EARLY_PRINTK
{
char *s = strstr(*cmdline_p, "earlyprintk=");
if (s) {
extern void setup_early_printk(char *);
setup_early_printk(s);
printk("early console enabled\n");
}
}
#endif
dmi_scan_machine();
#ifdef CONFIG_X86_GENERICARCH
generic_apic_probe(*cmdline_p);
#endif
if (efi_enabled)
efi_map_memmap();
/*
* Parse the ACPI tables for possible boot-time SMP configuration.
*/
acpi_boot_init();
#ifdef CONFIG_X86_LOCAL_APIC
if (smp_found_config)
get_smp_config();
#endif
register_memory(max_low_pfn);
#ifdef CONFIG_VT
#if defined(CONFIG_VGA_CONSOLE)
if (!efi_enabled || (efi_mem_type(0xa0000) != EFI_CONVENTIONAL_MEMORY))
conswitchp = &vga_con;
#elif defined(CONFIG_DUMMY_CONSOLE)
conswitchp = &dummy_con;
#endif
#endif
}
1. 函数功能概述
setup_arch()
是 Linux 内核架构相关的初始化函数,负责在系统启动时初始化与体系结构(如 x86、ARM)相关的硬件和内存布局。它的主要任务包括:
- CPU 和硬件信息初始化(如 CPU 型号、BIOS 数据、EFI 检测)
- 内存管理初始化(如物理内存映射、内存分配器设置)
- 核心数据结构设置(如内核代码/数据段的地址范围)
- ACPI/SMP 初始化(如多核支持)
- 设备信息收集(如 DMI、EDD 信息)
- 控制台初始化(如 VGA 终端)
2. 代码分段解析
2.1. CPU 和硬件信息初始化
c
memcpy(&boot_cpu_data, &new_cpu_data, sizeof(new_cpu_data));
pre_setup_arch_hook();
early_cpu_init();
- 初始化 CPU 相关数据
memcpy
将new_cpu_data
复制到boot_cpu_data
,保存当前 CPU 的信息pre_setup_arch_hook()
是架构相关的钩子函数,暂为空early_cpu_init()
进一步初始化 CPU 特性
2.2. EFI 和 BIOS 信息检测
c
#ifdef CONFIG_EFI
if ((LOADER_TYPE == 0x50) && EFI_SYSTAB)
efi_enabled = 1;
#endif
- 检测是否通过 EFI 启动
- 如果启动加载器类型(
LOADER_TYPE
)是0x50
且存在 EFI 系统表(EFI_SYSTAB
),则启用 EFI 支持(efi_enabled = 1
)
- 如果启动加载器类型(
2.3. 硬件参数传递
c
ROOT_DEV = old_decode_dev(ORIG_ROOT_DEV);
drive_info = DRIVE_INFO;
screen_info = SCREEN_INFO;
edid_info = EDID_INFO;
apm_info.bios = APM_BIOS_INFO;
ist_info = IST_INFO;
saved_videomode = VIDEO_MODE;
- 从启动加载器(如 GRUB)传递硬件参数到内核。
ORIG_ROOT_DEV
是启动时指定的根文件系统设备,old_decode_dev
将其转换为内核可用的格式- 其他变量(如
drive_info
、screen_info
)保存磁盘、屏幕、电源管理等硬件信息
2.4. 系统描述表解析
c
if (SYS_DESC_TABLE.length != 0) {
MCA_bus = SYS_DESC_TABLE.table[3] & 0x2;
machine_id = SYS_DESC_TABLE.table[0];
machine_submodel_id = SYS_DESC_TABLE.table[1];
BIOS_revision = SYS_DESC_TABLE.table[2];
}
- 解析 BIOS 提供的系统描述表
SYS_DESC_TABLE
- 检测是否使用 MCA总线
- 保存机器 ID、子型号和 BIOS 版本信息
2.5. 内存初始化准备
c
#ifdef CONFIG_BLK_DEV_RAM
rd_image_start = RAMDISK_FLAGS & RAMDISK_IMAGE_START_MASK;
rd_prompt = ((RAMDISK_FLAGS & RAMDISK_PROMPT_FLAG) != 0);
rd_doload = ((RAMDISK_FLAGS & RAMDISK_LOAD_FLAG) != 0);
#endif
- 初始化 RAMDISK(内存磁盘)参数
- 确定
initramfs
镜像在内存中的起始地址 ,内核后续会使用此地址找到initramfs
镜像并解压到根文件系统 - 控制是否在加载
initramfs
时显示交互式提示 ,内核在加载initramfs
前等待用户确认 - 控制是否实际加载
initramfs
,嵌入式设备可能禁用此标志以节省内存
- 确定
2.6. 架构特定初始化
c
ARCH_SETUP
if (efi_enabled)
efi_init();
else {
printk(KERN_INFO "BIOS-provided physical RAM map:\n");
print_memory_map(machine_specific_memory_setup());
}
- 调用架构相关的初始化代码
- 如果是 EFI 启动,调用
efi_init()
- 否则,打印 BIOS 提供的物理内存布局(通过
machine_specific_memory_setup()
获取)
- 如果是 EFI 启动,调用
2.7. EDD 和根文件系统设置
c
copy_edd();
if (!MOUNT_ROOT_RDONLY)
root_mountflags &= ~MS_RDONLY;
- 作用
copy_edd()
复制 BIOS 的 EDD(Enhanced Disk Drive)信息,帮助操作系统在启动时准确识别和访问磁盘设备- 根据
MOUNT_ROOT_RDONLY
决定根文件系统是否以只读方式挂载
2.8. 内核代码/数据段地址设置
c
init_mm.start_code = (unsigned long) _text;
init_mm.end_code = (unsigned long) _etext;
init_mm.end_data = (unsigned long) _edata;
init_mm.brk = init_pg_tables_end + PAGE_OFFSET;
code_resource.start = virt_to_phys(_text);
code_resource.end = virt_to_phys(_etext)-1;
data_resource.start = virt_to_phys(_etext);
data_resource.end = virt_to_phys(_edata)-1;
- 初始化内核内存管理结构
init_mm
和资源描述符code_resource
、data_resource
_text
、_etext
、_edata
是链接脚本定义的符号,标记代码段和数据段的虚拟地址virt_to_phys
将虚拟地址转换为物理地址
2.9. 命令行解析
c
parse_cmdline_early(cmdline_p);
- 作用:解析内核启动命令行参数,可能影响后续初始化(如内存大小、调试选项)
2.10. 物理内存初始化
c
max_low_pfn = setup_memory();
- 作用 :初始化物理内存管理器(如
bootmem
或memblock
),返回可用内存的最大页帧号(max_low_pfn
)
2.11. SMP 和分页初始化
c
#ifdef CONFIG_SMP
smp_alloc_memory(); /* AP processor realmode stacks in low memory */
#endif
paging_init();
- 作用
- 如果是多核系统(
CONFIG_SMP
),为 AP(Application Processor)分配启动栈。 paging_init()
初始化分页机制,设置内核页表
- 如果是多核系统(
2.12. 早期控制台和 DMI 扫描
c
#ifdef CONFIG_EARLY_PRINTK
{
char *s = strstr(*cmdline_p, "earlyprintk=");
if (s) {
extern void setup_early_printk(char *);
setup_early_printk(s);
printk("early console enabled\n");
}
}
#endif
dmi_scan_machine();
- 作用
- 如果内核命令行指定
earlyprintk
,启用早期调试控制台 dmi_scan_machine()
扫描 DMI(SMBIOS)信息(如厂商、型号)
- 如果内核命令行指定
2.13. ACPI 和 SMP 配置
c
#ifdef CONFIG_X86_GENERICARCH
generic_apic_probe(*cmdline_p);
#endif
if (efi_enabled)
efi_map_memmap();
acpi_boot_init();
#ifdef CONFIG_X86_LOCAL_APIC
if (smp_found_config)
get_smp_config();
#endif
- 作用
- 探测 APIC(高级可编程中断控制器)
- 如果是 EFI 系统,映射 EFI 内存布局
acpi_boot_init()
解析 ACPI 表(如 MADT 表)初始化多核- 如果发现 SMP 配置(
smp_found_config
),加载多核配置(get_smp_config
)
2.14. 内存注册和控制台初始化
c
register_memory(max_low_pfn);
#ifdef CONFIG_VT
#if defined(CONFIG_VGA_CONSOLE)
if (!efi_enabled || (efi_mem_type(0xa0000) != EFI_CONVENTIONAL_MEMORY))
conswitchp = &vga_con;
#elif defined(CONFIG_DUMMY_CONSOLE)
conswitchp = &dummy_con;
#endif
#endif
- 作用
register_memory
注册内存资源(如/proc/iomem
)- 初始化控制台:
- 如果是 VGA 控制台且非 EFI 或 EFI 未占用
0xA0000
地址,使用 VGA 终端 - 否则使用虚拟控制台(
dummy_con
)
- 如果是 VGA 控制台且非 EFI 或 EFI 未占用
早期内存初始化setup_memory
c
static unsigned long __init setup_memory(void)
{
unsigned long bootmap_size, start_pfn, max_low_pfn;
/*
* partially used pages are not usable - thus
* we are rounding upwards:
*/
start_pfn = PFN_UP(init_pg_tables_end);
find_max_pfn();
max_low_pfn = find_max_low_pfn();
#ifdef CONFIG_HIGHMEM
highstart_pfn = highend_pfn = max_pfn;
if (max_pfn > max_low_pfn) {
highstart_pfn = max_low_pfn;
}
printk(KERN_NOTICE "%ldMB HIGHMEM available.\n",
pages_to_mb(highend_pfn - highstart_pfn));
#endif
printk(KERN_NOTICE "%ldMB LOWMEM available.\n",
pages_to_mb(max_low_pfn));
/*
* Initialize the boot-time allocator (with low memory only):
*/
bootmap_size = init_bootmem(start_pfn, max_low_pfn);
register_bootmem_low_pages(max_low_pfn);
/*
* Reserve the bootmem bitmap itself as well. We do this in two
* steps (first step was init_bootmem()) because this catches
* the (very unlikely) case of us accidentally initializing the
* bootmem allocator with an invalid RAM area.
*/
reserve_bootmem(HIGH_MEMORY, (PFN_PHYS(start_pfn) +
bootmap_size + PAGE_SIZE-1) - (HIGH_MEMORY));
/*
* reserve physical page 0 - it's a special BIOS page on many boxes,
* enabling clean reboots, SMP operation, laptop functions.
*/
reserve_bootmem(0, PAGE_SIZE);
/* reserve EBDA region, it's a 4K region */
reserve_ebda_region();
/* could be an AMD 768MPX chipset. Reserve a page before VGA to prevent
PCI prefetch into it (errata #56). Usually the page is reserved anyways,
unless you have no PS/2 mouse plugged in. */
if (boot_cpu_data.x86_vendor == X86_VENDOR_AMD &&
boot_cpu_data.x86 == 6)
reserve_bootmem(0xa0000 - 4096, 4096);
#ifdef CONFIG_SMP
/*
* But first pinch a few for the stack/trampoline stuff
* FIXME: Don't need the extra page at 4K, but need to fix
* trampoline before removing it. (see the GDT stuff)
*/
reserve_bootmem(PAGE_SIZE, PAGE_SIZE);
#endif
#ifdef CONFIG_ACPI_SLEEP
/*
* Reserve low memory region for sleep support.
*/
acpi_reserve_bootmem();
#endif
#ifdef CONFIG_X86_FIND_SMP_CONFIG
/*
* Find and reserve possible boot-time SMP configuration:
*/
find_smp_config();
#endif
#ifdef CONFIG_BLK_DEV_INITRD
if (LOADER_TYPE && INITRD_START) {
if (INITRD_START + INITRD_SIZE <= (max_low_pfn << PAGE_SHIFT)) {
reserve_bootmem(INITRD_START, INITRD_SIZE);
initrd_start =
INITRD_START ? INITRD_START + PAGE_OFFSET : 0;
initrd_end = initrd_start+INITRD_SIZE;
}
else {
printk(KERN_ERR "initrd extends beyond end of memory "
"(0x%08lx > 0x%08lx)\ndisabling initrd\n",
INITRD_START + INITRD_SIZE,
max_low_pfn << PAGE_SHIFT);
initrd_start = 0;
}
}
#endif
return max_low_pfn;
}
1. 代码详细解析
1.1. 变量声明和起始页框计算
c
unsigned long bootmap_size, start_pfn, max_low_pfn;
/*
* partially used pages are not usable - thus
* we are rounding upwards:
*/
start_pfn = PFN_UP(init_pg_tables_end);
-
变量说明:
bootmap_size
:bootmem
分配器位图大小start_pfn
:可用内存的起始页框号max_low_pfn
:低端内存的最大页框号
-
PFN_UP(init_pg_tables_end)
:init_pg_tables_end
:页表初始化结束地址PFN_UP()
:将地址向上取整到页边界(跳过部分使用的页)- 确保内存分配从完整的页开始
1.2. 内存范围探测
c
find_max_pfn();
max_low_pfn = find_max_low_pfn();
find_max_pfn()
:探测系统最大物理内存大小,设置全局变量max_pfn
find_max_low_pfn()
:探测低端内存(直接映射区域)的最大页框号- 低端内存通常是物理内存的前896MB,可以直接由内核线性映射
1.3. 高端内存配置
c
#ifdef CONFIG_HIGHMEM
highstart_pfn = highend_pfn = max_pfn;
if (max_pfn > max_low_pfn) {
highstart_pfn = max_low_pfn;
}
printk(KERN_NOTICE "%ldMB HIGHMEM available.\n",
pages_to_mb(highend_pfn - highstart_pfn));
#endif
printk(KERN_NOTICE "%ldMB LOWMEM available.\n",
pages_to_mb(max_low_pfn));
- HIGHMEM配置 :
highstart_pfn
:高端内存起始页框号highend_pfn
:高端内存结束页框号- 如果存在高端内存(
max_pfn > max_low_pfn
),则设置高端内存范围
- 信息输出:打印可用低端内存和高端内存大小
1.4. Bootmem
分配器初始化
c
bootmap_size = init_bootmem(start_pfn, max_low_pfn);
register_bootmem_low_pages(max_low_pfn);
-
init_bootmem(start_pfn, max_low_pfn)
:- 初始化
bootmem
分配器(早期内存分配器) - 参数:起始页框号,低端内存结束页框号
- 返回值:
bootmem
位图所需大小
- 初始化
-
register_bootmem_low_pages(max_low_pfn)
:- 将所有低端内存页面注册为可用状态
- 遍历所有物理页,标记为空闲
- 最终会调用函数
free_bootmem_core
将bootmem
的位图清0
1.5. 关键内存区域保留
1.5.1. 保留bootmem
位图自身
c
reserve_bootmem(HIGH_MEMORY, (PFN_PHYS(start_pfn) +
bootmap_size + PAGE_SIZE-1) - (HIGH_MEMORY));
- 目的 :保护
bootmem
分配器使用的位图不被分配 - 计算 :从
HIGH_MEMORY
到start_pfn + bootmap_size
的区域 PFN_PHYS()
:将页框号转换为物理地址
1.5.2. 保留物理页0
c
reserve_bootmem(0, PAGE_SIZE);
- 重要性:物理地址0是特殊BIOS页面
- 必须保留以防止内核使用这个关键区域
1.5.3. 保留EBDA区域
c
reserve_ebda_region();
- EBDA:扩展BIOS数据区域
- 大小:4KB区域
- 作用:存储BIOS相关数据
1.6. 硬件特定保留
AMD芯片组特殊处理
c
if (boot_cpu_data.x86_vendor == X86_VENDOR_AMD &&
boot_cpu_data.x86 == 6)
reserve_bootmem(0xa0000 - 4096, 4096);
- 目标:AMD 768MPX芯片组
- 问题:存在PCI预取错误(errata #56)
- 解决方案:在VGA内存前保留4KB页面
- 位置:0xa0000 - 4096(即0x9F000-0xA0000之间)
1.7. 系统功能内存保留
1.7.1. SMP支持保留
c
#ifdef CONFIG_SMP
reserve_bootmem(PAGE_SIZE, PAGE_SIZE);
#endif
- 目的 :为SMP启动代码保留内存,保留物理地址的第1个页面
- 用途 :
- SMP处理器启动时的临时栈
- 蹦床代码,从实模式切换到保护模式的代码
- GDT相关结构
1.7.2. ACPI睡眠支持
c
#ifdef CONFIG_ACPI_SLEEP
acpi_reserve_bootmem();
#endif
- 功能:为ACPI睡眠功能保留低内存区域
- 用途:系统休眠/唤醒时保存状态信息
1.7.3. SMP配置查找
c
#ifdef CONFIG_X86_FIND_SMP_CONFIG
find_smp_config();
#endif
- 目的:查找并保留可能的启动时SMP配置信息
- 方法:扫描已知的SMP配置表位置
1.8. 初始RAM磁盘处理
c
#ifdef CONFIG_BLK_DEV_INITRD
if (LOADER_TYPE && INITRD_START) {
if (INITRD_START + INITRD_SIZE <= (max_low_pfn << PAGE_SHIFT)) {
reserve_bootmem(INITRD_START, INITRD_SIZE);
initrd_start = INITRD_START ? INITRD_START + PAGE_OFFSET : 0;
initrd_end = initrd_start+INITRD_SIZE;
}
else {
printk(KERN_ERR "initrd extends beyond end of memory "
"(0x%08lx > 0x%08lx)\ndisabling initrd\n",
INITRD_START + INITRD_SIZE,
max_low_pfn << PAGE_SHIFT);
initrd_start = 0;
}
}
#endif
- 条件检查 :
- 存在引导加载器类型
- INITRD起始地址有效
- 有效性验证 :确保
initrd
完全在低端内存内 - 成功情况 :
- 保留
initrd
内存区域 - 设置
initrd_start
和initrd_end
(转换为虚拟地址) - 失败情况 :
initrd
超出内存范围,禁用initrd
- 保留
1.9. 函数返回
c
return max_low_pfn;
- 返回值:低端内存的最大页框号
- 用途:为后续内存初始化提供关键信息
2. 函数功能总结
setup_memory()
是x86架构Linux内核启动过程中关键的早期内存初始化函数,主要完成:
- 内存探测 - 确定物理内存范围和布局
- 分配器初始化 - 建立
bootmem
早期内存分配器 - 关键区域保护 - 保留系统正常运行必需的内存区域
- 硬件兼容处理 - 处理特定硬件的内存需求
- 功能模块准备 - 为SMP、ACPI、
initrd
等预留内存
确定低端内存的最大页框号find_max_low_pfn
c
unsigned long __init find_max_low_pfn(void)
{
unsigned long max_low_pfn;
max_low_pfn = max_pfn;
if (max_low_pfn > MAXMEM_PFN) {
if (highmem_pages == -1)
highmem_pages = max_pfn - MAXMEM_PFN;
if (highmem_pages + MAXMEM_PFN < max_pfn)
max_pfn = MAXMEM_PFN + highmem_pages;
if (highmem_pages + MAXMEM_PFN > max_pfn) {
printk("only %luMB highmem pages available, ignoring highmem size of %uMB.\n", pages_to_mb(max_pfn - MAXMEM_PFN), pages_to_mb(highmem_pages));
highmem_pages = 0;
}
max_low_pfn = MAXMEM_PFN;
#ifndef CONFIG_HIGHMEM
/* Maximum memory usable is what is directly addressable */
printk(KERN_WARNING "Warning only %ldMB will be used.\n",
MAXMEM>>20);
if (max_pfn > MAX_NONPAE_PFN)
printk(KERN_WARNING "Use a PAE enabled kernel.\n");
else
printk(KERN_WARNING "Use a HIGHMEM enabled kernel.\n");
max_pfn = MAXMEM_PFN;
#else /* !CONFIG_HIGHMEM */
#ifndef CONFIG_X86_PAE
if (max_pfn > MAX_NONPAE_PFN) {
max_pfn = MAX_NONPAE_PFN;
printk(KERN_WARNING "Warning only 4GB will be used.\n");
printk(KERN_WARNING "Use a PAE enabled kernel.\n");
}
#endif /* !CONFIG_X86_PAE */
#endif /* !CONFIG_HIGHMEM */
} else {
if (highmem_pages == -1)
highmem_pages = 0;
#ifdef CONFIG_HIGHMEM
if (highmem_pages >= max_pfn) {
printk(KERN_ERR "highmem size specified (%uMB) is bigger than pages available (%luMB)!.\n", pages_to_mb(highmem_pages), pages_to_mb(max_pfn));
highmem_pages = 0;
}
if (highmem_pages) {
if (max_low_pfn-highmem_pages < 64*1024*1024/PAGE_SIZE){
printk(KERN_ERR "highmem size %uMB results in smaller than 64MB lowmem, ignoring it.\n", pages_to_mb(highmem_pages));
highmem_pages = 0;
}
max_low_pfn -= highmem_pages;
}
#else
if (highmem_pages)
printk(KERN_ERR "ignoring highmem size on non-highmem kernel!\n");
#endif
}
return max_low_pfn;
}
1. 代码详细解析
1.1. 变量初始化和基础检查
c
unsigned long max_low_pfn;
max_low_pfn = max_pfn;
if (max_low_pfn > MAXMEM_PFN) {
max_low_pfn = max_pfn
:初始假设所有内存都是低端内存MAXMEM_PFN
:低端内存的最大页框号- 条件判断:检查是否物理内存超过了低端内存限制
1.2. 高端内存情况处理(物理内存 > 低端内存限制)
1.2.1 高端内存页数计算
c
if (highmem_pages == -1)
highmem_pages = max_pfn - MAXMEM_PFN;
if (highmem_pages + MAXMEM_PFN < max_pfn)
max_pfn = MAXMEM_PFN + highmem_pages;
if (highmem_pages + MAXMEM_PFN > max_pfn) {
printk("only %luMB highmem pages available, ignoring highmem size of %uMB.\n",
pages_to_mb(max_pfn - MAXMEM_PFN), pages_to_mb(highmem_pages));
highmem_pages = 0;
}
max_low_pfn = MAXMEM_PFN;
highmem_pages == -1
:如果未指定高端内存大小,自动计算- 自动计算 :
max_pfn - MAXMEM_PFN
= 总内存 - 低端内存 = 高端内存 - 边界检查 :确保计算的高端内存不超过实际物理内存,如果超过,说明用户指定的
highmem_pages
过大,忽略用户配置并置0 - 强制设置 :
max_low_pfn = MAXMEM_PFN
,低端内存固定为最大值
1.2.2 未配置HIGHMEM的情况
c
#ifndef CONFIG_HIGHMEM
/* Maximum memory usable is what is directly addressable */
printk(KERN_WARNING "Warning only %ldMB will be used.\n", MAXMEM>>20);
if (max_pfn > MAX_NONPAE_PFN)
printk(KERN_WARNING "Use a PAE enabled kernel.\n");
else
printk(KERN_WARNING "Use a HIGHMEM enabled kernel.\n");
max_pfn = MAXMEM_PFN;
- 内核编译时未启用HIGHMEM支持
- 只能使用直接映射的低端内存
- 限制内存 :
max_pfn = MAXMEM_PFN
1.2.3 配置了HIGHMEM但未配置PAE的情况
c
#else /* !CONFIG_HIGHMEM */
#ifndef CONFIG_X86_PAE
if (max_pfn > MAX_NONPAE_PFN) {
max_pfn = MAX_NONPAE_PFN;
printk(KERN_WARNING "Warning only 4GB will be used.\n");
printk(KERN_WARNING "Use a PAE enabled kernel.\n");
}
#endif /* !CONFIG_X86_PAE */
#endif /* !CONFIG_HIGHMEM */
MAX_NONPAE_PFN
:非PAE系统最大支持4GB内存对应的页框号- 如果物理内存超过4GB,需要PAE(物理地址扩展)支持
- 否则限制为4GB
1.3. 纯低端内存情况处理(物理内存 ≤ 低端内存限制)
1.3.1 基础初始化
c
} else {
if (highmem_pages == -1)
highmem_pages = 0;
- 所有内存都在低端内存范围内
- 设置
highmem_pages = 0
(没有高端内存)
1.3.2 配置了HIGHMEM的验证
c
#ifdef CONFIG_HIGHMEM
if (highmem_pages >= max_pfn) {
printk(KERN_ERR "highmem size specified (%uMB) is bigger than pages available (%luMB)!.\n",
pages_to_mb(highmem_pages), pages_to_mb(max_pfn));
highmem_pages = 0;
}
if (highmem_pages) {
if (max_low_pfn-highmem_pages < 64*1024*1024/PAGE_SIZE){
printk(KERN_ERR "highmem size %uMB results in smaller than 64MB lowmem, ignoring it.\n",
pages_to_mb(highmem_pages));
highmem_pages = 0;
}
max_low_pfn -= highmem_pages;
}
合理性检查:
- 大小检查:高端内存不能超过总内存
- 低端内存保护:必须保留至少64MB低端内存
- 调整计算 :
max_low_pfn -= highmem_pages
1.3.3 未配置HIGHMEM的警告
c
#else
if (highmem_pages)
printk(KERN_ERR "ignoring highmem size on non-highmem kernel!\n");
#endif
- 内核不支持HIGHMEM但用户指定了
highmem
参数 - 忽略设置并警告用户
1.4. 函数返回
c
return max_low_pfn;
返回最终计算的低端内存最大页框号