Linux 内核构建体系分析

kernelversion

此目标用于输出内核版本号。

相关变量

变量 说明
VERSION 主版本号
PATCHLEVEL 补丁版本号
SUBLEVEL 子版本号
EXTRAVERSION 附加版本号
KERNELVERSION 内核版本号,详看注解 1

注解:

  1. KERNELVERSION

    内核版本号,目标 kernelversion 即输出此变量。其由 VERSIONPATCHLEVELSUBLEVELEXTRAVERSION 一起构成。其定义在根 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

注解:

  1. LOCALVERSION

    本地版本号,会附加在 CONFIG_LOCALVERSION 的后面,成为构成发行版本号的一部分。通过传参或定义为环境变量的方式赋值。

  2. CONFIG_LOCALVERSION_AUTO

    指定内核发行版本号是否附加 git 仓库信息。通过 Kconfig 的方式配置此变量。

    当此变量为 y 时,发行版本号会以 -gxxxxxx 格式附加当前 git 提交号(HEAD 指向的 commit)的前 12 位。如果含有未提交的修改,则会自动附加 -dirty

    当此变量未设置时,发行版本号会根据其他条件决定是否附加一个字符 +。具体判断流程如下:

    ​ 1) 如果变量 LOCALVERSION 已赋值(包括赋值为空)时,不附加字符 +,否则下一步。

    ​ 2) 如果当前工作空间不在 git 仓库下,则不附加字符 +,否则下一步。

    ​ 3) 如果当前工作空间的 git 提交号(HEAD 指向的 commit)匹配一个附注标签,则附加字符 +,否则下一步。

    ​ 4) 如果当前工作空间有未提交的修改,则附加字符 +,否则不附加字符 +

  3. KERNELRELEASE

    内核发行版本号,目标 kernelrelease 的输出和此变量一致。其由 KERNELVERSIONCONFIG_LOCALVERSIONLOCALVERSION 依次拼接,以及 CONFIG_LOCALVERSION_AUTO 控制的额外信息共同构成。

内部实现

目标 kernelrelease 的定义在根 Makefile,它会输出内核版本号 KERNELVERSION 和脚本 setlocalversion 附加的本地版本号。如下:

kernelrelease:
	@echo "$(KERNELVERSION)$$($(CONFIG_SHELL) $(srctree)/scripts/setlocalversion $(srctree))"

脚本 scripts/setlocalversion 附加的本地版本号由 KERNELVERSIONCONFIG_LOCALVERSIONLOCALVERSION 依次拼接,以及 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 进行配置。

注解:

  1. INSTALL_MOD_PATH

    指定模块安装的根目录。可通过传参或定义为环境变量覆盖默认值,默认值为空。其构成模块的完整安装路径,如下:

    MODLIB	= $(INSTALL_MOD_PATH)/lib/modules/$(KERNELRELEASE)
    

内部实现

目标 modules_install 依赖于目标 __modinst_pre,后者在模块安装目录生成如下文件:

  1. source - 指向源码路径的软链接。
  2. build - 指向构建路径(由 O= 在命令行指定的路径)的软链接。
  3. modules.order
  4. 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)下生成如下文件:

  1. vmlinux

    依据不同平台,可能需要对其进行压缩或者添加一些额外信息,生成 vmlinuzuImage 等。

  2. initramfs.img

    依据不同平台,可能由工具 mkinitrdmkinitramfs 生成。

相关变量

变量 说明
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 脚本,会执行真正的安装:

  1. 备份原有文件
  2. 安装内核镜像
  3. 安装符号表
  4. 添加 GRUB 启动项
  5. 生成 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

内部实现 - arm

posted @ 2021-08-23 16:59  fluidog  阅读(457)  评论(0编辑  收藏  举报