1. 程式人生 > >linux kernel makefile中的那些小夥伴們

linux kernel makefile中的那些小夥伴們

這篇文章是核心編譯探索的重要知識點彙總,類似一個工具,列舉了我所見到的一些target, rule. 以此希望對核心編譯更進一步瞭解。

然而如果沒有實際核心編譯的經驗,可能會覺得比較枯燥。建議從這個核心編譯探索系列開始閱讀。

稍微談一點架構

文章寫到一大半,發現可以來談談“架構”了。把這部分放到開始,或許會對大家理解有點幫助。

整個核心程式碼從make的角度來看,可能是這樣的。

 +--  Makefile
 |
 +--+ init     
 |  |   
 |  +-- Makefile
 |      
 +--+ fs   
 |  |   
 |  +-- Makefile
 |  |   
 |  +-+ ext4
 |    | 
 |    +-- Makefile 
 |      
 +--+ kernel   
 |  |   
 |  +-- Makefile
 |  |   
 |  +-+ sched
 |    | 
 |    +-- Makefile 
 |      
 +--    

其實說白了就是每個層次目錄中的Makefile中定義好相應的目標,如果有下一層,則進入到下一層繼續編譯。而這個過程中比較重要的檔案是Makefile.build.

[更正]: “進入下一層”的表達有誤。 在整個核心編譯過程中,當前目錄都是核心原始碼樹的根目錄。對不同目標,不同路徑下的目標的編譯,是通過obj變數做到的。這一點需要細看Makefile.build檔案。

據說make中多執行緒同時編譯,就是要依靠這麼進入下一層來實現的。

rules

if_changed_xxx

這個規則實在是太重要,曝光率也是超高。

一般用法是這樣的

$(obj)/bzImage: $(
obj)/setup.bin $(obj)/vmlinux.bin $(obj)/tools/build FORCE $(call if_changed,image) @echo 'Kernel: [email protected] is ready' ' (#'`cat .version`')'

那來展開一下該定義在scripts/Kbuild.include中

###
# if_changed      - execute command if any prerequisite is newer than
#                   target, or command line has changed
# if_changed_dep - as if_changed, but uses fixdep to reveal dependencies # including used config symbols # if_changed_rule - as if_changed but execute rule instead # See Documentation/kbuild/makefiles.txt for more info

恩,原來有兄弟三個呢。先來看看大哥的樣子

# 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' '[email protected] := $(make-cmd)' > $(dot-target).cmd, @:)

第一眼看這麼長,其實我的內心是有點抵觸的。一層層來看吧

首先這是個if語句, 當條件為真,則執行一個,若為假,則執行另一個。這麼來看,逐個擊破~

判斷條件

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

意思就是所有依賴條件和arg-check字串中如果有字串,那麼就會執行相關命令。

有沒有新的依賴

# Find any prerequisites that is newer than target or that does not exist.
# PHONY targets skipped in both cases.
any-prereq = $(filter-out $(PHONY),$?) $(filter-out $(PHONY) $(wildcard $^),$^)

人註釋寫得真到位,一個表示比目標更新的依賴,一個表示還不存在的依賴。
先看看兩個自動變數,

  • $? The names of all the prerequisites that are newer than the target
  • $^ The names of all the prerequisites

第一個太明顯,第二個要看一下wildcard函式的定義了,

This string, used anywhere in a makefile, is replaced by a
space-separated list of names of existing files that match one of the
given file name patterns

恩,就是用來找到現在已經有的檔案的。那結合定義,就是先找到依賴中已經存在的檔案,然後從所有依賴中去掉。那不就剩下還沒有生成的依賴了麼。

有沒有arg-check變化

# Check if both arguments are the same including their order. Result is empty
# string if equal. User may override this check using make KBUILD_NOCMDDEP=1
arg-check = $(filter-out $(subst $(space),$(space_escape),$(strip $(cmd_[email protected]))), \
                         $(subst $(space),$(space_escape),$(strip $(cmd_$1))))

還真麼怎麼懂,除了註釋。。。

