2011年9月27日 星期二

linux2.6 makefiles.txt學習及實例分析

linux2.6 makefiles.txt學習及實例分析 (2008-08-28 10:50)


本篇blog主要分為四部分,地一部分和第二部分主要是參考網上的文章,第三部分為自己在學習過程中總結的一些知識,第四部分想自己編寫一個簡單的Makefile,以鞏固學習成果!



本篇blog目的:通過對Makefile的學習,進一步理解linux內核如何通過makefile實現對make過程的自動化,掌握makefile語言編寫規則,最終實現自己能夠編寫出makefile檔。



本篇blog預計完成時間一個禮拜。



這篇文章主要是參考資料,不敢邀功,現給出原地址和參考書籍,感謝這兩位大蝦的慷慨分享。

參考文件:

http://forum.eepw.com.cn/forum/main?url=http%3A%2F%2Fbbs.edw.com.cn%2Fthread%2F128730%2F1

http://blog.chinaunix.net/u2/66601/showart_1150788.html Author:cyliu

《嵌入式linux系統開發技術詳解-基於arm》孫紀坤 張小全 人民郵電出版社

________________________________________

第一部分:對makefiles.txt的學習

內核目錄documention/kbuild/makefiles.txt中文版的翻譯

This document describes the Linux kernel Makefiles

=== 目錄

=== 1 概述

=== 2 用戶與作用

=== 3 Kbuild文件

--- 3.1 目標定義

--- 3.2 編譯進內核 - obj-y

--- 3.3 編譯可裝載模組 - obj-m

--- 3.4 輸出的符號

--- 3.5 目標庫檔 - lib-y

--- 3.6 遞迴躺下訪問目錄

--- 3.7 編輯標誌

--- 3.8 命令行的依賴關係(原文中沒有寫:-))

--- 3.9 跟蹤依賴

--- 3.10 特殊規則

--- 3.11 $(CC) 支援的函數



=== 4 本機程式支援

--- 4.1 簡單的本機程式

--- 4.2 複合的本機程式

--- 4.3 定義共用庫

--- 4.4 使用用C++編寫的本機程式

--- 4.5 控制本機程式的編譯選項

--- 4.6 編譯主機程式時

--- 4.7 使用 hostprogs-$(CONFIG_FOO)



=== 5 Kbuild清理



=== 6 架構Makefile

--- 6.1 調整針對某一具體架構生成的鏡像

--- 6.2 將所需文件加到 archprepare 中

--- 6.3 遞迴下向時要訪問的目錄列表

--- 6.4 具體架構的啟動鏡像

--- 6.5 構造非Kbuild目標

--- 6.6 構建啟動鏡像的命令

--- 6.7 Kbuild自定義命令

--- 6.8 聯接器預處理腳本



=== 7 Kbuild 變數

=== 8 Makefile語言

=== 9 關於作者

=== 10 TODO

________________________________________



=== 1 概述



Linux內核的Makefile分為5個部分:



Makefile 頂層Makefile

.config 內核配置檔

arch/$(ARCH)/Makefile 具體架構的Makefile

scripts/Makefile.* 通用的規則等。面向所有的Kbuild Makefiles。

kbuild Makefiles 內核源代碼中大約有500個這樣的檔



頂層Makefile閱讀的.config檔,而該檔是由內核配置程式生成的。



頂層Makefile負責制作:vmlinux(內核檔)與模組(任何模組檔)。製作的過程主要是通過遞迴向下訪問子目錄的形式完成。並根據內核配置檔確定訪問哪些子目錄。頂層Makefile要原封不動的包含一具體架構的Makefile(由頂層Makefile語句include $(srctree)/arch/$(ARCH)/Makefile指明),其名字類似於 arch/$(ARCH)/Makefile。該架構Makefile向頂層Makefile提供其架構的特別資訊。



每一個子目錄都有一個Kbuild Makefile檔,用來執行從其上層目錄傳遞下來的命令。Kbuild Makefile從.config檔中提取資訊,生成Kbuild完成內核編譯所需的檔列表。



scripts/Makefile.*包含了所有的定義、規則等資訊。這些檔被用來編譯基於kbuild Makefile的內核。(**有點不通**)



=== 2 用戶與作用



可以將人們與內核Makefile的關係分成4類。



*使用者* 編譯內核的人。他們只是鍵入"make menuconfig"或"make"這樣的命令。一般

情況下是不會讀或編輯任何內核Makefile(或者任何的原始檔案)。



*普通開發人員* 這是一群工作在內核某一功能上的人,比如:驅動開發,檔系統或

網路協定。他們所需要維護的只是他們所工作的子系統的Kbuild Makefile。為了提高

工作的效率,他們也需要對內核Makefile有一個全面的認識,並且要熟悉Kbuild的介面





*架構開發人員* 這是一些工作在具體架構,比如sparc 或者ia64,上面的人。架構開

發者需要在熟悉kbuild Makefile的同時,也要熟悉他所工作架構的Makefile。



*Kbuild開發者* 維護Kbuild系統的人。他們需要知曉內核Makefile的方方面面。



該文件是為普通開發人員與架構開發人員所寫。





=== 3 Kbuild文件



大部分內核中的Makefile都是使用Kbuild組織結構的Kbuild Makefile。這章介紹了

Kbuild Makefile的語法。

Kbuild檔傾向於"Makefile"這個名字,"Kbuild"也是可以用的。但如果"Makefile"

"Kbuild"同時出現的話,使用的將會是"Kbuild"檔。



3.1節 目標定義是一個快速介紹,以後的幾章會提供更詳細的內容以及實例。



--- 3.1 目標定義



目標定義是Kbuild Makefile的主要部分,也是核心部分。主要是定義了要編

譯的檔,所有的選項,以及到哪些子目錄去執行遞迴操作。



最簡單的Kbuild makefile 只包含一行:



例子:

obj-y += foo.o



該例子告訴Kbuild在這目錄裏,有一個名為foo.o的目標檔。foo.o將從foo.c

或foo.S檔編譯得到。



如果foo.o要編譯成一模組,那就要用obj-m了。所採用的形式如下:



例子:

obj-$(CONFIG_FOO) += foo.o



$(CONFIG_FOO)可以為y(編譯進內核) 或m(編譯成模組)。如果CONFIG_FOO不是y

和m,那麼該檔就不會被編譯聯接了。



--- 3.2 編譯進內核 - obj-y



Kbuild Makefile 規定所有編譯進內核的目標檔都存在$(obj-y)列表中。而

這些列表依賴內核的配置。



Kbuild編譯所有的$(obj-y)檔。然後,調用"$(LD) -r"將它們合併到一個

build-in.o文件中。稍後,該build-in.o會被其父Makefile聯接進vmlinux中。



$(obj-y)中的檔是有順序的。列表中有重複項是可以的:當第一個文件被聯

接到built-in.o中後,其餘文件就被忽略了。



聯接也是有順序的,那是因為有些函數(module_init()/__initcall)將會在啟

動時按照他們出現的順序進行調用。所以,記住改變聯接的順序可能改變你

SCSI控制器的檢測順序,從而導致你的硬碟資料損害。



例子:

#drivers/isdn/i4l/Makefile

# Makefile for the kernel ISDN subsystem and device drivers.

# Each configuration option enables a list of files.

obj-$(CONFIG_ISDN) += isdn.o

obj-$(CONFIG_ISDN_PPP_BSDCOMP) += isdn_bsdcomp.o



--- 3.3 編譯可裝載模組 - obj-m



$(obj-m) 列舉出了哪些檔要編譯成可裝載模組。



一個模組可以由一個檔或多個檔編譯而成。如果是一個原始檔案,Kbuild Makefile只需簡單的將其加到$(obj-m)中去就可以了。



例子:

#drivers/isdn/i4l/Makefile

obj-$(CONFIG_ISDN_PPP_BSDCOMP) += isdn_bsdcomp.o



注意:此例中 $(CONFIG_ISDN_PPP_BSDCOMP) 的值為'm'



如果內核模組是由多個原始檔案編譯而成,那你就要採用上面那個例子一樣的方法去聲明你所要編譯的模組。



Kbuild需要知道你所編譯的模組是基於哪些檔,所以你需要通過變數 $(-objs)來告訴它。



例子:

#drivers/isdn/i4l/Makefile

obj-$(CONFIG_ISDN) += isdn.o

isdn-objs := isdn_net_lib.o isdn_v110.o isdn_common.o



在這個例子中,模組名將是isdn.o,Kbuild將編譯在$(isdn-objs)中列出的所有檔,然後使用"$(LD) -r"生成isdn.o。



Kbuild能夠識別用於組成目標檔的尾碼-objs和尾碼-y。這就讓Kbuild

Makefile可以通過使用 CONFIG_ 符號來判斷該物件是否是用來組合物件的。



例子:

#fs/ext2/Makefile

obj-$(CONFIG_EXT2_FS) += ext2.o

ext2-y := balloc.o bitmap.o

ext2-$(CONFIG_EXT2_FS_XATTR) += xattr.o



在這個例子中,如果 $(CONFIG_EXT2_FS_XATTR) 是 'y',xattr.o將是複合物件 ext2.o的一部分。



