初始化页面地址映射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_pfnfind_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;
        返回最终计算的低端内存最大页框号