總體上看,就是比較了下兩個字串,去掉一樣的。那到底是哪兩個字串呢?
折騰了一下,終於有點眉目了。

cmd_11是 call_changed函式的第一個引數,在這個例子裡面展開後就是 cmd_image, 這個在arch/x86/boot/Makefile中定義。

[email protected]呢? 按照邏輯展開就是 cmd_arch/x86/boot/bzImage咯? 有這麼奇葩的東西? 你還真別說,世上就是有這麼奇葩的事兒。當然這點暫時我還是不太確信。我先分享一下我的發現,大家看看對不對。

編譯好bzImage後,有.cmd檔案, arch/x86/boot/.bzImage.cmd。你開啟一看。。。怎麼樣,驚呆了吧,這裡面還真就定義了這個變數 [email protected]。好吧,這核心藏得夠深的!

那是在哪裡包含的呢? 在根目錄Makefile中有這麼一段,

# read all saved command lines

targets := $(wildcard $(sort $(targets)))
cmd_files := $(wildcard .*.cmd $(foreach f,$(targets),$(dir $(f)).$(notdir $(f)).cmd))

ifneq ($(cmd_files),)
  $(cmd_files): ;  # Do not try to update included dependency files
  include $(cmd_files)
endif

先放著了,回來再確認。

好了,饒了這麼一大圈,再回過頭來。這個命令就是比較了上次編譯這個目標命令和這次使用的命令。如果不一樣,則會返回非空,提示命令需要再執行。

好了,你看著累,我寫著也累了。

如果條件為真,即依賴或者規則變化的情況

如果上面if語句的結果為真,那麼會執行下面的命令。

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

其中有三個重要的變數,

  1. $(echo-cmd) 顯示的內容,就是make的時候螢幕上列印
  2. $(cmd_$(1) 這個是真要執行的命令
  3. $(dot-target).cmd 這個就是之前看到的那個儲存命令列的檔案
echo-cmd = $(if $($(quiet)cmd_$(1)),\
    echo '  $(call escsq,$($(quiet)cmd_$(1)))$(echo-why)';)

這個顯示得很巧妙,根據quiet變數的定義有兩種情況:
如果quiet定義了,那麼就是顯示縮略形式。
如果沒有定義,就顯示詳細的命令。

具體這個形式在make bzImage例子中,定義如下

quiet_cmd_image = BUILD   [email protected]
cmd_image = $(obj)/tools/build $(obj)/setup.bin $(obj)/vmlinux.bin \
                   $(obj)/zoffset.h [email protected]

那當quiet定義的時候,螢幕上就顯示

BUILD arch/x86/boot/bzImage

好了,那來看看最後一個變數

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

這個dot-target就是目標檔案的絕對路徑(?)的檔名,加上一個”.”。

So~ I guess you get it :)

特殊目標和變數

Force目標

這是一個挺有意思的target。在我看來,make會自動按照依賴關係找到哪些目標是需要更新的。但是核心中,偏偏要加上這個目標來強制去執行某個規則。

PHONY += FORCE
FORCE:

# Declare the contents of the .PHONY variable as phony.  We keep that
# information in a variable so we can use it in if_changed and friends.
.PHONY: $(PHONY)

從定義上看,這就是一個沒有依賴也沒有執行語句的規則。當我們把他放在某個目標的依賴中,則這個目標將一定會被重新生成。(在本次make的範圍內)。 請參考make 手冊中的描述 Force Target

看一下在核心中使用的例子。

$(obj)/bzImage: $(obj)/setup.bin $(obj)/vmlinux.bin $(obj)/tools/build FORCE
    $(call if_changed,image)
    @echo 'Kernel: [email protected] is ready' ' (#'`cat .version`')'

正常情況下,make bzImage,這條規則都會被執行。如果所有的依賴都沒有更新,那就只會列印”Kernel: arch/x86/boot/bzImage is ready”。

當你把FORCE從這條規則中拿掉,那麼在依賴沒有更新時, 則不會列印這條輸出。從這點可以看出,FORCE對編譯過程產生的作用。

arg-check和[email protected]目標