注意:當然,當你要將其編譯進內核時,上面的語法同樣適用。所以,如果你的 CONFIG_EXT2_FS=y,那Kbuild會按你所期望的那樣,生成 ext2.o文件,然後將其聯接到 built-in.o中。



--- 3.4 輸出的符號



在Makefile中,沒有對模組輸出的符號有特殊要求。



--- 3.5 目標庫檔 - lib-y



在 obj-* 中所列檔是用來編譯模組或者是聯接到特定目錄中的 built-in.o。同樣,也可以列出一些將被包含在lib.a庫中的文件。在 lib-y 中所列出的檔用來組成該目錄下的一個庫檔。



在 obj-y 與 lib-y 中同時列出的檔,因為都是可以訪問的,所以該檔是不會被包含在庫檔中的。同樣的情況,lib-m 中的檔就要包含在lib.a庫文件中。



注意,一個Kbuild makefile可以同時列出要編譯進內核的檔與要編譯成庫的檔。所以,在一個目錄裏可以同時存在 built-in.o 與 lib.a 兩個文件。



例子:

#arch/i386/lib/Makefile

lib-y := chechsum.o delay.o



這將由 checksum.o 和delay.o 兩個檔創建一個庫檔 lib.a。為了讓 Kbuild 真正認識到這裏要有一個庫檔 lib.a 要創建,其所在的目錄要加到 libs-y 列表中。 還可參考"6.3 遞迴下向時要訪問的目錄列表" lib-y 使用一般限制在 lib/ 和 arch/*/lib 中。



--- 3.6 遞迴向下訪問目錄



一個Makefile只對編譯所在目錄的物件負責。在子目錄中的檔的編譯要由其所在的子目錄的Makefile來管理。只要你讓Kbuild知道它應該遞迴操作,那麼該系統就會在其子目錄中自動的調用 make 遞迴操作。



這就是 obj-y 和 obj-m 的作用。

ext2 被放的一個單獨的目錄下,在fs目錄下的Makefile會告訴Kbuild使用下面的賦值進行向下遞迴操作。



例子:

#fs/Makefile

obj-$(CONFIG_EXT2_FS) += ext2/



如果 CONFIG_EXT2_FS 被設置為 'y'(編譯進內核)或是'm'(編譯成模組),相應的 obj- 變數就會被設置,並且Kbuild就會遞迴向下訪問 ext2 目錄。Kbuild只是用這些資訊來決定它是否需要訪問該目錄,而具體怎麼編譯由該目錄中的Makefile來決定。



將 CONFIG_ 變數 設置成目錄名是一個好的編程習慣。這讓Kbuild在完全忽略那些相應的 CONFIG_ 值不是'y'和'm'的目錄。



--- 3.7 編輯標誌



EXTRA_CFLAGS, EXTRA_AFLAGS, EXTRA_LDFLAGS, EXTRA_ARFLAGS



所有的 EXTRA_ 變數只在所定義的 Kbuild Makefile 中起作用。EXTRA_ 變數可

以在Kbuild Makefile中所有命令中使用。



$(EXTRA_CFLAGS) 是用 $(CC) 編譯C原始檔案時的選項。



例子:

# drivers/sound/emu10kl/Makefile

EXTRA_CFLAGS += -I$(obj)

ifdef DEBUG

EXTRA_CFLAGS += -DEMU10KL_DEBUG

endif





該變數是必須的,因為頂層Makefile擁有變數 $(CFLAGS) 並用來作為整個源

代碼樹的編譯選項。



$(EXTRA_AFLAGS) 也是一個針對每個目錄的選項,只不過它是用來編譯彙編

源代碼的。



例子:

#arch/x86_64/kernel/Makefile

EXTRA_AFLAGS := -traditional





$(EXTRA_LDFLAGS) 和 $(EXTRA_ARFLAGS)分別與 $(LD)和 $(AR)類似,只不

過,他們是針對每個目錄的。



例子:

#arch/m68k/fpsp040/Makefile

EXTRA_LDFLAGS := -x



CFLAGS_$@, AFLSGA_$@



CFLAGS_$@ 和 AFLAGS_$@ 只能在當前Kbuild Makefile中的命令中使用。



$(CFLAGS_$@) 是 $(CC) 針對每個檔的選項。$@ 表明了具體操作的檔。



例子:

# drivers/scsi/Makefile

CFLAGS_aha152x.o = -DAHA152X_STAT -DAUTOCONF

CFLAGS_gdth.o = # -DDEBUG_GDTH=2 -D__SERIAL__ -D__COM2__ \

-DGDTH_STATISTICS

CFLAGS_seagate.o = -DARBITRATE -DPARITY -DSEAGATE_USE_ASM



以上三行分別設置了aha152x.o,gdth.o 和 seagate.o的編輯選項。



$(AFLAGS_$@) 也類似,只不是是針對組合語言的。



例子:

# arch/arm/kernel/Makefile

AFLAGS_head-armv.o := -DTEXTADDR=$(TEXTADDR) -traditional

AFLAGS_head-armo.o := -DTEXTADDR=$(TEXTADDR) -traditional



--- 3.9 跟蹤依賴



Kbuild 跟蹤在以下方面依賴:

1) 所有要參與編譯的檔(所有的.c 和.h文件)

2) 在參與編譯檔中所要使用的 CONFIG_ 選項

3) 用於編譯目標的命令行



因此,如果你改變了 $(CC) 的選項,所有受影響的檔都要重新編譯。



--- 3.10 特殊規則



特殊規則就是那Kbuild架構不能提供所要求的支援時,所使用的規則。一個典型的例子就是在構建過程中生成的頭檔。另一個例子就是那些需要採用特殊規則來準備啟動鏡像。



特殊規則的寫法與普通Make規則一樣。Kbuild並不在Makefile所在的目錄執行,所以所有的特殊規則都要提供參與編譯的檔和目標檔的相對路徑。



在定義特殊規則時,要使用以下兩個變數:



$(src)

$(src) 表明Makefile所在目錄的相對路徑。經常在定位源代碼樹中的檔時,使用該變數。



$(obj)

$(obj) 表明目標檔所要存儲目錄的相對路徑。經常在定位所生成的檔時,使用該變數。



例子:

#drivers/scsi/Makefile

$(obj)/53c8xx_d.h: $(src)/53c7,8xx.scr $(src)/script_asm.pl

$(CPP) -DCHIP=810 - < $<
... $(src)/script_asm.pl



這就是一個特殊規則,遵守著make所要求的普通語法。目標檔依賴於兩個原始檔案。用$(obj)來定位目標檔,用$(src)來定位原始檔案(因為它們不是我們生成的檔)。



--- 3.11 $(CC) 支援的函數



內核可能由多個不同版本的$(CC)編譯,而每個版本都支援一不同的功能集與選項集。Kbuild提供了檢查 $(CC) 可用選項的基本功能。$(CC)一般情況下是gcc編譯器,但也可以使用其他編譯器來代替gcc。



as-option

as-option,當編譯彙編文件(*.S)時,用來檢查 $(CC) 是否支援特定選項。如果第一個選項不支援的話,可選的第二個選項可以用來指定。



例子:

#arch/sh/Makefile

cflags-y += $(call as-option,-Wa$(comma)-isa=$(isa-y),)



在上面的例子裏,如果 $(CC) 支援選項 -Wa$(comma)-isa=$(isa-y),cflags-y就會被賦予該值。第二個參數是可選的,當第一個參數不支援時,就會使用該值。



ld-option

ld-option,當鏈結目標檔時,用來檢查 $(CC) 是否支援特定選項。如果第一個選項不支援的話,可選的第二個選項可以用來指定。



例子:

#arch/i386/kernel/Makefile

vsyscall-flags += $(call ld-option, -Wl$(comma)--hash-style=sysv)



在上面的例子中,如果 $(CC)支援選項 -Wl$(comma)--hash-style=sysv,ld-option就會被賦予該值。第二個參數是可選的,當第一個參數不支援時,就會使用該值。





cc-option

cc-option,用來檢查 $(CC) 是否支援特定選項,並且不支援使用可選的第二項。



例子:

#arch/i386/Makefile

cflags-y += $(call cc-option,-march=pentium-mmx,-march=i586)



在上面的例子中,如果 $(CC)支援選項 -march=pentium-mmx,cc-option就會被賦予該值,否則就賦 -march-i586。cc-option的第二個參數是可選的。如果忽略的話,當第一個選項不支援時,cflags-y 不會被賦值。



cc-option-yn

cc-option-yn,用來檢查 gcc 是否支援特定選項,返回'y'支持,否則為'n'。



例子:

#arch/ppc/Makefile

biarch := $(call cc-option-yn, -m32)

aflags-$(biarch) += -a32

cflags-$(biarch) += -m32



在上面的例子裏,當 $(CC) 支援 -m32選項時,$(biarch)設置為y。當

$(biarch) 為y時,擴展的 $(aflags-y) 和 $(cflags-y)變數就會被賦值為

-a32 和 -m32。



cc-option-align

gcc版本大於3.0時,改變了函數,迴圈等用來聲明記憶體對齊的選項。當用到對齊選項時,$(cc-option-align) 用來選擇正確的首碼:

gcc < 3.00

cc-option-align = -malign

gcc >= 3.00

cc-option-align = -falign



例子:

CFLAGS += $(cc-option-align)-functions=4



在上面的例子中,選項 -falign-funcions=4 被用在gcc >= 3.00的時候。對於小於3.00時, 使用 -malign-funcions=4 。



cc-version

cc-version以數學形式返回 $(CC) 編譯器的版本號。

其格式是:,二者都是數學。比如,gcc 3.41 會返回 0341。當某版本的$(CC) 在某方面有缺陷時,cc-version就會很有用。比如,選項-mregparm=3 雖然會被gcc接受,但其實現是有問題的。



例子:

#arch/i386/Makefile

cflags-y += $(shell \

if [ $(call cc-version) -ge 0300 ] ; then \

echo "-meregparm=3"; fi ;)



在上面的例子中,-mregparm=3只會在gcc的版本號大於等於3.0的時候使用。



cc-ifversion

cc-ifversion測試 $(CC) 的版本號,如果版本運算式為真,就賦值為最後的參數。



例子:

#fs/reiserfs/Makefile

EXTRA_CFLAGS := $(call cc-ifversion, -lt, 0402, -O1)



在這個例子中,如果 $(CC) 的版本小於4.2,EXTRA_CFLAGS就被賦值 -O1。cc-ifversion 可使用所有的shell 操作符:-eq,-ne,-lt,-le,-gt,和-ge。第三個參數可以像上面例子一樣是個文本,但也可以是個擴展的變數或巨集。



/*這段翻譯的不好*/

