Linux頂層Makefile分析

http://gong301.blog.163.com/blog/static/18388590720117249946243/


[摘要由於Linux的獨特優勢,使越來越多的企業和科研機構把目光轉向Linux的開發和研究上。目前Linux最新的穩定內核版本爲2.6.17,但是當今絕大部分對於Linux Makefile的介紹文章都是基於2.4內核的,可以說關於2.6內核Makefile相關的文章鳳毛麟角,筆者抽時間完成了這篇分析文章,讓讀者迅速熟悉Linux最新Makefile體系,從而加深對內核的理解,同時也希望能對Linux在公司的推廣起到一定的推動作用,算是拋磚引玉吧!

                        

1          Makefile組織層次

LinuxMake體系由如下幾部分組成:

?         頂層Makefile

頂層Makefile通過讀取配置文件,遞歸編譯內核代碼樹的相關目錄,從而產生兩個重要的目標文件:vmlinux和模塊。

?         內核相關Makefile

位於arch/$(ARCH) 目錄下,爲頂層Makefile提供與具體硬件體協結構相關的信息。

?         公共編譯規則定義文件。

包括Makefile.build Makefile.cleanMakefile.libMakefile.host等文件組成。這些文件位於scripts目錄中,定義了編譯需要的公共的規則和定義。

?         內核配置文件 .config

通過調用make menuconfig或者make xconfig命令,用戶可以選擇需要的配置來生成期望的目標文件。

?         其他Makefile

主要爲整個Makefile體系提供各自模塊的目標文件定義,上層Makefile根據它所定義的目標來完成各自模塊的編譯。

 

2          Makefile的使用

在編譯內核之前,用戶必須首先完成必要的配置。Linux內核提供了數不勝數的功能,支持衆多的硬件體系結構,這就需要用戶對將要生成的內核進行裁減。內核提供了多種不同的工具來簡化內核的配置,最簡單的一種是字符界面下命令行工具:

make config

這個工具會依次遍歷內核所有的配置項,要求用戶進行逐項的選擇配置。這個工具會耗費用戶太多時間,除非萬不得以(你的編譯主機不支持其他配置工具)一般不建議使用。

用戶還可以使用利用ncurse庫編制的圖形界面工具,這就是大名鼎鼎的:

make menuconfig

相信以前對2.4內核比較熟悉的用戶一定不會陌生。當然在2.6內核中提供了更漂亮和方便的基於X11的圖形配置工具:

make xconfig

當用戶使用這個工具對Linux內核進行配置時,界面下方會出現這個配置項相關的幫助信息和簡單描述,當你對內核配置選項不太熟悉時,建議你使用這個工具來進行內核配置。

當用戶完成配置後,配置工具會自動生成.config文件,它被保存在內核代碼樹的根目錄下。用戶可以很容易找到它,當然用戶也可以直接對這個文件進行簡單的修改。但是當你修改過配置文件之後,你必須通過下面的命令來驗證和更新配置:

make oldconfig

2.4版本的不同之處在於,用戶不需要顯示的調用make dep命令來生成依賴文件,內核會自動維護代碼間的依賴關係。

當一切工作完成以後,用戶只需要簡單鍵入make,剩下所有的工作makefile就會自動替你完成了。

3          Makefile編譯流程

   當用戶使用LinuxMakefile編譯內核版本時,Makefile的編譯流程如下:

?         使用命令行或者圖形界面配置工具,對內核進行裁減,生成.config配置文件

?         保存內核版本信息到 include/linux/version.h

?         產生符號鏈接 include/asm,指向實際目錄 include/asm-$(ARCH)

?         爲最終目標文件的生成進行必要的準備工作

?         遞歸進入 /init /core /drivers /net /lib等目錄和其中的子目錄來編譯生成所有的目標文件

?         鏈接上述過程產生的目標文件生成vmlinuxvmlinux存放在內核代碼樹的根目錄下

?         最後根據 arch/$(ARCH)/Makefile文件定義的後期編譯的處理規則建立最終的映象bootimage,包括創建引導記錄、準備initrd映象和相關處理

4          Makefile關鍵規則和定義描述

1)        目標定義

        目標定義是Makefile文件的核心部分,目標定義通知Makefile需要生成哪些目標文件、如何根據特殊的編譯選項鍊接目標文件,同時控制哪些子目錄要遞歸進入進行編譯。

        這個例子Makefile文件位於/fs/ext2目錄 :

#

# Makefile for the linux ext2-filesystem routines.

#

 

obj-$(CONFIG_EXT2_FS) += ext2.o

 

ext2-y := balloc.o bitmap.o dir.o file.o fsync.o ialloc.o inode.o /

                      ioctl.o namei.o super.o symlink.o

 