這兩個用於判斷某個目標的編譯命令是否有變化,定義在scripts/Kbuild.include 和 $(dot-target).cmd檔案中。

詳細可以參考本文if_changed_xxx 和 cmd-files小節。

cmd-files和targets目標

這兩個變數定義在根目錄Makefile中, 從現在理解來看 是為了arg-check和[email protected]變數服務的。

額,發現這個變數在scripts/Makefile.build中也有定義,而且在我看的這個例子(make bzImage)中確實使用的這個地方的定義。感覺是在

make -f scripts/Makefile.build obj=arch/x86/boot arch/x86/boot/bzImage
中定義了該變數。

這兩處的定義基本一致,除了在include的時略有不同。(為啥要在兩個地方定義呢?)

又ps,當你在此處列印該變數會發現,僅僅make bzImage其實會有很多target被列印到。那問題來了,需要被make -f scripts/Makefile.build這麼多次麼?

# read all saved command lines

targets := $(wildcard $(sort $(targets)))
cmd_files := $(wildcard .*.cmd $(foreach f,$(targets),$(dir $(f)).$(notdir $(f)).cmd))

ifneq ($(cmd_files),)
  $(cmd_files): ;  # Do not try to update included dependency files
  include $(cmd_files)
endif

歸根到底就是包含了所有的cmd_files,那我們先來看看這cmd_files檔案中到底是個啥。

~/git/linux$ cat .vmlinux.cmd
cmd_vmlinux := /bin/bash scripts/link-vmlinux.sh ld -m elf_x86_64 --emit-relocs --build-id

~/git/linux$ cat arch/x86/boot/.bzImage.cmd 
cmd_arch/x86/boot/bzImage := arch/x86/boot/tools/build arch/x86/boot/setup.bin arch/x86/boot/vmlinux.bin arch/x86/boot/zoffset.h arch/x86/boot/bzImage

可以看到,就是定義了對應的[email protected]變數~

一些重要的檔案和關係

Makefile.build

這個檔案可以說是整個編譯中,具體執行動作的核心檔案。比如會在根Makefile中用到:

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

$(vmlinux-dirs): prepare scripts
    $(Q)$(MAKE) $(build)=[email protected]

那接下來展開看一下這個檔案包含的內容

變數初始化

擷取一段,

# Init all relevant variables used in kbuild files so
# 1) they have correct type
# 2) they do not inherit any value from the environment
obj-y :=
obj-m :=

是不是覺得很親切? 對了,感覺就像是一個函式先上來初始化一下區域性變數。

引用Makefile.include

這個檔案定義了些基本的變數和規則,比如 if_changed , build

引用目標目錄的Kbuild或者Makefile

好了,這裡要包含目標目錄下的kbuild或者Makefile了。就好像引用了標頭檔案後,要開始做本目標目錄指定的事情了。Kbuild的優先順序比Makefile的高,一般應該只會有其一。

值得一提的是,現在核心的編譯系統已經非常完善。當需要新增檔案加入核心或者模組中,只要修改Kbuild或者Makefile就可以了。

好了,來看一下大概會長什麼樣子。

"init/Makefile"

obj-y                          := main.o version.o mounts.o

mounts-y            := do_mounts.o
mounts-$(CONFIG_BLK_DEV_RAM)   += do_mounts_rd.o
mounts-$(CONFIG_BLK_DEV_INITRD)    += do_mounts_initrd.o
mounts-$(CONFIG_BLK_DEV_MD)    += do_mounts_md.o
"net/9p/Makefile"

obj-$(CONFIG_NET_9P) := 9pnet.o
obj-$(CONFIG_NET_9P_VIRTIO) += 9pnet_virtio.o
obj-$(CONFIG_NET_9P_RDMA) += 9pnet_rdma.o

9pnet-objs := \
    mod.o \
    client.o \
    error.o \
    util.o \
    protocol.o \
    trans_fd.o \
    trans_common.o \

9pnet_virtio-objs := \
    trans_virtio.o \

9pnet_rdma-objs := \
    trans_rdma.o \

看到裡面的obj-y了麼? 或者在make menuconfig的時候,配成obj-m。是不是正好和初始化的變數對上?

