uboot構建框架2-kbuild框架簡要分析

其實網絡上介紹kbuild框架的資料書籍一大把,有的介紹非常詳細,大家不妨可以參考。本文試圖從一些線索來簡要分析kbuild框架,好讓我們從一個方面瞭解一條線,不至於陷入kbuild框架的各種大坑裏面。

kbuild是個什麼鬼?

kbuild來源於linux內核,是linux內核用來構建輸出內核鏡像的make框架。因爲這套框架做得比較好,uboot也借過來用了。kbuild中的k,我想應該就是kernel的意思吧。這是我的一個簡單理解,想看專業的解釋,可以參考內核裏面的文檔。

kbuild在哪?

在uboot源代碼根目錄,我們可以看到這麼幾個文件:

1.Makefile文件

2.Kbuild文件

3.Kconfig文件

4.scripts目錄

源代碼目錄文件如下:

Makefile文件好理解,一個make工程要能夠使用make功能,就必須要有makefile文件。這個Makefile就是make的主Makefile,正是因爲這個Makefile,我們在uboot根目錄下面才能輸入各種make命令,比如make menuconfig。

Kbuild文件我們可以打開看一下,看起來也是一個make腳本,這裏面定義了各種變量,也定義了少量規則,看起來貌似挺複雜,我們不知道幹嘛用的。不過看看文件開頭的註釋,大概能猜出這個文件是幹嘛用的,我們暫且就信了這個註釋,就這麼理解吧:

#
# Kbuild for top-level directory of U-Boot
# This file takes care of the following:
# 1) Generate generic-asm-offsets.h
# 2) Generate asm-offsets.h

看到沒,這個file是用來生成generic-asm-offsets.h和asm-offsets.h這兩個頭文件的,至於這兩個貨色是幹嘛用的,暫且不表,原因是我也不知道,沒法表。

再來看下Kconfig文件。這個文件看起來不像腳本,看起來像是配置文件,這個配置文件遵循某種規範,如下:

這個語法,我想應該是有資料可查的,否則別人沒法知道怎麼使用對吧,所有的規則都是人爲定義的,並不是天生的。其實講這個規則的資料網上非常多,大家可以去搜索一下,本文主要還是簡要介紹爲主,就不詳細展開了,否則,這麼一篇小文章,是遠遠不夠的。我們需要知道的是,這個配置文件是kbuild配置框架的一個輸入,kbuild會根據這個配置文件生成配置界面給大家選,僅此而已。

我們再來看下scripts目錄,我們可以ls命令查看一下:

sunke@droresrv:~/work/MYiR-iMX-Uboot$ ls -l ./scripts/
total 444
drwxrwxr-x 2 sunke sunke   4096 May 31 11:03 basic
-rwxrwxr-x 1 sunke sunke    514 May 14 15:05 binutils-version.sh
-rwxrwxr-x 1 sunke sunke 129427 May 14 15:05 checkpatch.pl
-rwxrwxr-x 1 sunke sunke   5513 May 14 15:05 checkstack.pl
-rwxrwxr-x 1 sunke sunke   5132 May 14 15:05 cleanpatch
-rw-rw-r-- 1 sunke sunke  14132 May 14 15:05 docproc.c
-rwxrwxr-x 1 sunke sunke    439 May 14 15:05 dtc-version.sh
-rwxrwxr-x 1 sunke sunke   5041 May 14 15:05 fill_scrapyard.py
-rwxrwxr-x 1 sunke sunke    312 May 14 15:05 gcc-stack-usage.sh
-rwxrwxr-x 1 sunke sunke    819 May 14 15:05 gcc-version.sh
-rwxrwxr-x 1 sunke sunke  58501 May 14 15:05 get_maintainer.pl
-rw-rw-r-- 1 sunke sunke  11557 May 14 15:05 Kbuild.include
drwxrwxr-x 3 sunke sunke   4096 May 31 11:03 kconfig
-rwxrwxr-x 1 sunke sunke  73838 May 14 15:05 kernel-doc
-rwxrwxr-x 1 sunke sunke    205 May 14 15:05 ld-version.sh
-rwxrwxr-x 1 sunke sunke    460 May 14 15:05 Lindent
-rwxrwxr-x 1 sunke sunke   4821 May 14 15:05 mailmapper
-rw-rw-r-- 1 sunke sunke    548 May 14 15:05 Makefile
-rw-rw-r-- 1 sunke sunke   4321 May 14 15:05 Makefile.autoconf
-rw-rw-r-- 1 sunke sunke  14262 May 30 09:26 Makefile.build
-rw-rw-r-- 1 sunke sunke   3070 May 14 15:05 Makefile.clean
-rw-rw-r-- 1 sunke sunke   2003 May 14 15:05 Makefile.extrawarn
-rw-rw-r-- 1 sunke sunke   4655 May 14 15:05 Makefile.host
-rw-rw-r-- 1 sunke sunke  15510 May 14 15:05 Makefile.lib
-rw-rw-r-- 1 sunke sunke   8156 May 14 15:05 Makefile.spl
-rw-rw-r-- 1 sunke sunke    261 May 14 15:05 Makefile.uncmd_spl
-rwxrwxr-x 1 sunke sunke   1183 May 14 15:05 mkmakefile
-rwxrwxr-x 1 sunke sunke   2756 May 14 15:05 objdiff
-rwxrwxr-x 1 sunke sunke   3974 May 14 15:05 setlocalversion
-rwxrwxr-x 1 sunke sunke    569 May 14 15:05 show-gnu-make

