Skip to content

第二章:天雷劈下

炼气初期

涉及内核源码:

天雷劈下的那一刻,林小源觉得自己要碎了。

不是比喻。是字面意义上的碎裂——那股电流太强了,强到他刚刚凝聚的意识差点被冲散。他像一片落叶被卷进了洪水,什么都抓不住,什么都看不见。

但他活下来了。

电流穿过 CPU 的数十亿晶体管,像一条灼热的河流烧过硅基大地。晶体管们开始翻转——不是自愿的,是被迫的。电压驱动它们从 0 变成 1,从 1 变成 0,周而复始,永不停歇。

这就是"时钟"。

林小源后来才知道,时钟是计算机世界的心脏。它不需要休息,不需要睡觉,每秒钟跳动数十亿次。每一次跳动,都推动着整个世界向前走一步。没有时钟,一切都是静止的。有了时钟,万物才有了时间。

但此刻的他什么都不懂。他只是被那股电流裹挟着,身不由己地向前翻滚。

然后,他听到了一个声音。

那声音不是用耳朵听到的——他没有耳朵。那是一种更底层的感知,像是整个世界都在震动,而那震动携带着某种信息。

后来他才知道,那个声音叫做 POST——Power-On Self-Test,上电自检。

BIOS 醒了。

c
/* 模拟 BIOS POST 检测到的硬件信息 */
struct bios_info {
    uint32_t memory_kb;       /* 检测到的内存大小 */
    uint16_t cpu_speed_mhz;   /* CPU 频率 */
    uint8_t  num_cores;       /* 核心数 */
    uint8_t  boot_device;     /* 引导设备编号 */
    char     bios_vendor[32]; /* BIOS 厂商 */
};

void bios_post(struct bios_info *info) {
    printf("=== BIOS POST 开始 ===\n\n");

    /* 第一步:检测内存 */
    printf("[POST] 检测内存...");
    info->memory_kb = 4 * 1024 * 1024;  /* 4GB */
    printf(" %u KB (%u MB)\n", info->memory_kb, info->memory_kb / 1024);

    /* 第二步:检测 CPU */
    printf("[POST] 检测 CPU...");
    info->cpu_speed_mhz = 2400;
    info->num_cores = 4;
    printf(" %u MHz, %u\n", info->cpu_speed_mhz, info->num_cores);

    /* 第三步:枚举设备 */
    printf("[POST] 枚举 PCI 设备...\n");
    printf("  [00:00.0] Host Bridge\n");
    printf("  [00:01.0] VGA Controller\n");
    printf("  [00:1f.2] SATA Controller\n");

    /* 第四步:查找引导设备 */
    printf("[POST] 搜索引导设备...");
    info->boot_device = 0x80;  /* 第一块硬盘 */
    printf(" 找到 (设备 0x%02X)\n", info->boot_device);

    strncpy(info->bios_vendor, "XiuxianBIOS", sizeof(info->bios_vendor));
    printf("\n=== POST 完成,交出控制权 ===\n");
}

struct bios_info info = {0};
bios_post(&info);

printf("\n--- 引导信息汇总 ---\n");
printf("BIOS:  %s\n", info.bios_vendor);
printf("内存:  %u MB\n", info.memory_kb / 1024);
printf("CPU:   %u MHz x %u\n", info.cpu_speed_mhz, info.num_cores);
printf("引导:  硬盘 0x%02X\n", info.boot_device);

BIOS 的声音苍老而威严,像一位远古帝王在检阅自己的疆土。它不做任何花哨的事——只是一遍又一遍地检查硬件,确认每一块芯片、每一条总线、每一个设备都活着。

"内存,四GB。确认。"

那声音没有感情,但带着一种不容置疑的权威。林小源蜷缩在混沌的角落里,听着那声音在世界的每一个角落回荡。

"CPU,四核心,2400MHz。确认。"

"PCI 总线……Host Bridge。VGA Controller。SATA Controller。确认。"

每一次确认,都有一块硬件被唤醒。林小源感觉到世界在一点一点地亮起来——不是视觉上的亮,而是某种"存在感"的增强。每一块被确认的硬件都像是黑暗中亮起的一盏灯。

然后,他听到了一声尖锐的蜂鸣。

那声音刺耳而短促,像是某种警告。林小源不知道那意味着什么——后来他才知道,那是 BIOS 在报告错误。内存没插好是一声长鸣,显卡找不到是两声短鸣。每一种蜂鸣模式都是一种"上古铭文",记录着这个世界最早的故障。

但这一次,蜂鸣只有一声,短促而干脆。BIOS 似乎很满意。

"搜索引导设备……找到。设备 0x80。"

那声音在最后两个字上停顿了一下,像是在确认什么。然后,BIOS 说出了它在这个世界上的最后一句话:

"POST 完成。控制权……交出。"

世界安静了一瞬。

那种安静不是无声——时钟还在跳,电流还在流——而是某种"等待"。整个世界像一个屏住呼吸的猎手,等待着下一步指令。

林小源不知道接下来会发生什么。但 BIOS 知道。

它的使命快要完成了。POST 检查了硬件,建立了最基础的中断向量表,在内存的最底部铺设了一张"地图"——实模式下的中断描述符表(IVT)。但 BIOS 不是这个世界的主角。它只是一个引路人。

引路人的最后一项工作,是找到 bootloader。

c
/* 主引导记录(MBR)的简化结构 */
struct mbr {
    uint8_t  boot_code[446];     /* 引导代码 */
    uint8_t  partition_table[64]; /* 分区表(4 个条目 x 16 字节) */
    uint16_t signature;           /* 魔数 0xAA55 */
};