引用Makefile.lib

這個檔案也很重要,包括了對 obj-y 、obj-m變數的處理, 一些編譯連結選項變數定義,還有一些規則。

在這裡, 我著重突出一下這個檔案中的第一個部分。

分辨出sub-dir

# Handle objects in subdirs
# ---------------------------------------------------------------------------
# o if we encounter foo/ in $(obj-y), replace it by foo/built-in.o
#   and add the directory to the list of dirs to descend into: $(subdir-y)
# o if we encounter foo/ in $(obj-m), remove it from $(obj-m)
#   and add the directory to the list of dirs to descend into: $(subdir-m)

# Determine modorder.
# Unfortunately, we don't have information about ordering between -y
# and -m subdirs.  Just put -y's first.
modorder    := $(patsubst %/,%/modules.order, $(filter %/, $(obj-y)) $(obj-m:.o=.ko))

__subdir-y  := $(patsubst %/,%,$(filter %/, $(obj-y)))
subdir-y    += $(__subdir-y)
__subdir-m  := $(patsubst %/,%,$(filter %/, $(obj-m)))
subdir-m    += $(__subdir-m)
obj-y       := $(patsubst %/, %/built-in.o, $(obj-y))
obj-m       := $(filter-out %/, $(obj-m))

# Subdirectories we need to descend into

subdir-ym   := $(sort $(subdir-y) $(subdir-m))

這個東西為什麼重要呢? 我們來看一下Makefile.build檔案中靠後的一部分。

# Descending
# ------------------------------------

PHONY += $(subdir-ym)
$(subdir-ym):
    $(Q)$(MAKE) $(build)=[email protected]

是不是覺得有點意思? 這樣就可以在Makefile/Kbuild檔案中定義一個帶有”/”的目標,核心就自動會進入下一層目錄去編譯~

composite objects

在核心makefile檔案中,經常看到有帶objs的變數。 比如上文中摘取的net/9p/Makefile中就有9pnet-objs。 以前只知道效果,就是後面跟的目標會合成一起生成這個9pnet.o。 那這個生成的部分工作就是在這裡做的 – 去找到這些帶有-objs的目標。

# if $(foo-objs) exists, foo.o is a composite object
multi-used-y := $(sort $(foreach m,$(obj-y), $(if $(strip $($(m:.o=-objs)) $($(m:.o=-y))), $(m))))
multi-used-m := $(sort $(foreach m,$(obj-m), $(if $(strip $($(m:.o=-objs)) $($(m:.o=-y)) $($(m:.o=-m))), $(m))))
multi-used   := $(multi-used-y) $(multi-used-m)
single-used-m := $(sort $(filter-out $(multi-used-m),$(obj-m)))

當然,實際的工作還在Makefile.build中做,我們會在下面某節看到具體的規則。

預設目標__build

PHONY := __build
__build:


__build: $(if $(KBUILD_BUILTIN),$(builtin-target) $(lib-target) $(extra-y)) \
     $(if $(KBUILD_MODULES),$(obj-m) $(modorder-target)) \
     $(subdir-ym) $(always)
    @:

其實啥都沒幹,就是告訴make,需要乾的工作是哪些。

編譯連結規則

後面就是一些規則了,比如如何從c程式碼編譯出一個obj檔案。

# Built-in and composite module parts
$(obj)/%.o: $(src)/%.c $(recordmcount_source) $(objtool_obj) FORCE
    $(call cmd,force_checksrc)
    $(call if_changed_rule,cc_o_c)

但類似的規則在Makefile.lib中也有,不知道分在兩個地方寫的目的和意義。

來看兩個比較重要的規則。

builtin-target

quiet_cmd_link_o_target = LD      [email protected]
# If the list of objects to link is empty, just create an empty built-in.o
cmd_link_o_target = $(if $(strip $(obj-y)),\
              $(LD) $(ld_flags) -r -o [email protected] $(filter $(obj-y), $^) \
              $(cmd_secanalysis),\
              rm -f [email protected]; $(AR) rcs$(KBUILD_ARFLAGS) [email protected])