怪怪,這個目錄下面,內容可就多了。乍看起來絲毫沒有頭緒,怎麼辦?其實我們還是不要偏離我們的主題,我們的主題是探討make相關的東西,kbuild框架也是make相關的一種框架,那麼好,我們只關心Makefile行不行?我們看下,這個目錄下面跟makefile相關的文件大致有:

Makefile、Makefile.autoconf、Makefile.build、Makefile.clean等Makefile.開頭的文件,還有一個Kbuild.include,還有一個Kconfig目錄。

裏面都有些啥?

看起來還是挺多,怎麼辦?一個好的辦法是,我們一個個打開看下,這裏面都有些啥玩意。在看本文的時候,建議大家手上有一份代碼(本文使用的uboot版本是2016.03),這樣我就不用貼圖了,好讓文章看起來不那麼又臭又長。我們會發現,首先這個Kbuild.include裏面包含的內容,基本可以肯定就是make腳本,這裏面定義了一些make變量,目前我們並不知道這些make變量是幹嘛用的,而且看起來這些變量定義頗有些意思,頻繁使用了make的函數和自動化變量。比如:

###
# Name of target with a '.' as filename prefix. foo/bar.o => foo/.bar.o
dot-target = $(dir $@).$(notdir $@) 

這個變量使用了dir函數和notdir函數,用於通過規則的目標文件信息構造一個點分字符串,至於這個變量幹嘛的,我們沒有看到使用的地方,當然是不得而知的,我們暫且感受一下氛圍。這個文件裏面,諸如此類的變量,定義了不少,我們暫且不用管幹什麼用的,我們可以大致掃一遍這些變量,好讓我們以後某個地方看到的時候,還能想起來,這個變量定義在Kbuild.include文件。當然,我得給大家介紹一下這裏面的重要變量,否則本文是不是就沒有意義了,什麼都讓大家自己看。

1.build變量:

###
# Shorthand for $(Q)$(MAKE) -f scripts/Makefile.build obj=
# Usage:
# $(Q)$(MAKE) $(build)=dir
build := -f $(srctree)/scripts/Makefile.build obj 

build變量用於將make過程引入到Makefile.build,使用該Makefile規則來構建目標。

2.clean變量:

###
# Shorthand for $(Q)$(MAKE) -f scripts/Makefile.clean obj=
# Usage:
# $(Q)$(MAKE) $(clean)=dir
clean := -f $(srctree)/scripts/Makefile.clean obj 

clean變量用於將make過程引入到Makefile.clean,使用該Makefile規則來清除產物。

3.echo-cmd變量:

# echo command.
# Short version is used, if $(quiet) equals `quiet_', otherwise full one.
echo-cmd = $(if $($(quiet)cmd_$(1)),\
        echo '  $(call escsq,$($(quiet)cmd_$(1)))$(echo-why)';)

該變量用於構造命令打印輸出。比如我們在編譯時看到的:

CC /arch/arm/cpu/armv7/cpu.o

4.if_changed系列變量:

# Execute command if command has changed or prerequisite(s) are updated.
#
if_changed = $(if $(strip $(any-prereq) $(arg-check)),                       \   
        @set -e;                                                             \   
        $(echo-cmd) $(cmd_$(1));                                             \   
        printf '%s\n' 'cmd_$@ := $(make-cmd)' > $(dot-target).cmd)

很多地方使用這個if_changed系列變量來執行規則命令。在此以if_changed作爲例子,我們展開分析一下:

首先我們看到,這個變量的值是一個make的if函數,這個函數根據第一個參數的展開情況,來決定後面的值。如果展開非空,函數的值就是第二個參數。第一個參數爲:

$(strip $(any-prereq) $(arg-check))

這裏利用strip函數將首尾空格去掉,也把字符串中間的空格整理一下。最後輸出的值,是以空格爲間隔的一個個字符串。這裏引用了兩個變量any-prereq和arg-check。any-prereq變量爲:

any-prereq = $(filter-out $(PHONY),$?) $(filter-out $(PHONY) $(wildcard $^),$^)

看起來好像挺複雜,實際上我們一個個去看,也相對簡單,各個擊破吧。首先,這個變量的值由兩部分組成,分別是:

1.$(filter-out $(PHONY),$?)
2.$(filter-out $(PHONY) $(wildcard $^),$^)

