(来自 "linux/arch/i386/kernel/head.S")
"linux/arch/i386/boot/setup.S" 中的引导代码将执行转移到 "linux/arch/i386/kernel/head.S" 中的起始代码(标记为 "startup_32:")。
为了到达这一点,一个小的未压缩内核函数解压缩剩余的压缩内核镜像,然后跳转到新的内核代码。
这是对 "head.S" 代码所做工作的描述。
swapper_pg_dir 是顶层页目录,地址为 0x00101000。
在入口处,%esi 指向实模式代码,作为一个 32 位指针。
将 %ds、%es、%fs 和 %gs 寄存器设置为 __KERNEL_DS。
#ifdef CONFIG_SMP
如果 %bx 为零,则这是在引导处理器 (BSP) 上引导,因此跳过此步骤。否则,对于 AP(应用处理器)
如果期望的 %cr4 设置为非零,则启用分页选项(PSE、PAE 等),并跳过“初始化页表”(跳转到“启用分页”)。
#endif /* CONFIG_SMP */
从 pg0(第 0 页)开始,并将所有页初始化为 007(PRESENT + RW + USER)。
将 %cr3(页表指针)设置为 swapper_pg_dir。
将 %cr0 的分页 ("PG") 位设置为
********** 启用分页 **********.
跳转 $ 以刷新预取队列。
跳转 *[$] 以确保 %eip 被重定位。
设置堆栈指针 (lss stack_start, %esp)。
#ifdef CONFIG_SMP
如果这不是 BSP(引导处理器),则清除所有标志位并跳转到 checkCPUtype。
#endif /* CONFIG_SMP */
BSP 清除内核的所有 BSS(__bss_start 和 _end 之间的区域)。
为 32 位模式设置 IDT(调用 setup_idt)。setup_idt 设置一个 IDT,其中包含 256 个条目,指向默认中断处理程序 "ignore_int" 作为中断门。它实际上并没有加载 IDT;这只能在分页启用且内核移动到 PAGE_OFFSET 之后才能完成。中断在其他地方启用,那时我们可以相对确定一切正常。
清除 eflags 寄存器(在切换到保护模式之前)。
_empty_zero_page 的前 2 KB 用于启动参数,第二个 2 KB 用于命令行。
将 X86_CPUID 初始化为 -1。
使用标志寄存器、push/pop 结果和 CPUID 指令来确定 CPU 类型和供应商:设置 X86、X86_CPUID、X86_MODEL、X86_MASK 和 X86_CAPABILITY。相应地设置 %cr0 中的位。
还检查是否存在 80287 或 80387 协处理器。如果找到数学协处理器或浮点单元,则设置 X86_HARD_MATH。
对于 CONFIG_SMP 构建,递增 "ready" 计数器,以记录已初始化的 CPU 数量。
使用 gdt_descr 加载 GDT,并使用 idt_descr 加载 IDT。GDT 包含 2 个内核条目(代码和数据各 4 GB,从 0 开始)和 2 个用户空间条目(代码和数据各 4 GB,从 0 开始)。在用户空间描述符和 APM 描述符之间有 2 个空描述符。
GDT 还包含 4 个 APM 段的条目。APM 段具有字节粒度,其基址和限制在运行时设置。
gdt_table 的其余部分(APM 段之后)是 TSS 和 LDT 的空间。
跳转到 __KERNEL_CS:%eip 以使 GDT 被使用。现在在
********** 保护模式 **********.
重新加载所有段寄存器:将 %ds、%es、%fs 和 %gs 寄存器设置为 __KERNEL_DS。
#ifdef CONFIG_SMP
仅使用 __KERNEL_DS 重新加载堆栈指针段 (%ss)。
#else /* not CONFIG_SMP */
使用 stack_start 重新加载堆栈指针 (%ss:%esp)。
#endif /* CONFIG_SMP */
将 LDT 指针清除为 0。
将处理器的方向标志 (DF) 清除为 0,以供 gcc 使用。
对于 CONFIG_SMP 构建,如果这不是第一个(引导)CPU,则调用 initialize_secondary(),它不会返回。辅助 (AP) 处理器被初始化,然后进入空闲状态,直到进程被调度到它们上面。
如果这是第一个或唯一的 CPU,则调用 start_kernel()。(见下文)
/* 上面的调用永远不应该返回,但以防万一:*/
L6: jmp L6