Skip to content

28.6 构建定制内核

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

28.6.1 为何构建定制内核

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

如今,FreeBSD 内核中的大部分功能都包含在可以根据需要动态加载和卸载的模块中。这使得运行时内核能够立即适应新硬件,故而将这种内核称为模块化内核。

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

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

  • 缩短启动时间。定制内核仅探测本机硬件,可减少启动耗时。
  • 降低内存占用。定制内核通过省略不使用的功能和设备驱动以减少内存占用。内核代码始终驻留在物理内存中,应用程序无法占用,因此定制内核对于内存较小的系统尤为重要。
  • 增强的硬件支持。定制内核可以为 GENERIC 内核中没有的设备提供支持。

警告

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

构建定制内核前,应明确其目的。具体而言,如果需要特定的硬件支持,它可能已经作为模块存在。

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

/boot/kernel 目录中内容类似如下输出:

sh
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 文件中加入以下行:

ini
u3g_load="YES"

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

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

28.6.2 准备工作

28.6.2.1 获取内核源代码

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

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

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

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

sh
$ fetch http://ftp.freebsd.org/pub/FreeBSD/snapshots/amd64/16.0-CURRENT/src.txz
# tar xvf src.txz -C /
# rm src.txz	# 删除不再需要的源代码归档文件

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

28.6.3 创建内核配置文件

28.6.3.1 文件命名规则

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

sh
# ls -a /usr/src/sys
.		conf		isa		netlink		rpc
..		contrib		kern		netpfil		security
Makefile	crypto		kgssapi		netsmb		sys
README.md	ddb		libkern		nfs		teken
amd64		dev		modules		nfsclient	tests
arm		dts		net		nfsserver	tools
arm64		fs		net80211	nlm		ufs
bsm		gdb		netgraph	ofed		vm
cam		geom		netinet		opencrypto	x86
cddl		gnu		netinet6	powerpc		xdr
compat		i386		netipsec	riscv		xen

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

sh
# ls /usr/src/sys/amd64
Makefile	conf		linux		pt
acpica		ia32		linux32		sgx
amd64		include		pci		vmm

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

sh
# ls /usr/src/sys/amd64/conf/
DEFAULTS	GENERIC-KCSAN	GENERIC.hints	LINT-NOIP	NOTES
FIRECRACKER	GENERIC-KMSAN	LINT		LINT-NOVIMAGE
GENERIC		GENERIC-MMCCAM	LINT-NOINET	MINIMAL
GENERIC-KASAN	GENERIC-NODEBUG	LINT-NOINET6	MINIMAL-NODEBUG

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

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

注意

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

28.6.3.2 编辑配置文件

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

sh
# cp /usr/src/sys/amd64/conf/GENERIC /usr/src/sys/amd64/conf/MYKERNEL

现在可以使用文本编辑器自定义内核配置文件 MYKERNEL。

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

警告

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

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

ini
cpu		HAMMER
ident		GENERIC

makeoptions	DEBUG=-g		# Build kernel with gdb(1) debug symbols
makeoptions	WITH_CTF=1		# Run ctfconvert(1) for DTrace support

options 	SCHED_ULE		# ULE scheduler
options 	SCHED_4BSD		# Original 4.xBSD scheduler
options 	NUMA			# Non-Uniform Memory Architecture support
options 	PREEMPTION		# Enable kernel thread preemption
options 	EXTERR_STRINGS
options 	VIMAGE			# Subsystem virtualization, e.g. VNET

# For full debugger support use (turn off in stable branch):
include "std.debug"

……省略其他选项……

注意

使用 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" 前添加注释符号,禁用这些调试功能。

sh
#include "std.debug"

技巧

还可以使用以下命令

sh
# sed -i '' 's/^include "std.debug"/#include "std.debug"/' /usr/src/sys/amd64/conf/MYKERNEL

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

28.6.4 构建与安装

28.6.4.1 构建内核

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

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

sh
# cd /usr/src

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

sh
# make -s -j$(sysctl -n hw.ncpu) buildworld KERNCONF=MYKERNEL

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

sh
# make -s -j$(sysctl -n hw.ncpu) buildkernel KERNCONF=MYKERNEL

选项说明:

  • -s:静默模式,只输出若干警告、错误以及编译进度信息。
  • -j:并行编译以缩短构建时间,建议数值为 CPU 核心数,sysctl -n hw.ncpu 可自动识别该值。