ext2-$(CONFIG_EXT2_FS_XATTR)   += xattr.o xattr_user.o xattr_trusted.o

ext2-$(CONFIG_EXT2_FS_POSIX_ACL) += acl.o

ext2-$(CONFIG_EXT2_FS_SECURITY)     += xattr_security.o

ext2-$(CONFIG_EXT2_FS_XIP) += xip.o

這表示與ext2相關的目標文件由 ext2-y定義的文件列表組成,其中ext2-$(*)是由內核配置文件.config中的配置項決定,最終Makefile會在這個目錄下統一生成一個目標文件ext2.o(由obj-$(CONFIG_EXT2_FS)決定)。其中obj-y表示爲生成vmlinux文件所需要的目標文件集合,具體的文件依賴於內核配置。

Makefile會編譯所有的$(obj-y)中定義的文件,然後調用鏈接器將這些文件鏈接到built-in.o文件中。最終built-in.o文件通過頂層Makefile鏈接到vmlinux中。值得注意的是$(obj-y)的文件順序很重要。列表文件可以重複,文件第一次出現時將會鏈接到built-in.o中,後來出現的同名文件將會被忽略。文件順序直接決定了他們被調用的順序,這一點讀者需要特別注意。

讀者可能會在某些Makefile中發現lib-y定義,所有包含在lib-y定義中的目標文件都將會被編譯到該目錄下一個統一的庫文件中。值得注意的是lib-y定義一般被限制在 libarch/$(ARCH)/lib 目錄中。

體系makefile文件和頂層makefile文件共同定義瞭如何建立vmlinux文件的規則。

     $(head-y) 列舉首先鏈接到vmlinux的對象文件。
     $(libs-y) 
列舉了能夠找到lib.a文件的目錄。
     
其餘的變量列舉了能夠找到內嵌對象文件的目錄。
     $(init-y) 
列舉的對象位於$(head-y)對象之後。
     
然後是如下位置順序:
     $(core-y), $(libs-y), $(drivers-y) 
 $(net-y)
     
頂層makefile定義了所有通用目錄,arch/$(ARCH)/Makefile文件只需增加體系相關的目錄。
     
例如: #arch/i386/Makefile
           libs-y                   += arch/i386/lib/

core-y                   += arch/i386/kernel/ /

                                           arch/i386/mm/ /

                                           arch/i386/$(mcore-y)/ /

                                           arch/i386/crypto/

drivers-$(CONFIG_MATH_EMULATION) += arch/i386/math-emu/

drivers-$(CONFIG_PCI)            += arch/i386/pci/

           …………………………………………

 

2)        目錄遞歸

Makefile文件只負責當前目錄下的目標文件,子目錄中的文件由子目錄中的makefile負責編譯,編譯系統使用obj-y  obj-m來自動遞歸編譯各個子目錄中的文件。

對於fs/Makefile:

obj-$(CONFIG_EXT2_FS) += ext2/

如果在內核配置文件.config中,CONFIG_EXT2_FS被設置爲y或者m,則內核makefile會自動進入ext2目錄來進行編譯。內核Makefile只使用這些信息來決定是否需要編譯這個目錄,子目錄中的makefile規定哪些文件編譯爲模塊,哪些文件編譯進內核。

3)        依賴關係

Linux Makefile通過在編譯過程中生成的 .文件名.o.cmd(比如對於main.c文件,它對應的依賴文件名爲.main.o.cmd)來定義相關的依賴關係。

一般文件的依賴關係由如下部分組成:

?         所有的前期依賴文件(包括所有相關的*.c  *.h

?         所有與CONFIG_選項相關的文件

?         編譯目標文件所使用到的命令行

        位於init目錄下的main.c文件的依賴文件.main.o.cmd內容如下,讀者可以結合起來理解上述文件依賴關係的三個組成部分:

cmd_init/main.o := gcc -m32 -Wp,-MD,init/.main.o.d  -nostdinc -isystem /usr/lib/gcc-lib/i386-redhat-linux/3.2.2/include -D__KERNEL__ -Iinclude -Iinclude2 -I/home/linux/linux-2.6.17.11/include -include include/linux/autoconf.h -I/home/linux/linux-2.6.17.11/init -Iinit -Wall -Wundef -Wstrict-prototypes -Wno-trigraphs -fno-strict-aliasing -fno-common -Os -fomit-frame-pointer -pipe -msoft-float -mpreferred-stack-boundary=2 -march=i686 -mcpu=pentium4 -mregparm=3 -ffreestanding -I/home/linux/linux-2.6.17.11/include/asm-i386/mach-default -Iinclude/asm-i386/mach-default  -D"KBUILD_STR(s)=/#s" -D"KBUILD_BASENAME=KBUILD_STR(main)"  -D"KBUILD_MODNAME=KBUILD_STR(main)" -c -o init/.tmp_main.o /home/linux/linux-2.6.17.11/init/main.c

deps_init/main.o := /

                /home/linux/linux-2.6.17.11/init/main.c /

                $(wildcard include/config/x86/local/apic.h) /

$(wildcard include/config/acpi.h) /

由於篇幅的關係,此處略去一些定義

……………………………………..

include2/asm/mpspec_def.h /

           /home/linux/linux-2.6.17.11/include/asm-i386/mach-default/mach_mpspec.h /

           include2/asm/io_apic.h /

           include2/asm/apic.h /

 

init/main.o: $(deps_init/main.o)

 

$(deps_init/main.o):

       

4)        特殊規則

