linux內核makefile概覽
本博客參照內核官方英文文檔
linux的內核makefile主要用於編譯整個內核源碼,按照用戶的需求生成各種目標文件,對於用戶來說,編譯內核時非常簡單的,只需要幾個指令就可以做到,但是對於一個驅動開發者而言,了解內核源碼的編譯機制是非常必要的。
make 和 makefile
需要了解的是:make是linux下的一個程序軟件,makefile相當於針對make程序的配置文件,當我們執行make命令時,make將會在當前目錄尋找Makefile文件,然后根據Makefile的配置對源文件進行編譯。
linux內核源代碼的編譯也是使用make工具和makefile,但是它在普通的C程序編譯的基礎上對配置和編譯選項進行了擴展,這就是kbuild系統,專門針對linux的內核編譯,使得linux內核的編譯更加簡潔而高效。
linux的內核鏡像文件
首先我們需要認識一下linux內核鏡像的各種形式,畢竟編譯內核最主要的目的就是生成內核鏡像,它有幾種形式:vmlinux、vmlinux.bin、vmlinuz、zImage、bzImage。
- vmlinux:這是編譯linux內核直接生成的原始鏡像文件,它是由靜態鏈接之后生成的可執行文件,但是它一般不作為最終的鏡像使用,不能直接boot啟動,用於生成vmlinuz,可以debug的時候使用。
- vmlinux.bin:與vmlinux相同,但采用可啟動的原始二進制文件格式。丟棄所有符號和重定位信息。通過objcopy -O binary vmlinux vmlinux.bin從vmlinux生成。
- vmlinuz:由vmlinux經過gzip(也可以是bzip)壓縮而來,同時在vmlinux的基礎上進一步添加了啟動和解壓縮代碼,是可以引導boot啟動內核的最終鏡像。vmlinuz通常被放置在/boot目錄,/boot目錄下存放的是系統引導需要的文件,同時vmlinuz文件解壓出的vmlinux不帶符號表的目標文件,所以一般/boot目錄下會帶一個符號表System.map文件。
- zImage:這是小內核的舊格式,有指令make zImage生成,僅適用於只有640K以下內存的linux kernel文件。
- bzImage: big zImage,需要注意的是這個bz與bzip沒有任何關系,適用於更大的linux kernel文件。現代處理器的linux鏡像都是生成bzImage文件,同時,vmlinuz和bzImage是同一類型的文件,一般情況下這個和vmlinuz是同一個東西。
對於這一系列的生成文件可以參考官方文檔
kbuild系統
各種各樣的makeifle文件
在linux中,由於內核代碼的分層模型,以及兼容很多平台的特性,makefile文件分布在各個目錄中,對每個模塊進行分離編譯,降低耦合性,使編譯方式更加靈活。
makefile主要是以下五個部分:
- 頂層makefile : 在源代碼的根目錄有個頂層makefile,頂層makefile的作用就是負責生成兩個最重要的部分:編譯生成vmlinux和各種模塊。
- .config文件 : 這個config文件主要是產生自用戶對內核模塊的配置,有三種配置方式:
- 編譯進內核
- 編譯成可加載模塊
- 不進行編譯。
- arch/$(ARCH)/Makefile : 從目錄可以看出,這個makefile主要是根據指定的平台對內核鏡像進行相應的配置,提供平台信息給頂層makefile。
- scirpts/makefile. : *這些makefile配置文件包含了構建內核的規則。
- kbuild makefiles : 每一個模塊都是單獨被編譯然后再鏈接的,所以這一種kbiuld makefile幾乎在每個模塊中都存在.在這些模塊文件(子目錄)中,也可以使用Kbuild文件代替Makefile,當兩者同時存在時,優先選擇Kbuild文件進行編譯工作,只是用戶習慣性地使用Makefile來命名。
kbuild makefile
編譯進內核的模塊
如果需要將一個模塊配置進內核,需要在makefile中進行配置:
obj-y += foo.o
將foo.o編譯進內核,根據make的自動推導原則,make將會自動將foo.c編譯成foo.o。
上述方式基本上用於開發時的模塊單獨編譯,當需要一次編譯整個內核時,通常是在top makefile中這樣寫:
obj-$(CONFIG_FOO) += foo.o
在.config文件中將CONFIG_FOO變量配置成y,當需要修改模塊的編譯行為時,就可以統一在配置文件中修改,而不用到makefile中去找。
kbuild編譯所有的obj-y的文件,然后調用$(AR) rcSTP將所有被編譯的目標文件進行打包,打包成build-in.o文件,需要注意的是這僅僅是一份壓縮版的存檔,這個目標文件里面並不包含符號表,既然沒有符號表,它就不能被鏈接。
緊接着調用scripts/link-vmlinux.sh,將上面產生的不帶符號表的目標文件添加符號表和索引,作為生成vmlinux鏡像的輸入文件,鏈接生成vmlinux。
對於這些被編譯進內核的模塊,模塊排列的順序是有意義的,允許一個模塊被重復配置,系統將會取用第一個出現的配置項,而忽略隨后出現的配置項,並不會出現后項覆蓋前項的現象。
鏈接的順序同時也是有意義的,因為編譯進內核的模塊通常由xxx_initcall()來描述,內核對這些模塊分了相應的初始化優先級,相同優先級的模塊初始化函數將會被依次放置在同一個段中,而這些模塊執行的順序就取決於放置的先后順序,由鏈接順序所決定。
linux的initcall機制可以參考另一篇博客:linux的initcall機制
編譯可加載的模塊
所有在配置文件中標記為-m的模塊將被編譯成可加載模塊.ko文件。
如果需要將一個模塊配置為可加載模塊,需要在makefile中進行配置:
obj-m += foo.o
同樣的,通常可以寫成這樣的形式:
obj-$(CONFIG_FOO) += foo.o
在.config文件中將CONFIG_FOO變量配置成m,在配置文件中統一控制,編譯完成時將會在當前文件夾中生成foo.ko文件,在內核運行時使用insmod或者是modprobe指令加載到內核。
模塊編譯依賴多個文件
通常的,驅動開發者也會將單獨編譯自己開發的驅動模塊,當一個驅動模塊依賴多個源文件時,需要通過以下方式來指定依賴的文件:
obj-m += foo.o
foo-y := a.o b.o c.o
foo.o 由a.o,b.o,c.o生成,然后調用$(LD) -r 將a.o,b.o,c.o鏈接成foo.o文件。
同樣地,makefile支持以變量的形式來指定是否生成foo.o,我們可以這樣:
obj-$(CONFIG_FOO) += foo.o
foo-$(CONFIG_FOO_XATTR) += a.o b.o c.o
根據CONFIG_FOO_XATTR的配置屬性來決定是否生成foo.o,然后根據CONFIG_FOO屬性來決定將foo.o模塊編入內核還是作為模塊。
makefile目錄層次關系的處理
需要理解的一個原則就是:一個makefile只負責處理本目錄中的編譯關系,自然地,其他目錄中的文件編譯由其他目錄的makefile負責,整個linux內核的makefile組成一個樹狀結構,對於上層makefile的子目錄而言,只需要讓kbuild知道它應該怎樣進行遞歸地進入目錄即可。
kbuild利用目錄指定的方式來進行目錄指定操作,舉個例子:
obj-$(CONFIG_FOO) += foo/
當CONFIG_FOO被配置成y或者m時,kbuild就會進入到foo/目錄中,但是需要注意的是,這個信息僅僅是告訴kbuild應該進入到哪個目錄,而不對其目錄中的編譯做任何指導。
編譯選項
*** 需要注意的是,在之前的版本中,編譯的選項由EXTRA_CFLAGS, EXTRA_AFLAGS和 EXTRA_LDFLAGS修改成了ccflags-y asflags-y和ldflags-y. ***
ccflags-y asflags-y和ldflags-y
ccflags-y asflags-y和ldflags-y這三個變量的值分別對應編譯、匯編、鏈接時的參數。
同時,所有的ccflags-y asflags-y和ldflags-y這三個變量只對有定義的makefile中使用,簡而言之,這些flag在makefile樹中不會有繼承效果,makefile之間相互獨立。
subdir-ccflags-y, subdir-asflags-y
這兩個編譯選項與ccflags-y和asflags-y效果是一致的,只是添加了subdir-前綴,意味着這兩個編譯選項對本目錄和所有的子目錄都有效。
CFLAGS_$@, AFLAGS_$@
使用CFLAGS_或者AFLAGS_前綴描述的模塊可以為模塊的編譯單獨提供參數,舉個例子:
CFLAGS_foo.o = -DAUTOCONF
在編譯foo.o時,添加了-DAUTOCONF編譯選項。
kbuild中的變量
頂層makefile中定義了以下變量:
KERNELRELEASE
這是一個字符串,用於構建安裝目錄的名字(一般使用版本號來區分)或者顯示當前的版本號。
ARCH
定義當前的目標架構平台,比如:"X86","ARM",默認情況下,ARCH的值為當前編譯的主機架構,但是在交叉編譯環境中,需要在頂層makefile或者是命令行中指定架構:
make ARCH=arm ...
INSTALL_PATH
指定安裝目錄,安裝目錄主要是為了放置需要安裝的鏡像和map(符號表)文件,系統的啟動需要這些文件的參與。
INSTALL_MOD_PATH, MODLIB
INSTALL_MOD_PATH:為模塊指定安裝的前綴目錄,這個變量在頂層makefile中並沒有被定義,用戶可以使用,MODLIB為模塊指定安裝目錄.
默認情況下,模塊會被安裝到$(INSTALL_MOD_PATH)/lib/modules/$(KERNELRELEASE)中,默認INSTALL_MOD_PATH不會被指定,所以會被安裝到/lib/modules/$(KERNELRELEASE)中。
INSTALL_MOD_STRIP
如果這個變量被指定,模塊就會將一些額外的、運行時非必要的信息剝離出來以縮減模塊的大小,當INSTALL_MOD_STRIP為1時,--strip-debug選項就會被使用,模塊的調試信息將被刪除,否則就執行默認的參數,模塊編譯時會添加一些輔助信息。
這些全局變量一旦在頂層makefile中被定義就全局有效,但是有一點需要注意,在驅動開發時,一般編譯單一的模塊,執行make調用的是當前目錄下的Makefile.
在這種情況下這些變量是沒有被定義的,只有先調用了頂層makefile之后,這些變量在子目錄中的makefile才被賦值。
生成header文件
vmlinux中打包了所有模塊編譯生成的目標文件,在驅動開發者眼中,在內核啟動完成之后,它的作用相當於一個動態庫,既然是一個庫,如果其他開發者需要使用里面的接口,就需要相應的頭文件。
自然地,build也會生成相應的header文件供開發者使用,一個最簡單的方式就是用下面這個指令:
make headers_install ARCH=arm INSTALL_HDR_PATH=/DIR
ARCH:指定CPU的體系架構,默認是當前主機的架構,可以使用以下命令查看當前源碼支持哪些架構:
ls -d include/asm-* | sed 's/.*-//'
INSTALL_HDR_PATH:指定頭文件的放置目錄,默認是./usr。
至此,build工具將在指定的DIR目錄生成基於arm架構的頭文件,開發者在開發時就可以引用這些頭文件。
小結
為了清晰地了解kbuild的執行,有必要對kbuild的執行過程做一下梳理:
- 根據用戶(內核)的配置生成相應的.config文件
- 將內核的版本號存入include/linux/version.h
- 建立指向 include/asm-$(ARCH) 的符號鏈接,選定平台
- 更新所有編譯所需的文件。
- 從頂層makefile開始,遞歸地訪問各個子目錄,對相應的模塊編譯生成目標文件
- 鏈接過程,在源代碼的頂層目錄鏈接生成vmlinux
- 根據具體架構提供的信息添加相應符號,生成最終的啟動鏡像,往往不同架構之間的啟動方式不一致。
- 這一部分包含啟動指令
- 准備initrd鏡像等平台相關的部分。
好了,關於linux內核編譯build系統的討論就到此為止啦,如果朋友們對於這個有什么疑問或者發現有文章中有什么錯誤,歡迎留言
關於linux可加載模塊編譯makefile介紹可參考另一篇博客:linux內核可加載模塊makefile簡述
原創博客,轉載請注明出處!
祝各位早日實現項目叢中過,bug不沾身.
