找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

2152

积分

0

好友

308

主题
发表于 7 天前 | 查看: 22| 回复: 0

Linux 6.12.32启动流程深度解析

架构说明: x86_64

本文基于对Linux内核版本6.12.32的分析,深入探讨其在x86_64架构下的完整启动过程。文章从BIOS/UEFI阶段开始,逐步解析Bootloader、Setup代码、压缩内核执行、长模式切换直至真正内核初始化的全过程,并结合内存布局与汇编代码揭示关键机制。

流程图

计算机启动过程中BIOS/UEFI阶段的相关信息

GRUB/LILO引导加载器的工作流程

x86架构下boot阶段的汇编代码执行流程

main.c文件中main函数的代码分析

go_to_protected_mode() 函数的步骤列表

pmjump.S文件中protected_mode_jump函数的实现过程

压缩内核执行的汇编代码分析

设置GDT(全局描述符表)和段寄存器

CPU验证和地址计算的汇编代码

建立4级页表的编程步骤

进入64位长模式的过程

压缩内核执行的汇编代码

解压内核并跳转到解压后的内核

真正的内核执行步骤

内存图

物理内存地址分配情况

实模式与保护模式

Real Address Mode (实模式):在此模式下地址访问的是真实内存地址所在位置。

  • 在实模式下,数据地址是根据 ds:xes:x 进行访问的。
    • 例如 ds0x7c0si0
      ds<<4+si
      0x7c00+0 = 0x7c00
  • 指令地址是根据 cs:x 寄存器进行寻址的。

Protected Address Mode (保护模式):采用虚拟内存、页等机制对内存进行保护。

BIOS阶段

CPU状态

电源接通时,CPU的寄存器值为:

  • CS : 0xfffff000 即此时代码执行的地址是0xfffffff0 (BIOS程序所在的ROM区域)
    • CS selector = 0xf000
    • CS base = 0xffff0000

BIOS 启动

BIOS 的固件程序会将硬盘启动区 512 字节的数据原封不动复制到 0x7c00 这个位置,并且跳转到 0x7c00

最后两字节即检查第一扇区的最后两字节 boot Signature 和 Magic Number 分别是否为 0x55 和 0xAA (01010101 10101010这种交叉数据最容易检查传输是否错误)
或许你会疑惑为什么是 0x7c00,这里可以看一下这个文章: https://blog.csdn.net/hbuxiaofei/article/details/134587909

Bootloader阶段

当 BIOS 执行完毕,就会将 bootloader 复制给 0x7c00 处,并将控制权交给 bootloader。

早期版本下 bootloader 一般是 Linux 的 boot/bootsect.s 中定义的;后续一般由独立存在的软件承担。

对于 x86/x86-64 架构:

  • GRUB2 (Grand Unified Bootloader 2) - 最常用
  • systemd-boot (以前叫 gummiboot) - 简单轻量
  • rEFInd - 主要用于 UEFI 系统
  • SYSLINUX/ISOLINUX - 用于 Live CD/USB

对于 ARM 架构:

  • U-Boot (Das U-Boot) - 嵌入式系统主流
  • GRUB2 - 也在一些 ARM 设备上使用

GRUB

一般来说 GRUB 是最常见的 bootloader,其实 bootloader 干的事情基本一致,所以我们主要分析 GRUB。

下文主要是引用的这个文章感兴趣可以直接看原文: https://xinqiu.gitbooks.io/linux-insides-cn/content/Booting/linux-bootstrap-1.html

grub_main 初始化控制台,计算模块基地址,设置 root 设备,读取 grub 配置文件,加载模块。最后,将 GRUB 置于 normal 模式,在这个模式中,grub_normal_execute(from grub-core/normal/main.c) 将被调用以完成最后的准备工作,然后显示一个菜单列出所用可用的操作系统。(这个界面我们就比较熟悉了对吧,比如进入网吧我们就会看见这个界面)

内存布局

经过引导后,内存界面大概就是下方所示(对于具有启动协议版本 >= 2.02 的现代 bzImage 内核,一般使用的是下方的内存布局),kernel 的 setup 程序(realmode code)加载到了内存中。

  • 当是 bzImage 时,保护模式下的内核被重定位到 0x100000
| Protected-mode kernel  |
100000   +------------------------+
| I/O memory hole        |
A0000    +------------------------+
| Reserved for BIOS      | Leave as much as possible unused
~ ~
| Command line           | (Can also be below the X+10000 mark)
X+10000  +------------------------+
| Stack/heap             | For use by the kernel real-mode code.
X+08000  +------------------------+
| Kernel setup           | The kernel real-mode code.
| Kernel boot sector     | The kernel legacy boot sector.
       X +------------------------+
| Boot loader            | <- Boot sector entry point 0x7C00
001000   +------------------------+
| Reserved for MBR/BIOS  |
000800   +------------------------+
| Typically used by MBR  |
000600   +------------------------+
| BIOS use only          |
000000   +------------------------+

内核加载器的传统内存映射,用于 Image 或 zImage 冐核,通常如下所示:

|
0A0000        +------------------------+
|  Reserved for BIOS     |      Do not use.  Reserved for BIOS EBDA.
09A000        +------------------------+
|  Command line          |
|  Stack/heap            | For use by the kernel real-mode code.
098000         +------------------------+
|  Kernel setup          |      The kernel real-mode code.
090200         +------------------------+
|  Kernel boot sector    |      The kernel legacy boot sector.
090000         +------------------------+
|  Protected-mode kernel |      The bulk of the kernel image.
010000         +------------------------+
|  Boot loader           | <- Boot sector entry point 0000:7C00
001000         +------------------------+
|  Reserved for MBR/BIOS |
000800         +------------------------+
|  Typically used by MBR |
000600         +------------------------+
|  BIOS use only         |
000000         +------------------------+

根据 arch/x86/boot/header.S 定义,可以知道 setup 一般在 0x90000code32_start 一般在 0x100000

setup_move_size:
    .word  0x8000          # size to move, when setup is not
                           # loaded at 0x90000. We will move setup
                           # to 0x90000 then just before jumping
                           # into the kernel. However, only the
                           # loader knows how much data behind
                           # us also needs to be loaded.

code32_start:
    # here loaders can put a different
    # start address for 32-bit code.
    .long  0x100000       # 0x100000 = default for big kernel

总结:

┌─────────────────────────────────────────────────────────────┐
│ [时刻1]Bootloader (GRUB) 加载阶段                           │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│ bzImage 文件结构:                                          │
│  ┌──────────────────────┐                                   │
│  │ Setup.bin (32KB)     │ ← header.S, main.c, pm.c 编译的  │
│  ├──────────────────────┤                                   │
│  │ vmlinux.bin.gz       │ ← head_64.S + 压缩的内核         │
│  └──────────────────────┘                                   │
│                                                             │
│ Bootloader 的操作:                                         │
│  1. 读取 Setup.bin → 加载到物理地址 0x90000                │
│  2. 读取 vmlinux.bin.gz → 加载到物理地址 0x100000(1MB)     │
│  3. 跳转到 0x90000 开始执行 (header.S::_start)              │
│                                                             │
│ 此时内存布局:                                              │
│  0x90000: ★Setup代码★ (header.S, main.c, pm.c...)           │
│  0x100000: ★压缩内核★ (head_64.S::startup_32)               │
└─────────────────────────────────────────────────────────────┘

Setup阶段

当 bootloader 完成工作后,我们的 kernel setup代码(realmode)就被加载进入了内存中了,然后 bootloader 会将执行权限交给 kernel (也就是 arch/x86/boot/header.S _start 函数开始执行)。

vmlinuz是压缩后的kernel,于是可以将它分为两部分:

  • setup.bin
    • 实模式的代码
  • vmlinux.bin
    • 保护模式的代码

setup.bin 程序的入口为__start

OBJCOPYFLAGS_setup.bin  := -O binary
$(obj)/setup.bin: $(obj)/setup.elf FORCE$(call if_changed,objcopy)
LDFLAGS_setup.elf   := -T
$(obj)/setup.elf: $(src)/setup.ld $(SETUP_OBJS) FORCE$(call if_changed,ld)
SETUP_OBJS = $(addprefix $(obj)/,$(setup-y))
setup-y     += a20.o bioscall.o cmdline.o copy.o cpu.o cpucheck.o edd.o
setup-y     += header.o main.o mca.o memory.o pm.o pmjump.o
setup-y     += printf.o regs.o string.o tty.o video.o video-mode.o
setup-y     += version.o
setup-$(CONFIG_X86_APM_BOOT)+= apm.o
setup-y     += video-vga.o
setup-y     += video-vesa.o
setup-y     += video-bios.o

__start

执行 arch/x86/boot/header.S _start代码。

_start 函数开始之前,还有很多的代码,一般这些代码就是 kenerl 自带的 bootloader (现在已经不再使用,只是会输出一些错误信息)

        .globl  _start
_start:
                # Explicitly enter this as bytes, or the assembler
                # tries to generate a 3-byte jump here, which causes
                # everything else to push off to the wrong offset.
                .byte 0xeb            # short (2-byte) jump
                .byte start_of_setup-1f

Start 后紧跟着是一个 jmp 指令(0xeb),这里相当于是跳转到 start_of_setup

jmp start_of_setup-1f

start_of_setup

这里算得上 Linux 真正执行的第一个代码。

主要做的事情就是:

  • 初始化段寄存器
  • 设置堆栈
  • 代码段规范化
  • 验证设置签名
  • 清空BSS段

然后就是跳转到 main 函数(arch/x86/boot/main.c)。

        .section ".entrytext", "ax"
start_of_setup:
# Force %es = %ds
        movw    %ds, %ax
        movw    %ax, %es
        cld

        movw    %ss, %dx
        cmpw    %ax, %dx        # %ds == %ss?
        movw    %sp, %dx
        je      2f              # -> assume %sp is reasonably set

# Invalid %ss, make up a new stack
        movw    $_end, %dx
        testb   $CAN_USE_HEAP, loadflags
        jz      1f
        movw    heap_end_ptr, %dx
1:      addw    $STACK_SIZE, %dx
        jnc     2f
        xorw    %dx, %dx        # Prevent wraparound
2:      # Now %dx should point to the end of our stack space
        andw    $~3, %dx        # dword align (might as well...)
        jnz     3f
        movw    $0xfffc, %dx    # Make sure we're not zero
3:      movw    %ax, %ss
        movzwl  %dx, %esp       # Clear upper half of %esp
        sti                     # Now we should have a working stack

# We will have entered with %cs = %ds+0x20, normalize %cs so
# it is on par with the other segments.
        pushw   %ds
        pushw   $6f
        lretw
6:

# Check signature at end of setup
        cmpl    $0x5a5aaa55, setup_sig
        jne     setup_bad

# Zero the bss
        movw    $__bss_start, %di
        movw    $_end+3, %cx
        xorl    %eax, %eax
        subw    %di, %cx
        shrw    $2, %cx
        rep; stosl

# Jump to C code (should not return)
        calll   main

在初始化后各个段寄存器的值应该是:

  • CS = DS (通过 lretw 技巧实现)
  • DS = ES = SS = 原始DS值(应该是0x1000)
  • 所有段寄存器现在都有相同的值

Call Main function

start_of_setup 设置了一些寄存器初始化后,就跳转到了 main 函数 (arch/x86/boot/main.c),主要是完成一些初始化操作后,进入 Protect Mode。

void main(void)
{
// 初始化默认I/O操作
init_default_io_ops();
// 启动协议块拷贝到 boot_params 的hdr字段
/* First, copy the boot header into the "zeropage" */
copy_boot_params();

/* Initialize the early-boot console */
console_init();
if (cmdline_find_option_bool("debug"))
puts("early console in setup code\n");

/* End of heap check */
// 初始化全局堆
init_heap();
// 确保 CPU 正在运行在正确的特权级上
/* Make sure we have all the proper CPU support */
if (validate_cpu()) {
puts("Unable to boot - please use a kernel appropriate for your CPU.\n");
die();
}
/* Tell the BIOS what CPU mode we intend to run in */
set_bios_mode();

// 从 BIOS 获取内存布局信息
/* Detect memory layout */
detect_memory();

// 键盘初始化,设置按键检测频率
/* Set keyboard repeat rate (why?) and query the lock flags */
keyboard_init();

/* Query Intel SpeedStep (IST) information */
query_ist();

/* Query APM information */
#if defined(CONFIG_APM) || defined(CONFIG_APM_MODULE)
query_apm_bios();
#endif

/* Query EDD information */
#if defined(CONFIG_EDD) || defined(CONFIG_EDD_MODULE)
query_edd();
#endif

/* Set the video mode */
set_video();
// 转移到保护模式
/* Do the last things and invoke protected mode */
go_to_protected_mode();
}

copy_boot_params

在正式切换到 protected mode 之前的内存准备工作,主要是将内核的 struct setup_header hdr 信息拷贝到 boot_params 中 hdr 去。

/*
 * Copy the header into the boot parameter block.  Since this
 * screws up the old-style command line protocol, adjust by
 * filling in the new-style command line pointer instead.
 */