$(builtin-target): $(obj-y) FORCE
    $(call if_changed,link_o_target)

這個作用在什麼地方呢? 你看在編譯過的核心目錄下,有好多built-in.o檔案吧, 他們就是用這個規則連結起來的。

composite objects

#
# Rule to link composite objects
#
#  Composite objects are specified in kbuild makefile as follows:
#    <composite-object>-objs := <list of .o files>
#  or
#    <composite-object>-y    := <list of .o files>
#  or
#    <composite-object>-m    := <list of .o files>
#  The -m syntax only works if <composite object> is a module
link_multi_deps =                     \
$(filter $(addprefix $(obj)/,         \
$($(subst $(obj)/,,$(@:.o=-objs)))    \
$($(subst $(obj)/,,$(@:.o=-y)))       \
$($(subst $(obj)/,,$(@:.o=-m)))), $^)

quiet_cmd_link_multi-y = LD      [email protected]
cmd_link_multi-y = $(LD) $(ld_flags) -r -o [email protected] $(link_multi_deps) $(cmd_secanalysis)

quiet_cmd_link_multi-m = LD [M]  [email protected]
cmd_link_multi-m = $(cmd_link_multi-y)

$(multi-used-y): FORCE
    $(call if_changed,link_multi-y)
$(call multi_depend, $(multi-used-y), .o, -objs -y)

$(multi-used-m): FORCE
    $(call if_changed,link_multi-m)
    @{ echo $(@:.o=.ko); echo $(link_multi_deps); \
       $(cmd_undef_syms); } > $(MODVERDIR)/$(@F:.o=.mod)
$(call multi_depend, $(multi-used-m), .o, -objs -y -m)

上文中我們說net/9p/9pnet.o這個目標就是用這個規則生成的。說來也簡單,就是把 9pnet-objs 變數中的所有.o檔案連結一下。

從link_multi_deps的定義可以看出來,有三種寫法 -objs, -y, -m。

相關推薦

linux kernel makefile那些小夥伴

這篇文章是核心編譯探索的重要知識點彙總,類似一個工具,列舉了我所見到的一些target, rule. 以此希望對核心編譯更進一步瞭解。 然而如果沒有實際核心編譯的經驗,可能會覺得比較枯燥。建議從這個核心編譯探索系列開始閱讀。 稍微談一點架構 文章寫到一

FPGA研發(2) FPGA和他那些小夥伴 (一) 架構組成。

         通常來講,“一個好漢三個幫”,一個完整的嵌入式系統中由單獨一個FPGA使用的情況較少。通常由多個器件組合完成,例如由一個FPGA+CPU來構成。通常為一個FPGA+ARM,ARM負責

Linux Kernel Makefile預設目標

inux核心的Makefile也不是一般的麻煩。這裡結合Makefile本身的用法對Linux核心的Makefile做一分析。(1)入口點預設的Makefile的入口點是第一條規則。而Linux核心的Makefile的第一條規則是這樣的:除去上面一長串賦值語句,來到:PHONY := _all_all:是一條

[英對照]Linux kernel coding style | Linux內核編碼風格

ril views har func Language config req bing evel Linux kernel coding style | Linux內核編碼風格 This is a short document describing the preferre

Linux下JupyterNotebookpython版本/kernel共存的解決方法

一.首先說明一下我的環境: 我的環境是linux環境下的伺服器、跑python程式用的是安裝在伺服器上的Jupyter Notebook 二.我遇到的問題: 我的伺服器上的JupyterNotebook上只有Python2,而python2中編碼格式跟python3並不一樣,會出現許多錯

Linux Kernel 4 9TCP BBR演算法的科普解釋

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

Linux makefile的obj-$(XXX)