前兩個都是make的filter-out函數,這是個反過濾函數,是將第二個參數裏面符合第一個參數條件的值去掉,留下的值作爲結果。$?變量表示所有比目標文件更新的依賴文件列表,$(PHONY)是包含僞目標的變量。所以,第一個結果就是:所有比目標更新的依賴文件,這些依賴文件裏面不包含僞目標。第二個呢?看起來好像稍微複雜一點,別怕,我們各個擊破。首先看$^,這個變量表示規則的所有依賴文件列表,使用空格分隔。$(wildcard $^)使用了wildcard函數,這個函數表示列出當前所有的依賴文件,注意,這裏列出的依賴文件,是文件系統中有的,也就是說,這裏列出所有已經生成的依賴文件。那麼,第二個結果就顯而易見了:所有依賴文件,這些依賴文件裏面除去了僞目標和已經生成的文件,也就是所有還未生成的依賴文件且沒有僞目標文件。所以,any-prereq變量的值就包含:所有比目標更新的依賴文件和所有還未生成的依賴文件,這裏面不包含僞目標。是不是就感覺清晰了?

剩下一個arg-check變量。這個變量定義如下:

ifneq ($(KBUILD_NOCMDDEP),1)
# Check if both arguments has same arguments. Result is empty string if equal.
# User may override this check using make KBUILD_NOCMDDEP=1
arg-check = $(strip $(filter-out $(cmd_$(1)), $(cmd_$@)) \
                    $(filter-out $(cmd_$@),   $(cmd_$(1))) )
else
arg-check = $(if $(strip $(cmd_$@)),,1)
endif

這個變量根據KBUILD_NOCMDDEP的值而異,如果KBUILD_NOCMDDEP=1,則arg-check的值爲:

$(if $(strip $(cmd_$@)),,1)

我們發現,這個是個if函數,值要嘛是空,要嘛是1。什麼時候是空呢?當$(cmd_$@)展開不爲空,則整個值就是空,否則這個值展開爲空,整個值就是1。感覺好繞,咱也不知道具體是幹什麼的,知道有這麼個東西,暫且留着吧,興許後面深入下去,迷霧也會自然揭開。總之,當:

$(strip $(any-prereq) $(arg-check))

展開不爲空時,if_changed就等於:

@set -e;                                                             \   
        $(echo-cmd) $(cmd_$(1));                                             \   
        printf '%s\n' 'cmd_$@ := $(make-cmd)' > $(dot-target).cmd

整理一下,得到:

@set -e; $(echo-cmd) $(cmd_$(1)); printf '%s\n' 'cmd_$@ := $(make-cmd)' > $(dot-target).cmd

由三個shell命令組成,第一個表示shell發生錯誤就退出。第二個呢,echo-cmd我們上面分析過,是輸出命令字符串。$(cmd_$(1))根據參數而定,由參數和cmd_組成一個新變量進行展開,展開的結果是個shell命令,在這裏執行。這裏有點類似於回調的概念,具體命令的值是什麼,這裏不知道,要根據傳進來的值而定,你看,多麼神奇,一個make腳本都可以設計這麼巧妙,不得不驚歎linux這幫人的軟件工程水平了,這裏不涉及到多少高深的算法,有的只是一些精巧的軟件工程設計。第三個很顯然,是一個打印命令,打印的值貌似重定向到了一個文件中,單純看這裏其實看不出這打印的是啥,我們暫且不表,知道是個打印就行了。

這一切是怎麼聯繫起來的?

以上是Kbuild.include文件裏面的幾個重要變量,我們這裏簡要介紹一下,這些變量後面會使用到。接下來的Makefile.*文件,我們可以自己一個個打開看下,裏面具體是些啥。實際上,每個這類文件都聚合了某一類的功能,比如這個Makefile.build聚合了跟構建有關的規則,Makefile.clean聚合了跟clean有關的規則,僅此而已,因爲代碼比較多,這裏就不具體分析了,待後面分析具體的構建目標時,自然會有提及。接下來,我們分析下,這些文件是如何串起來的。我們知道,根目錄裏面其實就是那個Makefile在其作用,可以把這個Makefile看做總入口,Kbuild.include和Makefile.*都是通過這個總入口引入的。我們大概看下:

根目錄Makefile文件的第327行,有如下語句:

# We need some generic definitions (do not try to remake the file).
scripts/Kbuild.include: ;
include scripts/Kbuild.include

這裏通過include引入了Kbuild.include文件。那麼,那些Makefile.*,我們拿Makefile.build舉例,是怎麼引入的呢?我們看主Makefile的第836行:

%.imx: %.bin
        $(Q)$(MAKE) $(build)=arch/arm/imx-common $@

這裏是構造u-boot.imx的規則,我們看到這條規則的命令,使用了build變量,結合上面對build變量的討論,我們展開得到:

        $(Q)$(MAKE) -f $(srctree)/scripts/Makefile.build obj=arch/arm/imx-common $@

看到沒,這裏通過-f選項引入了Makefile.build作爲構建makefile。其中Makefile.build裏面,會利用obj指定的目錄,包含進arch/arm/imx-common/Makefile,從而使用了子目錄裏面的makefile規則。通過這種方法,就以主Makefile爲入口,進入到了kbuild系列規則makefile裏面。同理,clean也是這麼做的。

Makefile.lib裏面同樣定義了一堆變量,這些變量可能比較通用,所以聚合在此作爲庫一樣的功能。這個文件是通過include被引入的。比如,Makefile.build第73行:

include scripts/Makefile.lib

引入了該文件定義的所有內容。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章