static void copy_boot_params(void)
{
struct old_cmdline {
u16 cl_magic;
u16 cl_offset;
};
const struct old_cmdline * const oldcmd = absolute_pointer(OLD_CL_ADDRESS);

BUILD_BUG_ON(sizeof(boot_params) != 4096);
memcpy(&boot_params.hdr, &hdr, sizeof(hdr));

if (!boot_params.hdr.cmd_line_ptr && oldcmd->cl_magic == OLD_CL_MAGIC) {
/* Old-style command line protocol */
u16 cmdline_seg;

/*
 * Figure out if the command line falls in the region
 * of memory that an old kernel would have copied up
 * to 0x90000...
 */
if (oldcmd->cl_offset < boot_params.hdr.setup_move_size)
        cmdline_seg = ds();
else
        cmdline_seg = 0x9000;

boot_params.hdr.cmd_line_ptr = (cmdline_seg << 4) + oldcmd->cl_offset;
}
}

boot_params

引导加载程序(如GRUB)与Linux内核之间传递启动参数的关键数据结构。

  • 使用固定偏移量,确保引导加载程序和内核对字段位置有统一理解
/* The so-called "zeropage" */
struct boot_params {
struct screen_info screen_info;                 /* 0x000 */
struct apm_bios_info apm_bios_info;             /* 0x040 */
        __u8  _pad2[4];                             /* 0x054 */
        __u64 tboot_addr;                          /* 0x058 */
struct ist_info ist_info;                       /* 0x060 */
        __u64 acpi_rsdp_addr;                      /* 0x070 */
        __u8  _pad3[8];                             /* 0x078 */
        __u8  hd0_info[16];     /* obsolete! */        /* 0x080 */
        __u8  hd1_info[16];     /* obsolete! */        /* 0x090 */
struct sys_desc_table sys_desc_table; /* obsolete! */  /* 0x0a0 */
struct olpc_ofw_header olpc_ofw_header;         /* 0x0b0 */
        __u32 ext_ramdisk_image;                    /* 0x0c0 */
        __u32 ext_ramdisk_size;                     /* 0x0c4 */
        __u32 ext_cmd_line_ptr;                     /* 0x0c8 */
        __u8  _pad4[112];                           /* 0x0cc */
        __u32 cc_blob_address;                      /* 0x13c */
struct edid_info edid_info;                   /* 0x140 */
struct efi_info efi_info;                   /* 0x1c0 */
        __u32 alt_mem_k;                            /* 0x1e0 */
        __u32 scratch;          /* Scratch field! */  /* 0x1e4 */
        __u8  e820_entries;                         /* 0x1e8 */
        __u8  eddbuf_entries;                       /* 0x1e9 */
        __u8  edd_mbr_sig_buf_entries;              /* 0x1ea */
        __u8  kbd_status;                           /* 0x1eb */
        __u8  secure_boot;                          /* 0x1ec */
        __u8  _pad5[2];                             /* 0x1ed */
/*
 * The sentinel is set to a nonzero value (0xff) in header.S.
 *
 * A bootloader is supposed to only take setup_header and put
 * it into a clean boot_params buffer. If it turns out that
 * it is clumsy or too generous with the buffer, it most
 * probably will pick up the sentinel variable too. The fact
 * that this variable then is still 0xff will let kernel
 * know that some variables in boot_params are invalid and
 * kernel should zero out certain portions of boot_params.
 */
        __u8  sentinel;                             /* 0x1ef */
        __u8  _pad6[1];                             /* 0x1f0 */
struct setup_header hdr;    /* setup header */  /* 0x1f1 */
        __u8  _pad7[0x290-0x1f1-sizeof(struct setup_header)];
        __u32 edd_mbr_sig_buffer[EDD_MBR_SIG_MAX];      /* 0x290 */
struct boot_e820_entry e820_table[E820_MAX_ENTRIES_ZEROPAGE]; /* 0x2d0 */
        __u8  _pad8[48];                            /* 0xcd0 */
struct edd_info eddbuf[EDDMAXNR];               /* 0xd00 */
        __u8  _pad9[276];                           /* 0xeec */
} __attribute__((packed));
boot_params 的三次传递
┌─────────────────────────────────────────────────────────────────┐
│            boot_params 的生命周期和传递过程                     │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  [传递1]Bootloader → Setup 代码                                 │
│  ┌────────────────────────────────────────────────────────────┐ │
│  │ 时机: Bootloader 加载完 bzImage 后                         │ │
│  │                                                            │ │
│  │ 操作:                                                      │ │
│  │   1. Bootloader 分配 4KB 内存 (称为 zeropage)              │ │
│  │      位置: 通常在 0x10000 附近                             │ │
│  │                                                            │ │
│  │   2. Bootloader 填充信息:                                  │ │
│  │      - 从 bzImage 的 header.S 读取 setup_header            │ │
│  │      - 填充内存映射 (E820)                                 │ │
│  │      - 填充命令行参数位置                                  │ │
│  │      - 填充 initrd 信息                                    │ │
│  │                                                            │ │
│  │   3. 将地址传递给内核:                                     │ │
│  │      %esi = boot_params 物理地址                          │ │
│  │                                                            │ │
│  │   4. 跳转到 0x90000 (Setup 代码)                           │ │
│  └────────────────────────────────────────────────────────────┘ │
│                   ↓                                            │
│  [传递2] Setup 代码内部拷贝                                   │
│  ┌────────────────────────────────────────────────────────────┐ │
│  │ 位置: arch/x86/boot/main.c::copy_boot_params()            │ │
│  │ 时机: main() 函数的第一步                                 │ │
│  │                                                            │ │
│  │ 源地址 1: &hdr (header.S 中定义的 setup_header)            │ │
│  │   ┌──────────────────────────────────────────────────┐   │ │
│  │   │ .globl hdr                                       │   │ │
│  │   │ hdr:                                             │   │ │
│  │   │     .byte setup_sects - 1                        │   │ │
│  │   │     .word ROOT_RDONLY                            │   │ │
│  │   │     ...                                          │   │ │
│  │   │ code32_start:                                    │   │ │
│  │   │     .long 0x100000                               │   │ │
│  │   │     ...                                          │   │ │
│  │   │                                                  │   │ │
│  │   │ 物理地址: 0x90000 + 0x1F1 ≈ 0x901F1            │   │ │
│  │   └──────────────────────────────────────────────────┘   │ │
│  │                                                            │ │
│  │ 源地址 2: Bootloader 传递的 boot_params (%esi)             │ │
│  │   - 包含 Bootloader 填充的额外信息                         │ │
│  │   - E820 内存映射、命令行等                                │ │
│  │                                                            │ │
│  │ 目标地址: &boot_params (BSS 段全局变量)                   │ │
│  │   ┌──────────────────────────────────────────────────┐   │ │
│  │   │ // arch/x86/boot/main.c                          │   │ │
│  │   │ struct boot_params boot_params __attribute__(   │   │ │
│  │   │     (aligned(16)));                              │   │ │
│  │   │                                                    │   │ │
│  │   │ 位于 Setup 代码的 BSS 段                         │   │ │
│  │   │ 物理地址: 0x90000 + offset                       │   │ │
│  │   └──────────────────────────────────────────────────┘   │ │
│  │                                                            │ │
│  │ 拷贝操作:                                                  │ │
│  │   ┌──────────────────────────────────────────────────┐   │ │
│  │   │ void copy_boot_params(void) {                  │   │ │
│  │   │     // 第一步: 复制 header.S 中的 hdr          │   │ │
│  │   │     memcpy(&boot_params.hdr,                   │   │ │
│  │   │            &hdr,                               │   │ │
│  │   │            sizeof(hdr));                       │   │ │
│  │   │     // 此时 boot_params 有了完整的 setup_header│   │ │
│  │   │                                                  │   │ │
│  │   │     // 第二步: 处理命令行 (如果有)             │   │ │
│  │   │     if (!boot_params.hdr.cmd_line_ptr && ...) {│   │ │
│  │   │         // 兼容老式 bootloader                 │   │ │
│  │   │     }                                          │   │ │
│  │   │ }                                                │   │ │
│  │   └──────────────────────────────────────────────────┘   │ │
│  │                                                            │ │
│  │ 结果: boot_params 现在包含:                              │ │
│  │   - header.S 中的所有配置字段                            │ │
│  │   - code32_start = 0x100000                              │ │
│  │   - Bootloader 填充的运行时信息                          │ │
│  └────────────────────────────────────────────────────────────┘ │
│                   ↓                                            │
│  [传递3] Setup 代码 → 压缩内核                                │
│  ┌────────────────────────────────────────────────────────────┐ │
│  │ 位置: arch/x86/boot/pmjump.S::protected_mode_jump()       │ │
│  │                                                            │ │
│  │ protected_mode_jump(                                      │ │
│  │     boot_params.hdr.code32_start,  // = 0x100000          │ │
│  │     (u32)&boot_params + (ds() << 4) // 物理地址           │ │
│  │ );                                                          │ │
│  │                                                            │ │
│  │ 在 protected_mode_jump 中:                                │ │
│  │   movl %edx, %esi   # %esi = &boot_params (物理地址)      │ │
│  │   ...                                                     │ │
│  │   jmpl *%eax       # 跳转到 0x100000                      │ │
│  │         # %esi 寄存器保持不变                             │ │
│  │                                                            │ │
│  │ ★ %esi 一直保持着 boot_params 的物理地址 ★               │ │
│  │ ★ 传递给压缩内核和真正的内核 ★                           │ │
│  └────────────────────────────────────────────────────────────┘ │
│                   ↓                                            │
│  [传递4] 压缩内核 → 真正的内核                                │
│  ┌────────────────────────────────────────────────────────────┐ │
│  │ %esi 在整个压缩内核执行期间保持不变                      │ │
│  │                                                            │ │
│  │ 在 startup_32, startup_64, extract_kernel 中都使用 %esi   │ │
│  │                                                            │ │
│  │ 最后跳转到真正内核时:                                    │ │
│  │   movq %rsi, %rdi   # 传递给 secondary_startup_64         │ │
│  └────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘

setup_header

struct setup_header {
        __u8    setup_sects;
        __u16   root_flags;
        __u32   syssize;
        __u16   ram_size;
        __u16   vid_mode;
        __u16   root_dev;
        __u16   boot_flag;
        __u16   jump;
        __u32   header;
        __u16   version;
        __u32   realmode_swtch;
        __u16   start_sys_seg;
        __u16   kernel_version;
        __u8    type_of_loader;
        __u8    loadflags;
        __u16   setup_move_size;
        __u32   code32_start;
        __u32   ramdisk_image;
        __u32   ramdisk_size;
        __u32   bootsect_kludge;
        __u16   heap_end_ptr;
        __u8    ext_loader_ver;
        __u8    ext_loader_type;
        __u32   cmd_line_ptr;
        __u32   initrd_addr_max;
        __u32   kernel_alignment;
        __u8    relocatable_kernel;
        __u8    min_alignment;
        __u16   xloadflags;
        __u32   cmdline_size;
        __u32   hardware_subarch;
        __u64   hardware_subarch_data;
        __u32   payload_offset;
        __u32   payload_length;
        __u64   setup_data;
        __u64   pref_address;
        __u32   init_size;
        __u32   handover_offset;
        __u32   kernel_info_offset;
} __attribute__((packed));

go_to_protected_mode(切换保护模式)

/*
 * Actual invocation sequence
 */
void go_to_protected_mode(void)
{
/* Hook before leaving real mode, also disables interrupts */
realmode_switch_hook();

// 开启 A20 地址线,此时有能力访问所有的地址空间
/* Enable the A20 gate */
        if (enable_a20()) {
puts("A20 gate not responding, unable to boot...\n");
die();
}

// 重置处理器
/* Reset coprocessor (IGNNE#) */
reset_coprocessor();

/* Mask all interrupts in the PIC */
mask_all_interrupts();

// 设置 IDT 和 GDT
/* Actual transition to protected mode... */
setup_idt();
setup_gdt();
/* 跳转到code32_start处,它的地址位于启动协议块的头部 */
protected_mode_jump(boot_params.hdr.code32_start,
                    (u32)&boot_params + (ds() << 4));
}

protected_mode_jump

结合参数protected_mode_jump(boot_params.hdr.code32_start,(u32)&boot_params + (ds() << 4)); 一起分析。

传参规范(传统寄存器传参):

  • 参数1 → %eax
  • 参数2 → %edx
  • 参数3 → %ebx
  • 参数4 → %ecx
  • 参数5 → %esi
  • 参数6 → %edi
SYM_FUNC_START_NOALIGN(protected_mode_jump)
        // boot_params 保存给 esi
        movl    %edx, %esi              # Pointer to boot_params table
        // ebx 清空
        xorl    %ebx, %ebx
        // 获取当前的 CS 段(realmode)
        movw    %cs, %bx
        // 转化为线性地址 cs << 4 (cs * 16)
        shll    $4, %ebx
        // 
        addl    %ebx, 2f
        jmp     1f              # Short jump to serialize on 386/486
