33.6 构建定制内核

内核是 FreeBSD 操作系统的核心,负责管理内存、执行安全控制、网络通信、磁盘访问等任务。虽然 FreeBSD 的大部分内容是动态可配置的,但一些用户可能希望配置并编译自定义内核。

为何构建定制内核

传统上,FreeBSD 使用宏内核。宏内核是一种大型程序,支持固定的设备列表,如果需改变内核行为,必须重新编译并重启至新内核。

目前,FreeBSD 内核中的大部分功能都包含在可以根据需要动态加载和卸载的模块中。这使得运行时内核无需重启即可适应新硬件,因此将这种内核称为模块化内核。

有时,仍需静态配置内核。某些功能与内核紧密绑定,无法动态加载。此外,一些安全环境会阻止加载和卸载内核模块,要求只能将所需功能静态编译进内核。

构建定制内核的过程虽然耗时,但有其实际价值。与默认的 GENERIC 内核不同(GENERIC 必须支持大量硬件),定制内核可以精简为仅支持本机硬件。以下为定制内核的主要优点:

  • 缩短启动时间。定制内核仅探测本机硬件,可减少启动耗时。

  • 降低内存占用。定制内核通过省略未使用的功能和设备驱动来减少内存占用。内核代码始终驻留在物理内存中,应用程序无法使用这部分内存,因此定制内核对于内存较小的系统尤为重要。

  • 增强硬件支持。定制内核可以为 GENERIC 内核中没有的设备提供支持。

警告

非默认配置的测试不如 GENERIC 配置充分。虽然定制内核可以带来特定收益,但也增加了遇到构建或运行时问题的风险。仅建议有明确理由更改、且愿意在必要时调试的专业用户使用定制内核配置。

构建定制内核前,应明确其目的。具体而言,若需特定硬件支持,该支持可能已以模块形式存在,可先尝试使用 kldload(8) 加载相关模块。

内核模块位于 /boot/kernel 中,可使用 kldload(8) 将其动态加载至运行中的内核。

/boot/kernel 目录内容示例如下:

ykla@ykla:~ $ ls /boot/kernel
aac.ko				iwn105fw.ko
ahci.ko				lindebugfs.ko

……省略部分输出……

u3g.ko				xdr.ko
iwi_ibss.ko			xhci.ko
iwi_monitor.ko			xz.ko
zfs.ko				zlib.ko

大多数内核驱动程序都有可加载的模块。

如果需在启动时以模块方式加载驱动程序 u3g,可在 /boot/loader.conf 文件中加入以下行:

系统将在启动时动态加载此模块。

然而,在某些情况下,/boot/kernel 中没有关联的模块,这通常涉及特定的子系统。

准备工作

获取内核源代码

FreeBSD 操作系统作为整体开发维护,因此内核和用户空间均位于 freebsd-src 项目中。

如果要创建自定义内核配置文件并构建自定义内核,必须先安装完整的 FreeBSD 源代码树。

如果 /usr/src/ 目录不存在或为空,则说明源代码未安装。可通过 Git 获取源代码,或在镜像站下载对应的 src 归档文件。

示例:通过 16.0-CURRENT 归档文件获得 FreeBSD 源代码,并解压至目录 /usr/src/

内核源代码路径为 /usr/src/sys

创建内核配置文件

文件命名规则

安装完成后,请查看 /usr/src/sys 目录的内容:

该目录包含多个子目录,其中包括表示受支持架构的目录(如 amd64、i386、arm、riscv、powerpc 等)。

每个特定架构的目录只处理该架构的相关内容,其他代码则是所有平台通用的机器无关代码。

每个受支持架构都有 conf 子目录,其中包含该架构的 GENERIC 内核配置文件。

因此,不应直接编辑 GENERIC 文件,而应复制该文件并编辑其副本。建议配置文件名称 全部使用大写字母。管理多台硬件不同的 FreeBSD 主机时,建议以主机名命名配置文件。

注意

构建内核时,系统会在 /usr/obj/usr/src/amd64.amd64/sys/ 下创建与配置同名的目录存放构建产物。

编辑配置文件

以下示例针对 amd64 架构的 16.0-CURRENT 系统,为其 GENERIC 配置文件创建副本 MYKERNEL:

随后可使用文本编辑器自定义内核配置文件 MYKERNEL。

内核配置文件的格式简洁。

警告

删除设备或选项的支持可能导致内核损坏。例如,如果从内核配置文件中删除 NVMe 驱动程序,使用 nvme 磁盘驱动程序的系统可能无法启动。如有疑问,建议将支持保留在内核中。

配置文件中可使用 include 指令,将另一个配置文件包含到当前配置文件中,便于在现有配置的基础上维护少量变更。如果只需要少量额外选项或驱动程序,可通过这种方法维护与 GENERIC 的差异。

注意

使用 include 指令时,其行为类似 C 语言的 #include,会将目标文件的全部内容插入当前位置。以 GENERIC 为例,其首行为 include "std.amd64",引入了标准的 amd64 基础配置。使用这种方法,本地配置文件表示与 GENERIC 内核的差异。在执行升级时,除非使用 nooptionsnodevice 明确排除,否则 GENERIC 中新增的特性也会添加到本地内核。

常用配置指令

指令
用途

machine

指定目标体系结构

ident

指定内核名称,便于识别

options

启用手动配置的内核选项

nooptions

禁用不需要的内核选项

device

编译并包含设备驱动

nodevice

移除特定设备驱动

makeoptions

向 Makefile 传递构建参数