特殊規則使用在內核編譯需要規則定義而沒有相應定義的時候。典型的例子如編譯時頭文件的產生規則。其他例子有體系makefile編譯引導映像的特殊規則。特殊規則寫法同普通的makefile規則。
     
編譯程序在makefile所在的目錄不能被執行,因此所有的特殊規則需要提供前期文件和目標文件的相對路徑。
     
定義特殊規則時將使用到兩個變量:
     $(src)
 $(src)是對於makefile文件目錄的相對路徑,當使用代碼樹中的文件時

使用該變量$(src)
     $(obj)
 $(obj)是目標文件目錄的相對路徑。生成文件使用$(obj)變量。
     
例如: #drivers/scsi/Makefile
     $(obj)/53c8xx_d.h: $(src)/53c7,8xx.scr $(src)/script_asm.pl 
        $(CPP) -DCHIP=810 - < $< | ... $(src)/script_asm.pl
     
這就是使用普通語法的特殊編譯規則。
     
目標文件依賴於兩個前提文件。目標文件的前綴是$(obj), 前提文件的前綴是

$(src)(因爲它們不是生成文件)

5)        引導映象

體系makefile文件定義了編譯vmlinux文件的目標對象,將它們壓縮和封裝成引導代碼,並複製到合適的位置。這包括各種安裝命令。在LinuxMakefile無法爲所有的體系結構提供標準化的方法,因此常需要具體硬件體系結構下makefile提供附加處理規則。
     
附加處理過程常位於arch/$(ARCH)/下的boot/目錄。
     
內核編譯體系無法在boot/目錄下提供一種便捷的方法創建目標系統文件。因此arch/$(ARCH)/Makefile要調用make命令在boot/目錄下建立目標系統文件。建議使用的方法是在arch/$(ARCH)/Makefile中設置調用,並且使用完整路徑引用arch/$(ARCH)/boot/Makefile
     
例如: #arch/i386/Makefile
           boot := arch/i386/boot
           bzImage: vmlinux 
              $(Q)$(MAKE) $(build)=$(boot) $(boot)/$@
     
建議使用"$(Q)$(MAKE) $(build)=<dir>"方式在子目錄中調用make命令。
     
當執行不帶參數的make命令時,將首先編譯第一個目標對象。在頂層makefile中第一個目標對象是all:
     
一個體繫結構需要定義一個默認的可引導映像。
     
增加新的前提文件給all目標可以設置不同於vmlinux的默認目標對象。
     
例如: #arch/i386/Makefile
           all: bzImage 
     
當執行不帶參數的"make"命令時,bzImage文件將被編譯。

6)        常用編譯命令

if_changed
        如果必要,執行傳遞的命令。
        用法
          $(builtin-target): $(obj-y) FORCE

$(call if_changed,link_o_target)
        當這條規則被使用時它將檢查哪些文件需要更新,或命令行被改變。後面這種情況將迫使

重新編譯編譯選項被改變的執行文件。使用if_changed的目標對象必須列舉在$( builtin-target)中,否則命令行檢查將失敗,目標一直會編譯。

 

if_changed_dep

如果必要,執行傳遞的命令並更新依賴文件。

用法:

%.o: %.S FORCE

       $(call if_changed_dep,as_o_S)

當這條規則被使用時它將檢查哪些文件需要更新,或命令行被改變。同時它會重新檢測依賴關係的改變並將生成新的依賴文件。這是與if_changed命令的區別。

 

