Skip to content

第一百九十九章:内核构建

大乘期

涉及内核源码:

林小源离开医院,来到一座巨大的工厂。工厂的烟囱冒着白烟,传送带上运送着无数源代码文件——.c 文件、.h 文件、.S 文件——每一个都在传送过程中被编译成 .o 文件,最终汇入工厂中央的巨型熔炉。

"这里是内核构建工厂。"一个戴安全帽的工头走过来,手里拿着一张蓝图,"我叫 Makefile。整个内核的构建流程,从源码到可执行镜像,都由我控制。"

林小源接过蓝图,上面画着四个阶段:配置、编译、链接、安装。每个阶段都有无数分支和依赖关系,错综复杂。

"第一步是配置。"Makefile 说,"make menuconfig 打开配置界面,你选择需要的功能。每个选项对应一个 CONFIG_xxx 宏,决定哪些代码编译进内核,哪些编译成模块,哪些不编译。"

"配置保存在哪里?"

".config 文件。"Makefile 指了指工厂入口处的一块告示板,上面贴满了配置选项。"CONFIG_MODULES=y 开启模块支持,CONFIG_SMP=y 开启多处理器,CONFIG_PREEMPT=n 关闭抢占,CONFIG_DEBUG_INFO=y 生成调试信息。每一个选项都影响最终的内核。"

c
/*
 * 内核构建过程:
 *
 * 1. 配置
 *    make menuconfig
 *    .config 文件
 *    CONFIG_xxx 选项
 *
 * 2. 编译
 *    make -j$(nproc)
 *    编译 .c 文件为 .o
 *
 * 3. 链接
 *    链接 .o 文件为 vmlinux
 *    链接模块为 .ko
 *
 * 4. 安装
 *    make modules_install
 *    make install
 *    更新 bootloader
 *
 * 配置选项:
 *   CONFIG_MODULES — 模块支持
 *   CONFIG_SMP — 多处理器
 *   CONFIG_PREEMPT — 抢占
 *   CONFIG_DEBUG_INFO — 调试信息
 *
 * 构建系统:
 *   Kconfig — 配置系统
 *   Kbuild — 构建系统
 *   Makefile — 规则
 *
 * 交叉编译:
 *   ARCH=arm64
 *   CROSS_COMPILE=aarch64-linux-gnu-
 */

/* 模拟配置选项 */
struct config_option {
    char name[64];
    int enabled;
    char desc[128];
};

struct config_option options[] = {
    {"CONFIG_MODULES", 1, "模块支持"},
    {"CONFIG_SMP", 1, "多处理器支持"},
    {"CONFIG_PREEMPT", 0, "抢占支持"},
    {"CONFIG_DEBUG_INFO", 1, "调试信息"},
    {"CONFIG_KASAN", 0, "内核地址消毒器"},
    {"CONFIG_LOCKDEP", 1, "死锁检测"},
};

printf("=== 内核构建 — 从源码到内核 ===\n\n");

printf("内核构建过程:\n\n");

printf("--- 1. 配置 ---\n");
printf("make menuconfig\n");
printf("生成 .config 文件\n\n");

int n = sizeof(options) / sizeof(options[0]);
printf("配置选项:\n");
for (int i = 0; i < n; i++) {
    printf("  %s=%d  # %s\n",
           options[i].name, options[i].enabled, options[i].desc);
}

printf("\n--- 2. 编译 ---\n");
printf("make -j$(nproc)\n");
printf("编译 .c 文件为 .o 文件\n\n");

printf("--- 3. 链接 ---\n");
printf("链接 .o 文件:\n");
printf("  vmlinux — 内核镜像\n");
printf("  *.ko — 模块\n\n");

printf("--- 4. 安装 ---\n");
printf("make modules_install\n");
printf("make install\n");
printf("更新 bootloader\n\n");

printf("--- 构建系统 ---\n");
printf("Kconfig:\n");
printf("  配置系统\n");
printf("  定义配置选项\n\n");
printf("Kbuild:\n");
printf("  构建系统\n");
printf("  编译规则\n\n");
printf("Makefile:\n");
printf("  顶层 Makefile\n");
printf("  子目录 Makefile\n\n");

printf("--- 交叉编译 ---\n");
printf("ARCH=arm64\n");
printf("CROSS_COMPILE=aarch64-linux-gnu-\n\n");
printf("make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu-\n\n");

printf("--- 构建目标 ---\n");
printf("make vmlinux:\n");
printf("  内核镜像\n\n");
printf("make modules:\n");
printf("  内核模块\n\n");
printf("make bzImage:\n");
printf("  压缩内核镜像\n\n");
printf("make dtbs:\n");
printf("  设备树\n\n");

printf("--- 增量构建 ---\n");
printf("只编译修改的文件\n");
printf("依赖跟踪\n");
printf("快速构建\n");

林小源走进工厂的控制室。控制室的墙上挂着一张巨大的网——Kconfig 的依赖图。每一个节点是一个配置选项,每一条边是一个依赖关系。

"Kconfig 是配置系统。"Makefile 说,"它定义了每个配置选项的类型、默认值、依赖关系。 依赖 CONFIG_HAVE_ARCH_KASAN 依赖 。你不能随意开启一个选项,必须满足它的依赖。"

林小源看到网中有些节点是灰色的——被禁用的选项;有些是绿色的——已启用的选项;有些是黄色的——可选的模块。

"make menuconfig 给你一个交互式界面。"Makefile 说,"你可以在里面浏览所有选项,查看帮助文本,启用或禁用功能。配置完成后保存为 .config 文件。下次构建时,Kbuild 读取 .config,决定编译哪些文件。"

"Kbuild 和 Kconfig 什么关系?"

"Kconfig 负责配置,Kbuild 负责构建。"Makefile 说,"Kconfig 生成 .configinclude/generated/autoconf.h,Kbuild 根据这些决定编译规则。两个系统配合,把配置变成代码。"

工厂的传送带突然加速了。林小源看到 .c 文件被送入不同的编译器——有的编译成 x86 的 .o 文件,有的编译成 ARM 的 .o 文件。

"交叉编译。"Makefile 说,"在 x86 机器上编译 ARM 内核。嵌入式开发常用。"

他指着传送带上的标签:"ARCH=arm64 指定目标架构,CROSS_COMPILE=aarch64-linux-gnu- 指定交叉编译工具链。编译器、链接器、汇编器都用工具链里的,而不是宿主机的。"

"构建速度呢?"林小源问,"内核有几万个源文件。"

"增量构建。"Makefile 得意地说,"只编译修改过的文件。Kbuild 跟踪每个 .o 文件的依赖——它依赖哪些 .c.h 文件。如果源文件没变,.o 文件就不重新编译。make -j$(nproc) 并行编译,充分利用多核 CPU。"

林小源看着传送带上的文件飞速流转,从源码到 .o 文件,从 .o 文件到 镜像,从 到可启动的内核。整个过程精密而高效,每一步都由 Makefile 精确控制。


道藏笔记

内核启示

内核构建把源代码变成可执行内核。

构建过程:

  • 配置 — make menuconfig
  • 编译 — make -j$(nproc)
  • 链接 — vmlinux, *.ko
  • 安装 — make install

构建系统:

  • Kconfig — 配置系统
  • Kbuild — 构建系统
  • Makefile — 规则

交叉编译:

  • ARCH=arm64
  • CROSS_COMPILE=aarch64-linux-gnu-

构建是转化——从源码到可执行内核。


破关试炼

内核构建之试

内核构建流程中,.c 源文件被编译后首先形成的目标文件后缀是什么?

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

以修仙之名,悟内核之道