=== 4 本機程式支援



Kbuild 支持編譯那些將在編譯階段使用的可執行檔。為了使用該可執行檔,要將編譯分成二個階段。



第一階段是告訴Kbuild存在哪些可執行檔。這是通過變數 hostprogs-y來完成的。

第二階段是添加一個對可執行檔的顯性依賴。有兩種方法:增加依賴關係到一個規則

中,或是利用變數 $(always)。

以下是詳細敍述.



--- 4.1 簡單的本機程式



在編譯內核時,有時會需要編譯並運行一個程式。

下面這行就告訴了kbuild,程式bin2hex應該在本機上編譯。



例子:

hostprogs-y := bin2hex



在上面的例子中,Kbuild假設bin2hex是由一個與其在同一目錄下,名為

bin2hex.c 的C語言原始檔案編譯而成的。



--- 4.2 複合的本機程式



本機程式可以由多個檔編譯而成。

所使用的語法與內核的相應語法很相似。

$(-objs) 列出了聯接成最後的可執行檔所需的所有目標檔。



例子:

#scripts/lxdialog/Makefile

hostprogs-y := lxdialog

lxdialog-objs := checklist.o lxdialog.o



副檔名為.o的文件是從相應的.c檔編譯而來的。在上面的例子中,checklist.c 編譯成了checklist.o,lxdialog.c編譯成了lxdialog.o。最後,兩個.o檔聯接成了一可執行檔,lxdialog。注意:語法 -y不是只能用來生成本機程式。



--- 4.3 定義共用庫



副檔名為so的檔稱為共用庫,被編譯成位置無關物件。Kbuild也支援共用庫,但共用庫的使用很有限。在下面的例子中,libconfig.so共用庫用來聯接到可執行檔 conf中。



例子:

#scripts/kconfig/Makefile

hostprogs-y := conf

conf-objs := conf.o libkconfig.so

libkcofig-objs := expr.o type.o



共用庫檔經常要求一個相應的 -objs,在上面的例子中,共用庫libkconfig是由 expr.o 和 type.o兩個檔組成的。expr.o 和 type.o 將被編譯成位置無關碼,然後聯接成共用庫檔libkconfig.so。C++並不支援共用庫。



--- 4.4 使用用C++編寫的本機程式



kbuild也支援用C++編寫的本機程式。在此專門介紹是為了支持kconfig,並且

在一般情況下不推薦使用。



例子:

#scripts/kconfig/Makefile

hostprogs-y := qconf

qconf-cxxobjs := qconf.o



在上面的例子中,可執行檔是由C++檔 qconf.cc編譯而成的,由$(qconf-cxxobjs)來標識。



如果qconf是由.c和.cc一起編譯的,那麼就需要專門來標識這些檔了。



例子:

#scripts/kconfig/Makefile

hostprogs-y := qconf

qconf-cxxobjs := qconf.o

qconf-objs := check.o



--- 4.5 控制本機程式的編譯選項



當編譯本機程式時,有可能使用到特殊選項。程式經常是利用$(HOSTCC)編譯,其選項在 $(HOSTCFLAGS)變數中。可通過使用變數 HOST_EXTRACFLAGS,影響所有在Makefile檔中要創建的主機程式。



例子:

#scripts/lxdialog/Makefile

HOST_EXTRACFLAGS += -I/usr/include/ncurses

為一單個檔設置選項,可按形式進行:



例子:

#arch/ppc64/boot/Makefile

HOSTCFLAGS_pinggyback.o := -DKERNELBASE=$(KERNELBASE)

同樣也可以給聯接器聲明一特殊選項。



例子:

#scripts/kconfig/Makefile

HOSTLOADLIBES_qconf := -L$(QTDIR)/lib



當聯接qconf時,將會向聯接器傳遞附加選項 "-L$(QTDIR)/lib"。



--- 4.6 編譯主機程式時



Kbuild只在需要時編譯主機程式。

有兩種方法:



(1) 在一具體的規則中顯性列出所需要的檔



例子:

#drivers/pci/Makefile

hostprogs-y := gen-devlist

$(obj)/devlist.h: $(src)/pci.ids $(obj)/gen-devlist

( cd $(obj); ./gen-devlist ) < $<



目標 $(obj)/devlist.h 是不會在 $(obj)/gen-devlist 更新之前編譯的。注意

在該規則中所有有關主機程式的命令必須以$(obj)開頭。



(2) 使用 $(always)

當Makefile要編譯主機程式,但沒有適合的規則時,使用 $(always)。



例子:

#scripts/lxdialog/Makefile

hostprogs-y := lxdialog

always := $(hostprogs-y)



這就是告訴Kbuild,即使沒有在規則中聲明,也要編譯 lxdialog。



--- 4.7 使用 hostprogs-$(CONFIG_FOO)



一個典型的Kbuild模式如下:



例子:

#scripts/Makefile

hostprogs-$(CONFIG_KALLSYMS) += kallsyms



Kbuild 知道 'y' 是編譯進內核,而 'm' 是編譯成模組。所以,如果配置符號是'm',Kbuild仍然會編譯它。換句話說,Kbuild處理 hostprogs-m 與 hostprogs-y 的方式是完全一致的。只是,如果不用 CONFIG,最好用hostprogs-y。



=== 5 Kbuild清理(clean)



"make clean"刪除幾乎所有的在編譯內核時生成的檔,包括了主機程式在內。Kbuild 通過列表 $(hostprogs-y),$(hostprogs-m),$(always),$(extra-y) 和$(targets) 知道所要編譯的目標。這些目標檔都會被 "make clean" 刪除。另外,在"make clean"還會刪除匹 "*.oas]","*.ko" 的檔,以及由 Kbuild生成的輔助檔。



輔助文件由 Kbuild Makefile 中的 $(clean-files) 指明。



例子:

#drivers/pci/Makefile

clean-files := devlist.h classlist.h



當執行 "make clean" 時,"devlist.h classlist.h"這兩個檔將被刪除。如果不使用絕對路徑(路徑以'/'開頭)的話,Kbuild假設所要刪除的檔與Makefile在同一個相對路徑上。



要刪除一目錄:

例子:

#scripts/package/Makefile

clean-dirs := $(objtree)/debian/



這就會刪除目錄 debian,包括其所有的子目錄。如果不使用絕對路徑(路徑以'/'開頭)的話,Kbuild假設所要刪除的目錄與Makefile在同一個相對路徑上。



一般情況下,Kbuild會根據 "obj-* := dir/" 遞迴訪問其子目錄,但有的時候,Kbuild架構還不足以描述所有的情況時,還要顯式的指明所要訪問的子目錄。



例子:

#arch/i386/boot/Makefile

subdir- := compressed/



上面的賦值命令告訴Kbuild,當執行"make clean"時,要遞迴訪問目錄 compressed/。為了支援在最終編譯完成啟動鏡像後的架構清理工作,還有一可選的目標 archclean:



例子:

#arch/i386/Makefile

archclean:

$(Q)$(MAKE) $(clean)=arch/i386/boot



當"make clean"執行時,make會遞迴訪問並清理 arch/i386/boot。在 arch/i386/boot中的Makefile可以用來提示make進行下一步的遞迴操作。



注意1:arch/$(ARCH)/Makefile 不能使用"subdir-",因為該Makefile被包含在頂層的Makefile中,Kbuild是不會在此處進行操作的。



注意2:"make clean" 會訪問在 core-y,libs-y,drivers-y 和 net-y 列出的所有目錄。



