Linux內核移植之一:內核源碼結構與Makefile分析

內容來自 韋東山《嵌入式Linux應用開發完全手冊》

 

一、內核介紹

1、版本及其特點

        Linux內核的版本號可以從源代碼的頂層目錄下的Makefile中看到,比如下面幾行它們構成了Linux的版本號:2.6.22.6。
 

VERSION=2
PATCHLEVEL=6SUBLEVEL =22
EXTRAVERSION=.6

        其中的“VERSION”和“PATCHLEVEL”組成主版本號,比如2.4、2.5、2.6等,穩定版本的主版本號用偶數表示(比如2.4、2.6),每隔2~3年出現一個穩定版本。開發中的版本號用奇數來表示(比如2.3、2.5),它是下一個穩定版本的前身。
        “SUBLEVEL”稱爲次版本號,它不分奇偶,順序遞增。每隔1~2個月發佈一個穩定版本。

        “EXTRAVERSION”稱爲擴展版本號,它不分奇偶,順序遞增。每週發佈幾次擴展版本號,修正最新的穩定版本的問題。值得注意的是,“EXTRAVERSION”也可以不是數字,而是類似“-rc6”的字樣,表示這是一個測試版本。在新的穩定版本發佈之前,會先發布幾個測試版本用於測試。
        Linux內核的最初版本在1991年發佈,這是Linus Torvalds爲他的386開發的一個類Minix的操作系統。

2、獲取內核源碼

        登錄https://www.kernel.org/,可以看到如下:

        上面列舉了當前內核的最新穩定版本、測試版本、補丁等。

        一般而言,各種補丁文件都是基內核的某個正式版本生成的。比如有補丁文件patch-2.6.xx.1、patch-2.6.xx.2、patch-2.6.xx.3,它們都是基於內核2.6.xx生成的補丁文件。使用時可以在內核2.6.xx上直接打補丁patch-2.6.xx.3,並不需要先打上補丁patch-2.6.xx.1、patch-2.6.xx.2;相應地,如果已經打了補丁patch-2.6.xx.2,在打補丁patch-2.6.xx.3前,要先去除patch-2.6.xx.2。
        本書在Linux2.6.22.6上進行移植、開發,下載linux-2.6.22.6.tar.bz2後用如下命令解壓即可得到目錄linux-2.6.22.6,裏面存放了內核源碼:

$ tar xjf linux-2.6.22.6.tar.bz2

        也可以先下載內核源文件linux-2.6.22.tar.bz2、補丁文件patch-2.6.22.6.bz2,然後解壓、打補丁(假設源文件、補丁文件放在同一個目錄下),命令如下所示:

tar xjf linux-2.6.22.tar.bz2
tar xjf patch-2.6.22.6.bz2
cd linux-2.6.22
patch -pl < ../patch-2.6.22.6

        下面的內容,都假設內核源碼所在目錄爲linux-2.6.22.6。

3、內核源碼結構

        Linux內核文件數目將近2萬,除去其他架構CPU的相關文件,支持S3C2410、S3C2440這兩款芯片的完整內核文件有1萬多個。這些文件的組織結構並不複雜,它們分別位於頂層目錄下的17個子目錄,各個目錄功能獨立。下表描述了各目錄的功能,最後2個目錄不包含內核代碼。

表1