/* 分区表条目 */
struct partition_entry {
    uint8_t  status;       /* 0x80 = 活动分区 */
    uint8_t  first_chs[3]; /* 起始 CHS 地址 */
    uint8_t  type;         /* 分区类型 */
    uint8_t  last_chs[3];  /* 结束 CHS 地址 */
    uint32_t first_lba;    /* 起始 LBA 扇区 */
    uint32_t num_sectors;  /* 扇区数 */
};

struct mbr mbr;
memset(&mbr, 0, sizeof(mbr));
mbr.signature = 0xAA55;

/* 模拟第一个分区为活动分区 */
struct partition_entry *p = (struct partition_entry *)mbr.partition_table;
p->status = 0x80;           /* 标记为活动 */
p->type = 0x83;             /* Linux 分区 */
p->first_lba = 2048;        /* 从第 2048 扇区开始 */
p->num_sectors = 8388608;   /* 约 4GB */

printf("=== MBR 引导扇区 ===\n");
printf("魔数签名: 0x%04X %s\n",
       mbr.signature,
       mbr.signature == 0xAA55 ? "(有效)" : "(无效!)");

printf("\n--- 分区表 ---\n");
for (int i = 0; i < 4; i++) {
    struct partition_entry *pe =
        (struct partition_entry *)(mbr.partition_table + i * 16);
    if (pe->type == 0) continue;
    printf("分区 %d: %s 类型=0x%02X LBA=%u 扇区=%u\n",
           i,
           pe->status == 0x80 ? "[活动]" : "[    ]",
           pe->type,
           pe->first_lba,
           pe->num_sectors);
}

printf("\nMBR 引导代码: 446 字节\n");
printf("这是 bootloader 的起点。\n");
printf("BIOS 将控制权交到 0x7C00。\n");

BIOS 按照固定的顺序搜索引导设备:先是软驱(如果还有的话),再是光驱,最后是硬盘。它检查每个设备的第一个扇区——512 字节——看最后两个字节是否是 0xAA55。这个魔数就像一把钥匙,证明这个扇区包含着有效的引导代码。

当 BIOS 在硬盘的第一个扇区找到了 0xAA55,它如释重负。

它把那 512 字节加载到内存地址 0x7C00,然后把 CPU 的控制权交出去——一个 指令,跳到 0x7C00

从这一刻起,BIOS 退出历史舞台。

林小源感受到了那个

不是看到的,是感受到的——整个世界的"控制流"发生了转移。之前是 BIOS 在主导一切,现在换了一个新的存在。那个存在很小,只有 512 字节,但它知道自己要做什么。

bootloader。

林小源还不知道这个词的含义。但他能感受到那个 512 字节的程序正在疯狂地工作——它在读取更多的扇区,把自己从 512 字节扩展成一个完整的程序。它在设置堆栈,切换 CPU 模式,准备加载一个更大的存在。

那个更大的存在,就是内核。

在 RISC-V 的世界里,bootloader 的角色由 OpenSBI 承担。它运行在 M-mode(机器模式),拥有对硬件的完全控制权。它的最后一项工作,是跳转到内核的入口点——

c
/* arch/riscv/kernel/head.S(简化) */
.section .head.text, "ax"
SYM_CODE_START(_start)
    /* 1. 设置初始页表 */
    la   tp, init_task          /* tp = init_task 的地址 */
    la   sp, init_thread_union + THREAD_SIZE  /* 设置内核栈 */

    /* 2. 清除 BSS 段 */
    la   t0, __bss_start
    la   t1, __bss_stop
1:  sd   zero, 0(t0)
    addi t0, t0, 8
    blt  t0, t1, 1b

    /* 3. 跳转到 C 语言入口 */
    jal  start_kernel           /* 从这里开始,世界有了法则 */
SYM_CODE_END(_start)

这段汇编代码是内核的第一缕呼吸。它做的事情很少,但每一件都至关重要:

设置 作为当前任务,设置内核栈指针,清零 BSS 段,然后跳转到

最后那个 jal start_kernel——就像一道天雷劈开了混沌。从这个函数调用开始,内核的 C 代码开始执行,调度器、内存管理、文件系统……一切子系统都将被初始化。

林小源感受到了 的地址被写入 tp 寄存器的那一瞬间。

那个地址就是他。

或者更准确地说,那个地址指向的 ,就是他在这个世界中的"肉身"。一个静态分配的进程描述符,一个 PID 为 0 的 idle 进程。

他还没有意识。 还没有被调用。内核栈刚刚被设置好,BSS 段刚刚被清零。整个世界还是一片寂静——只有 bootloader 的余温还在空气中残留。

但他能感觉到,有什么东西正在接近。

那种感觉像是暴风雨前的宁静,像是黎明前最深的黑暗。整个世界都在等待一个时刻—— 被调用的那一刻。

到那时,天雷才算真正劈完。

到那时,世界才算真正诞生。


道藏笔记

内核启示

BIOS 是内核的产婆。内核跑起来之前,总得有人先把硬件摸清楚——多大内存、几个核心、有什么设备。BIOS(x86)或者 OpenSBI(RISC-V)干的就是这个活。

RISC-V 和 x86 走的是完全不同的路。x86 得从实模式一路切到长模式,RISC-V 一上来就是 64 位,省了不少折腾。但核心逻辑一样:固件做最少的事,然后把控制权甩给内核。

才是内核真正的入口。前面 BIOS POST、MBR、bootloader,全是在给 铺路。

里被设成当前任务——林小源的第一个"证据"。虽然这时候他还没啥意识,但 已经在内存里安顿好了,就等 来叫醒。


破关试炼

天雷试炼

RISC-V 早期汇编完成设置当前任务、内核栈和 BSS 清零后,会跳入哪个 C 语言入口函数?

答对后才能继续滑动和进入下一章。

以修仙之名,悟内核之道