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

Linux中setup_arch和setup_memory相关函数的实现

初始化页面地址映射page_address_init

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

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_EFIif ((LOADER_TYPE == 0x50) && EFI_SYSTAB)efi_enabled = 1;
#endifROOT_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_RAMrd_image_start = RAMDISK_FLAGS & RAMDISK_IMAGE_START_MASK;rd_prompt = ((RAMDISK_FLAGS & RAMDISK_PROMPT_FLAG) != 0);rd_doload = ((RAMDISK_FLAGS & RAMDISK_LOAD_FLAG) != 0);
#endifARCH_SETUPif (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_SMPsmp_alloc_memory(); /* AP processor realmode stacks in low memory*/
#endifpaging_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");}}
#endifdmi_scan_machine();#ifdef CONFIG_X86_GENERICARCHgeneric_apic_probe(*cmdline_p);
#endifif (efi_enabled)efi_map_memmap();/** Parse the ACPI tables for possible boot-time SMP configuration.*/acpi_boot_init();#ifdef CONFIG_X86_LOCAL_APICif (smp_found_config)get_smp_config();
#endifregister_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)相关的硬件和内存布局。它的主要任务包括:

  1. CPU 和硬件信息初始化(如 CPU 型号、BIOS 数据、EFI 检测)
  2. 内存管理初始化(如物理内存映射、内存分配器设置)
  3. 核心数据结构设置(如内核代码/数据段的地址范围)
  4. ACPI/SMP 初始化(如多核支持)
  5. 设备信息收集(如 DMI、EDD 信息)
  6. 控制台初始化(如 VGA 终端)

2. 代码分段解析

2.1. CPU 和硬件信息初始化

memcpy(&boot_cpu_data, &new_cpu_data, sizeof(new_cpu_data));
pre_setup_arch_hook();
early_cpu_init();
  • 初始化 CPU 相关数据
    • memcpynew_cpu_data复制到 boot_cpu_data,保存当前 CPU 的信息
    • pre_setup_arch_hook() 是架构相关的钩子函数,暂为空
    • early_cpu_init() 进一步初始化 CPU 特性

2.2. EFI 和 BIOS 信息检测

#ifdef CONFIG_EFIif ((LOADER_TYPE == 0x50) && EFI_SYSTAB)efi_enabled = 1;
#endif
  • 检测是否通过 EFI 启动
    • 如果启动加载器类型(LOADER_TYPE)是 0x50且存在 EFI 系统表(EFI_SYSTAB),则启用 EFI 支持(efi_enabled = 1

2.3. 硬件参数传递

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_infoscreen_info)保存磁盘、屏幕、电源管理等硬件信息

2.4. 系统描述表解析

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. 内存初始化准备

#ifdef CONFIG_BLK_DEV_RAMrd_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. 架构特定初始化

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() 获取)

2.7. EDD 和根文件系统设置

copy_edd();
if (!MOUNT_ROOT_RDONLY)root_mountflags &= ~MS_RDONLY;
  • 作用
    • copy_edd() 复制 BIOS 的 EDD(Enhanced Disk Drive)信息,帮助操作系统在启动时准确识别和访问磁盘设备
    • 根据 MOUNT_ROOT_RDONLY 决定根文件系统是否以只读方式挂载

2.8. 内核代码/数据段地址设置

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_resourcedata_resource
    • _text_etext_edata 是链接脚本定义的符号,标记代码段和数据段的虚拟地址
    • virt_to_phys 将虚拟地址转换为物理地址

2.9. 命令行解析

parse_cmdline_early(cmdline_p);
  • 作用:解析内核启动命令行参数,可能影响后续初始化(如内存大小、调试选项)

2.10. 物理内存初始化