1:
// 数据段选择子 GDT 第三个描述符 (数据段)
        movw    $__BOOT_DS, %cx
        // TSS 选择子 GDT 第四个描述符 (任务状态段)
        movw    $__BOOT_TSS, %di
        // 设置保护模式 bit,进入保护模式
        movl    %cr0, %edx
        orb     $X86_CR0_PE, %dl        # Protected mode
        movl    %edx, %cr0

# Transition to 32-bit mode
        .byte   0x66, 0xea      # ljmpl opcode
2:      .long   .Lin_pm32               # offset
        .word   __BOOT_CS               # segment
SYM_FUNC_END(protected_mode_jump)

        .code32
        .section ".text32","ax"
SYM_FUNC_START_LOCAL_NOALIGN(.Lin_pm32)
# Set up data segments for flat 32-bit mode
        // 设置数据段寄存器
        movl    %ecx, %ds
        movl    %ecx, %es
        movl    %ecx, %fs
        movl    %ecx, %gs
        movl    %ecx, %ss
# The 32-bit code sets up its own stack, but this way we do have
# a valid stack if some debugging hack wants to use it.
        addl    %ebx, %esp

# Set up TR to make Intel VT happy
        ltr     %di

# Clear registers to allow for future extensions to the
# 32-bit boot protocol
        xorl    %ecx, %ecx
        xorl    %edx, %edx
        xorl    %ebx, %ebx
        xorl    %ebp, %ebp
        xorl    %edi, %edi

# Set up LDTR to make Intel VT happy
        lldt    %cx
# 跳转到 32位入口
        jmpl    *%eax           # Jump to the 32-bit entrypoint
SYM_FUNC_END(.Lin_pm32)
protected_mode_jump 被调用
    ↓
计算线性地址,准备段选择子
    ↓
设置CR0.PE=1,进入保护模式
    ↓
远跳转到 .Lin_pm32 (刷新流水线)
    ↓
保护模式 (32位) - .Lin_pm32
    ↓
初始化段寄存器,设置堆栈
    ↓
设置系统表(TR, LDTR)
    ↓
清理寄存器状态
    ↓
跳转到 code32_start (arch/x86/boot/compressed/head_64.S)

压缩内核阶段

code32_start

  • code32_start 的地址一般在 0x100000
setup_move_size:
    .word  0x8000          # size to move, when setup is not
                           # loaded at 0x90000. We will move setup
                           # to 0x90000 then just before jumping
                           # into the kernel. However, only the
                           # loader knows how much data behind
                           # us also needs to be loaded.

code32_start:
    # here loaders can put a different
    # start address for 32-bit code.
    .long  0x100000       # 0x100000 = default for big kernel

调用链

1. Bootloader (GRUB)
   ↓
2. arch/x86/boot/main.c (实模式)
   ↓
3. arch/x86/boot/pm.c::go_to_protected_mode() (进入保护模式)
   ↓
4. 【当前位置】arch/x86/boot/compressed/head_64.S::startup_32 (压缩内核入口)
   ↓
5. arch/x86/boot/compressed/misc.c::extract_kernel() (解压内核)
   ↓
6. arch/x86/kernel/head_64.S::secondary_startup_64 (真正的内核入口)
   ↓
7. start_kernel() (C代码,内核主初始化)

初始化和地址计算

SYM_FUNC_START(startup_32)
        cld
        cli
        leal    (BP_scratch+4)(%esi), %esp
        call    1f
1:      popl    %ebp
        subl    $ rva(1b), %ebp
  • cld/cli: 清除方向标志,禁用中断
  • 地址重定位计算:通过 call/pop 技巧获取当前运行地址
    • call 1f 将返回地址压栈
    • popl %ebp 获取实际运行地址
    • subl $ rva(1b), %ebp 计算与编译地址的差值
    • %ebp 现在包含实际加载地址

设置 GDT 和段寄存器

/* Load new GDT with the 64bit segments using 32bit descriptor */
        leal        rva(gdt)(%ebp), %eax
        movl        %eax, 2(%eax)
        lgdt        (%eax)

/* Load segment registers with our descriptors */
        movl        $__BOOT_DS, %eax
        movl        %eax, %ds
        movl        %eax, %es
        movl        %eax, %fs
        movl        %eax, %gs
        movl        %eax, %ss

/* Setup a stack and load CS from current GDT */
        leal        rva(boot_stack_end)(%ebp), %esp

        pushl        $__KERNEL32_CS
        leal        rva(1f)(%ebp), %eax
        pushl        %eax
        lretl
1:
  • 加载包含 64 位段描述符的新 GDT
  • 设置所有段寄存器为平坦模式
  • 设置栈指针
  • 通过 lretl 远返回加载新的 CS

验证CPU和计算解压目标地址

/* Make sure cpu supports long mode. */
        call        verify_cpu
        testl        %eax, %eax
        jnz        .Lno_longmode

/*
 * Compute the delta between where we were compiled to run at
 * and where the code will actually run at.
 *
 * %ebp contains the address we are loaded at by the boot loader and %ebx
 * contains the address where we should move the kernel image temporarily
 * for safe in-place decompression.
 */
#ifdef CONFIG_RELOCATABLE
        movl        %ebp, %ebx
        movl        BP_kernel_alignment(%esi), %eax
        decl        %eax
        addl        %eax, %ebx
        notl        %eax
        andl        %eax, %ebx
        cmpl        $LOAD_PHYSICAL_ADDR, %ebx
        jae        1f
#endif
        movl        $LOAD_PHYSICAL_ADDR, %ebx
1:

/* Target address to relocate to for decompression */
        addl        BP_init_size(%esi), %ebx
        subl        $ rva(_end), %ebx
  • 验证 CPU 是否支持 64 位长模式
  • 计算内核解压目标地址:
    • 根据 kernel_alignment 对齐 %ebx
    • 确保地址安全(避免覆盖压缩内核)

建立早期页表

  • 启用 PAE 模式(CR4.PAE)
  • 清零页表区域
  • Level 4 (PML4):根页表,1 个条目
  • Level 3 (PDPT):页目录指针表,4 个条目
  • Level 2 (PD):页目录,2048 个条目,每个映射 2MB
    • 总共映射 4GB 内存(2048 × 2MB = 4GB)
    • 使用 大页(2MB) 映射(标志 0x183 = Present + RW + PS)
页表结构
 物理内存布局 (4级分页):

  ┌──────────────────────────────────────────────────────────┐
  │           Level 4 (PML4)                                 │
  │           位置: pgtable + 0x0                             │
  │           大小: 4KB (512条目)                             │
  ├──────────────────────────────────────────────────────────┤
  │ PML4[0] = pgtable + 0x1000 + 0x07 (Present+RW+User)      │
  │ PML4[1-511] = 0 (未使用)                                 │
  └──────────┬───────────────────────────────────────────────┘
             │
             └──────┐
                    ↓
  ┌──────────────────────────────────────────────────────────┐
  │             Level 3 (PDPT - 页目录指针表)                │
  │             位置: pgtable + 0x1000                       │
  │             大小: 4KB (512条目)                          │
  ├──────────────────────────────────────────────────────────┤
  │ PDPT[0] = pgtable + 0x2000 + 0x07                        │
  │ PDPT[1] = pgtable + 0x3000 + 0x07                        │
  │ PDPT[2] = pgtable + 0x4000 + 0x07                        │
  │ PDPT[3] = pgtable + 0x5000 + 0x07                        │
  │ PDPT[4-511] = 0 (未使用)                                 │
  └──┬───┬───┬───┬───────────────────────────────────────────┘
    │   │   │   │
    └┐ └┐ └┐ └┐
     ↓  ↓  ↓  ↓
  ┌──────────────────────────────────────────────────────────┐
  │              Level 2 (PD - 页目录) × 4                   │
  │              位置: pgtable + 0x2000 ~ 0x5000             │
  │              每个大小: 4KB (512条目)                      │
  ├──────────────────────────────────────────────────────────┤
  │ PD[0][0] = 0x00000000 + 0x183 (Present+RW+PS+User)       │
  │ PD[0][1] = 0x00200000 + 0x183 (2MB)                      │
  │ PD[0][2] = 0x00400000 + 0x183 (4MB)                      │
  │ ...                                                     │
  │ PD[0][511] = 0x3FE00000 + 0x183 (1GB - 2MB)              │
  │                                                         │
  │ PD[1][0-511] = 映射 1GB ~ 2GB                            │
  │ PD[2][0-511] = 映射 2GB ~ 3GB                            │
  │ PD[3][0-511] = 映射 3GB ~ 4GB                            │
  │                                                         │
  │ 总共: 2048 条目 × 2MB = 4GB                              │
  └──────────────────────────────────────────────────────────┘
           ↓ (使用 2MB 大页,无 Level 1)
  ┌──────────────────────────────────────────────────────────┐
  │              物理内存 0x00000000 ~ 0xFFFFFFFF            │
  │                        (4GB)                             │
  └──────────────────────────────────────────────────────────┘
建立 Level4 (PML4)

Page Map Level 4

/* Build Level 4 */
// edi = pgtable 物理地址
leal        rva(pgtable + 0)(%ebx), %edi
// eax = edi + 0x1007 -> 设置 P RW U/S 12+ bit位
// eax 为 level 3 表,并设置了标志位
leal        0x1007 (%edi), %eax
// 写入 PML4[0] 高低位为0
movl        %eax, 0(%edi)
addl        %edx, 4(%edi)
  • 内存构造
    • 也就是说 PML4[0]存储的 level 3的指针
PML4 表 (位于 pgtable + 0):
 Offset  |Value
--------|---------------------------------
 0x0000  | pgtable + 0x1007 (低32位)
 0x0004  | 0 或加密掩码 (高32位)
 0x0008  | 0 (PML4[1])
  ...     | 0 (PML4[2-511])
建立 level3 (PDPT)

Page Directory Pointer Table

        // edi 为 PDPT 表
        leal    rva(pgtable + 0x1000)(%ebx), %edi
        // eax 指向 level2 (pagetable + 0x2007)
        leal    0x1007(%edi), %eax
        // 4 PDPT 条目 4 * 1GB = 4GB space
        movl    $4, %ecx
        // 循环遍历指针填入
1:      movl    %eax, 0x00(%edi)
        addl    %edx, 0x04(%edi)
        addl    $0x00001000, %eax
        addl    $8, %edi
        decl    %ecx
        jnz     1b
  • 内存构造
PDPT 表 (位于 pgtable + 0x1000):
 Offset  |Value                   |映射范围
--------|--------------------------|----------
 0x0000  | pgtable + 0x2007 (低32)  |0-1GB
 0x0004  | 加密掩码 (高32)           |
 0x0008  | pgtable + 0x3007         |1-2GB
 0x000C  | 加密掩码                 |
 0x0010  | pgtable + 0x4007         |2-3GB
 0x0014  | 加密掩码                 |
 0x0018  | pgtable + 0x5007         |3-4GB
 0x001C  | 加密掩码                 |
建立 level2 (PD)
/* Build Level 2 */
// 获取第一个 PD 表起始地址
        leal    rva(pgtable + 0x2000)(%ebx), %edi
        movl    $0x00000183, %eax
// 循环 2048 次,每条目 2MB 2048 * 2 = 4 GB
        movl    $2048, %ecx
// pd[0][index] 低位= 0x183
1:      movl    %eax, 0(%edi)
// pd[0][index] 高位 = 加密掩码
        addl    %edx, 4(%edi)
// eax += 0x200000
        addl    $0x00200000, %eax
// 移动下一个条目
        addl    $8, %edi
        decl    %ecx
        jnz     1b
  • 0x183的标识位解析
    • 0x183 = 0001 1000 0011 (二进制)
    • Bit 0 (P):    Present = 1 (页存在)
    • Bit 1 (RW):   Read/Write = 1 (可写)
    • Bit 2 (U/S):  User/Supervisor = 0 (内核)
    • Bit 7 (PS):   Page Size = 1 (大页,2MB)
    • Bit 8 (G):    Global = 1 (全局页)
    • Bits 21-31:   物理地址高位 = 0
  • 内存构造

启动长模式

/* Enable the boot page tables */
        leal        rva(pgtable)(%ebx), %eax
        movl        %eax, %cr3

/* Enable Long mode in EFER (Extended Feature Enable Register) */
        movl        $MSR_EFER, %ecx
        rdmsr
        btsl        $_EFER_LME, %eax
        wrmsr

/* After gdt is loaded */
        xorl        %eax, %eax
        lldt        %ax
        movl    $__BOOT_TSS, %eax
        ltr        %ax

#ifdef CONFIG_AMD_MEM_ENCRYPT
/* Check if the C-bit position is correct when SEV is active */
        call        startup32_check_sev_cbit
#endif

/*
 * Setup for the jump to 64bit mode
 *
 * When the jump is performed we will be in long mode but
 * in 32bit compatibility mode with EFER.LME = 1, CS.L = 0, CS.D = 1
 * (and in turn EFER.LMA = 1).        To jump into 64bit mode we use
 * the new gdt/idt that has __KERNEL_CS with CS.L = 1.
 * We place all of the values on our mini stack so lret can
 * used to perform that far jump.
 */
        leal        rva(startup_64)(%ebp), %eax
