[Linux Kernel慢慢學]快速上手Makefile和Kbuild Makefile

Posted by John on 2020-11-07
Words 2.2k and Reading Time 8 Minutes
Viewed Times

前言

Linux Kernel中是透過Kbuild Makefile(Kernel build, 基於makefile的compile tool)來對kernel code編譯,然後最終產生vmlinux file。所以了解Kbuild是很重要的,在看一個kernel code的時候可以透過Makefile file來幫助了解編譯的架構構成。

要了解Kbuild Makefile就要知道什麼是Makefile,但是Makefile細節太多了,講不完齁

詳細完整的教學網路上很多了,所以這篇文章不講瑣碎的原理跟細節,而是試著從實際看code會看到的內容來介紹:

  1. 快速介紹Makefile的概念,我們只講重點
  2. 快速介紹Kbuild規則,我們只講重點和常會用到的部分
  3. 搭配kernel code來實際看一下例子

從0開始的make快速上手教學

先科普一下一個程式到產生執行檔的過程: 由c/c++等語言邊寫的程式碼檔案,會先經過編譯器(compiler)產生object file,然後再透過連結器(linker)將一個或多個object file組成執行檔executable。

今天如果需要編譯一個由c寫的程式碼我們可以直接下gcc指令來編譯,但下列一些狀況就很適合使用Makefile:

  1. 例如包含多個.c(object file)檔案,彼此檔案之間的相關性很複雜的時候,人工來維護這些相關性比較麻煩
  2. 有時候要編譯的檔案很多(例如linux kernel),但我們只改了一部分的code,那跟這部分無關的其實可以都不用重新編譯以節省時間

此時我們可以透過一些工具來幫我們做檢查並自動編譯,Makefile就是一個好工具。夠過指令make去找資料夾下的Makefile(還有其他名稱會被查找,這裡有個查找的順序)文件,然後會做這些檢查:

  1. 如果目標(target)相依性項目(dependencies)中需要的的source code file沒有被編譯過,則編譯相關檔案然後生成我們需要的檔案
  2. 如果目標的相依檔案有一個source code file被更動過,則他將會被重新編譯
  3. 如果有目標的相依項目有header file被修改過,則所有使用到該header file的都會被重新編譯

目標跟相依檔案是什麼意思?考慮一個gcc command作為例子:

0
gcc -o hello_make main.o

這段指令是說透過連結main.o產生一個hello_make的執行檔,但我們沒有main.o呀?

所以我們需要先編譯main.c來產生main.o,透過先下這個指令:

0
gcc -c main.c

上述的兩個步驟隱含了hello_make、main.o和main.c的關係:

  1. 所以為了產生hello_make(target),我們需要main.o(dependency)
  2. 而因為需要main.o(target),我們需要main.c(dependency)

這種目標跟相依性的概念就是Makefile的核心!

上面這個指令在等價於Makefile的這種寫法

0
1
2
3
4
all: main.o
gcc -o hello_make main.o

main.o: main.c
gcc -c main.c

這樣有沒有比較有概念了呢?

比較了解Makefile是什麼後,來正式看一下他的格式

0
1
2
3
4
# comment in Makefile
<target>: <dependiency1> <dependiency2> <dependiency3>...
# Note that makefile use tab!!! not space!!!
<rule1>
<rule2>

  1. 一個Makefile可以有很多個target,在下make預設會找第一個target作為最終目標
    • 如果要執行特定的target可以下make <target>command
  2. 如果要執行的target的相依性項目不存在,則繼續往下看其他的target來產生所需的檔案

最後,一些Makefile常用小知識:

  • Makefile是使用Tab而不是空格,所以如果編譯器有將Tab自動轉成4個空格的,打美蝶斯!
  • $是變數的意思,所以可以透過變數設定來控制我們的rule
    • 由於$是變數,所以如果要執行shell command會使用兩個錢錢符號
  • %是萬用匹配符號,例如%.o: %.c就是將所有.o檔對應的.c file作為dependency
  • 以及其他許多噁心的符號:
    • $@: rule當前的target name
    • $<: rule當前的(第一個)dependency name
    • $*: rule當前的dependency name,但不含副檔名
    • $?: rule當前比targets還新的dependencies

瞭解了這些就可以開始去找網路上的Makefile練功打怪了,詳細的教學不是本篇的重點,主要是讓大家對Makefile有個基本概念,有興趣的也可以去看References的相關文章繼續學習唷。

番外篇: 要如何遞迴make所有子目錄?

Makefile其實是可以進入某個目錄然後去執行該目錄下的make的,使用“-C”參數即可,例如:

0
make -C subdirectory

他就會先進到subdirectory這個資料夾中去執行make,然後再回來繼續執行這一層的make

可是如果這些資料夾名稱我都要一個一個手動填也是很麻煩,要如何自動取得底下所有資料夾,然後讓他一個一個自己進去make呢?搭配shell command來達成:

0
1
2
3
4
SUBDIRS = $(shell ls -d */)
all:
for dir in $(SUBDIRS) ; do \
make -C $$dir ; \
done

搞定!

Linux Kernel的Makefile: Kbuild Makefile