max_low_pfn = setup_memory();
  • 作用:初始化物理内存管理器(如 bootmemmemblock),返回可用内存的最大页帧号(max_low_pfn

2.11. SMP 和分页初始化

#ifdef CONFIG_SMPsmp_alloc_memory(); /* AP processor realmode stacks in low memory */
#endif
paging_init();
  • 作用
    • 如果是多核系统(CONFIG_SMP),为 AP(Application Processor)分配启动栈。
    • paging_init() 初始化分页机制,设置内核页表

2.12. 早期控制台和 DMI 扫描

#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 配置

#ifdef CONFIG_X86_GENERICARCHgeneric_apic_probe(*cmdline_p);
#endif
if (efi_enabled)efi_map_memmap();
acpi_boot_init();
#ifdef CONFIG_X86_LOCAL_APICif (smp_found_config)get_smp_config();
#endif
  • 作用
    • 探测 APIC(高级可编程中断控制器)
    • 如果是 EFI 系统,映射 EFI 内存布局
    • acpi_boot_init() 解析 ACPI 表(如 MADT 表)初始化多核
    • 如果发现 SMP 配置(smp_found_config),加载多核配置(get_smp_config

2.14. 内存注册和控制台初始化

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

早期内存初始化setup_memory

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_HIGHMEMhighstart_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));
#endifprintk(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 preventPCI 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_INITRDif (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;}}
#endifreturn max_low_pfn;
}

1. 代码详细解析

1.1. 变量声明和起始页框计算

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_sizebootmem分配器位图大小
    • start_pfn:可用内存的起始页框号
    • max_low_pfn:低端内存的最大页框号
  • PFN_UP(init_pg_tables_end)

    • init_pg_tables_end:页表初始化结束地址
    • PFN_UP():将地址向上取整到页边界(跳过部分使用的页)
    • 确保内存分配从完整的页开始

1.2. 内存范围探测

find_max_pfn();
max_low_pfn = find_max_low_pfn();
  • find_max_pfn():探测系统最大物理内存大小,设置全局变量 max_pfn
  • find_max_low_pfn():探测低端内存(直接映射区域)的最大页框号
  • 低端内存通常是物理内存的前896MB,可以直接由内核线性映射

1.3. 高端内存配置

#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分配器初始化

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_corebootmem的位图清0

1.5. 关键内存区域保留

1.5.1. 保留bootmem位图自身
reserve_bootmem(HIGH_MEMORY, (PFN_PHYS(start_pfn) +bootmap_size + PAGE_SIZE-1) - (HIGH_MEMORY));
  • 目的:保护bootmem分配器使用的位图不被分配
  • 计算:从 HIGH_MEMORYstart_pfn + bootmap_size 的区域
  • PFN_PHYS():将页框号转换为物理地址
1.5.2. 保留物理页0
reserve_bootmem(0, PAGE_SIZE);
  • 重要性:物理地址0是特殊BIOS页面
  • 必须保留以防止内核使用这个关键区域
1.5.3. 保留EBDA区域
reserve_ebda_region();
  • EBDA:扩展BIOS数据区域
  • 大小:4KB区域
  • 作用:存储BIOS相关数据

1.6. 硬件特定保留

AMD芯片组特殊处理

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支持保留
#ifdef CONFIG_SMP
reserve_bootmem(PAGE_SIZE, PAGE_SIZE);
#endif
  • 目的:为SMP启动代码保留内存,保留物理地址的第1个页面
  • 用途
    • SMP处理器启动时的临时栈
    • 蹦床代码,从实模式切换到保护模式的代码
    • GDT相关结构
1.7.2. ACPI睡眠支持
#ifdef CONFIG_ACPI_SLEEP
acpi_reserve_bootmem();
#endif
  • 功能:为ACPI睡眠功能保留低内存区域
  • 用途:系统休眠/唤醒时保存状态信息
1.7.3. SMP配置查找
#ifdef CONFIG_X86_FIND_SMP_CONFIG
find_smp_config();
#endif
  • 目的:查找并保留可能的启动时SMP配置信息
  • 方法:扫描已知的SMP配置表位置

1.8. 初始RAM磁盘处理

#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_startinitrd_end(转换为虚拟地址)
    • 失败情况initrd超出内存范围,禁用initrd

1.9. 函数返回

return max_low_pfn;
  • 返回值:低端内存的最大页框号
  • 用途:为后续内存初始化提供关键信息

2. 函数功能总结