#ifdef CONFIG_EFI_MIXED
        cmpb        $1, rva(efi_is64)(%ebp)
        je        1f
        leal        rva(startup_64_mixed_mode)(%ebp), %eax
1:
#endif

        pushl        $__KERNEL_CS
        pushl        %eax

/* Enter paged protected Mode, activating Long Mode */
        movl        $CR0_STATE, %eax
        movl        %eax, %cr0

/* Jump from 32bit compatibility mode into 64bit mode. */
        lret
  • 加载 CR3:启用页表
  • 设置 EFER.LME:启用长模式(Long Mode Enable)
  • 设置 CR0.PG:启用分页
    • 此时进入 兼容模式(Long Mode Compatibility)
    • EFER.LME=1, CS.L=0, CS.D=1
  • lret 远返回:跳转到 64 位代码段
    • 进入真正的 64 位长模式

startup_64 (解压内核)

核心功能主要是完成了内核的解压,随后跳转到解压后的内核。

流程

startup_64 执行流程:

┌─────────────────────────────────────────────────────────────┐
│ 入口: startup_64 (偏移 0x200)                                │
│ 来源: 从 startup_32 通过 lret 进入 OR 64位 bootloader       │
└────────────────────┬────────────────────────────────────────┘
                     ↓
┌─────────────────────────────────────────────────────────────┐
│ PHASE 1: 初始化环境 (行 298-307)                            │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 298: cld           # 清除方向标志                        │ │
│ │ 299: cli           # 禁用中断                            │ │
│ │ 302-307: 清零所有段寄存器 (ds, es, ss, fs, gs)           │ │
│ │          64位模式下段基址为0,使用平坦内存模型           │ │
│ └─────────────────────────────────────────────────────────┘ │
└────────────────────┬────────────────────────────────────────┘
                     ↓
┌─────────────────────────────────────────────────────────────┐
│ PHASE 2: 计算解压地址 (行 322-339)                          │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 324: leaq startup_32(%rip), %rbp                         │ │
│ │      # 使用 RIP 相对寻址获取当前位置                    │ │
│ │      # %rbp = 当前代码的物理地址                        │ │
│ │                                                        │ │
│ │ 325-329: 对齐计算 (与 startup_32 中类似)                │ │
│ │     movl BP_kernel_alignment(%rsi), %eax                │ │
│ │     # 从 boot_params 读取对齐要求 (2MB)                 │ │
│ │     decl %eax           # %eax = 0x1FFFFF               │ │
│ │     addq %rax, %rbp     # 向上对齐                      │ │
│ │     notq %rax           # %rax = 0xFFFFFFFFFFE00000     │ │
│ │     andq %rax, %rbp     # 清除低 21 位,对齐到 2MB      │ │
│ │                                                        │ │
│ │ 337-339: 计算解压目标地址                               │ │
│ │     movl BP_init_size(%rsi), %ebx                       │ │
│ │     # init_size = 内核运行时需要的内存大小              │ │
│ │     subl $ rva(_end), %ebx                              │ │
│ │     # 减去压缩内核大小                                  │ │
│ │     addq %rbp, %rbx                                     │ │
│ │     # %rbx = 解压目标地址                               │ │
│ │                                                        │ │
│ │ 结果: %rbp = 内核最终运行地址 (2MB对齐)                 │ │
│ │       %rbx = 解压临时缓冲区地址                         │ │
│ └─────────────────────────────────────────────────────────┘ │
└────────────────────┬────────────────────────────────────────┘
                     ↓
┌─────────────────────────────────────────────────────────────┐
│ PHASE 3: 设置栈和 GDT (行 342-374)                          │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 342: leaq rva(boot_stack_end)(%rbx), %rsp               │ │
│ │      # 设置栈指针到重定位后的栈顶                       │ │
│ │                                                        │ │
│ │ 366-368: 修正并加载 GDT                                 │ │
│ │     leaq gdt64(%rip), %rax                              │ │
│ │     addq %rax, 2(%rax)   # 修正 GDT 基址                │ │
│ │     lgdt (%rax)           # 加载 GDT                   │ │
│ │                                                        │ │
│ │ 371-374: 使用 lretq 重新加载 CS                         │ │
│ │     pushq $__KERNEL_CS                                 │ │
│ │     leaq .Lon_kernel_cs(%rip), %rax                    │ │
│ │     pushq %rax                                         │ │
│ │     lretq            # 远返回,加载新 CS               │ │
│ └─────────────────────────────────────────────────────────┘ │
└────────────────────┬────────────────────────────────────────┘
                     ↓
┌─────────────────────────────────────────────────────────────┐
│ PHASE 4: C 函数调用准备 (行 376-417)                        │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ .Lon_kernel_cs:                                        │ │
│ │ 382: movq %rsi, %r15      # 保存 boot_params 指针      │ │
│ │      # %r15 是 callee-saved,C函数调用不会破坏          │ │
│ │                                                        │ │
│ │ 384: call load_stage1_idt  # 加载早期 IDT              │ │
│ │                                                        │ │
│ │ 398: call sev_enable       # AMD SEV 初始化 (如果需要) │ │
│ │                                                        │ │
│ │ 401-404: 保留必要的 CR4 位                             │ │
│ │     movq %cr4, %rax                                    │ │
│ │     andl $(PAE|MCE|LA57), %eax                         │ │
│ │     movq %rax, %cr4                                    │ │
│ │     # 只保留 PAE, MCE, LA57,清除其他                  │ │
│ │                                                        │ │
│ │ 415-417: 配置 5级分页 (如果需要)                       │ │
│ │     movq %r15, %rdi     # arg1: boot_params            │ │
│ │     leaq rva(top_pgtable)(%rbx), %rsi# arg2: 页表    │ │
│ │     call configure_5level_paging                       │ │
│ └─────────────────────────────────────────────────────────┘ │
└────────────────────┬────────────────────────────────────────┘
                     ↓
┌─────────────────────────────────────────────────────────────┐
│ PHASE 5: 重定位压缩内核 (行 427-433)                        │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ # 将压缩内核移到缓冲区末尾,避免解压时覆盖              │ │
│ │ 427: leaq (_bss-8)(%rip), %rsi                          │ │
│ │      # 源地址: 压缩内核末尾 - 8                         │ │
│ │ 428: leaq rva(_bss-8)(%rbx), %rdi                       │ │
│ │      # 目标地址: 重定位后的位置                         │ │
│ │ 429: movl $(_bss - startup_32), %ecx                    │ │
│ │      # 大小: 从 startup_32 到 _bss                     │ │
│ │ 430: shrl $3, %ecx       # 转换为 QWORD 数量           │ │
│ │ 431: std                # 设置方向标志 (向下)          │ │
│ │ 432: rep movsq           # 从高地址往低地址复制        │ │
│ │ 433: cld                # 清除方向标志                 │ │
│ │                                                        │ │
│ │ 为什么反向复制?                                         │ │
│ │ ┌──────────────────────────────────────────────────┐   │ │
│ │ │ 原位置:  [====压缩内核====][空闲空间]           │   │ │
│ │ │ 新位置:  [空闲][====压缩内核====]               │   │ │
│ │ │ 如果正向复制,可能覆盖源数据                     │   │ │
│ │ │ 反向复制确保安全                                 │   │ │
│ │ └──────────────────────────────────────────────────┘   │ │
│ └─────────────────────────────────────────────────────────┘ │
└────────────────────┬────────────────────────────────────────┘
                     ↓
┌─────────────────────────────────────────────────────────────┐
│ PHASE 6: 重新加载 GDT 并跳转 (行 440-449)                   │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 440-443: 重新加载 GDT                                   │ │
│ │      # GDT 可能在复制时被覆盖,需要重新指向新位置       │ │
│ │      leaq rva(gdt64)(%rbx), %rax                        │ │
│ │      leaq rva(gdt)(%rbx), %rdx                          │ │
│ │      movq %rdx, 2(%rax)                                 │ │
│ │      lgdt (%rax)                                        │ │
│ │                                                        │ │
│ │ 448-449: 跳转到重定位后的代码                           │ │
│ │      leaq rva(.Lrelocated)(%rbx), %rax                 │ │
│ │      jmp *%rax                                          │ │
│ └─────────────────────────────────────────────────────────┘ │
└────────────────────┬────────────────────────────────────────┘
                     ↓
                  .Lrelocated
                     ↓
               (extract_kernel)

真正内核阶段

secondary_startup_64

从 secondary_startup_64 开始执行。

SYM_CODE_START(secondary_startup_64)
        UNWIND_HINT_END_OF_STACK
        ANNOTATE_NOENDBR

        ......

1:      cmpl    %edx, %eax
        je      1f
xor    %edx, %edx
        wrmsr                           /* Make changes effective */
1:

        /* Setup cr0 */
        movl    $CR0_STATE, %eax
        /* Make changes effective */
        movq    %rax, %cr0

        /* zero EFLAGS after setting rsp */
        pushq $0
        popfq

        /* Pass the boot_params pointer as first argument */
        movq    %r15, %rdi

.Ljump_to_C_code:
        xorl    %ebp, %ebp      # clear frame pointer
        ANNOTATE_RETPOLINE_SAFE
        callq   *initial_code(%rip)
        ud2
SYM_CODE_END(secondary_startup_64)

流程图

═══════════════════════════════════════════════════════════════════
secondary_startup_64 详细执行流程
═══════════════════════════════════════════════════════════════════

