Linux 核心編譯 LOCALVERSION 配置(分析核心版本號自動新增的"+"號)
因本人技術水平和知識面有限, 內容如有紕漏或者需要修正的地方, 歡迎大家指正, 也歡迎大家提供一些其他好的除錯工具以供收錄, 鄙人在此謝謝啦
1 問題發現
編譯主線 kernel
版本的時候發現, 的核心版本編譯成功後生成的版本號變成了 "x.y.z+"
, 為什麼後面會多一個加號呢?
剛開始考慮是不是 CONFIG_LOCALVERSION
的問題, 配置了 CONFIG_LOCALVERSION
, 還是會在核心版本的最後加上一個 "+"
後, 安裝完成之後, 每次 uname -a
都會出現 +
後真的感覺很鬱悶, 強迫症的我真的受不了.
2 原因分析
問題必然出現在 linux
Makefile
中發現一些端倪.
2.1 Makefile
中 LOCALVERSION
資訊
VERSION = 2
PATCHLEVEL = 6
SUBLEVEL = 35
EXTRAVERSION = .7
NAME = Yokohama
這些是我們核心版本的版本號, 生成出來的版本號理論上不應帶 +
號, 但為什麼帶 +
號呢.
核心中有兩個配置巨集 CONFIG_LOCALVERSION
和 CONFIG_LOCALVERSION_AUTO
配置了系統核心版本號和字尾的資訊.
2.2 Makefile
中讀取和設定版本號
我們檢索與這兩個巨集相關的資訊, 檢查 LOCALVERSION
巨集排除 arch/*/configs
和 Documentation
等目錄.
grep -r LOCALVERSION * grep --exclude-dir={arch,Documentation,Kconfig}
可以看到 scripts/setlocalversion
指令碼中讀取了相關的資訊.
不著急, 我們慢慢分析, 看看 Makefile
是怎麼讀取和設定這些資訊. 繼續從 Makefile
中分析 LOVALVERSION
的資訊.
define filechk_kernel.release
echo "$(KERNELVERSION)$$($(CONFIG_SHELL) $(srctree)/scripts/setlocalversion $(srctree))"
endef
# Store (new) KERNELRELEASE string in include/config/kernel.release
include/config/kernel.release: include/config/auto.conf FORCE
$(call filechk,kernel.release)
Makefile
使用 scripts/setlocalversion
工具來生成 include/config/kernel.release
. “+” 號就是在呼叫這個指令碼時新增的.
那麼可以通過執行如下命令生成版本檔案
make include/config/kernel.release
OR
make include/generated/utsrelease.h
檢視這兩個檔案的資訊就可以看到版本號資訊
另外 Makefile
還有如下定義 :
kernelrelease:
@echo "$(KERNELVERSION)$$($(CONFIG_SHELL) $(srctree)/scripts/setlocalversion $(srctree))"
也可以直接使用如下命令來顯示版本號資訊
make kernelrelease
2.3 setlocalversion
函式設定版本號資訊
閱讀 scripts/setlocalversion
檔案, 並查閱資料, 做如下筆記 :
# 如果當前核心使用SVN託管, 則只從.scmversion中讀取版本號資訊
if $scm_only; then
if test ! -e .scmversion; then
res=$(scm_version)
echo "$res" >.scmversion
fi
exit
fi
# 確認 auto.conf 檔案是否存在
if test -e include/config/auto.conf; then
. include/config/auto.conf
else
echo "Error: kernelrelease not valid - run 'make prepare' to update it" >&2
exit 1
fi
# 呼叫 localversion 從原始碼根目錄下的localversion檔案中讀取資訊
# localversion* files in the build and source directory
res="$(collect_files localversion*)"
if test ! "$srctree" -ef .; then
res="$res$(collect_files "$srctree"/localversion*)"
fi
# 設定 LOCALVERSION 資訊
# CONFIG_LOCALVERSION and LOCALVERSION (if set)
res="${res}${CONFIG_LOCALVERSION}${LOCALVERSION}"
# 呼叫 scm_version 函式讀取字尾資訊
# 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
2.3.1 LOCALVERSION 的設定
在 scripts/setlocalversion
檔案中還有有這麼一段 :
# CONFIG_LOCALVERSION and LOCALVERSION (if set)
res="${res}${CONFIG_LOCALVERSION}${LOCALVERSION}"
可以發現如果配置了 CONFIG_LOCALVERSION
和 LOCALVERSION
則會在版本號後面意思新增此後綴.
而 res
就是獲取到的本地版本號資訊, 比如 4.14-rc8
2.3.2 SCM_VERSION 字尾資訊的新增
最後根據是否配置了 CONFIG_LOCALVERSION_AUTO
和 CONFIG_LOCALVERSION
巨集, 新增版本字尾資訊
如果定義了
CONFIG_LOCALVERSION_AUTO=y
此時會執行執行
res="$res$(scm_version)"
其中 res
就是我們的版本號資訊, 而 scm_version
函式獲取了版本號字尾.
否則如果沒有設定 CONFIG_LOCALVERSION_AUTO
, 則執行如下片段.
# 呼叫 scm_version 函式讀取字尾資訊
# 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
對於 setlocalversion
中的一個語法解釋一下 :
語法 | 描述 |
---|---|
${var:-value1} |
在變數 var 不為空時, 保持 var 原有的值不變; 如果 var 變數未設定或者為空, 這表示式結果為 value1 , 但是變數 var 的值並不改變(未設定或為空) |
${var:+value1} |
在變數 var 不為空時, 表示式結果為 value1 ; 如果 var 變數未設定或者為空, 這表示式結果為空. ${var+value1} 的效果一樣 |
${var:=value1} |
在變數 var 不為空時, 保持 var 原有的值不變; 如果 var 變數未設定或者為空, 這表示式結果為value1 , 變數 var 也被賦值為 value1 |
${var:?value1} |
在變數 var 未設定或為空時, 指令碼會退出並丟擲一個錯誤資訊(包含value1 ) |
那麼上面的 shell
語句
如果
CONFIG_LOCALVERSION_AUTO = y
這段程式會通過scm_version
函式(不加引數)配置本地版本號.如果
CONFIG_LOCALVERSION_AUTO
未被設定, 而LOVALVERSION
為空, 則"${LOCALVERSION+set}" != "set"
, 那麼呼叫scm_version --short
會在最後新增一個+
號.
原來如此, 加號是這樣加上去的. 那麼加號具體怎麼新增上去的, 然後, scm_version
具體做了什麼工作, 這些配置巨集是如何影響版本號和字尾資訊的, 那只有研究 scm_version
函數了.
2.3.3 版本字尾資訊獲取
scm_version()
{
local short
short=false
cd "$srctree"
# 如果存在 .scmversion 檔案則直接獲取該檔案的字尾資訊
if test -e .scmversion; then
cat .scmversion
return
fi
# --short 引數的設定
if test "$1" = "--short"; then
short=true
fi
# Check for git and a git repo.
# 讀取 git 倉庫的版本資訊
# 如果 --short 被設定則直接列印 + 號
# 否則git讀取版本號資訊,
# 如果git tag號存在git describe | awk -F- '{printf("-%05d-%s", $(NF-1),$(NF))}'
# 否則直接列印commit號資訊
if test -z "$(git rev-parse --show-cdup 2>/dev/null)" &&
head=`git rev-parse --verify --short HEAD 2>/dev/null`; then
# If we are at a tagged commit (like "v2.6.30-rc6"), we ignore
# it, because this version is defined in the top level Makefile.
if [ -z "`git describe --exact-match 2>/dev/null`" ]; then
# If only the short version is requested, don't bother
# running further git commands
if $short; then
echo "+"
return
fi
# If we are past a tagged commit (like
# "v2.6.30-rc5-302-g72357d5"), we pretty print it.
if atag="`git describe 2>/dev/null`"; then
echo "$atag" | awk -F- '{printf("-%05d-%s", $(NF-1),$(NF))}'
# If we don't have a tag at all we print -g{commitish}.
else
printf '%s%s' -g $head
fi
fi
# Is this git on svn?
if git config --get svn-remote.svn.url >/dev/null; then
printf -- '-svn%s' "`git svn find-rev $head`"
fi
# Check for uncommitted changes
# 如果有未提交的檔案則會新增-dirty字尾
if git diff-index --name-only HEAD | grep -qv "^scripts/package"; then
printf '%s' -dirty
fi
# All done with git
return
fi
# Check for mercurial and a mercurial repo.
if test -d .hg && hgid=`hg id 2>/dev/null`; then
# Do we have an tagged version? If so, latesttagdistance == 1
if [ "`hg log -r . --template '{latesttagdistance}'`" == "1" ]; then
id=`hg log -r . --template '{latesttag}'`
printf '%s%s' -hg "$id"
else
tag=`printf '%s' "$hgid" | cut -d' ' -f2`
if [ -z "$tag" -o "$tag" = tip ]; then
id=`printf '%s' "$hgid" | sed 's/[+ ].*//'`
printf '%s%s' -hg "$id"
fi
fi
# Are there uncommitted changes?
# These are represented by + after the changeset id.
case "$hgid" in
*+|*+\ *) printf '%s' -dirty ;;
esac
# All done with mercurial
return
fi
# Check for svn and a svn repo.
# 獲取 svn 倉庫的版本號字尾資訊
if rev=`LANG= LC_ALL= LC_MESSAGES=C svn info 2>/dev/null | grep '^Last Changed Rev'`; then
rev=`echo $rev | awk '{print $NF}'`
printf -- '-svn%s' "$rev"
# All done with svn
return
fi
}
用 bash
判斷語句來判斷
git rev-parse --verify --short
來判斷當前是否是 git
版本庫管理, 接著輸出一個短的版本庫HEAD revision
的短編碼.
git rev-parse --verify --short HEAD 2>/dev/null
關鍵在下面這條語句的執行結果
git describe --exact-match
這一句是描述出當前的 tag
標識. 如果沒有 tag
就為空, 那麼整個 if
語句就為真, 就會執行下去, 下面的 echo "+"
, 這就會在版本號中輸出一個 +
號.
如果我們在版本庫中
git tag -a -m "v0.1" v0.1
後, 我們在執行 git describe --exact-match
這一句, 發現輸出的是我們的 tag
標識. 那 if
語句就不成裡了, 就不會 echo "+"
了.
繼續看上面的程式碼, 如果有未提交的程式碼, printf -dirty
的地方進行了 git diff
的檢查, 也就是說我有修改過的, 沒有上傳的檔案. 到此基本上原因全部查明, 我把檔案進行上傳後, 重新 make prepare
後, 生成的 kernel.release
果然正確.
結論, linux
對版本的管理相當嚴格,這也就讓我們在進行程式碼管理中必須嚴格要求自己,比如發版本前,先檢查是否還有修改為上傳的檔案,然後要在git版本庫中打一個tag。
如果程式碼屬於 git
管理
打了
tag
, 則會新增tag相關字元如果
tag
只是簡單的標記, 比如4.14-rc8
則跳過, 因為這些資訊已經從前面makefile
中獲取到了如果
tag
還有其他字尾標記, 比如v2.6.30-rc5-302-g72357d5
, 則將這些打印出來
沒有打
tag
, 則會新增log
字元
例如最新的commit
是commit cdebe039ded3e7fcd00c6e5603a878b14d7e564e
則編譯之後檔案
include/config/kernel.release
的內容為4.14.0-rc8-gcdebe03
按照從之前傳遞的引數過來
如果沒有定義了
CONFIG_LOCALVERSION_AUTO
和LOCALVERSION
, scm_version 函式會傳遞過去--short
引數版本號後面會新增"+"
號.if $short; then echo "+" return fi
2.4 總結
2.4.1 版本號的設定
指令碼 script/setlocalversion
中讀取了版本號的資訊
# localversion* files in the build and source directory
res="$(collect_files localversion*)"
if test ! "$srctree" -ef .; then
res="$res$(collect_files "$srctree"/localversion*)"
fi
# CONFIG_LOCALVERSION and LOCALVERSION (if set)
res="${res}${CONFIG_LOCALVERSION}${LOCALVERSION}"
由此可看出, 如果想往版本號裡新增字元, 有幾種方式 :
使用
LOCALVERSION
變數(或者在命令列, 或者新增為環境變數)在核心原始碼根目錄下新增檔案
localversion
檔案內容會自動新增到版本號裡去. 在本地建立 檔案中新增定義
CONFIG_LOCALVERSION
變數往版本號裡新增字元的方式
LOCALVERSION
變數可在命令列定義 :
make LOCALVERSION=.44 include/config/kernel.release
或者新增為環境變數
export LOCALVERSION=.44
make include/config/kernel.release
當前核心版本為 4.14.0-rc8
, 如果原始碼根目錄下有檔案 localversion
(其內容為 .33
), 也使用了 LOCALVERSION
變數(make
時指定), 也定義了CONFIG_LOCALVERSION=".XYZ"
.
make LOCALVERSION=.44 include/config/kernel.release
此時對 4.14-rc8
的核心, include/config/kernel.release
的內容為 4.14-rc8.33.XYZ.55
.
可看到新增的三種字元的順序
檔案 localversion
內容在前, 然後是 CONFIG_LOCALVERSION
的值, 最後是 LOCALVERSION
的值
即
版本號 | 標識 | 內容 |
---|---|---|
主版本號 | VERSION | 4 |
釋出版本 | PATCHLEVEL | 14 |
次版本號 | SUBLEVEL | 0 |
擴充套件版本號 | EXTRAVERSION | -rc8 |
檔案 | localversion | 33 |
配置巨集 | CONFIG_LOCALVERSION | XYZ |
本地巨集 | LOCALVERSION | 55 |
2.4.2 字尾資訊的獲取
如果
CONFIG_LOCALVERSION_AUTO = y
這段程式會通過scm_version
函式(不加引數)配置本地版本號字尾資訊. 字尾資訊一般都是託管倉庫的版本號, 比如git tag/commit
等如果
CONFIG_LOCALVERSION_AUTO
未被設定, 而LOVALVERSION
為空, 則"${LOCALVERSION+set}" != "set"
, 那麼呼叫scm_version --short
會在最後新增一個+
號.
另外, 關於
scripts/setlocalversion
檔案.在 `scripts/setlocalversion檔案中,可用echo “aaa” >&2來輸出顯示相關資訊,例如:
echo “LOCALVERSION=${LOCALVERSION}” >&2需要仔細注意
使用
modinfo
可檢視編譯出來的ko
檔案對應的核心版本號
使用uname
或者cat /proc/version
可在目標系統上檢視核心版本號.可檢視
kernel
編譯過程生成的檔案include/config/kernel.release
或者include/generated/utsrelease.h
, 確定編譯出來的核心的版本號.
2.4.3 驗證
LOCALVERSION
可以在版本號之後追加字尾資訊, 如果再定義CONFIG_LOCALVERSION_AUTO
, 將在最後進一步追加git
版本號為字尾資訊
巨集 | 定義 |
---|---|
CONFIG_LOCALVERSION | “” |
CONFIG_LOCALVERSION_AUTO | y |
LOCALVERSION | not set or set |
- 不定義
CONFIG_LOCALVERSION_AUTO
將不顯示git
倉庫資訊, 如果此時LOCALVERSION
變數定義也未定義, 將追加 “+”.
巨集 | 定義 |
---|---|
CONFIG_LOCALVERSION | “” |
CONFIG_LOCALVERSION_AUTO | not set |
LOCALVERSION | not set |
此時 scm_version –short 添加了一個 “+” 號
cat .config | grep -E "CONFIG_LOCALVERSION"
make kernelrelease
- 只要定義了 LOCALVERSION 即使定義為
NULL
, 也不會追加 “+”
巨集 | 定義 |
---|---|
CONFIG_LOCALVERSION | “” |
CONFIG_LOCALVERSION_AUTO | not set |
LOCALVERSION | 設定為空 |
make LOCALVERSION= kernelrelease
make LOCALVERSION="" kernelrelease
此時將不會新增 “+” 號
3 解決
LOCALVERSION
可以在版本號之後追加字尾資訊, 如果再定義CONFIG_LOCALVERSION_AUTO
, 將在最後進一步追加git
版本號為字尾資訊.不定義
CONFIG_LOCALVERSION_AUTO
將不顯示git
倉庫資訊, 如果此時LOCALVERSION
變數定義也未定義, 將追加 “+”.如果既不想新增字尾, 又不想有
"+"
號 : 不定義CONFIG_LOCALVERSION_AUTO
, 將LOCALVERSION
變數定義為空 :LOCALVERSION=
.只要定義了
LOCALVERSION
, 則就不會追加 “+” 號了