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

初始化页面地址映射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)相关的硬件和内存布局。它的主要任务包括:

  1. CPU 和硬件信息初始化(如 CPU 型号、BIOS 数据、EFI 检测)
  2. 内存管理初始化(如物理内存映射、内存分配器设置)
  3. 核心数据结构设置(如内核代码/数据段的地址范围)
  4. ACPI/SMP 初始化(如多核支持)
  5. 设备信息收集(如 DMI、EDD 信息)
  6. 控制台初始化(如 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 相关数据
    • memcpynew_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_infoscreen_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() 获取)

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

2.9. 命令行解析

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

2.10. 物理内存初始化

c 复制代码
max_low_pfn = setup_memory();
  • 作用 :初始化物理内存管理器(如 bootmemmemblock),返回可用内存的最大页帧号(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

早期内存初始化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_sizebootmem分配器位图大小
    • 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_corebootmem的位图清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_MEMORYstart_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_startinitrd_end(转换为虚拟地址)
    • 失败情况initrd超出内存范围,禁用initrd

1.9. 函数返回

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

2. 函数功能总结

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

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

合理性检查

  1. 大小检查:高端内存不能超过总内存
  2. 低端内存保护:必须保留至少64MB低端内存
  3. 调整计算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;

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

相关推荐
gplitems1234 小时前
Petslist – Pet listing WordPress Theme Free Download
linux·服务器·前端
1白天的黑夜14 小时前
Linux (5)| 入门进阶:Linux 权限管理的基础规则与实践
linux·运维·服务器·centos
xuecz12304 小时前
mmc-utils使用
linux·mmc
NiKo_W4 小时前
Linux 信号
linux·内核·信号
fwerfv3453455 小时前
Python 爬虫实战:玩转 Playwright 跨浏览器自动化(Chromium/Firefox/WebKit 全支持)
linux
vortex55 小时前
Shell脚本技巧:去除文件中字符串两端空白
linux·bash·shell·sed·awk
world-wide-wait5 小时前
python高级04——网络编程
linux·服务器·网络
迎風吹頭髮5 小时前
Linux内核架构浅谈26-Linux实时进程调度:优先级反转与解决方案
linux·服务器·架构
Java 码农6 小时前
CentOS 7上安装SonarQube10
linux·centos