┌───────────────────────────────────────────────────────────────┐
│ 入口状态:                                                     │
│   - CPU: 64位长模式 (CS.L=1, CS.D=0)                          │
│   - 页表: 已加载 (BSP 用 early_top_pgt, AP 用 trampoline_pgd) │
│   - %rsi: boot_params 地址 (BSP) 或 未定义 (AP)               │
└───────────────────────────────────────────────────────────────┘
↓
┌───────────────────────────────────────────────────────────────┐
│ 阶段 1: CPU验证 (行 152-163)                                  │
├───────────────────────────────────────────────────────────────┤
│  153: call verify_cpu                                         │
│       ↓                                                       │
│       检查 CPU 是否支持 64位 long mode                        │
│       验证必要的 CPU 特性 (如 NX 位)                          │
│                                                               │
│  164: SYM_INNER_LABEL(secondary_startup_64_no_verify)         │
│       ↓                                                       │
│       SEV-ES 客户机的特殊入口点(跳过 verify_cpu)            │
└───────────────────────────────────────────────────────────────┘
↓
┌───────────────────────────────────────────────────────────────┐
│ 阶段 2: 清除 boot_params 指针 (行 168-169)                    │
├───────────────────────────────────────────────────────────────┤
│  169: xorl %r15d, %r15d                                       │
│       ↓                                                       │
│       清除 %r15 (在 startup_64 中保存了 boot_params)          │
│                                                               │
│ 原因:                                                         │
│   - BSP 在 startup_64 中已经使用过 boot_params                │
│   - AP 核心不需要 boot_params                                 │
│   - 清零防止误用                                              │
└───────────────────────────────────────────────────────────────┘
↓
┌───────────────────────────────────────────────────────────────┐
│ 阶段 3: 计算并加载内核页表 (行 171-186)                      │
├───────────────────────────────────────────────────────────────┤
│  171-173: 计算 init_top_pgt 的物理地址                       │
│  ┌─────────────────────────────────────────────────────────┐  │
│  │ movq phys_base(%rip), %rax                               │  │
│  │ # phys_base = 内核实际加载的物理基址                     │  │
│  │                                                          │  │
│  │ addq $(init_top_pgt - __START_KERNEL_map), %rax          │  │
│  │ # __START_KERNEL_map = 0xFFFFFFFF80000000 (内核虚拟基址)│  │
│  │ # init_top_pgt - __START_KERNEL_map = 相对偏移           │  │
│  │ # %rax = phys_base + 偏移 = init_top_pgt 物理地址       │  │
│  └─────────────────────────────────────────────────────────┘  │
│                                                               │
│  179-181: 添加 SME 加密掩码 (如果启用了 AMD 内存加密)        │
│  ┌─────────────────────────────────────────────────────────┐  │
│  │ #ifdef CONFIG_AMD_MEM_ENCRYPT                           │  │
│  │    addq sme_me_mask(%rip), %rax                          │  │
│  │ #endif                                                  │  │
│  │ # sme_me_mask = C-bit 位置标记                          │  │
│  └─────────────────────────────────────────────────────────┘  │
│                                                               │
│  186: movq %rax, %cr3                                         │
│       ↓                                                       │
│       切换到内核的最终页表 init_top_pgt                       │
│                                                               │
│ 效果:                                                         │
│   - 卸载 trampoline_pgd (AP使用的临时页表)                   │
│   - 卸载恒等映射 (identity mapping)                           │
│   - 使用内核的虚拟地址空间                                   │
└───────────────────────────────────────────────────────────────┘
↓
┌───────────────────────────────────────────────────────────────┐
│ 阶段 4: 进入共同启动路径 (行 188)                            │
├───────────────────────────────────────────────────────────────┤
│  188: SYM_INNER_LABEL(common_startup_64, SYM_L_LOCAL)         │
│       ↓                                                       │
│       从这里开始,BSP 和 AP 执行相同的代码                   │
│       (BSP 从 startup_64 跳转到这里,AP 从上面流程下来)       │
└───────────────────────────────────────────────────────────────┘
↓
┌───────────────────────────────────────────────────────────────┐
│ 阶段 5: 配置 CR4 寄存器 (行 192-223)                         │
├───────────────────────────────────────────────────────────────┤
│  目的: 设置 CPU 特性控制位,刷新 TLB                         │
│                                                               │
│  201-213: 创建 CR4 保留掩码                                 │
│  ┌─────────────────────────────────────────────────────────┐  │
│  │ movl $(X86_CR4_PAE | X86_CR4_LA57), %edx                 │  │
│  │ # PAE  = Physical Address Extension                       │  │
│  │ # LA57 = 5级页表支持                                     │  │
│  │                                                          │  │
│  │ #ifdef CONFIG_X86_MCE                                     │  │
│  │    orl $X86_CR4_MCE, %edx                                 │  │
│  │ # MCE = Machine Check Exception                           │  │
│  │ #endif                                                    │  │
│  │                                                          │  │
│  │ movq %cr4, %rcx           # 读取当前 CR4                 │  │
│  │ andl %edx, %ecx           # 保留必要的位                 │  │
│  └─────────────────────────────────────────────────────────┘  │
│                                                               │
│ 关键点: 暂时不设置 PGE (Page Global Enable)                  │
│ ┌─────────────────────────────────────────────────────────┐  │
│ │ 为什么?                                                   │  │
│ │                                                           │  │
│ │ 从 SDM (Software Developer's Manual):                     │  │
│ │ "If CR4.PGE is changing from 1 to 0,                      │  │
│ │  there will be no global TLB entries after execution."    │  │
│ │                                                           │  │
│ │ 目的: 通过 PGE 0→1→0 的变化清除全局 TLB 条目              │  │
│ │      (特别是清除恒等映射的 TLB)                            │  │
│ └─────────────────────────────────────────────────────────┘  │
│                                                               │
│  216-217: 设置 PSE (Page Size Extension)                     │
│  ┌─────────────────────────────────────────────────────────┐  │
│  │ btsl $X86_CR4_PSE_BIT, %ecx                              │  │
│  │ movq %rcx, %cr4                                          │  │
│  │ # PSE = 支持 4MB/2MB 大页                               │  │
│  └─────────────────────────────────────────────────────────┘  │
│                                                               │
│  222-223: 重新启用 PGE                                       │
│  ┌─────────────────────────────────────────────────────────┐  │
│  │ btsl $X86_CR4_PGE_BIT, %ecx                              │  │
│  │ movq %rcx, %cr4                                          │  │
│  │ # PGE = 全局页启用,避免频繁刷新内核页表                 │  │
│  └─────────────────────────────────────────────────────────┘  │
└───────────────────────────────────────────────────────────────┘
↓
┌───────────────────────────────────────────────────────────────┐
│ 阶段 6: 确定 CPU 编号 (行 225-307)                          │
├───────────────────────────────────────────────────────────────┤
│  #ifdef CONFIG_SMP                                           │
│                                                               │
│  234-242: 检查启动模式                                       │
│  ┌─────────────────────────────────────────────────────────┐  │
│  │ movl smpboot_control(%rip), %ecx                         │  │
│  │ testl $STARTUP_READ_APICID, %ecx                         │  │
│  │ jnz .Lread_apicid                                        │  │
│  │                                                          │  │
│  │ 两种模式:                                                 │  │
│  │ 1. 单 CPU 启动: CPU# 直接在 smpboot_control 低 24 位     │  │
│  │ 2. 并行启动: 从 APIC 读取 APIC ID,然后查表              │  │
│  └─────────────────────────────────────────────────────────┘  │
│                                                               │
│  244-277: 读取 APIC ID (.Lread_apicid)                       │
│  ┌─────────────────────────────────────────────────────────┐  │
│  │ 方法 1: X2APIC MSR (性能更好)                            │  │
│  │   mov $MSR_IA32_APICBASE, %ecx                           │  │
│  │   rdmsr                                                  │  │
│  │   testl $X2APIC_ENABLE, %eax                             │  │
│  │   jnz .Lread_apicid_msr                                  │  │
│  │   ↓                                                      │  │
│  │   mov $APIC_X2APIC_ID_MSR, %ecx                          │  │
│  │   rdmsr          # %eax = APIC ID                       │  │
│  │                                                          │  │
│  │ 方法 2: MMIO 方式读取 (传统方法)                         │  │
│  │   movq apic_mmio_base(%rip), %rcx                        │  │
│  │   addq $APIC_ID, %rcx                                    │  │
│  │   movl (%rcx), %eax     # 内存映射读取                  │  │
│  │   shr $24, %eax         # 提取 APIC ID                  │  │
│  └─────────────────────────────────────────────────────────┘  │
│                                                               │
│  278-292: APIC ID → CPU 编号映射 (.Llookup_AP)              │
│  ┌─────────────────────────────────────────────────────────┐  │
│  │ xorl %ecx, %ecx             # %ecx = CPU 索引           │  │
│  │ leaq cpuid_to_apicid(%rip), %rbx                         │  │
│  │ # cpuid_to_apicid[] = 映射表: cpuid_to_apicid[CPU#] =   │  │
│  │                          APIC_ID                         │
│  │                                                          │  │
│  │ .Lfind_cpunr:                                            │  │
│  │     cmpl (%rbx,%rcx,4), %eax # 比较 APIC ID             │  │
│  │     jz .Lsetup_cpu          # 找到了,跳转               │  │
│  │     inc %ecx                # 下一个 CPU                │  │
│  │     cmpl $NR_CPUS, %ecx                                 │  │
│  │     jb .Lfind_cpunr         # 继续查找                  │  │
│  │                                                          │  │
│  │ # 如果找不到 APIC ID,释放锁并挂起                      │  │
│  │ movq trampoline_lock(%rip), %rax                         │  │
│  │ movl $0, (%rax)             # 释放锁                    │  │
│  │ cli; hlt; jmp 1b            # 挂起                      │  │
│  └─────────────────────────────────────────────────────────┘  │
│                                                               │
│  结果: %ecx = CPU 编号 (0, 1, 2, ...)
│  #endif                                                       │
└───────────────────────────────────────────────────────────────┘
↓
┌───────────────────────────────────────────────────────────────┐
│ 阶段 7: 设置 per-CPU 数据和栈 (行 303-326)                   │
├───────────────────────────────────────────────────────────────┤
│  .Lsetup_cpu:                                                 │
│                                                               │
│  304: 获取 per-CPU 偏移量                                    │
│  ┌─────────────────────────────────────────────────────────┐  │
│  │ #ifdef CONFIG_SMP                                         │  │
│  │     movq __per_cpu_offset(,%rcx,8), %rdx                 │  │
│  │     # __per_cpu_offset[] 数组存储每个 CPU 的数据区偏移  │  │
│  │     # %rdx = per-CPU 数据区的基址                       │  │
│  │ #else                                                     │  │
│  │     xorl %edx, %edx       # UP 系统没有偏移              │  │
│  │ #endif                                                    │  │
│  └─────────────────────────────────────────────────────────┘  │
│                                                               │
│  315-316: 设置栈指针                                         │
│  ┌─────────────────────────────────────────────────────────┐  │
│  │ movq pcpu_hot + X86_current_task(%rdx), %rax             │  │
│  │ # pcpu_hot.current_task = 当前任务 (task_struct)        │  │
│  │ # 对于 BSP: init_task                                   │  │
│  │ # 对于 AP: 预先分配的 idle 任务                         │  │
│  │                                                          │  │
│  │ movq TASK_threadsp(%rax), %rsp                          │  │
│  │ # %rsp = task->thread.sp (任务的内核栈顶)               │  │
│  └─────────────────────────────────────────────────────────┘  │
│                                                               │
│  关键点: 栈切换                                               │
│  ┌─────────────────────────────────────────────────────────┐  │
│  │ 为什么需要切换栈?                                         │  │
│  │                                                           │  │
│  │ - BSP: 之前使用 early stack (临时栈)                     │  │
│  │ - AP: 之前使用 trampoline 中的栈 (已被 CR3 切换卸载)     │  │
│  │                                                           │  │
│  │ 现在: 每个 CPU 都使用自己的 per-CPU 内核栈              │  │
│  └─────────────────────────────────────────────────────────┘  │
│                                                               │
│  322-325: 释放 trampoline 锁 (仅 AP)                         │
│  ┌─────────────────────────────────────────────────────────┐  │
│  │ movq trampoline_lock(%rip), %rax                         │  │
│  │ testq %rax, %rax          # BSP 的锁指针为 NULL         │  │
│  │ jz .Lsetup_gdt                                          │  │
│  │ movl $0, (%rax)           # AP 释放锁,允许下一个启动  │  │
│  │                                                          │  │
│  │ 作用: 串行化 AP 启动,一次只有一个 AP 执行这段代码      │  │
│  └─────────────────────────────────────────────────────────┘  │
└───────────────────────────────────────────────────────────────┘
↓
┌───────────────────────────────────────────────────────────────┐
│ 阶段 8: 加载 per-CPU GDT (行 327-353)                        │
├───────────────────────────────────────────────────────────────┤
│  .Lsetup_gdt:                                                 │
│                                                               │
│  334-339: 加载新的 GDT                                       │
│  ┌─────────────────────────────────────────────────────────┐  │
│  │ subq $16, %rsp          # 在栈上分配 GDT 描述符空间     │  │
│  │ movw $(GDT_SIZE-1), (%rsp) # GDT 界限                   │  │
│  │ leaq gdt_page(%rdx), %rax # GDT 基址 (per-CPU)          │  │
│  │ movq %rax, 2(%rsp)        # 存储基址                    │  │
│  │ lgdt (%rsp)               # 加载 GDT                    │  │
│  │ addq $16, %rsp            # 恢复栈                     │  │
│  │                                                          │  │
│  │ 为什么需要新的 GDT?                                       │  │
│  │ - 之前使用的是临时/共享的 GDT                            │  │
│  │ - 现在每个 CPU 需要自己的 GDT (在 per-CPU 数据区)       │  │
│  └─────────────────────────────────────────────────────────┘  │
│                                                               │
│  342-353: 设置段寄存器                                       │
│  ┌─────────────────────────────────────────────────────────┐  │
│  │ xorl %eax, %eax                                          │  │
│  │ movl %eax, %ds          # DS = 0 (64位模式平坦)         │  │
│  │ movl %eax, %ss          # SS = 0                        │  │
│  │ movl %eax, %es          # ES = 0                        │  │
│  │ movl %eax, %fs          # FS = 0                        │  │
│  │ movl %eax, %gs          # GS = 0 (稍后设置基址)         │  │
│  │                                                          │  │
│  │ 64位长模式下段寄存器的作用:                               │  │
│  │ - 选择子值不重要 (平坦内存模型)                           │  │
│  │ - 但 FS/GS 的基址仍然有用 (通过 MSR 设置)                │  │
│  └─────────────────────────────────────────────────────────┘  │
└───────────────────────────────────────────────────────────────┘
↓
┌───────────────────────────────────────────────────────────────┐
│ 阶段 9: 设置 GS 基址 (行 355-368)                            │
├───────────────────────────────────────────────────────────────┤
│  362-368: 设置 MSR_GS_BASE                                   │
│  ┌─────────────────────────────────────────────────────────┐  │
│  │ movl $MSR_GS_BASE, %ecx                                  │  │
│  │                                                          │  │
│  │ #ifdef CONFIG_SMP                                         │  │
│  │ # SMP: GS 指向 per-CPU 的 fixed_percpu_data             │  │
│  │ # (已经在 %rdx 中 = per-CPU 基址)                       │  │
│  │ #else                                                     │  │
│  │ # UP: GS 指向 BSS 中的 init_per_cpu 区域                 │  │
│  │ leaq INIT_PER_CPU_VAR(fixed_percpu_data)(%rip), %rdx     │  │
│  │ #endif                                                    │  │
│  │                                                          │  │
│  │ movl %edx, %eax         # MSR 低 32 位                  │  │
│  │ shrq $32, %rdx          # MSR 高 32 位                  │  │
│  │ wrmsr                 # 写入 MSR_GS_BASE                │  │
│  │                                                          │  │
│  │ 结果: GS 段的基址 = fixed_percpu_data                   │  │
│  │       %gs:0x28 = stack canary (栈溢出保护)               │  │
│  └─────────────────────────────────────────────────────────┘  │
└───────────────────────────────────────────────────────────────┘
↓
┌───────────────────────────────────────────────────────────────┐
│ 阶段 10: 设置 IDT (行 370-371)                               │
├───────────────────────────────────────────────────────────────┤
│  371: call early_setup_idt                                   │
│       ↓                                                       │
│       加载早期的 IDT (bringup_idt_table)                       │
│       包含基本的异常处理程序                                 │
│                                                               │
│ 为什么需要早期 IDT?                                            │
│   - 最终的 idt_table 还未完全初始化                          │
│   - 需要基本的异常处理能力 (如页错误)                        │
│   - 避免使用 tracing/KASAN 检测的代码                        │
└───────────────────────────────────────────────────────────────┘
↓
┌───────────────────────────────────────────────────────────────┐
│ 阶段 11: 配置 EFER 和 CR0 (行 373-401)                      │
├───────────────────────────────────────────────────────────────┤
│  374-376: 检查 NX 支持                                       │
│  ┌─────────────────────────────────────────────────────────┐  │
│  │ movl $0x80000001, %eax   # CPUID 扩展功能查询           │  │
│  │ cpuid                                                  │  │
│  │ movl %edx, %edi          # 保存特性位                   │  │
│  └─────────────────────────────────────────────────────────┘  │
│                                                               │
│  379-396: 设置 EFER (Extended Feature Enable Register)       │
│  ┌─────────────────────────────────────────────────────────┐  │
│  │ movl $MSR_EFER, %ecx                                     │  │
│  │ rdmsr              # 读取当前 EFER                      │  │
│  │ movl %eax, %edx    # 保存原值 (TDX 需要)                │  │
│  │                                                          │  │
│  │ btsl $_EFER_SCE, %eax  # SCE = System Call Enable       │  │
│  │                      # 启用 SYSCALL/SYSRET              │  │
│  │                                                          │  │
│  │ btl $20, %edi        # 检查 bit 20 (NX)                 │  │
│  │ jnc 1f               # 不支持则跳过                     │  │
│  │ btsl $_EFER_NX, %eax   # NX = No Execute                │  │
│  │ btsq $_PAGE_BIT_NX, early_pmd_flags(%rip)                │  │
│  │ # 同时在页表标志中启用 NX                               │  │
│  │                                                          │  │
│  │ 1:cmpl %edx, %eax        # 检查是否有变化               │  │
│  │   je 1f                # 没变化则跳过写入               │  │
│  │   xor %edx, %edx                                         │  │
│  │   wrmsr              # 写入 EFER                       │  │
│  └─────────────────────────────────────────────────────────┘  │
│                                                               │
│  398-401: 设置 CR0                                           │
│  ┌─────────────────────────────────────────────────────────┐  │
│  │ movl $CR0_STATE, %eax                                    │  │
│  │ # CR0_STATE = PE|MP|ET|NE|WP|AM|PG                       │  │
│  │ movq %rax, %cr0                                          │  │
│  └─────────────────────────────────────────────────────────┘  │
└───────────────────────────────────────────────────────────────┘
↓
┌───────────────────────────────────────────────────────────────┐
│ 阶段 12: 准备跳转到 C 代码 (行 403-414)                     │
├───────────────────────────────────────────────────────────────┤
│  403-405: 清零 EFLAGS                                        │
│  ┌─────────────────────────────────────────────────────────┐  │
│  │ pushq $0                                                 │  │
│  │ popfq              # EFLAGS = 0                          │  │
│  │ # 清除所有标志位,进入干净状态                           │  │
│  └─────────────────────────────────────────────────────────┘  │
│                                                               │
│  407-408: 传递参数                                           │
│  ┌─────────────────────────────────────────────────────────┐  │
│  │ movq %r15, %rdi      # 第一个参数                        │  │
│  │ # BSP: %r15 = boot_params 地址                          │  │
│  │ # AP:  %r15 = 0 (已清零)                                │  │
│  └─────────────────────────────────────────────────────────┘  │
│                                                               │
│  410-413: 跳转到 C 代码                                     │
│  ┌─────────────────────────────────────────────────────────┐  │
│  │ .Ljump_to_C_code:                                        │  │
│  │     xorl %ebp, %ebp      # 清除帧指针 (栈回溯结束)      │  │
│  │     callq *initial_code(%rip)                           │  │
│  │                                                          │  │
│  │ initial_code 变量的值:                                  │  │
│  │ - BSP: x86_64_start_kernel                              │  │
│  │ - AP: start_secondary                                   │  │
│  │                                                          │  │
│  │ ud2                # 不应该返回,如果返回则挂起         │  │
│  └─────────────────────────────────────────────────────────┘  │
└───────────────────────────────────────────────────────────────┘
↓
 [进入 C 代码]

为什么被加载到0x9d000?(realmode加载探索)

回到最初我们的问题,那么就得探究一下realmode trampoline在内存中的加载。

流程图

启动阶段流程:
═══════════════════════════════════════════════════════════════

[编译时]
   │
   ├─→ realmode.bin (编译实模式代码)
   │     - trampoline_64.S
   │     - 其他实模式代码
   │
   ├─→ realmode.relocs (重定位信息)
   │
   └─→ rmpiggy.S (.incbin包含上述文件)
        └─→ real_mode_blob (数据在内核镜像中)

[内核启动 - setup_arch() 之前]
   │
   ▼
┌──────────────────────────────────────┐
│  reserve_real_mode()                 │
│  (init.c:49)                         │
│  ┌────────────────────────────────┐  │
│  │ 1. 计算需要的大小              │  │
│  │    real_mode_size_needed()     │  │
│  │    = PAGE_ALIGN(blob_end -     │  │
│  │                blob_start)     │  │
│  │                                │  │
│  │ 2. 分配低内存(<1MB)            │  │
│  │    memblock_phys_alloc_range() │  │
│  │    范围: 0 ~ 1MB               │  │
│  │    对齐: PAGE_SIZE             │  │
│  │                                │  │
│  │ 3. 设置real_mode_header        │  │
│  │    set_real_mode_mem(mem)      │  │
│  │    real_mode_header =          │  │
│  │      __va(物理地址)            │  │
│  │                                │  │
│  │ 4. 保留整个1MB内存             │  │
│  │    memblock_reserve(0, 1M)     │  │
│  └────────────────────────────────┘  │
└──────────────────────────────────────┘
                │
                ▼
[内核启动 - do_pre_smp_initcalls()]
                │
                ▼
┌──────────────────────────────────────┐
│  init_real_mode()                    │
│  (init.c:221)                        │
│  ┌────────────────────────────────┐  │
│  │ setup_real_mode()               │  │
│  │ set_real_mode_permissions()     │  │
│  └────────────────────────────────┘  │
└──────────────────────────────────────┘
                │
                ▼
      [详细的setup流程见下]

生命周期

┌──────────────────────────────────────────────────────────────┐
│                    REAL_MODE 生命周期                          │
├──────────────────────────────────────────────────────────────┤
│                                                              │
│  编译时:                                                     │
│    ├─ 编译 trampoline_64.S 等实模式代码                      │
│    ├─ 生成 realmode.bin                                       │
│    └─ 通过 rmpiggy.S 打包到内核镜像                          │
│                                                              │
│  启动早期 (reserve_real_mode):                               │
│    ├─ 在低内存(<1MB)保留空间                                 │
│    └─ 设置 real_mode_header 指针                             │
│                                                              │
│  初始化时 (setup_real_mode):                                 │
│    ├─ 复制代码到低内存                                       │
│    ├─ 执行重定位修正                                         │
│    ├─ 配置 trampoline_header                                 │
│    ├─ 设置 trampoline_pgd                                    │
│    └─ 设置内存权限                                           │
│                                                              │
│  运行时:                                                     │
│    ├─ SMP启动: 每个AP通过trampoline启动                      │
│    ├─ CPU热插拔: 新CPU上线时使用                             │
│    ├─ ACPI S3: 从睡眠唤醒时使用                             │
│    └─ 系统重启: 某些重启路径使用                             │
│                                                              │
│  特点:                                                       │
│    ├─ 物理地址: ~0x90000 (低内存)                            │
│    ├─ 虚拟地址: 直接映射区 (0xffff8880_00090000)            │
│    ├─ 大小: ~4KB (1-2个页面)                                 │
│    ├─ 权限: 代码可执行,数据可读写                           │
│    └─ 生命周期: 从初始化到系统关闭一直存在                   │
│                                                              │
└──────────────────────────────────────────────────────────────┘

调用链

// 在启动过程中的调用顺序:
start_kernel()
    → setup_arch()         
        → reserve_real_mode()  // [给 realmode 代码留内存空间]
    → ... 其他初始化 ...
    → rest_init()      
        → kernel_init()
            → kernel_init_freeable()
                → do_basic_setup()
                    → do_initcalls()
                        → init_real_mode()  // 通过early_initcall注册

内存演化图

═══════════════════════════════════════════════════════════════════
           Linux内核启动的完整内存使用流程
═══════════════════════════════════════════════════════════════════

[时刻0: Bootloader工作阶段]
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Bootloader (如GRUB) 读取内核镜像文件 (bzImage):

bzImage结构:
┌────────────────────────────────────┐
│ Setup部分 (setup.bin)              │  ← 从文件开始
│ - header.S, main.c, pm.c编译的     │
│ - 16位实模式代码                   │
│ - 大小: ~32KB                      │
├────────────────────────────────────┤
│ 压缩内核部分 (vmlinux.bin.gz)     │
│ - head_64.S + 压缩的vmlinux       │
│ - 32/64位代码                     │
│ - 大小: 几MB                       │
└────────────────────────────────────┘

Bootloader执行操作:
    │
    ├─→ 1. 读取setup部分,加载到内存
    │      目标: 0x90000 (传统位置)
    │
    ├─→ 2. 读取压缩内核部分,加载到内存  
    │      目标: 0x100000 (1MB)
    │      ↑
    │      └─ 这个地址存储在header.S的code32_start字段
    │
    └─→ 3. 跳转到0x90000开始执行
          (跳转到Setup代码的_start)

[时刻1: Setup代码运行 - 第一次使用0x90000]
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
物理内存布局:

0x00000000  ┌─────────────────────────┐
            │ BIOS数据区              │
0x00007C00  ├─────────────────────────┤
            │ Bootloader残留          │
            │                         │
0x00090000  ├─────────────────────────┤  ← 当前执行位置!
            │ ★板块1: Setup代码★     │  ← Bootloader放这里的
            │                         │
            │ 包含:                   │
            │ • header.S (_start)     │
            │ • main.c (main)         │
            │ • pm.c                  │
            │ • boot_params           │
            │ • 命令行                │
            │ • 堆/栈                 │
            │                         │
0x00098000  │ (setup代码约32KB)       │
            ├─────────────────────────┤
0x000A0000  │ VGA显存                 │
            ├─────────────────────────┤
0x00100000  ├─────────────────────────┤  ← code32_start指向这里
            │ ★板块2: 压缩内核★      │  ← Bootloader放这里的
            │                         │
            │ 包含:                   │
            │ • startup_32            │
            │ • startup_64            │
            │ • 压缩的vmlinux数据     │
            │                         │
            │ (几MB大小)              │
            │                         │
            ├─────────────────────────┤
            │ 更高内存...             │

Setup代码执行流程:
    │
    ├─→ header.S:_start
    │   • 设置栈、段寄存器
    │   • 清零BSS
    │   • calll main
    │
    ├─→ main.c:main()
    │   • copy_boot_params()  ← 复制header到boot_params
    │   • detect_memory()     ← 检测内存
    │   • set_video()         ← 设置显示
    │   • go_to_protected_mode()
    │
    ├─→ pm.c:go_to_protected_mode()
    │   • enable_a20()
    │   • setup_idt()
    │   • setup_gdt()
    │   • protected_mode_jump(boot_params.hdr.code32_start, ...)
    │                         ↑
    │                         └─ 这是0x100000!
    │
    └─→ pmjump.S:protected_mode_jump()
        • 启用保护模式 (CR0.PE=1)
        • jmpl *%eax  ← 跳转到0x100000
                      ← 跳转到★板块2: 压缩内核★

[时刻2: 压缩内核运行 - 使用0x100000]
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
物理内存布局:

0x00090000  ┌─────────────────────────┐
            │ Setup代码 (不再执行)    │  ← 保留boot_params
            │ boot_params仍然在这里   │
            ├─────────────────────────┤
0x000A0000  │ VGA显存                 │
            ├─────────────────────────┤
0x00100000  ├─────────────────────────┤  ← 当前执行位置!
            │ ★板块2: 压缩内核★      │  ← 正在这里运行
            │                         │
            │ 执行:                   │
            │ • startup_32            │
            │   - 建立页表            │
            │   - 进入长模式          │
            │ • startup_64            │
            │   - 重定位代码          │
            │   - extract_kernel()    │
            │     (解压vmlinux)       │
            │                         │
            ├─────────────────────────┤
0x01000000  ├─────────────────────────┤  ← LOAD_PHYSICAL_ADDR
            │ 解压目标区域            │  ← extract_kernel()解压到这里
            │ (真正的内核)            │
            │                         │

压缩内核执行流程:
    │
    ├─→ head_64.S:startup_32 (入口点)
    │   • 建立早期页表
    │   • 启用PAE
    │   • 启用长模式
    │   • lret → startup_64
    │
    ├─→ head_64.S:startup_64
    │   • configure_5level_paging()
    │   • 重定位压缩内核
    │   • 清空BSS
    │   • extract_kernel()  ← 解压到0x1000000
    │   • jmp *%rax         ← 跳转到解压后的内核
    │
    └─→ 跳转到0x1000000处的真正内核

[时刻3: 真正内核运行 - 使用0x1000000]
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
物理内存布局:

0x00090000  ┌─────────────────────────┐
            │ Setup代码 (已失效)      │  ← 这块内存会被重用!
            │                         │
            ├─────────────────────────┤
0x000A0000  │ VGA显存                 │
            ├─────────────────────────┤
0x00100000  ├─────────────────────────┤
            │ 压缩内核 (不再使用)     │  ← 可能被回收
            │                         │
            ├─────────────────────────┤
0x01000000  ├─────────────────────────┤  ← 当前执行位置!
            │ ★真正的解压后内核★     │
            │                         │
            │ arch/x86/kernel/head_64.S│
            │ • startup_64            │
            │ • x86_64_start_kernel() │
            │ • start_kernel()        │
            │                         │

真正内核执行流程:
    │
    ├─→ kernel/head_64.S:startup_64
    │   • 设置内核GDT/IDT
    │   • 设置内核页表
    │   • __startup_64()
    │   • x86_64_start_kernel()
    │
    ├─→ kernel/head64.c:x86_64_start_kernel()
    │   • 复制boot_params
    │   • 早期初始化
    │   • start_kernel()
    │
    └─→ init/main.c:start_kernel()
        │
        ├─→ setup_arch()  ← 重要!这里设置realmode trampoline
        │   │
        │   ├─→ reserve_real_mode()
        │   │   ↓
        │   │   在0x90000附近保留内存
        │   │   ↓
        │   │   这是为★板块3★准备的!
        │   │
        │   └─→ init_real_mode()
        │       └─→ setup_real_mode()
        │           ↓
        │           将realmode trampoline复制到0x90000
        │           ↓
        │           这是★板块3: Realmode Trampoline★
        │
        └─→ 继续内核初始化...

[时刻4: 内核运行时 - 第二次使用0x90000]
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
物理内存布局:

0x00090000  ┌─────────────────────────┐
            │ ★板块3: Realmode       │  ← 内核重新设置的!
            │    Trampoline★         │  ← setup_real_mode()复制的
            │                         │
            │ 包含:                   │
            │ • trampoline_64.S       │
            │ • trampoline_header     │
            │ • trampoline_pgd        │
            │                         │
            │ 用途:                   │
            │ • SMP启动AP处理器       │
            │ • ACPI S3唤醒           │
            │ • 系统重启              │
            │                         │
            ├─────────────────────────┤
0x000A0000  │ VGA显存                 │
            ├─────────────────────────┤
            │                         │
0x01000000  ├─────────────────────────┤
            │ 运行中的内核            │  ← 当前执行位置
            │                         │
            │                         │
            ├─────────────────────────┤
            │ 内核数据结构            │
            │ 页表                    │
            │ 模块                    │
            │ ...                     │

何时使用板块3?
═══════════════════════════════════════════════════════════════
1. SMP启动时: 每个AP核心从0x90000开始执行
   BSP通过SIPI中断通知AP: "去0x90000执行代码"
   AP: 16位实模式 → 32位 → 64位 → secondary_startup_64

2. ACPI S3唤醒: 从睡眠恢复时重新初始化

3. 某些系统重启路径

预留内存空间(reserve_real_mode)

在系统内存低端(1MB以下)为实模式蹦床代码预留内存区域,确保多处理器启动时APs有可用的实模式执行环境。

void __init setup_arch(char **cmdline_p)
{
    ...
/*
 * 为实模式跳转程序(real mode trampoline)寻找空闲内存并放置。
 * 若 1MB 以下空间无足够空闲内存,在启用 EFI 的系统上,
 * 会在 efi_free_boot_services() 阶段再次尝试回收内存以分配给实模式跳转程序。
 *
 * 无条件保留物理内存的前 1MB —— 原因是已知 BIOS 会破坏低地址内存,
 * 而这几百 KB 的空间不值得通过复杂检测来判断哪些内存会被篡改。
 * Windows 也因类似原因采用了相同的策略。
 *
 * 此外,在搭载 SandyBridge 核显的设备或启用 crashkernel(崩溃内核)的配置中,
 * 前 1MB 内存本就会被自动保留。
 *
 * 注意:支持 TDX(Trust Domain Extensions)的宿主内核也要求保留前 1MB 内存。
 */
    x86_platform.realmode_reserve();
    ...
}

/*
        reserve_real_mode():在内存低端(<1MB)保留实模式代码运行所需的内存区域
        为AP(Application Processors,即非BSP处理器)启动代码提供运行环境
*/
void __init reserve_real_mode(void)
{
phys_addr_t mem;
size_t size = real_mode_size_needed();

if (!size)
return;

WARN_ON(slab_is_available());

/* Has to be under 1MB so we can execute real-mode AP code. */
        mem = memblock_phys_alloc_range(size, PAGE_SIZE, 0, 1<<20);
if (!mem)
pr_info("No sub-1M memory is available for the trampoline\n");
else
set_real_mode_mem(mem);

/*
 * Unconditionally reserve the entire first 1M, see comment in
 * setup_arch().
 */
memblock_reserve(0, SZ_1M);
}

寻找空闲内存过程

// arch/x86/realmode/init.c:49

void __init reserve_real_mode(void)
{
phys_addr_t mem;
size_t size = real_mode_size_needed();  // 通常4KB左右

// 关键:在 0 到 1MB 范围内分配
    mem = memblock_phys_alloc_range(size, PAGE_SIZE, 0, 1<<20);
//                         ↑   ↑
//                         起始 结束(1MB)
}

memblock分配器的行为:

  • 从高地址向低地址搜索可用内存
  • 寻找满足大小和对齐要求的第一个空闲块
  • 避开已保留的区域

那么我们分析一下前面出现的内存占用情况就能很快估算出来内存大概率应该存在的地址。

启动时间线:
═══════════════════════════════════════════════════════════════

[1]setup_arch() 早期
    ↓
reserve_bios_regions()  // 先保留BIOS相关区域
    ↓
    保留 0xA0000-0xFFFFF (VGA + BIOS ROM)
    保留 EBDA区域 (如果存在)
    ↓

[2]setup_arch() 中
    ↓
reserve_real_mode()
    ↓
    在剩余空间中分配 (~4KB)
    ↓
    memblock从高到低搜索:

    ┌─────────────────────────────────────────┐
    │ 0xA0000-0xFFFFF: 已保留(VGA/BIOS)       │
    │ ▼ 向下搜索                               │
    │ 0x9F000-0x9FFFF: 可能被EBDA占用          │
    │ ▼ 继续向下                               │
    │ 0x90000-0x9EFFF: ★找到了!可用空间★     │
    │   - 4KB大小足够                          │
    │   - PAGE_SIZE对齐                        │
    │   - 在<1MB范围内                         │
    │   - 实模式可访问                         │
    │   → 分配在这里!                         │
    └─────────────────────────────────────────┘

Set_real_mode_mem

static inline void set_real_mode_mem(phys_addr_t mem)
{
        real_mode_header = (struct real_mode_header *) __va(mem);
}

do_init_real_mode

Initcall 机制可以详细看知识库中的 initcall 机制板块,这里就代表着 do_init_real_mode 会在kernel_init_freeable的时候通过do_pre_smp_initcalls自动调用 (do_fork 启动一个 进程 执行 kernel_init 函数.  PID 为 1 的进程.   →  kernel_init_freeable函数   +   run_init_process)

static int __init do_init_real_mode(void)
{
        x86_platform.realmode_init();
return 0;
}
early_initcall(do_init_real_mode);
  • 然后根据 x86_platform赋值找到对应函数
struct x86_platform_ops x86_platform __ro_after_init = {
        .calibrate_cpu                  = native_calibrate_cpu_early,
        .calibrate_tsc                  = native_calibrate_tsc,
        .get_wallclock                  = mach_get_cmos_time,
        .set_wallclock                  = mach_set_cmos_time,
        .iommu_shutdown                 = iommu_shutdown_noop,
        .is_untracked_pat_range         = is_ISA_range,
        .nmi_init                       = default_nmi_init,
        .get_nmi_reason                 = default_get_nmi_reason,
        .save_sched_clock_state         = tsc_save_sched_clock_state,
        .restore_sched_clock_state      = tsc_restore_sched_clock_state,
        .realmode_reserve               = reserve_real_mode,
        .realmode_init                  = init_real_mode,
        .hyper.pin_vcpu                 = x86_op_int_noop,
        .hyper.is_private_mmio          = is_private_mmio_noop,

        .guest = {
                .enc_status_change_prepare = enc_status_change_prepare_noop,
                .enc_status_change_finish  = enc_status_change_finish_noop,
                .enc_tlb_flush_required    = enc_tlb_flush_required_noop,
                .enc_cache_flush_required  = enc_cache_flush_required_noop,
                .enc_kexec_begin           = enc_kexec_begin_noop,
                .enc_kexec_finish          = enc_kexec_finish_noop,
        },
};
  • realmode_init
void __init init_real_mode(void)
{
        if (!real_mode_header)
panic("Real mode trampoline was not allocated");

setup_real_mode();
set_real_mode_permissions();
}

调用链

do_init_real_mode()
init_real_mode()
setup_real_mode() [初始化 realmode]

setup_real_mode

static void __init setup_real_mode(void)
{
        ...
size_t size = PAGE_ALIGN(real_mode_blob_end - real_mode_blob);
#ifdef CONFIG_X86_64
        u64 *trampoline_pgd;
        u64 efer;
int i;
#endif

        base = (unsigned char *)real_mode_header;

/*
 * If SME is active, the trampoline area will need to be in
 * decrypted memory in order to bring up other processors
 * successfully. This is not needed for SEV.
 */
if (cc_platform_has(CC_ATTR_HOST_MEM_ENCRYPT))
set_memory_decrypted((unsigned long)base, size >> PAGE_SHIFT);
// 二进制代码复制到对应内存区域
memcpy(base, real_mode_blob, size);
        ...
}
  • 流程图
┌─────────────────────────────────────────────────────┐
│  setup_real_mode() 执行流程                         │
│  (init.c:92-181)                                    │
└─────────────────────────────────────────────────────┘
                 │
                 ▼
┌─────────────────────────────────────────────────────┐
│  步骤1: 获取目标地址                               │
│  ┌───────────────────────────────────────────────┐  │
│  │ base = (unsigned char *)real_mode_header      │  │
│  │                                             │  │
│  │ 虚拟地址: 例如 0xffff888000090000            │  │
│  │ 物理地址: __pa(base) (例如 0x90000)          │  │
│  │                                             │  │
│  │ size = PAGE_ALIGN(real_mode_blob_end -       │  │
│  │             real_mode_blob)                 │  │
│  │        通常为 1~2个页面                      │  │
│  └───────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────┘
                 │
                 ▼
┌─────────────────────────────────────────────────────┐
│  步骤2: 复制代码 (行117)                            │
│  ┌───────────────────────────────────────────────┐  │
│  │ memcpy(base, real_mode_blob, size)           │  │
│  │                                             │  │
│  │ 源: real_mode_blob                           │  │
│  │     - 位于内核镜像的.init.data段            │  │
│  │     - 虚拟地址: __init区域                  │  │
│  │     - 包含编译好的realmode.bin             │  │
│  │                                             │  │
│  │ 目标: base                                   │  │
│  │     - 位于低内存(<1MB)                      │  │
│  │     - 物理地址: 通常0x90000附近             │  │
│  │     - AP处理器启动时能访问                  │  │
│  └───────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────┘
                 │
                 ▼
┌─────────────────────────────────────────────────────┐
│  步骤3: 计算重定位基地址 (行119-120)               │
│  ┌───────────────────────────────────────────────┐  │
│  │ phys_base = __pa(base)                       │  │
│  │ real_mode_seg = phys_base >> 4               │  │
│  │                                             │  │
│  │ 举例:                                         │  │
│  │   phys_base = 0x90000                        │  │
│  │   real_mode_seg = 0x9000                     │  │
│  │   (实模式段地址格式: segment:offset)         │  │
│  └───────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────┘
                 │
                 ▼
┌─────────────────────────────────────────────────────┐
│  步骤4: 16位段重定位 (行124-129)                   │
│  ┌───────────────────────────────────────────────┐  │
│  │ rel = (u32 *) real_mode_relocs               │  │
│  │ count = *rel++  // 读取重定位项数量          │  │
│  │                                             │  │
│  │ while (count--) {                           │  │
│  │     u16 *seg = (u16 *)(base + *rel++)       │  │
│  │     *seg = real_mode_seg  // 修正段地址     │  │
│  │ }                                           │  │
│  │                                             │  │
│  │ 作用: 修正实模式代码中的段地址引用          │  │
│  │       例如: mov ax, SEGMENT_PLACEHOLDER     │  │
│  │           ↓ 修正为                          │  │
│  │           mov ax, 0x9000                    │  │
│  └───────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────┘
                 │
                 ▼
┌─────────────────────────────────────────────────────┐
│  步骤5: 32位线性重定位 (行131-136)                 │
│  ┌───────────────────────────────────────────────┐  │
│  │ count = *rel++                               │  │
│  │                                             │  │
│  │ while (count--) {                           │  │
│  │     u32 *ptr = (u32 *)(base + *rel++)       │  │
│  │     *ptr += phys_base  // 加上物理基地址    │  │
│  │ }                                           │  │
│  │                                             │  │
│  │ 作用: 修正32位保护模式中的线性地址          │  │
│  │       例如: ljmpl $__KERNEL32_CS, $0x0      │  │
│  │           ↓ 修正为                          │  │
│  │           ljmpl $__KERNEL32_CS, $0x90100    │  │
│  └───────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────┘
                 │
                 ▼
┌─────────────────────────────────────────────────────┐
│  步骤6: 获取trampoline_header (行139-140)          │
│  ┌───────────────────────────────────────────────┐  │
│  │ trampoline_header =                          │  │
│  │   (struct trampoline_header *)               │  │
│  │   __va(real_mode_header->trampoline_header)  │  │
│  │                                             │  │
│  │ real_mode_header->trampoline_header:         │  │
│  │   - 是一个偏移量(相对于base)                │  │
│  │   - 在header.S中定义为pa_trampoline_header  │  │
│  │                                             │  │
│  │ 最终地址 = base + 偏移量                    │  │
│  └───────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────┘
                 │
                 ▼
┌─────────────────────────────────────────────────────┐
│  步骤7: 配置trampoline_header (x86_64)             │
│  (行151-162)                                       │
│  ┌───────────────────────────────────────────────┐  │
│  │ struct trampoline_header {                   │  │
│  │     u64 start;    // 启动入口点              │  │
│  │     u64 efer;     // EFER寄存器值            │  │
│  │     u32 cr4;      // CR4特性位               │  │
│  │     u32 flags;    // 标志(SME等)             │  │
│  │     u32 lock;     // 自旋锁                 │  │
│  │ };                                           │  │
│  │                                             │  │
│  │ 初始化:                                       │  │
│  │ ┌─────────────────────────────────────────┐  │  │
│  │ │ rdmsrl(MSR_EFER, efer)                 │  │  │
│  │ │ th->efer = efer & ~EFER_LMA            │  │  │
│  │ │   // 清除LMA位,AP启动时还在实模式     │  │  │
│  │ │                                         │  │  │
│  │ │ th->start = secondary_startup_64       │  │  │
│  │ │   // 64位AP入口点                      │  │  │
│  │ │                                         │  │  │
│  │ │ th->cr4 = mmu_cr4_features             │  │  │
│  │ │   // 当前CPU的CR4特性                  │  │  │
│  │ │                                         │  │  │
│  │ │ th->flags = 0                          │  │  │
│  │ │ th->lock = 0                           │  │  │
│  │ └─────────────────────────────────────────┘  │  │
│  └───────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────┘
                 │
                 ▼
┌─────────────────────────────────────────────────────┐
│  步骤8: 设置trampoline页表 (行164-177)             │
│  ┌───────────────────────────────────────────────┐  │
│  │ trampoline_pgd = (u64 *)                   │  │
│  │   __va(real_mode_header->trampoline_pgd)     │  │
│  │                                             │  │
│  │ // 恒等映射低内存                           │  │
│  │ trampoline_pgd[0] = trampoline_pgd_entry.pgd │  │
│  │   ↑                                         │  │
│  │   这使得虚拟地址 == 物理地址 (对于低内存)   │  │
│  │   AP启动时需要这个映射                     │  │
│  │                                             │  │
│  │ // 复制内核映射                             │  │
│  │ for (i = pgd_index(__PAGE_OFFSET);          │  │
│  │     i < PTRS_PER_PGD; i++)                 │  │
│  │     trampoline_pgd[i] = init_top_pgt[i].pgd│  │
│  │   ↑                                         │  │
│  │   复制内核空间的所有映射                   │  │
│  │   这样AP可以访问内核代码和数据             │  │
│  └───────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────┘

trampoline

作用
┌────────────────────────────────────────────────────────┐
│  trampoline 的三大作用:                               │
├────────────────────────────────────────────────────────┤
│                                                        │
│  1. SMP多核启动 (主要用途)                             │
│     ┌────────────────────────────────────────────┐    │
│     │ • BSP (Bootstrap Processor) 已启动         │    │
│     │ • 需要启动其他AP (Application Processors)  │    │
│     │ • AP启动时处于16位实模式                   │    │
│     │ • 需要过渡: 16位实模式 → 32位 → 64位长模式 │    │
│     │ • trampoline提供这个过渡代码               │    │
│     └────────────────────────────────────────────┘    │
│                                                        │
│  2. ACPI S3睡眠唤醒                                     │
│     ┌────────────────────────────────────────────┐    │
│     │ • 系统从S3睡眠状态唤醒                     │    │
│     │ • CPU从实模式重新开始                      │    │
│     │ • 使用wakeup_start代码段                   │    │
│     └────────────────────────────────────────────┘    │
│                                                        │
│  3. APM/BIOS重启                                        │
│     ┌────────────────────────────────────────────┐    │
│     │ • 某些重启方式需要进入实模式               │    │
│     │ • 使用machine_real_restart_asm             │    │
│     └────────────────────────────────────────────┘    │
│                                                        │
└────────────────────────────────────────────────────────┘
生命周期
完整生命周期流程:
═══════════════════════════════════════════════════════════════

[阶段1: 编译时]
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
时间: 内核编译时
位置: arch/x86/realmode/rm/

┌─────────────────────────────────────────┐
│ 1. 编译实模式代码                        │
│    - trampoline_64.S                     │
│    - header.S                            │
│    - 生成 realmode.bin                   │
│    - 生成 realmode.relocs                │
└─────────────────────────────────────────┘
        │
        ▼
┌─────────────────────────────────────────┐
│ 2. 打包到内核镜像                        │
│    - rmpiggy.S 使用 .incbin             │
│    - 变成 real_mode_blob[] 数组         │
│    - 位于内核的 .init.data 段           │
└─────────────────────────────────────────┘

[阶段2: 内核启动早期 - BSP引导]
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
时间: setup_arch() 中
调用: reserve_real_mode()

┌─────────────────────────────────────────┐
│ 1. 保留低内存                            │
│    memblock_phys_alloc_range()           │
│    ↓                                     │
│    分配范围: 0 ~ 1MB                     │
│    大小: ~4KB                            │
│    对齐: PAGE_SIZE                       │
│    ↓                                     │
│    物理地址: 例如 0x90000               │
│    ↓                                     │
│    设置: real_mode_header = __va(mem)   │
└─────────────────────────────────────────┘
        │
        │ 此时内存已保留,但还未复制代码
        │

[阶段3: 初始化时 - 复制和配置]
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
时间: do_pre_smp_initcalls()
调用: init_real_mode() → setup_real_mode()

┌─────────────────────────────────────────┐
│ 1. 复制代码到低内存                      │
│    memcpy(base, real_mode_blob, size)   │
│    ↓                                     │
│    源: real_mode_blob (内核镜像)        │
│    目标: 0x90000 (物理内存)             │
└─────────────────────────────────────────┘
        │
        ▼
┌─────────────────────────────────────────┐
│ 2. 重定位修正                            │
│    - 修正16位段地址                     │
│    - 修正32位线性地址                   │
└─────────────────────────────────────────┘
        │
        ▼
┌─────────────────────────────────────────┐
│ 3. 配置trampoline_header                │
│    - start = secondary_startup_64       │
│    - efer = 当前EFER & ~EFER_LMA       │
│    - cr4 = mmu_cr4_features             │
│    - lock = 0                           │
└─────────────────────────────────────────┘
        │
        ▼
┌─────────────────────────────────────────┐
│ 4. 设置trampoline_pgd页表               │
│    - [0] = 恒等映射 (低内存)            │
│    - [高位] = 内核映射                 │
└─────────────────────────────────────────┘
        │
        ▼
┌─────────────────────────────────────────┐
│ 5. 设置内存权限                          │
│    set_real_mode_permissions()          │
│    - 代码段: 可执行                      │
│    - 数据段: 可读写                      │
│    - 只读段: 只读                        │
└─────────────────────────────────────────┘
        │
        │ 此时trampoline已完全准备好
        │

[阶段4: SMP启动 - 每个AP启动时]
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
时间: do_boot_cpu() 中
每个AP启动时都会执行

┌─────────────────────────────────────────┐
│ BSP准备启动AP                            │
│  ┌───────────────────────────────────┐  │
│  │ 1. 设置initial_stack (per-CPU栈) │  │
│  │ 2. 设置initial_code (入口点)     │  │
│  │ 3. 发送SIPI (启动处理器间中断)   │  │
│  │    目标地址: trampoline物理地址   │  │
│  └───────────────────────────────────┘  │
└─────────────────────────────────────────┘
        │
        ▼
┌─────────────────────────────────────────┐
│ AP开始执行 (16位实模式)                  │
│  ┌───────────────────────────────────┐  │
│  │ trampoline_start: (trampoline_64.S)│ │
│  │                                    │  │
│  │ [16位实模式]                       │  │
│  │ 1. cli / wbinvd                   │  │
│  │ 2. 设置段寄存器 CS=DS=ES=SS      │  │
│  │ 3. 获取自旋锁 (tr_lock)          │  │
│  │ 4. 设置实模式栈                   │  │
│  │ 5. 加载tr_gdt                     │  │
│  │ 6. 启用保护模式 (CR0.PE=1)       │  │
│  │ 7. 跳转到32位代码                 │  │
│  │    ljmpl $__KERNEL32_CS, $pa_... │  │
│  └───────────────────────────────────┘  │
└─────────────────────────────────────────┘
        │
        ▼
┌─────────────────────────────────────────┐
│ AP继续执行 (32位保护模式)                │
│  ┌───────────────────────────────────┐  │
│  │ startup_32: (trampoline_64.S)    │  │
│  │                                    │  │
│  │ [32位保护模式]                     │  │
│  │ 1. 设置段寄存器                   │  │
│  │ 2. 启用PAE (CR4.PAE=1)           │  │
│  │ 3. 加载CR3 (trampoline_pgd)      │  │
│  │ 4. 设置EFER (从trampoline_header)│  │
│  │ 5. 启用分页 (CR0.PG=1)           │  │
│  │    → 进入兼容模式                 │  │
│  │ 6. 跳转到64位代码                 │  │
│  │    ljmpl $__KERNEL_CS, $pa_...   │  │
│  └───────────────────────────────────┘  │
└─────────────────────────────────────────┘
        │
        ▼
┌─────────────────────────────────────────┐
│ AP继续执行 (64位长模式)                  │
│  ┌───────────────────────────────────┐  │
│  │ startup_64: (trampoline_64.S)    │  │
│  │                                    │  │
│  │ [64位长模式]                       │  │
│  │ 1. 从trampoline_header读取start  │  │
│  │ 2. jmpq *tr_start                │  │
│  │    → secondary_startup_64        │  │
│  └───────────────────────────────────┘  │
└─────────────────────────────────────────┘
        │
        ▼
┌─────────────────────────────────────────┐
│ AP进入内核 (head_64.S)                   │
│  ┌───────────────────────────────────┐  │
│  │ secondary_startup_64:            │  │
│  │   (arch/x86/kernel/head_64.S)   │  │
│  │                                    │  │
│  │ 1. 加载内核GDT/IDT                │  │
│  │ 2. 切换到内核页表                 │  │
│  │ 3. 设置per-CPU数据                │  │
│  │ 4. 初始化栈                       │  │
│  │ 5. 跳转到C代码                    │  │
│  │    → start_secondary()           │  │
│  └───────────────────────────────────┘  │
└─────────────────────────────────────────┘
        │
        ▼
    [AP完全启动]

[阶段5: 运行时 - 持续存在]
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
时间: 系统运行期间
状态: 保持在低内存中

┌─────────────────────────────────────────┐
│ trampoline代码持续存在于低内存           │
│                                         │
│ 可能的使用场景:                         │
│  • CPU热插拔 - 启动新CPU               │
│  • ACPI S3唤醒 - 从睡眠恢复             │
│  • 系统重启 - 某些重启路径             │
└─────────────────────────────────────────┘

[阶段6: 关闭/重启时]
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
时间: 系统关闭或重启

┌─────────────────────────────────────────┐
│ 某些重启路径会使用                     │
│   machine_real_restart_asm              │
│                                         │
│ ACPI睡眠会保留这块内存                 │
└─────────────────────────────────────────┘

那么这里总结一下,也就是说因为 realmode trampoline 在 Linux 内核启动后物理位置固定在 0x90000,然后trampoline_header->start 的位置相对于 realmode trampoline的位置也是固定的,因为setup_real_mode将secondary_startup_64存放在了trampoline_header->start,所以一般情况下 0x9d000 就固定存放着secondary_startup_64指针。




上一篇:搞懂量化交易:手把手拆解 17 个经典策略源码
下一篇:状态机与事件驱动:嵌入式系统按键处理的框架设计与实现
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-1-10 18:41 , Processed in 0.310087 second(s), 39 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 云栈社区.

快速回复 返回顶部 返回列表