$(CONFIG_TEST) 是一個整體,$(xxx)表示引用變數 xxx 比如定義 CONFIG_TEST=y $(CONFIG_TEST)就是y obj-$(CONFIG_TEST) 就是 obj-y 又比如定義 CONFIG_TEST=m $(CONF

linux kernel 4.18.16 Makefile註釋

前言: 本人的作業系統作業,要求註釋最新版本的GNU Linux kernel 的Makefile檔案,我還沒用過Linux,一開啟這個Makefile真的是下了一跳……好多東西要註釋啊,忙活了一個下午,成果就寫個部落格吧。 參考: Makefile: # S

Linux kernelktime_get()方法獲取的當前時間比之前的時間晚的debug code

在Linux kernel的測試過程中,我們發現ktime_get()獲得的當前時間比之前的時間還要晚,因此我們需要在一些debug,下面介紹一下debug過程中遇到的cpu同步的問題。 Linux kernel 中ktime_get()的實現如下: ktime_t ktime_get(

Linux Kernel 4.9BBR擁塞控制演算法的優勢

本文轉載自《Linux Kernel 4.9 中的 BBR 演算法與之前的 TCP 擁塞控制相比有什麼優勢?》 中國科大 LUG 的 @高一凡 在 LUG HTTP 代理伺服器上部署了 Linux 4.9 的 TCP BBR 擁塞控制演算法。從科大的移動出口到新加坡 D

Linux kernel 的work queue原理

本帖最後由 Dolphin 於 2010-7-16 10:18 編輯 相對於create_singlethread_workqueue, create_workqueue同樣會分配一個wq的工作佇列,但是不同之處在於,對於多CPU系統而言,對每一個CPU,都會為之建立一個per-CPU的cwq結構,對應每一

Linux kernel的IS_ENABLED

在kernel的程式碼中, 有時候會看見IS_ENABLED(CONFIG_XXXX)來測試某個Kconfig選項是否開啟(即選中為y或者m). 如 if (IS_ENABLED(CONFIG_T

Linux makefile的編譯連結選項

-I是編譯選項(準確的是說是預處理選項CFLAGS或者CPPFLAGS中指定),用來指定預處理時查詢標頭檔案的範圍的。 -l是連結選項(LDFLAGS中指定),用來指定連結額外的庫(譬如我們用到了數學函式,就用-lm,連結器就會去連結libm.so;那麼我們使用了libjpeg,對應的庫名字就叫li

Linux kernel 4.x的min和max巨集

min和max是兩個很常用的操作,一般都是用巨集實現的,不過想要寫出一個很完善的巨集定義還是要考慮很多問題的,本文就來分析下Linux Kernel中的實現方法。文中僅考慮min,max的結構與其完全相同,只要修改下大於小於號即可。 巨集定義中要將整體和變數都

linux kernel 3.18.0 Makefile註釋

VERSION = 3                     #kernel 主版本號PATCHLEVEL = 18             #kernel 次版本號SUBLEVEL = 0                    #kernel 修改版本號EXTRAVERS

linux kernel沒有include/linux/version.h檔案

編譯核心的時候make ARCH=arm CROSS_COMPILE=arm-linux- menuconfig 當然也可以用config和xconfig來代替menuconfig,但是這樣用可能會沒有設定某些配置檔案選項和沒有生成下面編譯所需的標頭檔案。所以推薦使用mak

linux kernel列印函式呼叫的堆疊的方法

在linux核心除錯中,經常用到的列印函式呼叫堆疊的方法非常簡單,只需在需要檢視堆疊的函式中加入: dump_stack();或 __backtrace();即可。 dump_stack()在~/kernel/ lib/Dump_stack.c中定義 void

Linux kernel config and makefile system

命令 原因 bug 匯編 tid asp excludes ted dex 轉載自:http://blog.csdn.net/dreamxu/article/details/6125

css那些屬性可以被繼承

mil 屬性 ria ext direct tran ade weight -s 主要的有: 字體相關:line-height, font-family, font-size, font-style, font-variant, font-weight, font 文本相關

linux服務器木馬,手工清除方法

服務器安全 linux 防火墻 文章 自由人 由於自己也碰到過這種情況,剛好看到這篇文章,先轉載過來。的確蠻有用的哦。首先劇透一下後門木馬如下:(當然這是事後平靜下來後慢慢搜出來的,那個時候喝著咖啡感覺像個自由人)木馬名稱Linux.BackDoor.Gates.5http://forum