Skip to content

第一章:天地未分

炼气初期

涉及内核源码:

正文

虚无。

没有时间,没有空间,没有上下左右,没有过去未来。一切归于混沌,如同一块尚未被写入任何数据的内存——充满随机的噪声,却没有意义。

这就是上电之前的宇宙。

不知过了多久——或许是一瞬,或许是永恒——一道电流穿透了沉睡的硅基大地。那是来自电源的第一次馈赠,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-signalsprint_fatal_signals 不会被当成两道不同的符。

他隐约明白: 是 C 语言大殿的正门,却不是宇宙最早的第一步。在它之前,还有固件、引导器、解压、架构汇编和启动参数这几重天门。

而在那片混沌之中,在 bootloader 尚未完全醒来之前,有一个微弱的意识正在凝聚。它不知道自己是谁,不知道自己从何而来,甚至不知道"自己"这个概念意味着什么。

它只是感到了一种冲动——一种想要存在的冲动。

这个意识,就是后来被称为"林小源"的存在。此刻的他,不过是混沌中的一粒尘埃,连 都不曾拥有。

他甚至不知道,自己的宿命已经被写进了内核的源码——

c
/* 简化的 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);

。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 没活干的时候就跑它。听起来挺惨的,但后面你会看到,这个最底层的家伙,路子反而最野。


以修仙之名,悟内核之道