上述示例出自 16.0-CURRENT 的 GENERIC 内核配置文件,-CURRENT 与 -STABLE、-RELEASE 的重要差异在于 -CURRENT 引入了大量的调试功能。该功能即由 include "std.debug" 引入,部分用户因此可能注意到 -CURRENT 系统性能显著下降。

接下来移除这些调试功能,以自定义一个标准配置的内核。编辑 /usr/src/sys/amd64/conf/MYKERNEL 文件,在 include "std.debug" 前添加注释符号,禁用这些调试功能。

技巧

还可以使用以下命令

实现上述替换。这在脚本文件中较为实用。

构建与安装

构建内核

保存自定义配置文件的编辑内容后,即可按以下步骤编译内核源代码。

编译内核需在 /usr/src 目录下执行,这取决于环境变量 SYSDIR 的设置(可覆盖 /usr/src/sys)。切换到此目录:

对于 PkgBase 系统:必须先构建完整的用户空间。必须将内核和用户空间构建为 repo 软件源后方可安装。使用以下命令构建用户空间:

通过指定自定义内核配置文件的名称来编译新内核:

选项说明:

  • -s:静默模式,只输出若干警告、错误以及编译进度信息。

  • -j:并行编译以缩短构建时间,建议数值为 CPU 核心数,sysctl -n hw.ncpu 可自动识别该值。

上述命令输出如下:

注意

首次构建可能需要较长时间(取决于硬件性能,通常为 10 到 60 分钟)。构建过程会在 /usr/obj/usr/src/amd64.amd64/sys/MYKERNEL/ 目录下生成产物。

在默认情况下,编译自定义内核时所有内核模块都会重新编译。如果需加快内核更新速度或仅构建自定义模块,可在构建内核之前编辑 /etc/make.conf 文件。

例如,以下变量指定要构建的模块列表,而非默认构建所有模块:

或者,此变量列出要从构建过程中排除的模块:

在传统方式安装的系统上安装新内核

警告

安装新内核将替换 /boot/kernel/ 中的当前内核,旧内核移动至 /boot/kernel.old/。如果新内核配置有误导致无法启动,需在引导加载器中手动指定 kernel.old 启动。远程管理场景下,请确保具备带外管理或物理访问能力后再执行此操作。

安装与指定配置文件关联的新内核。新内核和内核模块安装至 /boot/kernel 目录,旧内核移动至 /boot/kernel.old/kernel,旧内核模块保留在 /boot/kernel.old 目录:

如果构建成功,则命令最后的输出应为如下行所示:

安装完成后需要重新启动以加载新内核:

在 PkgBase 系统上安装新内核

警告

目前无法仅构建内核。

构建内核的 PkgBase 软件包:

PkgBase 构建结果将位于 /usr/obj/usr/src/repo/FreeBSD:16:amd64/(或对应的 ABI 目录)下,将其加入软件源或直接安装均可。

验证新内核

重启后通过以下命令验证是否成功加载了定制内核:

uname -a 输出中应显示自定义内核名称 MYKERNEL

验证 CURRENT 的调试选项是否未启用,如 WITNESS:

应无任何输出。

构建中的常见问题

内核启动失败(Kernel Panic)

如果新内核无法启动,可在加载器提示符下选择引导旧内核。系统默认将上一次安装的内核保留在 /boot/kernel.old/ 中。

在 FreeBSD 引导菜单(loader)中按 Esc 键进入加载器提示符(OK prompt),执行:

也可直接指定引导旧内核:

进入系统后,将旧内核恢复为默认:

警告

此操作将替换当前内核。如果 kernel.old 也有问题,系统将无法启动且无其他可回退的内核。建议在执行前先确认 kernel.old 内核可正常启动。

config 失败

如果 config 失败,将显示错误所在行号。例如,对于以下消息:

请确保第 17 行写入的内容正确,可与 GENERIC 或 NOTES 文件进行比较。

make 失败

如果 make 失败,通常是由于内核配置文件中的错误,但该错误还不足以触发 config 的检测。请检查配置文件。

内核无法启动

如果新内核无法启动或无法识别设备,可在 FreeBSD 启动加载器中选择要启动的内核。在系统启动菜单出现时,选择“Escape to a loader prompt”选项。在提示符下,输入 boot kernel.old,或输入任何已知能正常启动的内核名称。

使用可用的内核启动后,请检查配置文件并重新构建。/var/log/messages 记录了每次成功启动的内核信息,dmesg 将显示当前启动的内核信息。

警告

替换内核操作不可逆。如果 kernel.old 也有问题,系统将无法启动且无其他可回退的内核。kernel.old 是系统在安装新内核时自动保留的旧内核备份。建议在执行前先确认回退内核可正常启动。

内核正常工作,但用户空间命令无法使用

如果内核版本与系统工具的构建版本不同,例如,对 -RELEASE 系统安装了从 -CURRENT 源代码构建的内核,许多系统状态命令(如 ps 和 vmstat)将无法正常运行。为此,必须重新编译并安装与内核版本相同的用户空间。不建议使用与操作系统其他部分版本不同的内核。

构建错误:缺少依赖

如果只安装了内核源代码而未安装完整源代码树,构建内核模块可能失败。可通过以下命令安装完整源代码:

编译中断后恢复

构建过程中断后,重新执行相同的 make buildkernel 命令即可。构建系统会自动检测已完成的部分并从中断处继续编译。

如果需要完全重新构建(例如修改了头文件),可先清理构建产物:

参考文献

最后更新于