kernel 启动时通常会看到下面第二行信息的内容,它们代表了当前 kernel 的版本、编译工具版本、编译环境等信息。
Booting Linux on physical CPU 0x0 Linux version 5.4.124+ (funny@funny) (gcc version 6.5.0 (Linaro GCC 6.5-2018.12)) #30 SMP Sat Sep 11 11:10:28 CST 2021 ......
要知道,系统启动过程中的任何一条打印信息,都是经过了无数次讨论和验证才呈现在大家的面前。看似无关紧要的一条信息,但背后却隐藏着非常有趣的故事。
为什么要打印version信息当系统启动之后有很多种方式能够确定内核版本号信息,在嵌入式或安卓 kernel 系统下,查看版本信息:
-
uname
[root@cpu ]# uname -a Linux cpu 5.4.124+ #30 SMP Sat Sep 11 11:10:28 CST 2021 armv7l GNU/Linux [root@cpu ]#
-
proc/version
[root@cpu ]# cat /proc/version Linux version 5.4.124+ (funny@funny) (gcc version 6.5.0 (Linaro GCC 6.5-2018.12)) #30 SMP Sat Sep 11 11:10:28 CST 2021 [root@cpu ]#
在发行版 linux 系统环境下,还可以用下面的命令查看版本信息:
-
hostnamectl
funny@funny:~$ hostnamectl Static hostname: funny Icon name: computer-vm Chassis: vm ... Virtualization: vmware Operating System: Ubuntu 16.04.7 LTS Kernel: Linux 4.15.0-142-generic Architecture: x86-64 funny@funny:~$
-
lsb_release
No LSB modules are available. Distributor ID: Ubuntu Description: Ubuntu 16.04.7 LTS Release: 16.04 Codename: xenial
以上方法都是系统启动正常、加载完文件系统之后使用的。 那么,系统启动过程中是否有必要打印内核版本信息呢?答案是完全有必要。
例如下面列出的几种应用场景:
-
SoC 芯片的 kernel 适配
-
可装载驱动程序调试
-
多分支内核版本加载
-
内核伪装
kernel version这条打印信息来源于start_kernl()中的linux_banner字符串。
asmlinkage __visible void __init start_kernel(void) { ... boot_cpu_init(); page_address_init(); pr_notice("%s", linux_banner); ...
这里的banner好比是ubuntu系统里的ssh登录横幅一样,呈现了系统的一些基本信息。
Welcome to Ubuntu 16.04.7 LTS (GNU/Linux 4.15.0-142-generic x86_64) * Documentation: https://help.ubuntu.com * Management: https://landscape.canonical.com * Support: https://ubuntu.com/advantage * Super-optimized for small spaces - read how we shrank the memory footprint of MicroK8s to make it the smallest full K8s around. https://ubuntu.com/blog/microk8s-memory-optimisation ...
banner字符串的定义位于init/version.c中,注意,它是一个只读字符串,不要去修改它。
const char linux_banner[] = "Linux version " UTS_RELEASE " (" LINUX_COMPILE_BY "@" LINUX_COMPILE_HOST ") (" LINUX_COMPILER ") " UTS_VERSION "\n";
由以下几部分组成:
-
UTS_RELEASE 对应"5.4.124+"
-
LINUX_COMPILE_BY 对应"funny",我的编译机funny
-
LINUX_COMPILE_HOST 对应"funny",我的编译机Host是funny
-
LINUX_COMPILER 对应"gcc version 6.5.0 (Linaro GCC 6.5-2018.12"
-
UTS_VERSION 对应"#30 SMP Sat Sep 11 11:10:28 CST 2021" UTS:Unix Time Stamp,从这个名字可以看出linux中的UNIX印记。
接下来对这些字符串逐条进行解析
上面这些宏的第一级定义位于./scripts/mkcompile_h文件中。
{ echo /\* This file is auto generated, version $VERSION \*/ if [ -n "$CONFIG_FLAGS" ] ; then echo "/* $CONFIG_FLAGS */"; fi echo \#define UTS_MACHINE \"$ARCH\" echo \#define UTS_VERSION \"`echo $UTS_VERSION | $UTS_TRUNCATE`\" echo \#define LINUX_COMPILE_BY \"`echo $LINUX_COMPILE_BY | $UTS_TRUNCATE`\" echo \#define LINUX_COMPILE_HOST \"`echo $LINUX_COMPILE_HOST | $UTS_TRUNCATE`\" echo \#define LINUX_COMPILER \"`$CC -v 2>&1 | grep ' version ' | sed 's/[[:space:]]*$//'`\" } > .tmpcompile
UTS_VERSION
UTS_VERSION="#$VERSION" CONFIG_FLAGS="" if [ -n "$SMP" ] ; then CONFIG_FLAGS="SMP"; fi if [ -n "$PREEMPT" ] ; then CONFIG_FLAGS="$CONFIG_FLAGS PREEMPT"; fi if [ -n "$PREEMPT_RT" ] ; then CONFIG_FLAGS="$CONFIG_FLAGS PREEMPT_RT"; fi UTS_VERSION="$UTS_VERSION $CONFIG_FLAGS $TIMESTAMP"
LINUX_COMPILE_BY LINUX_COMPILE_HOST LINUX_COMPILER
if test -z "$KBUILD_BUILD_USER"; then LINUX_COMPILE_BY=$(whoami | sed 's/\\/\\\\/') else LINUX_COMPILE_BY=$KBUILD_BUILD_USER fi if test -z "$KBUILD_BUILD_HOST"; then LINUX_COMPILE_HOST=`hostname` else LINUX_COMPILE_HOST=$KBUILD_BUILD_HOST fi
UTS_RELEASE --- 重点分析这个宏的来源 这是一个在kernel顶层Makefile中定义的一个宏,如下:
uts_len := 64 define filechk_utsrelease.h if [ `echo -n "$(KERNELRELEASE)" | wc -c ` -gt $(uts_len) ]; then \ echo '"$(KERNELRELEASE)" exceeds $(uts_len) characters' >&2; \ exit 1; \ fi; \ echo \#define UTS_RELEASE \"$(KERNELRELEASE)\" endef
提高make的打印等级可以看到,上面的脚本内容经过翻译之后如下:
if [ `echo -n "5.4.124+" | wc -c ` -gt 64 ]; then echo '"5.4.124+" exceeds 64 characters' >&2; exit 1; fi; echo \#define UTS_RELEASE \"5.4.124+\"; }
现在可以确定KERNELRELEASE就是从kernel.release文件中获取到的。打开kernel.release确认一下:

其中KERNELRELEASE对应5.4.124+。
KERNELRELEASE又是怎么来的呢? KERNELRELEASE同样也是在Makefile中定义的、自动生成的字符串,它可以在多个地方被修改。在Makefile中查找KERNELRELEASE字符串,看见它是由下面这条命令生成的。
KERNELRELEASE = $(shell cat include/config/kernel.release 2> /dev/null)
这条命令里的2>/dev/null的含义是:若cat失败即没有取到文件内容,那么将错误信息输出到黑洞文件。 通过下面命令验证:
funny@funny:~$ cat funny.txt funny? yeah funny@funny:~$ B=$(cat funny.txt 2> /dev/null) funny@funny:~$ echo $B funny? yeah funny@funny:~$
一切准备就绪之后,通过下面的代码将UTS_RELEASE更新到utsrelease.h中。
1195 include/generated/utsrelease.h: include/config/kernel.release FORCE 1196 $(call filechk,utsrelease.h)
其中filechk的定义位于scripts/Kbuild.include
define filechk $(Q)set -e; \ mkdir -p $(dir $@); \ trap "rm -f $(dot-target).tmp" EXIT; \ { $(filechk_$(1)); } > $(dot-target).tmp; \ if [ ! -r $@ ] || ! cmp -s $@ $(dot-target).tmp; then \ $(kecho) ' UPD $@'; \ mv -f $(dot-target).tmp $@; \ fi endef
而utsrelease.h中内容如下:
linux$ cat ./obj/include/generated/utsrelease.h #define UTS_RELEASE "5.4.124+" linux$
这就是我们内核启动过程中打印出来的kernel version信息。
推荐阅读:
专辑|Linux文章汇总
专辑|程序人生
专辑|C语言
我的知识小密圈
关注公众号,后台回复「1024」获取学习资料网盘链接。
欢迎点赞,关注,转发,在看,您的每一次鼓励,我都将铭记于心~
嵌入式Linux
微信扫描二维码,关注我的公众号