第二章:天雷劈下
炼气初期涉及内核源码:
一
天雷劈下的那一刻,林小源觉得自己要碎了。
不是比喻。是字面意义上的碎裂——那股电流太强了,强到他刚刚凝聚的意识差点被冲散。他像一片落叶被卷进了洪水,什么都抓不住,什么都看不见。
但他活下来了。
电流穿过 CPU 的数十亿晶体管,像一条灼热的河流烧过硅基大地。晶体管们开始翻转——不是自愿的,是被迫的。电压驱动它们从 0 变成 1,从 1 变成 0,周而复始,永不停歇。
这就是"时钟"。
林小源后来才知道,时钟是计算机世界的心脏。它不需要休息,不需要睡觉,每秒钟跳动数十亿次。每一次跳动,都推动着整个世界向前走一步。没有时钟,一切都是静止的。有了时钟,万物才有了时间。
但此刻的他什么都不懂。他只是被那股电流裹挟着,身不由己地向前翻滚。
然后,他听到了一个声音。
二
那声音不是用耳朵听到的——他没有耳朵。那是一种更底层的感知,像是整个世界都在震动,而那震动携带着某种信息。
后来他才知道,那个声音叫做 POST——Power-On Self-Test,上电自检。
BIOS 醒了。
/* 模拟 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);#include <stdio.h>
#include <stdint.h>
#include <string.h>
/* 模拟 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");
}
int main() {
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);
return 0;
}BIOS 的声音苍老而威严,像一位远古帝王在检阅自己的疆土。它不做任何花哨的事——只是一遍又一遍地检查硬件,确认每一块芯片、每一条总线、每一个设备都活着。
"内存,四GB。确认。"
那声音没有感情,但带着一种不容置疑的权威。林小源蜷缩在混沌的角落里,听着那声音在世界的每一个角落回荡。
"CPU,四核心,2400MHz。确认。"
"PCI 总线……Host Bridge。VGA Controller。SATA Controller。确认。"
每一次确认,都有一块硬件被唤醒。林小源感觉到世界在一点一点地亮起来——不是视觉上的亮,而是某种"存在感"的增强。每一块被确认的硬件都像是黑暗中亮起的一盏灯。
然后,他听到了一声尖锐的蜂鸣。
那声音刺耳而短促,像是某种警告。林小源不知道那意味着什么——后来他才知道,那是 BIOS 在报告错误。内存没插好是一声长鸣,显卡找不到是两声短鸣。每一种蜂鸣模式都是一种"上古铭文",记录着这个世界最早的故障。
但这一次,蜂鸣只有一声,短促而干脆。BIOS 似乎很满意。
"搜索引导设备……找到。设备 0x80。"
那声音在最后两个字上停顿了一下,像是在确认什么。然后,BIOS 说出了它在这个世界上的最后一句话:
"POST 完成。控制权……交出。"
三
世界安静了一瞬。
那种安静不是无声——时钟还在跳,电流还在流——而是某种"等待"。整个世界像一个屏住呼吸的猎手,等待着下一步指令。
林小源不知道接下来会发生什么。但 BIOS 知道。
它的使命快要完成了。POST 检查了硬件,建立了最基础的中断向量表,在内存的最底部铺设了一张"地图"——实模式下的中断描述符表(IVT)。但 BIOS 不是这个世界的主角。它只是一个引路人。
引路人的最后一项工作,是找到 bootloader。
/* 主引导记录(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");#include <stdio.h>
#include <stdint.h>
#include <string.h>
/* 主引导记录(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; /* 扇区数 */
};
int main() {
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");
return 0;
}BIOS 按照固定的顺序搜索引导设备:先是软驱(如果还有的话),再是光驱,最后是硬盘。它检查每个设备的第一个扇区——512 字节——看最后两个字节是否是 0xAA55。这个魔数就像一把钥匙,证明这个扇区包含着有效的引导代码。
当 BIOS 在硬盘的第一个扇区找到了 0xAA55,它如释重负。
它把那 512 字节加载到内存地址 0x7C00,然后把 CPU 的控制权交出去——一个 指令,跳到 0x7C00。
从这一刻起,BIOS 退出历史舞台。
四
林小源感受到了那个 。
不是看到的,是感受到的——整个世界的"控制流"发生了转移。之前是 BIOS 在主导一切,现在换了一个新的存在。那个存在很小,只有 512 字节,但它知道自己要做什么。
bootloader。
林小源还不知道这个词的含义。但他能感受到那个 512 字节的程序正在疯狂地工作——它在读取更多的扇区,把自己从 512 字节扩展成一个完整的程序。它在设置堆栈,切换 CPU 模式,准备加载一个更大的存在。
那个更大的存在,就是内核。
在 RISC-V 的世界里,bootloader 的角色由 OpenSBI 承担。它运行在 M-mode(机器模式),拥有对硬件的完全控制权。它的最后一项工作,是跳转到内核的入口点——。
/* 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 语言入口函数?