Linux 内核构建体系分析
kernelversion
此目标用于输出内核版本号。
相关变量
变量 | 说明 |
---|---|
VERSION | 主版本号 |
PATCHLEVEL | 补丁版本号 |
SUBLEVEL | 子版本号 |
EXTRAVERSION | 附加版本号 |
KERNELVERSION | 内核版本号,详看注解 1 |
注解:
KERNELVERSION
内核版本号,目标
kernelversion
即输出此变量。其由VERSION
、PATCHLEVEL
、SUBLEVEL
、EXTRAVERSION
一起构成。其定义在根Makefile
。如下:VERSION = 5 PATCHLEVEL = 12 SUBLEVEL = 0 EXTRAVERSION = KERNELVERSION = $(VERSION)$(if $(PATCHLEVEL),.$(PATCHLEVEL)$(if $(SUBLEVEL),.$(SUBLEVEL)))$(EXTRAVERSION) kernelversion: @echo $(KERNELVERSION)
kernelrelease
此目标用于输出内核发行版本号,其由内核版本号和本地版本号构成。
相关变量
变量 | 说明 |
---|---|
CONFIG_LOCALVERSION | 本地版本号,通过 Kconfig 进行赋值。 |
LOCALVERSION | 本地版本号,详看注解 1 |
CONFIG_LOCALVERSION_AUTO | 指定版本号是否附加 git 仓库信息,详看注解 2 |
KERNELRELEASE | 内核发行版本号,详看注解 3 |
注解:
LOCALVERSION
本地版本号,会附加在
CONFIG_LOCALVERSION
的后面,成为构成发行版本号的一部分。通过传参或定义为环境变量的方式赋值。CONFIG_LOCALVERSION_AUTO
指定内核发行版本号是否附加 git 仓库信息。通过 Kconfig 的方式配置此变量。
当此变量为
y
时,发行版本号会以-gxxxxxx
格式附加当前 git 提交号(HEAD
指向的commit
)的前12
位。如果含有未提交的修改,则会自动附加-dirty
。当此变量未设置时,发行版本号会根据其他条件决定是否附加一个字符
+
。具体判断流程如下: 1) 如果变量
LOCALVERSION
已赋值(包括赋值为空)时,不附加字符+
,否则下一步。 2) 如果当前工作空间不在 git 仓库下,则不附加字符
+
,否则下一步。 3) 如果当前工作空间的 git 提交号(
HEAD
指向的commit
)匹配一个附注标签,则附加字符+
,否则下一步。 4) 如果当前工作空间有未提交的修改,则附加字符
+
,否则不附加字符+
。KERNELRELEASE
内核发行版本号,目标
kernelrelease
的输出和此变量一致。其由KERNELVERSION
、CONFIG_LOCALVERSION
、LOCALVERSION
依次拼接,以及CONFIG_LOCALVERSION_AUTO
控制的额外信息共同构成。
内部实现
目标 kernelrelease
的定义在根 Makefile
,它会输出内核版本号 KERNELVERSION
和脚本 setlocalversion
附加的本地版本号。如下:
kernelrelease:
@echo "$(KERNELVERSION)$$($(CONFIG_SHELL) $(srctree)/scripts/setlocalversion $(srctree))"
脚本 scripts/setlocalversion
附加的本地版本号由 KERNELVERSION
、CONFIG_LOCALVERSION
、LOCALVERSION
依次拼接,以及 CONFIG_LOCALVERSION_AUTO
控制的额外信息共同构成。其核心代码如下:
res="${res}${CONFIG_LOCALVERSION}${LOCALVERSION}"
# scm version string if not at a tagged commit
if test "$CONFIG_LOCALVERSION_AUTO" = "y"; then
# full scm version string
res="$res$(scm_version)"
else
# append a plus sign if the repository is not in a clean
# annotated or signed tagged state (as git describe only
# looks at signed or annotated tags - git tag -a/-s) and
# LOCALVERSION= is not specified
if test "${LOCALVERSION+set}" != "set"; then
scm=$(scm_version --short)
res="$res${scm:++}"
fi
fi
echo "$res"
变量 KERNELRELEASE
的值、文件 include/config/kernel.release
的内容和目标 kernelrelease
的输出相同,核心代码在根 Makefile
如下:
KERNELRELEASE = $(shell cat include/config/kernel.release 2> /dev/null)
filechk_kernel.release = \
echo "$(KERNELVERSION)$$($(CONFIG_SHELL) $(srctree)/scripts/setlocalversion $(srctree))"
# Store (new) KERNELRELEASE string in include/config/kernel.release
include/config/kernel.release: FORCE
$(call filechk,kernel.release)
modules_install
此目标的作用是安装 linux 模块。主要是将所有生成的模块安装到指定目录下。
相关变量
变量 | 说明 |
---|---|
INSTALL_MOD_PATH | 模块安装的根目录。详看注解 1 |
INSTALL_MOD_STRIP | 指定是否剔除模块的调试信息。通过传参或定义环境变量的方式定义为 1 ,使其生效。 |
CONFIG_MODULE_SIG_ALL | 指定是否对所有模块签名。通过 Kconfig 进行配置。 |
注解:
INSTALL_MOD_PATH
指定模块安装的根目录。可通过传参或定义为环境变量覆盖默认值,默认值为空。其构成模块的完整安装路径,如下:
MODLIB = $(INSTALL_MOD_PATH)/lib/modules/$(KERNELRELEASE)
内部实现
目标 modules_install
依赖于目标 __modinst_pre
,后者在模块安装目录生成如下文件:
- source - 指向源码路径的软链接。
- build - 指向构建路径(由
O=
在命令行指定的路径)的软链接。 - modules.order
- modules.builtin
在根 Makefile
下的目标 modules_insatll
会直接执行 Makefile.modinst
。如下:
modules_install:
$(Q)$(MAKE) -f $(srctree)/scripts/Makefile.modinst
$(call cmd,depmod)
此文件会安装文件 modules.order
里所有的模块,并根据配置进行签名或剔除调试信息。核心代码如下:
modules := $(sort $(shell cat $(MODORDER)))
__modinst: $(modules)
@:
...
quiet_cmd_install = INSTALL $@
cmd_install = mkdir -p $(dir $@); cp $< $@
...
$(dst)/%.ko: $(extmod_prefix)%.ko FORCE
$(call cmd,install)
$(call cmd,strip)
$(call cmd,sign)
install
此目标的作用是安装 linux 内核。其具体行为在不同硬件架构的不同引导程序(GRUB、Uboot等)下略有差异,因此其定义在具体的架构(arch)相关的目录下。但是其主要的基本作用是在安装目录(默认为 /boot
)下生成如下文件:
-
vmlinux
依据不同平台,可能需要对其进行压缩或者添加一些额外信息,生成
vmlinuz
、uImage
等。 -
initramfs.img
依据不同平台,可能由工具
mkinitrd
或mkinitramfs
生成。
相关变量
变量 | 说明 |
---|---|
INSTALL_PATH | 内核安装路径。可通过传参或定义为环境变量覆盖默认值,默认值为 /boot 。 |
内部实现 - x86
文件 arch/x86/Makefile
会被直接包含到根 Makefile
,它会继续以 install
为目标,来执行 scripts/Makefile.build
,而又会间接包含 arch/x86/boot/Makefile
。如下:
boot := arch/x86/boot
...
PHONY += install bzlilo
install bzlilo:
$(Q)$(MAKE) $(build)=$(boot) $@
此文件下的目标 install
会执行同目录下的 install.sh
。参数解释如下:
- $1 - 内核发行版本号
- $2 - 内核镜像文件
- $3 - 内核符号表文件
- $4 - 安装目录(省略时默认为
/boot
)
代码如下:
install:
sh $(srctree)/$(src)/install.sh $(KERNELRELEASE) $(obj)/bzImage \
System.map "$(INSTALL_PATH)"
此脚本文件会执行引导程序提供的专门用于安装内核的程序,比如 GRUB
提供的安装程序 /sbin/installkernel
。如下:
if [ -x /sbin/${INSTALLKERNEL} ]; then exec /sbin/${INSTALLKERNEL} "$@"; fi
此程序为 shell
脚本,会执行真正的安装:
- 备份原有文件
- 安装内核镜像
- 安装符号表
- 添加 GRUB 启动项
- 生成
initramfs.img
其生成 GRUB 启动项 和 initramfs.img
由调用脚本 new-kernel-pkg 生成。其核心代码如下:
# /usr/libexec/installkernel/installkernel
cat $BOOTIMAGE > $INSTALL_PATH/$KERNEL_NAME-$KERNEL_VERSION
cp $MAPFILE $INSTALL_PATH/System.map-$KERNEL_VERSION
...
kernel-install add $KERNEL_VERSION $INSTALL_PATH/$KERNEL_NAME-$KERNEL_VERSION
...
new-kernel-pkg --mkinitrd --dracut --host-only --depmod --install --kernel-name $KERNEL_NAME $KERNEL_VERSION