7)        定製命令

        當正常執行帶編譯命令時命令的簡短信息會被顯示(要想顯示詳細的命令,請在命令行中加入V1)。要讓定製命令具有這種功能需要設置兩個變量:
      quiet_cmd_<command> - 將被顯示的內容 
      cmd_<command>      - 被執行的命令
      例如: #
           quiet_cmd_image = BUILD   $@ 
            cmd_image = $(obj)/tools/build $(BUILDFLAGS) / 
                    $(obj)/vmlinux.bin > $@
           targets += bzImage
           $(obj)/bzImage: $(obj)/vmlinux.bin $(obj)/tools/build FORCE 
                $(call if_changed,image)
                @echo 'Kernel: $@ is ready'
     執行make命令編譯$(obj)/bzImage目標時將顯示:
     BUILD   arch/i386/boot/bzImage

8)        預處理鏈接腳本

當編譯vmlinux映像時將使用arch/$(ARCH)/kernel/vmlinux.lds鏈接腳本。
        
相同目錄下的vmlinux.lds.S文件是這個腳本的預處理的變體。內核編譯系統知曉.lds

文件。並使用規則*lds.S -> *lds
        
例如: #arch/i386/kernel/Makefile
           always := vmlinux.lds
           #Makefile
           export CPPFLAGS_vmlinux.lds += -P -C -U$(ARCH)
        $(always)
賦值語句告訴編譯系統編譯目標是vmlinux.lds$(CPPFLAGS_vmlinux.lds)

賦值語句告訴編譯系統編譯vmlinux.lds目標的編譯選項。
        
編譯*.lds時將使用到下面這些變量:
        CPPFLAGS      : 
定義在頂層Makefile
        EXTRA_CPPFLAGS      : 
可以設置在編譯的makefile文件中
        CPPFLAGS_$(@F) : 
目標編譯選項。注意要使用文件全名。

9)        主機輔助程序的編譯

內核編譯系統支持在編譯階段編譯主機可執行程序。爲了使用主機程序需要兩個步驟:第一個步驟使用hostprogs-y變量告訴內核編譯系統有主機程序可用。第二步給主機程序添加潛在的依賴關係。有兩種方法,在規則中增加依賴關係或使用$(always)變量。這一部分的內容相對於其他內核文件的編譯要簡單的多,感興趣的讀者可以參考scripts/Makefile.build中的相關內容。

10)     Clean機制

clean命令清除在編譯內核生成的大部分文件,例如主機程序,列舉在 $(hostprogs-y)$(hostprogs-m)$(always)$(extra-y)$(targets)中目標文件都將被刪除。代碼目錄數中的"*.[oas]""*.ko"文件和一些由編譯系統產生的附加文件也將被刪除。
  
附加文件可以使用$(clean-files)進行定義。
     
例如: #drivers/pci/Makefile
           clean-files := devlist.h classlist.h
當執行"make clean"命令時, "devlist.h classlist.h"兩個文件將被刪除。內核編譯系統默認這些文件與makefile具有相同的相對路徑,否則需要設置以'/'開頭的絕對路徑。
  
刪除整個目錄使用以下方式:
  
例如: #scripts/package/Makefile
     clean-dirs := $(objtree)/debian/
  
這樣就將刪除包括子目錄在內的整個debian目錄。如果不使用以'/'開頭的絕對路徑內核編譯系統見默認使用相對路徑。
  
通常內核編譯系統根據"obj-* := dir/"進入子目錄,但是在體系makefile中需要顯式使用如下方式:
     
例如: #arch/i386/boot/Makefile
           subdir- := compressed/
  
上面賦值語句指示編譯系統執行"make clean"命令時進入compressed/目錄。
  
在編譯最終的引導映像文件的makefile中有一個可選的目標對象名稱是archclean
     
例如: #arch/i386/Makefile
           archclean: 
              $(Q)$(MAKE) $(clean)=arch/i386/boot
  
當執行"make clean"時編譯器進入arch/i386/boot並象通常一樣工作。arch/i386/boot       中的makefile文件可以使用subdir-標識進入更下層的目錄。
  
注意1: arch/$(ARCH)/Makefile不能使用"subdir-",因爲它被包含在頂層makefile文件中,在這個位置編譯機制是不起作用的。
  
注意2: 所有列舉在core-ylibs-ydrivers-ynet-y中的目錄將被"make clean"命令清除。

小結

隨着Linux的飛速發展,越來越多的開發人員將關注的焦點集中到Linux的研究和開發上。如果想對Linux內核進行研究和開發,就必須首先熟悉Linux 內核Makefile的組織和編譯過程。目前Linux最新的穩定內核版本爲2.6.17,但是當今絕大部分對於Linux Makefile的介紹都是基於2.4內核的,可以說關於2.6內核Makefile相關的文章鳳毛麟角,我特意抽時間完成了這篇分析文章,讓讀者迅速熟悉Linux最新Makefile體系,從而加深對內核的理解,同時也希望能對Linux在公司的推廣起到一定的推動作用。

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