目錄名 描述
arch 體系結構相關的代碼,對於每個架構的CPU,arch目錄下都有一個對應的子目錄,比如arch/arm/、arch/i386/
block 塊設備的通用函數
crypto 常用加密和散列算法(如AES、SHA等),還有一些壓縮和CRC校驗算法
drivers 所有的設備驅動程序,裏面每一個子目錄對應一類驅動程序,比如drivers/block/爲塊設備驅動程序,drivers/char/爲字符設備驅動程序,drivers/mtd/爲NOR Flash、NAND Flash等存儲設備的驅動程序
fs Linux支持的文件系統的代碼,每個子目錄對應一種文件系統,比如fsjffs2/、fs/ext2/、fs/ext3/
include 內核頭文件,有基本頭文件(存放在include/linux/目錄下)、各種驅動或功能部件的頭文件(比如include/media/、include/mtd/、include/net/)、各種體系相關的頭文件(比如include/asm-arm/、include/asm-i386/)。當配置內核後,include/asm/是某個include/asm-xxx/(比如include/asm-arm/)的鏈接
init 內核的初始化代碼(不是系統的引導代碼),其中的main.c文件中的start_kernel函數是內核引導後運行的第一個函數
ipc 進程間通信的代碼
Kermel 內核管理的核心代碼,與處理器相關的代碼位於arch/*/kemel/目錄下
lib 內核用到的一些庫函數代碼,比如crc32.c、string.c,與處理器相關的庫函數代碼位於arch/*/lib/目錄下
mm 內存管理代碼,與處理器相關的內存管理代碼位於arch/*/mm/目錄下
net 網絡支持代碼,每個子目錄對應於網絡的一個方面
security 安全、密鑰相關的代碼
sound 音頻設備的驅動程序
usr 用來製作一個壓縮的cpio歸檔文件:initrd的鏡像,它可以作爲內核啓動後掛接(mount)的第一個文件系統(一般用不到)
Documentation 內核文檔
scripts 用於配置、編譯內核的腳本文件

        對於ARM架構的S3C2410、S3C2440,其體系相關的代碼在arch/arm/目錄下,在後面進行Linux移植時,開始的工作正是修改這個目錄下的文件。如下圖所示爲內核代碼的層次結構。

二、Makefile分析

        Makefile的作用主要有以下3點:
(1)決定編譯哪些文件。
(2)怎樣編譯這些文件?
(3)怎樣連接這些文件,最重要的是它們的順序如何?
        Linux內核源碼中含有很多個Makefile文件,這些Makefile文件又要包含其他一些文件(比如配置信息、通用的規則等)。這些文件構成了Linux的Makefile體系,可以分爲下表中的5類。

名稱 描述
頂層Makefile 它是所有Makefile文件的核心,從總體上控制着內核的編譯、連接
.config 配置文件,在配置內核時生成。所有Makefile文件(包括頂層目錄及各級子目錄)都是根據.config來決定使用哪些文件
arch/$(ARCH)/Makefile 對應體系結構的Makefile,它用來決定哪些體系結構相關的文件參與內核的生成,並提供一些規則來生成特定格式的內核映象
scripts/Makefile.* Makefile共用的通用規則、腳本等
kbuild Makefiles 各級子目錄下的Makefile,它們相對簡單,被上一層Makefile 調用來編譯當前目錄下的文件

        內核文檔Documentation/kbuild/makefiles.txt對內核中Makefile的作用、用法講解得非常透徹,以下根據前面總結的Makefile的3大作用分析這5類文件。

1、決定編譯哪些文件

        Linux內核的編譯過程從頂層Makefile開始,然後遞歸地進入各級子目錄調用它們的Makefile,分爲3個步驟。
(1)頂層Makefile決定內核根目錄下哪些子目錄將被編進內核。
(2)arch/$(ARCH)/Makefile 決定arch/$(ARCH)目錄下哪些文件、哪些目錄將被編進內核。
(3)各級子目錄下的Makefile決定所在目錄下哪些文件將被編進內核,哪些文件將被編成模塊(即驅動程序),進入哪些子目錄繼續調用他們的Makefile。

(1)步驟1

        先看步驟(1),在頂層Makefile中可以看到如下內容:

433 init-y    :=init/
434 drivers-y :=drivers/sound/
435 net-y     :=net/
436 libs-y    :=lib/
437 core-y    :=usr/
...
556 core-y    +=kernel/mm/fs/ipc/security/crypto/block/

        可見,頂層Makefile將這13個子目錄分爲5類:init-y、drivers-y、net-y、libs-y和core-y。表1中有17個子目錄,除去include目錄和後面兩個不包含內核代碼的目錄外,還有一個arch目錄沒有出現在內核中。它在arch/$(ARCH)/Makefile中被包含進內核,在頂層Makefile中直接包含了這個Makefile,如下所示:

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

        對於ARCH變量,可以在執行make命令時傳入,比如“make ARCH=arm …”。另外,對於非x86平臺,還需要指定交叉編譯工具,這也可以在執行make命令時傳入,比如“make CROSS_COMPILE=arm-linux- …”。爲了方便,常在頂層Makefile中進行如下修改。

修改前:
185 ARCH          ?= &(SUBARCH)
186 CROSS_COMPILE ?=
修改後:
185 ARCH          ?=arm
186 CROSS_COMPILE ?=arm-linux-

(2)步驟2
        對於步驟(2)的arch/$(ARCH)/Makefile,以ARM體系爲例,在arch/arm/Makefile中可以看到如下內容:

94  head-y :=arch/arm/kernel/head$(MMUEXT).o arch/arm/kernel/init_task.o
...
171 core-y +=arch/arm/kernel/ arch/arm/mm/ arch/arm/common/
172 core-y +=$(MACHINE)
173 core-$(CONFIG_ARCH_S3C2410) += arch/arm/mach-s3c2400/
174 core-$(CONFIG_ARCH_S3C2410) += arch/arm/mach-s3c2412/
175 core-$(CONFIG_ARCH S3C2410) += arch/arm/mach-s3c2440/
...
191 1ibs-y := arch/arm/lib/ $(libs-y)
...


        從第94行可知,除前面的5類子目錄外,又出現了另一類:head-y,不過它直接以文件名出現。MMUEXT在arch/arm/Makefile前面定義,對於沒有MMU的處理器,MMUEXT的值爲-nommu,使用文件head-nommu.S;對於有MMU的處理器,MMUEXT的值爲空,使用文件head.S。
        arch/arm/Makefile中類似第171、172、173行的代碼進一步擴展了core-y的內容,第191行擴展了libs-y的內容,這些都是體系結構相關的目錄。

        第173~175行中的CONFIG_ARCH_S3C2410在配置內核時定義,它的值有3種:y、m或空。y表示編進內核,m表示編爲模塊,空表示不使用
        編譯內核時,將依次進入init-y、core-y、libs-y、drivers-y和net-y所列出的目錄中執行它們的Makefile,每個子目錄都會生成一個built-in.o(libs-y所列目錄下,有可能生成lib.a文件)。最後,head-y所表示的文件將和這些built-in.o、lib.a一起被連接成內核映象文件vmlinux。

(3)步驟3
        最後,看一下步驟(3)是怎麼進行的。
        在配置內核時,生成配置文件.config(具體的配置過程在後面會講述)。內核頂層Makefile 使用如下語句間接包含.config文件,以後就根據.config中定義的各個變量決定編譯哪些文件。之所以說是“間接”包含,是因爲包含的是include/config/auto.conf文件,而auto.conf文件只是將.config文件中的註釋去掉,再根據頂層Makefile中定義的變量增加了一些變量而已。

441 #Read in config
442 -include include/config/auto.conf 

        include/config/auto.conf文件的生成過程不再描述,它與.config的格式相同,摘選部分內容如下(注意,下面以“#”開頭的行是本書加的註釋):

CONEIG_ARCH_SMDK2410=y 
CONFIG_ARCH_S3C2440=y
#.config中沒有下面這行,它是根據頂層Makefile中定義的內核版本號增加的
CONFIG_KERNELVERSION="2.6.22.6"
#.config中沒有下面這行,它是根據頂層Makefile中定義的ARCH變量增加的
CONFIG_ARCH="arm"
CONFIG_JFFS2_FS=y 
CONFIG_LEDS_S3C24XX=m

        在include/config/auto.conf文件中,變量的值主要有兩類:“y”和“m”。各級子目錄的Makefile使用這些變量來決定哪些文件被編進內核中,哪些文件被編成模塊(即驅動程序),要進入哪些下一級子目錄繼續編譯,這通過以下4種方法來確定(obj-y、obj-m、lib-y是Makefile中的變量)。
        (1)obj-y用來定義哪些文件被編進(built-in)內核。
        obj-y中定義的.o文件由當前目錄下的.c或.S文件編譯生成,它們連同下級子目錄的built-in.o文件一起被組合成(用“$(LD) -r”命令)當前目錄下的built-in.o文件。這個built-in.o文件將被它的上一層Makefile使用。
        obj-y中各個.o文件的順序是有意義的,因爲內核中用module_init()或__initcall定義的函數將按照它們的連接順序被調用。


        例16.1:當下面的CONFIG_ISDN、CONFIG_ISDN_PPP_BSDCOMP在.config中被定義爲y時,isdn.c或isdn.S、isdn_bsdcomp.c或isdn_bsdcomp.S被編譯成isdn.o、isdn_bsdcomp.o。這兩個o文件被組合進built-in.o文件中,最後被連接進入內核。假如isdn.o、isdn_bsdcomp.o中分別用module_init(A)、module_init(B)定義了函數A、B,則內核啓動時A先被調用,然後是B。

obj-$(CONFIG_ISDN)+=isdn.o 
obj-$(CONFIG_ISDN_PPP_BSDCOMP)+=isdn_bsdcomp.o


        (2)obj-m用來定義哪些文件被編譯成可加載模塊(Loadable module)。
        obj-m中定義的.o文件由當前目錄下的.c或.S文件編譯生成,它們不會被編進built-in.o中,而是被編成可加載模塊。

        一個模塊可以由一個或幾個.o文件組成。對於只有一個源文件的模塊,在obi-m中直接增加它的.o文件即可。對於有多個源文件的模塊,除在obj-m中增加一個.o文件外,還要定義一個<module name>-objs變量來告訴Makefile這個.o文件由哪些文件組成。

        例16.2:當下面的CONFIG_ISDN_PPP_BSDCOMP在.config文件中被定義爲m時,isdn_bsdcomp.c或isdn_bsdcomp.S將被編譯成isdn_bsdcomp.o文件,它最後被製作成isdn_bsdcomp.ko模塊,如下所示:

#drivers/isdn/i41/Makefile 
obj-$(CONEIG_ISDN_PPP_BSDCOMP) += isdn_bsdcomp.o

        例16.3:當下面的CONFIG_ISDN在.config文件中被定義爲m時,將會生成一個isdn.o文件,它由isdn-objs中定義的isdn_net_ lib.o、isdn_v110.o、isdn_common.o等3個文件組合而成。isdn.o最後被製作成isdn.ko模塊。

#drivers/isdn/i41/Makefile
obj-$(CONFIG_ISDN) += isdn.o 
isdn-objs := isdn_net_lib.o isdn_v110.o isdn_common.o

        (3)lib-y用來定義哪些文件被編成庫文件。

        lib-y中定義的.o文件由當前目錄下的.c或.S文件編譯生成,它們被打包成當前目錄下的個庫文件:lib.a。

        同時出現在obj-y、lib-y中的.o文件,不會被包含進lib.a中。

        要把這個lib.a編進內核中,需要在頂層Makefile中libs-y變量中列出當前目錄。要編成庫文件的內核代碼一般都在這兩個目錄下:lib/、arch/$(ARCH)/lib/。

        (4)obj-y、obj-m還可以用來指定要進入的下一層子目錄。

        Linux中一個Makefile文件只負責生成當前日錄下的目標文件,子目錄下的目標文件由子目錄的Makefile生成。Linux的編譯系統會自動進入這些子目錄調用它們的Makefile,只是在這之前需要指定這些子目錄。

        這要用到obj-y、obj-m,只要在其中增加這些子目錄名即可。

        例16.4:fs/Makefile中有如下一行,當CONFIG_JFFS2_FS被定義爲y或m時,在編譯時將會進入jffs2/目錄進行編譯。Linux的編譯系統只會根據這些信息決定是否進入下一級目錄,而下一級中的文件如何編譯成built-in.o或模塊由它的Makefile決定。

101 obj-$(CONFIG_JFFS2_FS) += jffs2/

 

2、怎樣編譯這些文件。

        即編譯選項、連接選項是什麼。這些選項分3類:全局的,適用於整個內核代碼樹;局部的,僅適用於某個Makefile中的所有文件;個體的,僅適用於某個文件。

        全局選項在頂層Makefile 和arch/$(ARCH)/Makefile中定義,這些選項的名稱爲:CFLAGS、AFLAGS、LDFLAGS、ARFLAGS,它們分別是編譯C文件的選項、編譯彙編文件的選項、連接文件的選項、製作庫文件的選項。

        需要使用局部選項時,它們在各個子目錄中定義,名稱爲:EXTRA_CFLAGS、EXTRA_AFLAGS、EXTRA_LDFLAGS、EXTRA_ARFLAGS,它們的用途與前述選項相同,只是適用範圍比較小,它們針對當前Makefile中的所有文件。

        另外,如果想針對某個文件定義它的編譯選項,可以使用CFLAGS_$@,AFLAGS _$@。前者用於編譯某個C文件,後者用於編譯某個彙編文件。$@表示某個目標文件名,比如以下代碼表示編譯ahal52x.c時,選項中要額外加上“-DAHA152X_STAT-DAUTOCONF”。

#drivers/scsi/Makefile 
CFLAGS_aha152x.0 = -DAHA152X_STAT -DAUTOCONF

        需要注意的是,這3類選項是一起使用的,在scripts/Makefile.lib中可以看到。

_c_flags = $(CFLAGS) $(EXTRA_CFLAGS) $(CFLAGS_$(basetarget).o)

 

3、怎樣連接這些文件,它們的順序如何。

        前面分析有哪些文件要編進內核時,頂層Makefile和arch/$(ARCH)/Makefile定義了6類目錄(或文件):head-y、init-y、drivers-y、net-y、libs-y和core-y。它們的初始值如下(以ARM體系爲例)。

        arch/arm/Makefile中:

94  head-y := arch/arm/kernel/head$ (MMUEXT) .o arch/arm/kernel/init_task.o
...
171 core-y += arch/arm/kernel/arch/arm/mm/arch/arm/common/
172 core-y += $(MACHINE)
173 core-$(CONFIG_ARCH_S3C2410) += arch/arm/mach-s3c2400/
174 core-$(CONFIG_ARCH_S3C2410) += arch/arm/mach-s3c2412/
175 core-$(CONEIG_ARCHS3C2410)  += arch/arm/mach-s3c2440/
...
191 libs-y := arch/arm/lib/$(libs-y)
...

        頂層Makefile中:

433 init-y    :=init/
434 drivers-y :=drivers/sound/
435 net-y     :=net/
436 libs-y    :=lib/
437 core-y    :=usr/
...
556 core-y += kernel/mm/fs/ipc/security/crypto/block/

        可見,除head-y外,其餘的init-y、drivers-y等都是目錄名。在頂層Makefile中,這些目錄名的後面直接加上built-in.o或lib.a,表示要連接進內核的文件,如下所示:

567 init-y   :=$(patsubst %/,%/built-in.o,$(init-y))
568 core-y   :=$(patsubst %/,%/built-in.o,$core-y))
569 drivers-y:=$(patsubst %/,%/built-in.o,$(drivers-y))
570 net-y    :=$(patsubst %/,%/built-in.o,$(net-y))
571 libs-y1  :=$((patsubst %/,%/lib.a,$(libs-y))
572 libs-y2i :=$(patsubst %/,%/built-in.o,$(libs-y))
573 libs-y   :=$(libs-y1)  $(libs-y2)

        上面的patsubst是個字符串處理函數,它的用法如下:

$(patsubst pattern,replacement,text)

        表示尋找“text”中符合格式“pattern”的字,用“replacement”替換它們。比如上面的init-y初值爲“init/”,經過第567行的交換後,“init-y”變爲“init/built-in.o”。

        頂層Makefile中,再往下看。

602 vmlinux-init :=$(head-y) $(init-y)
603 vmlinux-main :=$(core-y) $(libs-y) $(drivers-y) $(net-y)
604 vmlinux-al1  :=$(vmlinux-init) $(vmlinux-main)
605 vmlinux-1ds  := arch/$(ARCH)/kerne1/vmlinux.lds

        第604行的vmlinux-all表示所有構成內核映象文件vmlinux的目標文件,從第602~604行可知這些目標文件的順序爲:head-y、init-y、core-y、libs-y、drivers-y、net-y,即arch/arm/kernel/head.o(假設有MMU,否則爲head-nommu.o)、arch/arm/kernel/init task.o、init/built-in.o、usr/built-in.o等。

        第605行表示連接腳本爲arch/$(ARCH)/kernel/ymlinux.lds。對於ARM體系,連接腳本就是arch/arm/kernel/vmlinux.lds,它由arch/arm/kernel/vmlinux.lds.S文件生成,規則在scripts/Makefile.build中,如下所示:

248 $(obj)/%.lds: $(src)/%.lds.S FORCE
249     $(ca1l if_changed_dep,cpp_lds_s)
250

        現將生成的arch/arm/kernel/vmlinux.lds摘錄如下:

286 SECTIONS
287 {
291 .=(0xc0000000)+0X00008000; /*代碼段起始地址,這是個虛擬地址*/
292
293 .text.head:{
294 _stext=.;
295 _sinittext=.;
296 *(.text.head)
297 }
298
299 .init:{/*內核初始化的代碼和數據*/
...
343 }
344
...
355 .text:{/*真正的代碼段*/
356 _text=.;/*代碼段和只讀數據段的開始地址*/
...
372}
373 /*只讀數據*/
374 .=ALIGN((4096));.rodata:AT(ADDR(.rodata)-0){.……}.=ALIGN((4096));
375
376 _etext=.;/*代碼段和只讀數據段的結束地址*/
...
386 .data:Ar(_data_loc){/*數據段*/
387 data_start=.;/*數據段起始地址*/
...
422 edata=.;/*數據段結束地址*/
423
424 edataloc =_dataloc+SrZEOF(.data);/*數據段結束地址*/
425
426 .b33:{/*BSS段,沒有初化或初值爲0的全局、靜態變量*/
427 _bs8_start=.;/*BSS段起始地址*/
428 *(.bss)
429 *(COMMON)
430 end=.;/*BsS段結束地址*/
431
432 /*調試信息段*/
433 .stab 0:{*(.stab)}
...
440}

        下面對本節分析Makefile的結果作一下總結。

(1)配置文件.config中定義了一系列的變量,Makefile將結合它們來決定哪些文件被編進內核、哪些文件被編成模塊、涉及哪些子目錄。

(2)頂層Makefile和arch/$(ARCH)/Makefile決定根目錄下哪些子目錄、arch/$(ARCH)目錄下哪些文件和目錄將被編進內核。

(3)最後,各級子目錄下的Makefile決定所在目錄下哪些文件將被編進內核,哪些文件將被編成模塊(即驅動程序),進入哪些子目錄繼續調用它們的Makefile。

(4)頂層Makefile 和arch/$(ARCH)/Makefile 設置了可以影響所有文件的編譯、連接選項:CFLAGS、AFLAGS、LDFLAGS、ARFLAGS。

(5)各級子日錄下的Makefile中可以設置能夠影響當前目錄下所有文件的編譯、連接選項:EXTRA CFLAGS、EXTRAAFLAGS、EXTRA_LDFLAGS、EXTRAARFLAGS;還可以設置可以影響某個文件的編譯選項:CFLAGS_$@,AFLAGS_$@。

(6)頂層Makefile按照一定的順序組織文件,根據連接腳本 arch/$(ARCH)/kermel/vmlinux.lds生成內核映象文件vmlinux。

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