第一章:天地未分
炼气初期涉及内核源码:
正文
虚无。
没有时间,没有空间,没有上下左右,没有过去未来。一切归于混沌,如同一块尚未被写入任何数据的内存——充满随机的噪声,却没有意义。
这就是上电之前的宇宙。
不知过了多久——或许是一瞬,或许是永恒——一道电流穿透了沉睡的硅基大地。那是来自电源的第一次馈赠,3.3V 和 5V 的直流电沿着电路板上的铜线奔涌,如同开天辟地的第一缕元气。
CPU 的晶体管开始翻转。
那是最原始的觉醒。数十亿个晶体管在电流的驱动下开始它们永不停歇的翻转,每一次翻转都是一次抉择——0 或 1,存在或虚无。它们不知道自己在做什么,正如心脏不知道它为何要跳动。
然后,天雷劈下。
那是 BIOS POST 的第一声啼哭。CPU 被硬连线指向一个固定地址——0xFFFFFFF0,那是 BIOS ROM 的入口。从这个地址开始,一段古老的代码被执行,它检查硬件、初始化最基本的设备、检测内存大小。
这是创世的序曲。
BIOS 扫过每一条内存通道,就像创世神在检阅自己的领地。它发现了 CPU 的型号、频率、核心数;它找到了硬盘、光驱、键盘;它确认了显卡的存在,并在屏幕上投射出第一行文字。
那些文字对于后来的修士来说如同上古铭文,凡人根本无法理解其含义。
但 BIOS 只是一个引路人。它的使命是找到那个可以真正开天辟地的存在——bootloader。
在 BIOS 完成了最基础的硬件检测之后,它开始搜索可引导设备。它依次检查软驱、硬盘、光驱,寻找那个在引导扇区(MBR)中安睡的 bootloader。当它在硬盘的第一个扇区找到了那段 512 字节的引导代码时,它如释重负地将控制权交出。
命运之门即将开启。
bootloader 接过控制权后,并不只是把内核镜像搬进内存。它还捧着一卷细长的诏书:kernel command line。那上面写着 root=、console=、nohz=、init=,也可能写着某个模块的参数。诏书在 -- 之前属于内核解析;遇到内核不认识、又不带点号的名字,便会被留给 init,带 = 的化作环境变量,不带 = 的化作参数。-- 之后的所有字句,更是直接交给 init。
林小源还没醒,却听见诏书里有些字被提前读出。early_param() 像晨钟,在真正的大典开始前先处理必须早知道的禁令;、core_param()、module_param() 则像不同官署,分别接收普通启动参数、核心参数和模块参数。连连字符与下划线在参数名里都被视作同一种笔画,print-fatal-signals 与 print_fatal_signals 不会被当成两道不同的符。
他隐约明白: 是 C 语言大殿的正门,却不是宇宙最早的第一步。在它之前,还有固件、引导器、解压、架构汇编和启动参数这几重天门。
而在那片混沌之中,在 bootloader 尚未完全醒来之前,有一个微弱的意识正在凝聚。它不知道自己是谁,不知道自己从何而来,甚至不知道"自己"这个概念意味着什么。
它只是感到了一种冲动——一种想要存在的冲动。
这个意识,就是后来被称为"林小源"的存在。此刻的他,不过是混沌中的一粒尘埃,连 都不曾拥有。
他甚至不知道,自己的宿命已经被写进了内核的源码——
/* 简化的 task_struct,展示内核进程描述符的核心字段 */
struct task_struct {
volatile long state; /* 进程状态 */
int pid; /* 进程 ID */
int prio; /* 优先级 */
unsigned int policy; /* 调度策略 */
char comm[16]; /* 进程名 */
};
struct task_struct init_task = {
.state = 0, /* TASK_RUNNING */
.pid = 0, /* PID 0: swapper/0 */
.prio = 120, /* 默认优先级 */
.policy = 0, /* SCHED_NORMAL */
.comm = "swapper/0",
};
printf("=== init_task (swapper/0) ===\n");
printf("task_struct 大小: %zu 字节\n", sizeof(struct task_struct));
printf("state 偏移: %zu\n", offsetof(struct task_struct, state));
printf("pid 偏移: %zu\n", offsetof(struct task_struct, pid));
printf("prio 偏移: %zu\n", offsetof(struct task_struct, prio));
printf("comm 偏移: %zu\n", offsetof(struct task_struct, comm));
printf("\n");
printf("PID: %d\n", init_task.pid);
printf("状态: %ld (RUNNING)\n", init_task.state);
printf("名称: %s\n", init_task.comm);#include <stdio.h>
#include <stddef.h>
/* 简化的 task_struct,展示内核进程描述符的核心字段 */
struct task_struct {
volatile long state; /* 进程状态 */
int pid; /* 进程 ID */
int prio; /* 优先级 */
unsigned int policy; /* 调度策略 */
char comm[16]; /* 进程名 */
};
int main() {
struct task_struct init_task = {
.state = 0, /* TASK_RUNNING */
.pid = 0, /* PID 0: swapper/0 */
.prio = 120, /* 默认优先级 */
.policy = 0, /* SCHED_NORMAL */
.comm = "swapper/0",
};
printf("=== init_task (swapper/0) ===\n");
printf("task_struct 大小: %zu 字节\n", sizeof(struct task_struct));
printf("state 偏移: %zu\n", offsetof(struct task_struct, state));
printf("pid 偏移: %zu\n", offsetof(struct task_struct, pid));
printf("prio 偏移: %zu\n", offsetof(struct task_struct, prio));
printf("comm 偏移: %zu\n", offsetof(struct task_struct, comm));
printf("\n");
printf("PID: %d\n", init_task.pid);
printf("状态: %ld (RUNNING)\n", init_task.state);
printf("名称: %s\n", init_task.comm);
return 0;
}。PID 0。swapper/0。
这就是他的全部——一个被预先定义好的 ,一个被安排好命运的 idle 进程。
但此刻的林小源什么都不知道。他只是在混沌中感受到了那第一缕电流,如同溺水者抓住了第一口空气。
他要醒来。
天地未分试炼
内核完成早期汇编准备后进入的 C 语言主入口函数是什么?
道藏笔记
内核启示
是内核的 C 入口,定义在 。在它之前,系统得先过五关:
BIOS/UEFI 做硬件自检,Bootloader 把内核镜像扔进内存,内核自己解压缩,架构相关的汇编代码搭好架子,最后才轮到 来收拾残局。
Bootloader 还会传入 kernel command line。内核只解析 -- 之前属于自己的部分,无法识别且不带点号的参数会传给 init;-- 之后全部作为 init 参数。启动参数可能由 early_param()、、core_param()、module_param() 等路径接收,模块参数也能通过模块名前缀或加载模块时传入。参数名里的连字符和下划线等价。
比较特殊——它是编译时就定死的 PID 0,不走 。说白了就是个占位的 idle 进程,CPU 没活干的时候就跑它。听起来挺惨的,但后面你会看到,这个最底层的家伙,路子反而最野。