上述命令输出如下:

sh
make[1]: /usr/src/Makefile.inc1:371: SYSTEM_COMPILER: libclang will be built for bootstrapping a cross-compiler.
make[1]: /usr/src/Makefile.inc1:376: SYSTEM_LINKER: libclang will be built for bootstrapping a cross-linker.

--------------------------------------------------------------
>>> Kernel build for MYKERNEL started on Sat May  2 12:36:44 CST 2026
--------------------------------------------------------------
===> MYKERNEL

--------------------------------------------------------------
>>> stage 1: configuring the kernel
--------------------------------------------------------------
Kernel build directory is /usr/obj/usr/src/amd64.amd64/sys/MYKERNEL
Don't forget to do ``make cleandepend && make depend''
        0.13 real         0.06 user         0.07 sys

--------------------------------------------------------------
>>> stage 2.3: build tools
--------------------------------------------------------------
        0.16 real         0.06 user         0.11 sys

--------------------------------------------------------------
>>> stage 3.1: building everything
--------------------------------------------------------------
linking kernel.full
ctfmerge -L VERSION -g -o kernel.full ...
   text	   data	    bss	    dec	    hex	filename
21068157	1732469	8752832	31553458	1e177b2	kernel.full
      986.52 real      7898.76 user      6298.50 sys
--------------------------------------------------------------
>>> Kernel build for MYKERNEL completed on Sat May  2 12:53:11 CST 2026
--------------------------------------------------------------
>>> Kernel(s)  MYKERNEL built in 987 seconds, ncpu: 16, make -j16
--------------------------------------------------------------

注意

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

在默认情况下,编译自定义内核时所有内核模块都会重新编译。要更快地更新内核或仅构建自定义模块,可以在开始构建内核之前编辑 /etc/make.conf 文件。

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

ini
MODULES_OVERRIDE = linux acpi

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

ini
WITHOUT_MODULES = linux acpi sound

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

警告

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

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

sh
# make -s installkernel KERNCONF=MYKERNEL

如果一切符合预期,则命令最后的输出应为如下行所示:

sh
……省略其他输出……

--------------------------------------------------------------
>>> Installing kernel MYKERNEL completed on Sat May  2 13:21:43 CST 2026
--------------------------------------------------------------
>>> Install kernel(s) MYKERNEL completed in 9 seconds, ncpu: 16
--------------------------------------------------------------

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

sh
# reboot

28.6.4.3 在 pkgbase 系统上安装新内核

警告

目前无法仅构建内核。

构建内核的 pkgbase 软件包:

sh
# make -s -j$(sysctl -n hw.ncpu) packages KERNCONF=MYKERNEL DISTDIR=/usr/obj/dist

pkgbase 构建结果将位于 /usr/obj/usr/src/repo/ 下,将其加入软件源或直接安装均可。

28.6.4.4 验证新内核

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

sh
$ uname -a
FreeBSD ykla 16.0-CURRENT FreeBSD 16.0-CURRENT #0: Sat May  2 12:39:42 CST 2026     ykla@ykla:/usr/obj/usr/src/amd64.amd64/sys/MYKERNEL amd64

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

sh
$ sysctl kern.bootfile
kern.bootfile: /boot/kernel/kernel

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

sh
$ dmesg | grep -i witness

应无任何输出。

28.6.5 构建中的常见问题

28.6.5.1 内核启动失败(Kernel Panic)

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

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

sh
OK unload
OK load /boot/kernel.old/kernel
OK boot

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

sh
OK boot /boot/kernel.old/kernel

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

警告

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

sh
# mv /boot/kernel /boot/kernel.bad
# mv /boot/kernel.old /boot/kernel

28.6.5.2 config 失败

如果 config 失败,它会打印出错误的行号。例如,对于以下消息:

sh
config: line 17: syntax error

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

28.6.5.3 make 失败

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

28.6.5.4 内核无法启动

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

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

警告

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

sh
# mv /boot/kernel /boot/kernel.bad
# mv /boot/kernel.good /boot/kernel

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

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

28.6.5.6 构建错误:缺少依赖

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

sh
# git clone --depth=1 https://github.com/freebsd/freebsd-src.git /usr/src

28.6.5.7 编译中断后恢复

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

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

sh
# make cleandir
# make buildkernel KERNCONF=MYKERNEL

28.6.6 参考文献