kernel 啟動(dòng)時(shí)通常會(huì)看到下面第二行信息的內(nèi)容,它們代表了當(dāng)前 kernel 的版本、編譯工具版本、編譯環(huán)境等信息。
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
......
要知道,系統(tǒng)啟動(dòng)過程中的任何一條打印信息,都是經(jīng)過了無(wú)數(shù)次討論和驗(yàn)證才呈現(xiàn)在大家的面前。看似無(wú)關(guān)緊要的一條信息,但背后卻隱藏著非常有趣的故事。
為什么要打印version信息
當(dāng)系統(tǒng)啟動(dòng)之后有很多種方式能夠確定內(nèi)核版本號(hào)信息,在嵌入式或安卓 kernel 系統(tǒng)下,查看版本信息:
- 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 ]#
在發(fā)行版 linux 系統(tǒng)環(huán)境下,還可以用下面的命令查看版本信息:
- 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
以上方法都是系統(tǒng)啟動(dòng)正常、加載完文件系統(tǒng)之后使用的。
那么,系統(tǒng)啟動(dòng)過程中是否有必要打印內(nèi)核版本信息呢?答案是完全有必要。
例如下面列出的幾種應(yīng)用場(chǎng)景:
- SoC 芯片的 kernel 適配
- 可裝載驅(qū)動(dòng)程序調(diào)試
- 多分支內(nèi)核版本加載
- 內(nèi)核偽裝
kernel version實(shí)現(xiàn)原理
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系統(tǒng)里的ssh登錄橫幅一樣,呈現(xiàn)了系統(tǒng)的一些基本信息。
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中,注意,它是一個(gè)只讀字符串,不要去修改它。
const char linux_banner[] =
"Linux version " UTS_RELEASE " (" LINUX_COMPILE_BY "@"
LINUX_COMPILE_HOST ") (" LINUX_COMPILER ") " UTS_VERSION "\\n";
由以下幾部分組成:
-
UTS_RELEASE
對(duì)應(yīng)"5.4.124+"
-
LINUX_COMPILE_BY
對(duì)應(yīng)"funny",我的編譯機(jī)funny
-
LINUX_COMPILE_HOST
對(duì)應(yīng)"funny",我的編譯機(jī)Host是funny
-
LINUX_COMPILER
對(duì)應(yīng)"gcc version 6.5.0 (Linaro GCC 6.5-2018.12"
-
UTS_VERSION
對(duì)應(yīng)"#30 SMP Sat Sep 11 11:10:28 CST 2021"
UTS:Unix Time Stamp,從這個(gè)名字可以看出linux中的UNIX印記。
接下來對(duì)這些字符串逐條進(jìn)行解析
上面這些宏的第一級(jí)定義位于./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 --- 重點(diǎn)分析這個(gè)宏的來源
這是一個(gè)在kernel頂層Makefile中定義的一個(gè)宏,如下:
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的打印等級(jí)可以看到,上面的腳本內(nèi)容經(jīng)過翻譯之后如下:
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+\"; }
現(xiàn)在可以確定KERNELRELEASE就是從kernel.release文件中獲取到的。打開kernel.release確認(rèn)一下:
其中KERNELRELEASE
對(duì)應(yīng)5.4.124+
。
KERNELRELEASE又是怎么來的呢?
KERNELRELEASE同樣也是在Makefile中定義的、自動(dòng)生成的字符串,它可以在多個(gè)地方被修改。在Makefile中查找KERNELRELEASE字符串,看見它是由下面這條命令生成的。
KERNELRELEASE = $(shell cat include/config/kernel.release 2 > /dev/null)
這條命令里的2>/dev/null
的含義是:若cat失敗即沒有取到文件內(nèi)容,那么將錯(cuò)誤信息輸出到黑洞文件。
通過下面命令驗(yàn)證:
funny@funny:~$ cat funny.txt
funny? yeah
funny@funny:~$ B=$(cat funny.txt 2 > /dev/null)
funny@funny:~$ echo $B
funny? yeah
funny@funny:~$
一切準(zhǔn)備就緒之后,通過下面的代碼將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中內(nèi)容如下:
linux$ cat ./obj/include/generated/utsrelease.h
#define UTS_RELEASE "5.4.124+"
linux$
這就是我們內(nèi)核啟動(dòng)過程中打印出來的kernel version信息。
-
嵌入式系統(tǒng)
+關(guān)注
關(guān)注
41文章
3593瀏覽量
129499 -
Linux系統(tǒng)
+關(guān)注
關(guān)注
4文章
594瀏覽量
27407 -
SoC芯片
+關(guān)注
關(guān)注
1文章
612瀏覽量
34925 -
驅(qū)動(dòng)程序
+關(guān)注
關(guān)注
19文章
836瀏覽量
48049 -
LSB算法
+關(guān)注
關(guān)注
0文章
7瀏覽量
5817
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論