=== 6 架構Makefile



在遞迴訪問目錄之前,頂層Makefile要完成設置環境變數以及遞迴訪問的準備工作。頂層Makefile包含的公共部分,而 arch/$(ARCH)/Makefile 包含著針對某一特定架構的配置資訊。所以,要在 arch/$(ARCH)/Makefile 中設置一部分變數,並定義一些目標。



Kbuild執行的幾個步驟(大致):

1) 根據內核配置生成檔 .config

2) 將內核的版本號存儲在 include/linux/version.h

3) 生成指向 include/asm-$(ARCH) 的符號鏈結

4) 更新所有編譯所需的檔:

-附加的檔由 arch/$(ARCH)/Makefile 指定。

5) 遞迴向下訪問所有在下列變數中列出的目錄: init-* core-* drivers-* net-* libs-*,並編譯生成目標檔。

-這些變數的值可以在 arch/$(ARCH)/Makefile 中擴充。

6) 聯接所有的目標檔,在源代碼樹頂層目錄中生成 vmlinux。最先聯接是在 head-y中列出的檔,該變數由 arch/$(ARCH)/Makefile 賦值。

7) 最後完成具體架構的特殊要求,並生成最終的啟動鏡像。

-包含生成啟動指令

-準備 initrd 鏡像或類似文件





--- 6.1 調整針對某一具體架構生成的鏡像



LDFLAGS 一般是 $(LD) 選項



該選項在每次調用聯接器時都會用到。一般情況下,只用來指明模擬器。



例子:

#arch/s390/Makefile

LDFLAGS := -m elf_s390

注意:EXTRA_LDFLAGS 和 LDFLAGS_$@ 可用來進一步自定義選項。請看第七章。



LDFLAGS_MODULE 聯接模組時的聯接器的選項



LDFLAGS_MODULE 所設置的選項將在聯接器在聯接模組檔 .ko 時使用。

預設值為 "-r",指定輸出檔是可重定位的。



LDFLAGS_vmlinux 聯接vmlinux時的選項



LDFLAGS_vmlinux用來傳遞聯接vmlinux時的聯接器的選項。

LDFLAGS_vmlinux需 LDFLAGS_$@ 支持。



例子:

#arch/i386/Makefile

LDFLAGS_vmlinux := -e stext



OBJCOPYFLAGS objcopy 選項



當用 $(call if_changed,objcopy) 來轉換(translate)一個.o檔時,該選項就會被使用。

$(call if_changed,objcopy) 經常被用來為vmlinux生成原始的二進位碼。



例子:

#arch/s390/Makefile

OBJCOPYFLAGS := -O binary



#arch/s390/boot/Makefile

$(obj)/image: vmlinux FORCE

$(call if_changed,objcopy)



在此例中,二進位檔 $(obj)/image 是 vmlinux 的一個二進位版本。

$(call if_chagned,xxx)的用法稍後描述。



AFLAGS $(AS) 彙編編譯器選項



預設值在頂層Makefile 擴充或修改在各具體架構的Makefile



例子:

#arch/sparc64/Makefile

AFLAGS += -m64 -mcpu=ultrasparc



CFLAGS $(CC) 編譯器選項



預設值在頂層Makefile 擴充或修改在各具體架構的Makefile。



一般,CFLAGS要根據內核配置設置。



例子:

#arch/i386/Makefile

cflags-$(CONFIG_M386) += -march=i386

CFLAGS += $(cflags-y)



許多架構Makefile都通過調用所要使用的C編譯器,動態的檢查其所支援的選項:



#arch/i386/Makefile



...

cflags-$(CONFIG_MPENTIUMII) += $(call cc-option,\

-march=pentium2,-march=i686)

...

# Disable unit-at-a-time mode ...

CFLAGS += $(call cc-option,-fno-unit-at-a-time)

...





第一個例子利用了一個配置選項,當其為'y'時,擴展。



CFLAGS_KERNEL :



#arch/i386/Makefile



...

cflags-$(CONFIG_MPENTIUMII) += $(call cc-option,\

-march=pentium2,-march=i686)

...

# Disable unit-at-a-time mode ...

CFLAGS += $(call cc-option,-fno-unit-at-a-time)

...





第一個例子利用了一個配置選項,當其為'y'時,擴展。



CFLAGS_KERNEL 編譯進內核時,$(CC) 所用的選項



$(CFLAGS_KERNEL) 包含了用於編譯常駐內核代碼的附加編譯器選項。



CFLAGS_MODULE 編譯成模組時,$(CC)所用的選項



$(CFLAGS_MODULE) 包含了用於編譯可裝載模組的附加編譯器選項。





--- 6.2 將所需文件加到 archprepare 中:



archprepare規則在遞迴訪問子目錄之前,列出編譯目標檔所需檔。

一般情況下,這是一個包含彙編常量的頭檔。(assembler constants)



例子:

#arch/arm/Makefile

archprepare: maketools



此例中,目標檔 maketools 將在遞迴訪問子目錄之前編譯。 在TODO一章可以看到,Kbuild是如何支援生成分支頭檔的。(offset header files)



--- 6.3 遞迴下向時要訪問的目錄列表



如何生成 vmlinux,是由架構makefile和頂層Makefile一起來定義的。注意,架構Makefile是不會定義與模組相關的內容的,所有構建模組的定義是與架構無關的。





head-y,init-y,core-y,libs-y,drivers-y,net-y



$(head-y) 列出了最先被聯接進 vmlinux 的目標檔。

$(libs-y) 列出了生成的所有 lib.a 所在的目錄。

其餘所列的目錄,是 built-in.o 所在的目錄。



$(init-y) 在 $(head-y) 之後所要使用的檔。

然後,剩下的步驟如下:

$(core-y),$(libs-y),$(drivers-y)和$(net-y)。



頂層makefile定義了通用的部分,arch/$(ARCH)/Makefile 添加了架構的特殊要求。



例子:

#arch/sparc64/Makefile

core-y += arch/sparc64/kernel/

libs-y += arch/sparc64/prom/ arch/sparc64/lib/

drivers-$(CONFIG_OPROFILE) += arch/sparc64/oprofile/





--- 6.4 具體架構的啟動鏡像



一具體架構Makefile的具體目的就是,將生成並壓縮 vmlinux 檔,寫入啟動代碼,並將其拷貝到正確的位置。這就包含了多種不同的安裝命令。該具體目的也無法在各個平臺間進行標準化。



一般,附加的處理命令入在 arch/$(ARCH)/下的boot目錄。



Kbuild並沒有為構造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)/$@



當在子目錄中調用 make 時,推薦使用 "$(Q)$(MAKE) $(build)=

" 。



並沒有對架構特殊目標的命名規則,但用命令 "make help" 可以列出所有的相關目標。

為了支持 "make help",$(archhelp) 必須被定義。



例子:

#arch/i386/Makefile

define archhelp

echo '* bzImage - Image (arch/$(ARCH)/boot/bzImage)'

endef



當make 沒帶參數執行時,所遇到的第一個目標將被執行。在頂層,第一個目標就是 all:。

每個架構Makefile都要默認構造一可啟動的鏡像檔。在 "make help"中,默認目標就是被加亮的'*'。添加一新的前提檔到 all:,就可以構造出一不同的vmlinux。



例子:

#arch/i386/Makefile

all: bzImage



當 make 沒有參數時,bzImage將被構造。



--- 6.5 構造非Kbuild目標



extra-y



extra-y 列出了在當前目錄下,所要創建的附加檔,不包含任何已包含在obj-* 中的檔。



用 extra-y 列目標,主要是兩個目的:

1) 可以使Kbuild檢查命令行是否發生變化

- 使用 $(call if_changed,xxx) 的時候

2) 讓Kbuild知道哪些檔要在 "make clean" 時刪除



例子:

#arch/i386/kernel/Makefile

extra-y := head.o init_task.o



在此例子中,extra-y用來列出所有只編譯,但不聯接到 built-in.o的目標檔。



--- 6.6 構建啟動鏡像的命令



Kbuild 提供了幾個用在構建啟動鏡像時的宏。



if_changed



if_changed 為下列命令的基礎。



使用方法:

target: source(s) FORCE

$(call if_changed,ld/objcopy/gzip)



當執行該規則時,就檢查是否有檔需要更新,或者在上次調用以後,命令行發生了改變。如果有選項發生了改變,後者會導致重新構造。只有在 $(targets)列出的的目標檔,才能使用if_changed,否則命令行的檢查會失敗,並且目標總會被重建。給 $(targets)的賦值沒有首碼 $(obj)/ 。 if_changed 可用來聯接自定義的Kbuild命令,關於Kbuild自定義命令請看 6.7節。



注意:忘記 FORCE 是一種典型的錯誤。還有一種普遍的錯誤是,空格有的時候是有意義的;比如。下面的命令就會錯誤(注意在逗號後面的那個多餘的空格):

target: source(s) FORCE

#WRONG!# $(call if_changed, ld/objcopy/gzip)



ld

聯接目標。經常是使用LDFLAGS_$@來設置ld的特殊選項。



objcopy