Kbuild是Kernel build的縮寫,為了方便對kernel進行編譯而提供了一些特別的功能。

Linux Kbuild主要有四個部分:

  • .config: kernel相關設置
  • arch/$(ARCH)/Makefile: 與架構相關的Makefile
  • scripts/Makefile.*: Kbuild的一些規則
  • 根目錄下的Makefile以及其他資料夾下的Makefiles

Kbuild的用法相當簡單,對於一個driver的撰寫者最常會碰到的就2種:

  • obj-y: 將module編進kernel中
  • obj-m: 將module編成.ko檔,以便之後可以進行載入

其他還有產生library的lib-y、產生executable的hostprogs-y…有興趣的再自己去看

那Kbuild要怎麼進入一個資料夾呢? 透過obj-$(CONFIG_) += directory/的方式

  • 在kernel code裡面較少使用上面番外篇的遞迴形式來跑所有的子目錄,因為通常kernel的Makefile還會搭配一堆config,這些config可以讓你選擇這次編譯時要將哪些module編進來哪些不要,所以會需要一個一個資料夾的方式條列在Makefile內

實際來看一了例子吧!kernel 4.14的/drivers/clk/Makefile

0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# /drivers/clk/Makefile

# SPDX-License-Identifier: GPL-2.0
# common clock types
obj-$(CONFIG_HAVE_CLK) += clk-devres.o clk-bulk.o
obj-$(CONFIG_CLKDEV_LOOKUP) += clkdev.o
obj-$(CONFIG_COMMON_CLK) += clk.o
obj-$(CONFIG_COMMON_CLK) += clk-divider.o
obj-$(CONFIG_COMMON_CLK) += clk-fixed-factor.o
...

# hardware specific clock types
# please keep this section sorted lexicographically by file path name
obj-$(CONFIG_MACH_ASM9260) += clk-asm9260.o
obj-$(CONFIG_COMMON_CLK_AXI_CLKGEN) += clk-axi-clkgen.o
obj-$(CONFIG_ARCH_AXXIA) += clk-axm5516.o
obj-$(CONFIG_COMMON_CLK_CDCE706) += clk-cdce706.o
obj-$(CONFIG_COMMON_CLK_CDCE925) += clk-cdce925.o
obj-$(CONFIG_ARCH_CLPS711X) += clk-clps711x.o
obj-$(CONFIG_COMMON_CLK_CS2000_CP) += clk-cs2000-cp.o
obj-$(CONFIG_ARCH_EFM32) += clk-efm32gg.o
...


# please keep this section sorted lexicographically by directory path name
obj-$(CONFIG_COMMON_CLK_AT91) += at91/
obj-$(CONFIG_ARCH_ARTPEC) += axis/
obj-$(CONFIG_ARC_PLAT_AXS10X) += axs10x/
obj-y += bcm/
obj-$(CONFIG_ARCH_BERLIN) += berlin/
obj-$(CONFIG_H8300) += h8300/
...

透過CONFIG_是m或是y來決定kernel要怎麼去編譯這些modules,而這些CONFIG_是透過Kconfig來進行設置,最後記錄在.config內。

Kernel各個driver下的Makefile幾乎都長的差不多這樣,甚至比外面琳瑯滿目的Makefile還相對單純,所以看一兩個就熟悉了。

Linux Makefile Code Trace

最後,既然是Makefile,總該有個進入點吧?

想像上應該是對根目錄的Makefile下make相關指令,然後應該有一個地方會定義需要被一同編譯進去的子資料夾。

稍微來trace一下根目錄下的Makefile:

  1. 第一個target在_all
    120
    121
    122
    # That's our default target when none is given on the command line
    PHONY := _all
    _all:
  2. line 194: _all依賴all,這段就不放code了
  3. all依賴vmlinux
    619
    620
    621
    622
    623
    # The all: target is the default when no target is given on the
    # command line.
    # This allow a user to issue only 'make' to build a kernel including modules
    # Defaults to vmlinux, but the arch makefile usually adds further targets
    all: vmlinux
  4. vmlinux依賴四個項目: scripts/link-vmlinux.sh, vmlinux_prereq, $(vmlinux-deps), FORCE
    1004
    1005
    vmlinux: scripts/link-vmlinux.sh vmlinux_prereq $(vmlinux-deps) FORCE
    +$(call if_changed,link-vmlinux)
  5. 其中,$(vmlinux-deps)依賴$(vmlinux-dirs),$(vmlinux-dirs)定義了所有應該被編譯進kernel的folder。後續的細節就不追了,有興趣可以參考linux kernel Makefile编译流程分析
    951
    952
    953
    vmlinux-dirs	:= $(patsubst %/,%,$(filter %/, $(init-y) $(init-m) \
    $(core-y) $(core-m) $(drivers-y) $(drivers-m) \
    $(net-y) $(net-m) $(libs-y) $(libs-m) $(virt-y)))

最後,介紹一下關於FORCE的用法:

實際上他就是一個沒有dependency和rule的target,在Makefile中如果依賴和指令都為空的target每次都需要強制生成,所以對於每次都需要強制重新產生的target中可以在最後加上FORCE的項目。

1714
1715
PHONY += FORCE
FORCE:

References


>