setup_memory() 是x86架构Linux内核启动过程中关键的早期内存初始化函数,主要完成:

  1. 内存探测 - 确定物理内存范围和布局
  2. 分配器初始化 - 建立bootmem早期内存分配器
  3. 关键区域保护 - 保留系统正常运行必需的内存区域
  4. 硬件兼容处理 - 处理特定硬件的内存需求
  5. 功能模块准备 - 为SMP、ACPI、initrd等预留内存

确定低端内存的最大页框号find_max_low_pfn

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");elseprintk(KERN_WARNING "Use a HIGHMEM enabled kernel.\n");max_pfn = MAXMEM_PFN;
#else /* !CONFIG_HIGHMEM */
#ifndef CONFIG_X86_PAEif (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_HIGHMEMif (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;}
#elseif (highmem_pages)printk(KERN_ERR "ignoring highmem size on non-highmem kernel!\n");
#endif}return max_low_pfn;
}

1. 代码详细解析

1.1. 变量初始化和基础检查

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 高端内存页数计算
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的情况
#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");
elseprintk(KERN_WARNING "Use a HIGHMEM enabled kernel.\n");
max_pfn = MAXMEM_PFN;
  • 内核编译时未启用HIGHMEM支持
  • 只能使用直接映射的低端内存
  • 限制内存max_pfn = MAXMEM_PFN
1.2.3 配置了HIGHMEM但未配置PAE的情况
#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 基础初始化
} else {if (highmem_pages == -1)highmem_pages = 0;
  • 所有内存都在低端内存范围内
  • 设置 highmem_pages = 0(没有高端内存)
1.3.2 配置了HIGHMEM的验证
#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;
}

合理性检查

  1. 大小检查:高端内存不能超过总内存
  2. 低端内存保护:必须保留至少64MB低端内存
  3. 调整计算max_low_pfn -= highmem_pages
1.3.3 未配置HIGHMEM的警告
#else
if (highmem_pages)printk(KERN_ERR "ignoring highmem size on non-highmem kernel!\n");
#endif
  • 内核不支持HIGHMEM但用户指定了highmem参数
  • 忽略设置并警告用户

1.4. 函数返回

return max_low_pfn;

返回最终计算的低端内存最大页框号

http://www.dtcms.com/a/477424.html

相关文章:

  • 智能合约在分布式密钥管理系统中的应用
  • Spark大数据分析与实战笔记(第六章 Kafka分布式发布订阅消息系统-01)
  • 做网络竞拍的网站需要什么厦门网站设计哪家公司好
  • React Native:从react的解构看编程众多语言中的解构
  • C++ 手写 List 容器实战:从双向链表原理到完整功能落地,附源码与测试验证
  • 化工课设代做网站网络宣传网站建设价格
  • 【第1篇】2025年羊城工匠杯nl2sql比赛介绍
  • 2025年ASP.NETMVC面试题库全解析
  • 机器学习:支持向量机
  • C 标准库 - `<locale.h>`
  • YOLO系列——Ubuntu20.04下通过conda虚拟环境安装Labelme
  • 流量安全优化:基于 Sentinel 实现网站流量控制和熔断
  • Ansible 自动化部署K8S1.34.1
  • 1. 使用VSCode开发uni-app环境搭建
  • Docker监控:cAdvisor+Prometheus+Grafana实战指南
  • Redis-持久化之AOF
  • Python Redis 教程
  • R语言绘制热图
  • GPU微架构
  • Vue-- Axios 交互(二)
  • 中煤浙江基础建设有限公司网站曹妃甸网站建设
  • phpcms做汽车网站wordpress如何关注博客
  • 读《华为基本法》,聚焦可复用的方法论
  • CAD多面体密堆积_圆柱体试件3D V1.1版本更新
  • JavaScript变量完全指南:从基础定义到高级用法
  • 什么是VR?什么是AR?
  • NineData云原生智能数据管理平台新功能发布|2025年9月版
  • 基于AR技术交互式设备维修的技术方案剖析|阿法龙XR云平台
  • 利用 Meshery 提升云原生管理效率
  • [3dmax自研插件]——3ds Max 智能材质检查器