拷貝二進位碼。一般是在 arch/$(ARCH)/Makefile 中使用 OBJCOPYFLAGS。

OBJCOPYFLAGS_$@ 可以用來設置附加選項。



gzip

壓縮目標檔。盡可能的壓縮目標檔。



例子:

#arch/i386/boot/Makefile

LDFLAGS_bootsect := -Ttext 0x0 -s --oformat binary

LDFLAGS_setup := -Ttext 0x0 -s --oformat binary -e begtext



targets += setup setup.o bootsect bootsect.o

$(obj)/setup $(obj)/bootsect: %: %.o FORCE

$(call if_changed,ld)



在這個例子中,有兩個可能的目標檔,分別要求不同的聯接選項。定義聯接器的選項使用的是 LDFLAGS_$@ 語法,每個潛在的目標一個。$(targets) 被分配給所有的潛在目標,因此知道目標是哪些,並且還會:

1) 檢查命令行是否改變

2) 在 "make clean" 時,刪除目標檔



前提部分中的 ": %: %.o" 部分使我們不必在列出檔 setup.o 和 bootsect.o 。

注意:一個普遍的錯誤是忘記了給 "target"賦值,導致在target中的檔總是無緣無故的被重新編譯。





--- 6.7 Kbuild自定義命令



當Kbuild的變數 KBUILD_VERBOSE 為0時,只會顯示命令的簡寫。如果要為自定義命令使用這一功能,需要設置2個變數:

quiet_cmd_ - 要顯示的命令

cmd_ - 要執行的命令



例子:

#

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 KBUILD_VERBOSE=0"更新 $(obj)/bzImage 目標時顯示:



BUILD arch/i386/boot/bzImage





--- 6.8 聯接器預處理腳本



當構造 vmlinux 鏡像時,使用聯接器腳本:

arch/$(ARCH)/kernel/vmlinux.lds。

該腳本是由在同一目錄下的 vmlinux.lds.S 生成的。Kbuild認識.lds檔,並包含由*.lds.S檔生成*.lds檔的規則。



例子:

#arch/i386/kernel/Makefile

always := vmlinux.lds



#Makefile

export CPPFLAGS_vmlinux.lds += -P -C -U$(ARCH)



$(always)的值是用來告訴Kbuild,構造目標 vmlinux.lds。

$(CPPFLAGS_vmlinux.lds),Kbuild在構造目標vmlinux.lds時所用到的特殊選項。



當構造 *.lds 目標時,Kbuild要用到下列變數:

CPPFLAGS : 在頂層目錄中設置

EXTRA_CPPFLAGS : 可以在Kbuild Makefile中設置

CPPFLAGS_$(@F) : 目標特別選項

注意,此處的賦值用的完整的檔案名。



針對*.lds文件的Kbuild構架還被用在許多具體架構的文件中。(***不通***)



=== 7 Kbuild 變數



頂層Makefile輸出以下變數:



1、VERSION,PATCHLEVEL,SUBLEVEL,EXTRAVERSION



這些變數定義了當前內核的版本號。只有很少一部分Makefile會直接用到這些

變數;可使用 $(KERNELRELEASE)代替。



$(VERSION),$(PATCHLEVEL),和$(SUBLEVEL) 定義了最初使用的三個數位的版本

號,比如"2""4"和"0"。這三個值一般是數字。



$(EXTRAVERSION) 為了補丁定義了更小的版本號。一般是非數位的字串,比如

"-pre4" ,或就空著。



KERNELRELEASE



$(KERNELRELEASE) 是一個字串,類似"2.4.0-pre4",用於安裝目錄的命名或

顯示當前的版本號。一部分架構Makefile使用該變數。



2、ARCH



該變數定義了目標架構,比如"i386","arm" 或"sparc"。有些Kbuild Makefile

根據 $(ARCH) 決定編譯哪些文件。



默認情況下,頂層Makefile將其設置為本機架構。如果是跨平臺編譯,用戶可以

用下麵的命令覆蓋該值:



make ARCH=m68k ...





3、INSTALL_PATH



該變數為架構Makefile定義了安裝內核鏡像與 System.map 檔的目錄。主要用來指明架構特殊的安裝路徑。



4、INSTALL_MOD_PATH,MODLIB



$(INSTALL_MOD_PATH) 為了安裝模組,給 $(MODLIB) 聲明了首碼。該變數不能

在Makefile中定義,但可以由用戶傳給Makefile。



$(MODLIB) 具體的模組安裝的路徑。頂層Makefile將$(MODLIB)定義為$(INSTALL_MOD_PATH)/lib/modules/$(KERNELRELEASE)。用戶可以通過命令行參數的形式將其覆蓋。



5、INSTALL_MOD_STRIP



如果該變數有定義,模組在安裝之前,會被剝出符號表。如果

INSTALL_MOD_STRIP 為 "1",就使用默認選項 --strip-debug。否則,

INSTALL_MOD_STRIP 將作為命令 strip 的選項使用。





=== 8 Makefile語言



內核的Makefile使用的是GNU Make。該Makefile只使用GNU Make已注明的功能,並使用

了許多GNU 的擴展功能。



GNU Make支援基本的顯示處理過程的函數。內核Makefile 使用了一種類似小說的方式

,顯示"if"語句的構造、處理過程。



GNU Make 有2個賦值操作符,":="和"="。":=",將對右邊的運算式求值,並將所求的值

賦給左邊。"="更像是一個公式定義,只是將右邊的值簡單的賦值給左邊,當左邊的表達

式被使用時,才求值。



有時使用"="是正確的。但是,一般情況下,推薦使用":="。



=== 9 關於作者

第一版由 Michael Elizabeth Chastain,

修改:kai Germaschewski

Sam Ravnborg



=== 10 TODO



- 描述Kbuild是如何用 _shipped 來支持 shipped 檔的。

- 生成分支頭檔

- 在第7節加入更多的變數

________________________________________

第二部分:Makefile 實例分析



VERSION = 2

PATCHLEVEL = 6

SUBLEVEL = 20

EXTRAVERSION = .7

NAME = Homicidal Dwarf Hamster



# 以上表明了內核版本。組合起來就是:2.6.20.7 ,yeah,這就是我分析的內核版本



# 注意寫makefile時不要使用makefile的內建的規則和變數



#要想不列印"Entering directory ..."字樣,請使用no-print-directory選項

MAKEFLAGS += -rR --no-print-directory



# 因為需要遞迴執行build, 所以必須注意要保證按照正確順序執行make.



# 使用 'make V=1' 可以看到完整命令



ifdef V

ifeq ("$(origin V)", "command line")

KBUILD_VERBOSE = $(V)

endif

endif

ifndef KBUILD_VERBOSE

KBUILD_VERBOSE = 0

endif



# 使用 'make C=1' 僅檢查需要重新使用c編譯器編譯的檔

# 使用 'make C=2' 檢查所有c編譯器編譯的檔



ifdef C

ifeq ("$(origin C)", "command line")

KBUILD_CHECKSRC = $(C)

endif

endif

ifndef KBUILD_CHECKSRC

KBUILD_CHECKSRC = 0

endif



# 使用 make M=dir 表明需要編譯的模組目錄

# 舊的語法make ... SUBDIRS=$PWD 依然支持

# 這裏通過環境變數 KBUILD_EXTMOD來表示

ifdef SUBDIRS

KBUILD_EXTMOD ?= $(SUBDIRS)

endif

ifdef M

ifeq ("$(origin M)", "command line")

KBUILD_EXTMOD := $(M)

endif

endif





# kbuild 可以把輸出檔放到一個分開的目錄下

# 定位輸出檔目錄有兩種語法支援:

# 兩種方法都要求工作目錄是內核原始檔案目錄的根目錄

# 1) O=

# 使用 "make O=dir/to/store/output/files/"

#

# 2) 設置 KBUILD_OUTPUT

# 設置環境變數 KBUILD_OUTPUT 來指定輸出檔存放的目錄

# export KBUILD_OUTPUT=dir/to/store/output/files/

# make

#

# 但是 O= 方式優先於KBUILD_OUTPUT環境變數方式



# KBUILD_SRC 會再obj目錄的make調用

# KBUILD_SRC 到目前還不是為了給一般用戶使用的

ifeq ($(KBUILD_SRC),)



ifdef O

ifeq ("$(origin O)", "command line")

KBUILD_OUTPUT := $(O)

endif

endif



# 缺剩目標

PHONY := _all

_all:



ifneq ($(KBUILD_OUTPUT),)

# 調用輸出目錄的第二個make, 傳遞相關變數檢查輸出目錄確實存在



saved-output := $(KBUILD_OUTPUT)

KBUILD_OUTPUT := $(shell cd $(KBUILD_OUTPUT) && /bin/pwd)

$(if $(KBUILD_OUTPUT),, \

$(error output directory "$(saved-output)" does not exist))



PHONY += $(MAKECMDGOALS)



$(filter-out _all,$(MAKECMDGOALS)) _all:

$(if $(KBUILD_VERBOSE:1=),@)$(MAKE) -C $(KBUILD_OUTPUT) \

KBUILD_SRC=$(CURDIR) \

