1. 程式人生 > >Linux 核心編譯 LOCALVERSION 配置(分析核心版本號自動新增的"+"號)

Linux 核心編譯 LOCALVERSION 配置(分析核心版本號自動新增的"+"號)

知識共享許可協議

因本人技術水平和知識面有限, 內容如有紕漏或者需要修正的地方, 歡迎大家指正, 也歡迎大家提供一些其他好的除錯工具以供收錄, 鄙人在此謝謝啦

1 問題發現

編譯主線 kernel 版本的時候發現, 的核心版本編譯成功後生成的版本號變成了 "x.y.z+", 為什麼後面會多一個加號呢?

剛開始考慮是不是 CONFIG_LOCALVERSION 的問題, 配置了 CONFIG_LOCALVERSION, 還是會在核心版本的最後加上一個 "+" 後, 安裝完成之後, 每次 uname -a 都會出現 + 後真的感覺很鬱悶, 強迫症的我真的受不了.

2 原因分析

問題必然出現在 linux

構建過程中的版本控制這一塊, 既然是在構建的過程中新增的, 那我們就可以從 Makefile 中發現一些端倪.

2.1 MakefileLOCALVERSION 資訊

VERSION = 2
PATCHLEVEL = 6
SUBLEVEL = 35
EXTRAVERSION = .7
NAME = Yokohama

這些是我們核心版本的版本號, 生成出來的版本號理論上不應帶 + 號, 但為什麼帶 + 號呢.

核心中有兩個配置巨集 CONFIG_LOCALVERSIONCONFIG_LOCALVERSION_AUTO 配置了系統核心版本號和字尾的資訊.

2.2 Makefile
中讀取和設定版本號

我們檢索與這兩個巨集相關的資訊, 檢查 LOCALVERSION 巨集排除 arch/*/configsDocumentation 等目錄.

grep -r LOCALVERSION * grep --exclude-dir={arch,Documentation,Kconfig}

檢查 <code>LOCALVERSION</code> 相關的資訊

可以看到 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

檢視這兩個檔案的資訊就可以看到版本號資訊

<code>make include/config/kernel.release</code>

另外 Makefile 還有如下定義 :

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

也可以直接使用如下命令來顯示版本號資訊

make kernelrelease

make release

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_LOCALVERSIONLOCALVERSION 則會在版本號後面意思新增此後綴.

res 就是獲取到的本地版本號資訊, 比如 4.14-rc8

2.3.2 SCM_VERSION 字尾資訊的新增

最後根據是否配置了 CONFIG_LOCALVERSION_AUTOCONFIG_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 語句

  1. 如果 CONFIG_LOCALVERSION_AUTO = y 這段程式會通過 scm_version 函式(不加引數)配置本地版本號.

  2. 如果 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_AUTOLOCALVERSION, 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 字尾資訊的獲取

  1. 如果 CONFIG_LOCALVERSION_AUTO = y 這段程式會通過 scm_version 函式(不加引數)配置本地版本號字尾資訊. 字尾資訊一般都是託管倉庫的版本號, 比如 git tag/commit

  2. 如果 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, 則就不會追加 “+” 號了

4 參考資料