KBUILD_EXTMOD="$(KBUILD_EXTMOD)" -f $(CURDIR)/Makefile $@



# Leave processing to above invocation of make

skip-makefile := 1

endif # ifneq ($(KBUILD_OUTPUT),)

endif # ifeq ($(KBUILD_SRC),)



ifeq ($(skip-makefile),)



PHONY += all

ifeq ($(KBUILD_EXTMOD),)

_all: all

else

_all: modules

endif



srctree := $(if $(KBUILD_SRC),$(KBUILD_SRC),$(CURDIR))

TOPDIR := $(srctree)

# FIXME - TOPDIR is obsolete, use srctree/objtree

objtree := $(CURDIR)

src := $(srctree)

obj := $(objtree)



VPATH := $(srctree)$(if $(KBUILD_EXTMOD),:$(KBUILD_EXTMOD))



export srctree objtree VPATH TOPDIR





# SUBARCH 告訴 usermode build 當前的機器是什麼體系. 它是第一個設置的,並且如果

# 是usermode build ,命令行中的 "ARCH=um" 優先下面的 ARCH 設置. 如果是 native build ,

# 設置ARCH , 獲取正常的數值, 將會忽略SUBARCH .



SUBARCH := $(shell uname -m
sed -e s/i.86/i386/ -e s/sun4u/sparc64/ \

-e s/arm.*/arm/ -e s/sa110/arm/ \

-e s/s390x/s390/ -e s/parisc64/parisc/ \

-e s/ppc.*/powerpc/ -e s/mips.*/mips/ )



# 交叉編譯和選擇不同的gcc/bin-utils

# ---------------------------------------------------------------------------

#

# 當交叉編譯其他體系的內核時,ARCH 應該設置為目標體系.

# ARCH 可以再make執行間設置:

# make ARCH=ia64

# 另外一種方法是設置 ARCH 環境變數.

# 默認 ARCH 是當前執行的宿主機.



# CROSS_COMPILE 作為編譯時所有執行需要使用的首碼

# 只有gcc 和 相關的 bin-utils 使用 $(CROSS_COMPILE)設置的首碼.

# CROSS_COMPILE 可以再命令行設置:

# make CROSS_COMPILE=ia64-linux-

# 另外 CROSS_COMPILE 也可以再環境變數中設置.

# CROSS_COMPILE 的預設值是空的

# Note: 一些體系的 CROSS_COMPILE 是再其 arch/*/Makefile中設置的



ARCH ?= $(SUBARCH)

CROSS_COMPILE ?=



# Architecture as present in compile.h

UTS_MACHINE := $(ARCH)



KCONFIG_CONFIG ?= .config



# SHELL used by kbuild

CONFIG_SHELL := $(shell if [ -x "$$BASH" ]; then echo $$BASH; \

else if [ -x /bin/bash ]; then echo /bin/bash; \

else echo sh; fi ; fi)



HOSTCC = gcc

HOSTCXX = g++

HOSTCFLAGS = -Wall -Wstrict-prototypes -O2 -fomit-frame-pointer

HOSTCXXFLAGS = -O2



# Decide whether to build built-in, modular, or both.

# Normally, just do built-in.



KBUILD_MODULES :=

KBUILD_BUILTIN := 1



# 如果僅編譯模組 "make modules", 就不會編譯內建的 objects.

# 當使用modversions編譯 modules 時 , 就需要考慮build-in objects,

# 目的是再記錄前確認 checksums 已經更新.



ifeq ($(MAKECMDGOALS),modules)

KBUILD_BUILTIN := $(if $(CONFIG_MODVERSIONS),1)

endif



# If we have "make modules", compile modules

# in addition to whatever we do anyway.

# Just "make" or "make all" shall build modules as well



ifneq ($(filter all _all modules,$(MAKECMDGOALS)),)

KBUILD_MODULES := 1

endif



ifeq ($(MAKECMDGOALS),)

KBUILD_MODULES := 1

endif



export KBUILD_MODULES KBUILD_BUILTIN

export KBUILD_CHECKSRC KBUILD_SRC KBUILD_EXTMOD



# Beautify output

# ---------------------------------------------------------------------------

#

# 一般,make再執行命令前會列印整個命令資訊. 現在通過$($(quiet)$(cmd))方式

# 可以設置 $(quiet) 來選擇不同的命令輸出方式等.

#

# quiet_cmd_cc_o_c = Compiling $(RELDIR)/$@

# cmd_cc_o_c = $(CC) $(c_flags) -c -o $@ $<

#

# 如果 $(quiet) 為空, 整個命令行將會列印.

# 如果 $(quiet)設置為 "quiet_", 只列印簡短的版本資訊.

# 如果 $(quiet)設置為 "silent_", 將不會列印任何資訊, 因為沒有$(silent_cmd_cc_o_c) 變數存在.

#

# 一個簡單的首碼 $(Q)放到命令前面,以便再 non-verbose 模式下可以隱藏命令:

#

# $(Q)ln $@ :<

#

# 如果 KBUILD_VERBOSE 等於 0 ,那麼上面的命令將隱藏.

# 如果 KBUILD_VERBOSE 等於 1 ,那麼上面的命令將顯示.



ifeq ($(KBUILD_VERBOSE),1)

quiet =

Q =

else

quiet=quiet_

Q = @

endif



# 如果make -s (silent mode), 不會顯示命令



ifneq ($(findstring s,$(MAKEFLAGS)),)

quiet=silent_

endif



export quiet Q KBUILD_VERBOSE





# Look for make include files relative to root of kernel src

MAKEFLAGS += --include-dir=$(srctree)



# We need some generic definitions.

include $(srctree)/scripts/Kbuild.include



# Make variables (CC, etc...)



AS = $(CROSS_COMPILE)as

LD = $(CROSS_COMPILE)ld

CC = $(CROSS_COMPILE)gcc

CPP = $(CC) -E

AR = $(CROSS_COMPILE)ar

NM = $(CROSS_COMPILE)nm

STRIP = $(CROSS_COMPILE)strip

OBJCOPY = $(CROSS_COMPILE)objcopy

OBJDUMP = $(CROSS_COMPILE)objdump

AWK = awk

GENKSYMS = scripts/genksyms/genksyms

DEPMOD = /sbin/depmod

KALLSYMS = scripts/kallsyms

PERL = perl

CHECK = sparse



CHECKFLAGS := -D__linux__ -Dlinux -D__STDC__ -Dunix -D__unix__ -Wbitwise $(CF)

MODFLAGS = -DMODULE

CFLAGS_MODULE = $(MODFLAGS)

AFLAGS_MODULE = $(MODFLAGS)

LDFLAGS_MODULE = -r

CFLAGS_KERNEL =

AFLAGS_KERNEL =





# Use LINUXINCLUDE when you must reference the include/ directory.

# Needed to be compatible with the O= option

LINUXINCLUDE := -Iinclude \

$(if $(KBUILD_SRC),-Iinclude2 -I$(srctree)/include) \

-include include/linux/autoconf.h



CPPFLAGS := -D__KERNEL__ $(LINUXINCLUDE)



CFLAGS := -Wall -Wundef -Wstrict-prototypes -Wno-trigraphs \

-fno-strict-aliasing -fno-common

AFLAGS := -D__ASSEMBLY__



# Read KERNELRELEASE from include/config/kernel.release (if it exists)

KERNELRELEASE = $(shell cat include/config/kernel.release 2> /dev/null)

KERNELVERSION = $(VERSION).$(PATCHLEVEL).$(SUBLEVEL)$(EXTRAVERSION)



export VERSION PATCHLEVEL SUBLEVEL KERNELRELEASE KERNELVERSION

export ARCH CONFIG_SHELL HOSTCC HOSTCFLAGS CROSS_COMPILE AS LD CC

export CPP AR NM STRIP OBJCOPY OBJDUMP MAKE AWK GENKSYMS PERL UTS_MACHINE

export HOSTCXX HOSTCXXFLAGS LDFLAGS_MODULE CHECK CHECKFLAGS



export CPPFLAGS NOSTDINC_FLAGS LINUXINCLUDE OBJCOPYFLAGS LDFLAGS

export CFLAGS CFLAGS_KERNEL CFLAGS_MODULE

export AFLAGS AFLAGS_KERNEL AFLAGS_MODULE



# When compiling out-of-tree modules, put MODVERDIR in the module

# tree rather than in the kernel tree. The kernel tree might

# even be read-only.

export MODVERDIR := $(if $(KBUILD_EXTMOD),$(firstword $(KBUILD_EXTMOD))/).tmp_versions



# Files to ignore in find ... statements



RCS_FIND_IGNORE := \( -name SCCS -o -name BitKeeper -o -name .svn -o -name CVS -o -name .pc -o -name .hg -o -name .git \) -prune -o

export RCS_TAR_IGNORE := --exclude SCCS --exclude BitKeeper --exclude .svn --exclude CVS --exclude .pc --exclude .hg --exclude .git

[未完,待續]

# ===========================================================================

# 下面是設置config和build 內核時共同使用的規則



# scripts裏面是最基本的幫助builds,在scripts/basic目錄下的makefile是所有build時都會用到的工具:fixdep/

PHONY += scripts_basic

scripts_basic:

$(Q)$(MAKE) $(build)=scripts/basic



# 把對scripts/basic/makefile轉化到scripts_basic處理

scripts/basic/%: scripts_basic ;



#這裏先說說script目錄裏面的makefile檔作用

#makefile:配置目標,包括編譯島內核和模組方式的target

#makefile_clean:當然是刪除了

#kbuild_include:一般的配置選項,會在makefile_build中調用

#makefile_build:build配置

#makefile_lib:module,vmlinux的配置

#makefile_host:配置binary

#makefile_modinst:安裝module

#makefile_modpost:



PHONY += outputmakefile

# outputmakefile規則目的是在輸出目錄產生一個makefile檔。這會為make提供方便。這裏是調用scripts/mkmakefile創建makfile檔

outputmakefile:

ifneq ($(KBUILD_SRC),)

$(Q)$(CONFIG_SHELL) $(srctree)/scripts/mkmakefile \

$(srctree) $(objtree) $(VERSION) $(PATCHLEVEL)

endif



# 設置是.config,還是module,還是build命令



no-dot-config-targets := clean mrproper distclean \

cscope TAGS tags help %docs check% \

include/linux/version.h headers_% \

kernelrelease kernelversion



config-targets := 0

mixed-targets := 0

dot-config := 1



ifneq ($(filter $(no-dot-config-targets), $(MAKECMDGOALS)),)

ifeq ($(filter-out $(no-dot-config-targets), $(MAKECMDGOALS)),)

dot-config := 0

endif

endif



ifeq ($(KBUILD_EXTMOD),)

ifneq ($(filter config %config,$(MAKECMDGOALS)),)

config-targets := 1

ifneq ($(filter-out config %config,$(MAKECMDGOALS)),)

mixed-targets := 1

endif

endif

endif



ifeq ($(mixed-targets),1)

# ===========================================================================

# We're called with mixed targets (*config and build targets).

# Handle them one by one.



%:: FORCE

$(Q)$(MAKE) -C $(srctree) KBUILD_SRC= $@



else

ifeq ($(config-targets),1)

# ===========================================================================

#僅配置linux



# Read arch specific Makefile to set KBUILD_DEFCONFIG as needed.

# KBUILD_DEFCONFIG may point out an alternative default configuration

# used for 'make defconfig'

include $(srctree)/arch/$(ARCH)/Makefile

export KBUILD_DEFCONFIG



config %config: scripts_basic outputmakefile FORCE

$(Q)mkdir -p include/linux include/config

$(Q)$(MAKE) $(build)=scripts/kconfig $@



else

# ===========================================================================

# 僅編譯內核



ifeq ($(KBUILD_EXTMOD),)

# Additional helpers built in scripts/

# Carefully list dependencies so we do not try to build scripts twice

# in parallel

PHONY += scripts

scripts: scripts_basic include/config/auto.conf

$(Q)$(MAKE) $(build)=$(@)



#下麵就不用說了吧

# Objects we will link into vmlinux / subdirs we need to visit

init-y := init/

drivers-y := drivers/ sound/

net-y := net/

libs-y := lib/

core-y := usr/

endif # KBUILD_EXTMOD



ifeq ($(dot-config),1)

# Read in config

-include include/config/auto.conf



ifeq ($(KBUILD_EXTMOD),)

# Read in dependencies to all Kconfig* files, make sure to run

# oldconfig if changes are detected.

-include include/config/auto.conf.cmd



# To avoid any implicit rule to kick in, define an empty command

$(KCONFIG_CONFIG) include/config/auto.conf.cmd: ;



# If .config is newer than include/config/auto.conf, someone tinkered

# with it and forgot to run make oldconfig.

# if auto.conf.cmd is missing then we are probably in a cleaned tree so

# we execute the config step to be sure to catch updated Kconfig files

include/config/auto.conf: $(KCONFIG_CONFIG) include/config/auto.conf.cmd

$(Q)$(MAKE) -f $(srctree)/Makefile silentoldconfig

else

# external modules needs include/linux/autoconf.h and include/config/auto.conf

# but do not care if they are up-to-date. Use auto.conf to trigger the test

PHONY += include/config/auto.conf



include/config/auto.conf:

$(Q)test -e include/linux/autoconf.h -a -e $@

( \

echo; \

echo " ERROR: Kernel configuration is invalid."; \

echo " include/linux/autoconf.h or $@ are missing."; \

echo " Run 'make oldconfig && make prepare' on kernel src to fix it."; \

echo; \

/bin/false)



endif # KBUILD_EXTMOD



else

# Dummy target needed, because used as prerequisite

include/config/auto.conf: ;

endif # $(dot-config)





# ===========================================================================

# 僅編譯內核



ifeq ($(KBUILD_EXTMOD),)

# Additional helpers built in scripts/

# Carefully list dependencies so we do not try to build scripts twice

# in parallel

PHONY += scripts

scripts: scripts_basic include/config/auto.conf

$(Q)$(MAKE) $(build)=$(@)



#下麵就不用說了吧

# Objects we will link into vmlinux / subdirs we need to visit

init-y := init/

drivers-y := drivers/ sound/

net-y := net/

libs-y := lib/

core-y := usr/

endif # KBUILD_EXTMOD



.....



all: vmlinux



#配置gcc選項

ifdef CONFIG_CC_OPTIMIZE_FOR_SIZE

CFLAGS += -Os

else

CFLAGS += -O2

endif

....



# 缺剩的編譯的內核鏡像

export KBUILD_IMAGE ?= vmlinux



#

# vmlinux與map安裝路徑,默認時boot目錄

export INSTALL_PATH ?= /boot



#

#module安裝目錄

#



MODLIB = $(INSTALL_MOD_PATH)/lib/modules/$(KERNELRELEASE)

export MODLIB



#

# 如果定義了INSTALL_MOD_STRIP,在安裝module後會strip這些安裝的modules



ifdef INSTALL_MOD_STRIP

ifeq ($(INSTALL_MOD_STRIP),1)

mod_strip_cmd = $(STRIP) --strip-debug

else

mod_strip_cmd = $(STRIP) $(INSTALL_MOD_STRIP)

endif # INSTALL_MOD_STRIP=1

else

mod_strip_cmd = true

endif # INSTALL_MOD_STRIP

export mod_strip_cmd



#如果不是安裝module

ifeq ($(KBUILD_EXTMOD),)

core-y += kernel/ mm/ fs/ ipc/ security/ crypto/ block/



vmlinux-dirs := $(patsubst %/,%,$(filter %/, $(init-y) $(init-m) \

$(core-y) $(core-m) $(drivers-y) $(drivers-m) \

$(net-y) $(net-m) $(libs-y) $(libs-m)))



vmlinux-alldirs := $(sort $(vmlinux-dirs) $(patsubst %/,%,$(filter %/, \

$(init-n) $(init-) \

$(core-n) $(core-) $(drivers-n) $(drivers-) \

$(net-n) $(net-) $(libs-n) $(libs-))))



init-y := $(patsubst %/, %/built-in.o, $(init-y))

core-y := $(patsubst %/, %/built-in.o, $(core-y))

drivers-y := $(patsubst %/, %/built-in.o, $(drivers-y))

net-y := $(patsubst %/, %/built-in.o, $(net-y))

libs-y1 := $(patsubst %/, %/lib.a, $(libs-y))

libs-y2 := $(patsubst %/, %/built-in.o, $(libs-y))

libs-y := $(libs-y1) $(libs-y2)



# Build vmlinux

...



#配置syms

...



#以下是內核link時的配置

....


2011年9月20日 星期二

2410下寄存器位址虛實映射的實現

轉載自:http://blog.csdn.net/aaronychen/article/details/4541553
我們知道在我們的驅動裏面一般操作的寄存器的位址都是虛擬位址, 然而一般在cpu的datasheet裏描述的寄存器的位址都是物理位址, 那linux內核是如何把我們驅動中指定操作的虛擬位址轉換成正真可定址並操作的物理位址的呢? 這篇文檔以s3c2410為例, 將詳細的描述這麼一個實現流程。


S3c2410使用的是arm920T的核,它支持MMU,正因為這樣它才可以進行虛擬位址到物理位址的轉換。而諸如使用arm7核的CPU一般都無法進行類似的轉換, 就在於它沒有MMU,所以它上面能跑的作業系統也是去掉了MMU功能後的linux如uClinux, 關於MMU的原理可參考相關文檔。

我們先來看檔Map.h

include/asm-arm/arch-s3c2410/Map.h:

#ifndef __ASSEMBLY__

#define S3C2410_ADDR(x) ((void __iomem __force *)0xF0000000 + (x))

#else

#define S3C2410_ADDR(x) (0xF0000000 + (x))

#endif



#define S3C2400_ADDR(x) S3C2410_ADDR(x)



/* interrupt controller is the first thing we put in, to make

* the assembly code for the irq detection easier

*/

#define S3C24XX_VA_IRQ S3C2410_ADDR(0x00000000)

#define S3C2400_PA_IRQ (0x14400000)

#define S3C2410_PA_IRQ (0x4A000000)

#define S3C24XX_SZ_IRQ SZ_1M



/* memory controller registers */

#define S3C24XX_VA_MEMCTRL S3C2410_ADDR(0x00100000)

#define S3C2400_PA_MEMCTRL (0x14000000)

#define S3C2410_PA_MEMCTRL (0x48000000)

#define S3C24XX_SZ_MEMCTRL SZ_1M

……



我們可以看到IRQ的寄存器虛擬位址定義為0xF0000000, 而物理位址為0x4A000000(這可從2410的datasheet上查到), memory控制器的寄存器虛擬位址定義為0xF0000000 + 0x00100000的位址處,物理位址為0x48000000, 其他如lcd 等寄存器都在這裏定義了虛擬位址,當然這裏僅僅是定義而已, 還沒有和物理位址達成映射的聯繫。 我們接著看。

我們來看2410的machine_desc結構:

arch/arm/mach-s3c2410/Mach-smdk2410.c:

MACHINE_START(SMDK2410, "SMDK2410") /* @TODO: request a new identifier and switch

* to SMDK2410 */

/* Maintainer: Jonas Dietsche */

.phys_io = S3C2410_PA_UART,

.io_pg_offst = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,

.boot_params = S3C2410_SDRAM_PA + 0x100,

.map_io = smdk2410_map_io,

.init_irq = s3c24xx_init_irq,

.init_machine = smdk2410_init,

.timer = &s3c24xx_timer,

MACHINE_END

這裏定義了一個描述2410開發板的結構, 其中的map_io, init_irq, init_machine都會在系統跑起來的時候被調用, 我們這裏要看的是smdk2410_map_io, 這個函數完成後我們的虛擬位址和物理位址的映射關係就完成了。

arch/arm/mach-s3c2410/Mach-smdk2410.c:

static void __init smdk2410_map_io(void)

{

s3c24xx_init_io(smdk2410_iodesc, ARRAY_SIZE(smdk2410_iodesc)); //重點在這行

s3c24xx_init_clocks(0);

s3c24xx_init_uarts(smdk2410_uartcfgs, ARRAY_SIZE(smdk2410_uartcfgs));

}

該函數調用s3c24xx_init_io 來完成實質的東西。smdk2410_iodesc的定義如下:

arch/arm/mach-s3c2410/Mach-smdk2410.c:

static struct map_desc smdk2410_iodesc[] __initdata = {

/* nothing here yet */

};

這是一個保存虛擬位址和物理位址的映射關係表,內核通過這個參數的指導來完成映射關係。當然我們可以在這裏添加我們需要映射的東東了。不過這裏並沒有條目, 很簡單因為在後面還會有這樣的映射表。

arch/arm/plat-s3c24xx/cpu.c:

void __init s3c24xx_init_io(struct map_desc *mach_desc, int size)

{

unsigned long idcode = 0x0;



/* initialise the io descriptors we need for initialisation */

iotable_init(s3c_iodesc, ARRAY_SIZE(s3c_iodesc)); //完成映射



//獲取當前系統的CPU

if (cpu_architecture() >= CPU_ARCH_ARMv5) {

idcode = s3c24xx_read_idcode_v5();

} else {

idcode = s3c24xx_read_idcode_v4();

}



cpu = s3c_lookup_cpu(idcode);



if (cpu == NULL) {

printk(KERN_ERR "Unknown CPU type 0x%08lx/n", idcode);

panic("Unknown S3C24XX CPU");

}



printk("CPU %s (id 0x%08lx)/n", cpu->name, idcode);



if (cpu->map_io == NULL

cpu->init == NULL) {

printk(KERN_ERR "CPU %s support not enabled/n", cpu->name);

panic("Unsupported S3C24XX CPU");

}



(cpu->map_io)(mach_desc, size); //調用CPU相關的映射函數

}

完成映射關係的核心就在這個函數裏了, 對iotable_init()的調用完成了s3c_iodesc 裏的映射條目的映射。而這個函數也是最最核心的東東了。我們先來看要完成映射的條目有那些

arch/arm/plat-s3c24xx/cpu.c:

static struct map_desc s3c_iodesc[] __initdata = {

IODESC_ENT(GPIO), //GPIO寄存器虛實位址映射

IODESC_ENT(IRQ), //中斷寄存器虛實位址映射

IODESC_ENT(MEMCTRL), /…

IODESC_ENT(UART) //….

};

這個就是要進行虛實位址映射的映射表了, 裏面的每個條目都對應一個映射關係, 用map_desc來描述, 我們來看map_desc

include/asm-arm/mach/Map.h:

struct map_desc {

unsigned long virtual; //虛擬位址

unsigned long pfn; //對應的物理位址

unsigned long length; //映射長度

unsigned int type; //類型。

};

我們以IRQ的映射關係為例來看一下:

先看IODESC_ENT的定義:

#define IODESC_ENT(x) { (unsigned long)S3C24XX_VA_##x, __phys_to_pfn(S3C24XX_PA_##x), S3C24XX_SZ_##x, MT_DEVICE }

則 IODESC_ENT(IRQ) 就是:

{

(unsigned long)S3C24XX_VA_IRQ,

_phys_to_pfn(S3C24XX_PA_IRQ),

S3C24XX_SZ_IRQ,

MT_DEVICE

}

而S3C24XX_VA_IRQ 等都在include/asm-arm/arch-s3c2410/Map.h下定義過了。因此這個條目解釋就是虛擬位址為0xF0000000開始長度為1M的虛擬位址空間由MMU轉換成物理位址為0x4A000000開始的1M位址空間, 這正是IRQ寄存器物理位址所在位置。 條目定義好了, 剩下的就是要讓系統MMU知道並在遇到這樣一個虛擬位址時能正確映射到具體的物理位址。而這個工作就是由iotable_init完成的。

Arch/arm/mm/Mmu.c:

void __init iotable_init(struct map_desc *io_desc, int nr)

{

int i;



for (i = 0; i < nr; i++)

create_mapping(io_desc + i); //為每個條目創建一個映射表

}

我們接著看核心函數:create_mapping(), 它是如何讓MMU部件知道映射關係的

void __init create_mapping(struct map_desc *md)

{

unsigned long phys, addr, length, end;

const struct mem_type *type;

pgd_t *pgd;



//參數檢查, 是否可以進行映射

if (md->virtual != vectors_base() && md->virtual < TASK_SIZE) {

printk(KERN_WARNING "BUG: not creating mapping for "

"0x%08llx at 0x%08lx in user region/n",

__pfn_to_phys((u64)md->pfn), md->virtual);

return;

}



if ((md->type == MT_DEVICE

md->type == MT_ROM) &&

md->virtual >= PAGE_OFFSET && md->virtual < VMALLOC_END) {

printk(KERN_WARNING "BUG: mapping for 0x%08llx at 0x%08lx "

"overlaps vmalloc space/n",

__pfn_to_phys((u64)md->pfn), md->virtual);

}





type = &mem_types[md->type]; //獲取memory類型



/*

* Catch 36-bit addresses

*/

if (md->pfn >= 0x100000) {

create_36bit_mapping(md, type);

return;

}



addr = md->virtual & PAGE_MASK; //得到虛擬位址

phys = (unsigned long)__pfn_to_phys(md->pfn); //得到物理位址

length = PAGE_ALIGN(md->length + (md->virtual & ~PAGE_MASK)); //映射長度



if (type->prot_l1 == 0 && ((addr
phys
length) & ~SECTION_MASK)) {

printk(KERN_WARNING "BUG: map for 0x%08lx at 0x%08lx can not "

"be mapped using pages, ignoring./n",

__pfn_to_phys(md->pfn), addr);

return;

}



pgd = pgd_offset_k(addr);

end = addr + length;

do {

unsigned long next = pgd_addr_end(addr, end);



alloc_init_section(pgd, addr, next, phys, type);



phys += next - addr;

addr = next;

} while (pgd++, addr != end);

}

這個函數涉及到了相當多的MMU方面的知識, 一般系統中會有一個頁表,頁表中的每個條目都是一個映射關係,包括對應的物理位址,及該塊物理位址區域訪問的各種屬性等, 而該頁表的位址被保存在了MMU中的一個寄存器中。當我們在驅動中要訪問某個寄存器時, 一般都使用的是虛擬位址, 這個虛擬位址被分成了兩個區域, 高位址的若干位其實是個index, 用於指定需要頁表中的哪個條目來映射該虛擬位址, 而剩下的若干位則是偏移位址, 即具體要操作的物理位址處。

當MMU需要進行虛擬位址到物理位址的映射時會先通過該寄存器找到這張頁表,然後從虛擬位址的高若干位元來得到一個具體的映射條目, 從這個條目我們可以知道該虛擬位址所在區間對應的物理位址區間, 然後在通過虛擬位址的低若干位元來精確定位到具體的物理位址。

而這個函數實際上就是完成這個頁表下面的相關映射條目項的填寫工作, 以後當訪問到這個虛擬位址時, 就會使用這裏的條目進行虛實轉換, 從而最終找到我們需要的物理位址並進行讀寫操作。