我們在編譯Linux內核時,往往在Linux內核的頂層目錄會執行一些命令,這里我以RK3288舉例,比如:make firefly-rk3288-linux_defconfig、make menuconfig、make firefly-rk3288.img、make zImage等等。先不管這具體的含義,首先提出幾個疑問:
本文引用地址://www.einuk.cn/emb/Column/7565.html
(1)Linux內核如此龐大(幾萬個文件),目錄又分為很多層,它是如何將各層目錄下的文件關聯起來的?
(2)Linux支持如此多的架構(X86、ARM、AVR、mips等等),為何我們在使用某一架構的芯片,比如RK3288時,其他架構的代碼不會被編譯?并且同為ARM架構下的其他系列SOC架構相關的代碼不被編譯?
(3)在編譯內核前,執行命令:make menuconfig的意義為何?
(4)編譯內核時,執行命令:make zImage的意義為何?
(5)Linux內核中各層目錄下的Makefile文件和Kconfig文件是如何編寫的。
個人認為,當理解了解決了以上的幾個問題,那么本文題目的疑問就自然而然的解決了。以下筆者來一一回答這個問題:
問題1.Linux內核如此龐大(幾萬個文件),目錄又分為很多層,它是如何將各層目錄下的文件關聯起來的?
關于此問題,如果要準確的回答,估計只有真正的大神才能回答了,這里只講自己的理解。Linux內核源碼的代碼管理是非常科學的,在Linux內核源碼的頂層目錄下,分配了相應的目錄,在對應的目錄下,代表這就一些功能或者是屬性的集群,這樣就實現了模塊化,便于管理,比如arch目錄與平臺架構相關、include目錄存放著大量的內核頭文件、drivers目錄存放著各種驅動代碼,比如顯卡、網卡、USB總線、PCI總線等等、kernel目錄存放著支持體系結構特有的諸如信號量處理和SMP之類特征的實現、mm目錄存放著體系結構特有的內存管理程序的實現等等;然而在各個子目錄下,又會進行細分,比如arch目錄下,就存在和x86架構相關的目錄x86、與ARM架構相關的目錄arm、與MIPS目錄相關的目錄mips等等,以此類推。所以這就構成了一顆樹形結構。
學習過數據結構的童鞋應該知道,對于一棵非標準樹,還是有辦法將其進行遍歷的,只是算法比較復雜而已。那么在Linux內核源碼的這棵樹,就是通過Kconfig文件建立各層子目錄之間的連接,通過Makefile文件來選擇各個目錄下的對應的文件是否被編譯,而.config文件就像是作為總控制臺吧,控制著Makefile文件去編譯指定的程序代碼文件(主要是C和匯編)。而這一切控制關系是由Kconfig文件建立起來的。
在我理解而言,這其實就是在遍歷樹形結構時,用的一種方法,即指路法。每當我們在某個陌生的地方問路時,假設從A地到E地,情景通常如下:
通常我們也會按照路人的回答,很準確的就找到了目的地E,所以,在程序中或者代碼架構中也是一樣的。所以在Linux內核源碼中,.config文件就相當于路人,而Kconfig就為問路的人,而Makefile就為兩只腳了,聽指令干苦力的貨!嘿嘿!
問題2.Linux支持如此多的架構(X86、ARM、AVR、mips等等),為何我們在使用某一架構的芯片,比如RK3288時,其他架構的代碼不會被編譯?并且同為ARM架構下的其他系列SOC架構相關的代碼不被編譯?
關于這個問題,其實和我們在編譯Linux內核時,執行的命令:make firefly-rk3288-linux_defconfig或者make menuconfig有關了,執行以上兩個命令的目的是為了生成.config文件。往往我們執行命令make menuconfig只是為了修改一些驅動模塊和要編譯的一些程序,往往我們是不會去選擇架構相關的,比如說,自行的去選擇RK3288這顆SOC,再去選擇基于Firefly平臺的板卡,其實這也是可以的,只要你開心,都可以自行在菜單里面選擇,但是通常我們不會這樣,我們通常是先執行make firefly-rk3288-linux_defconfig生成了一個基于Firefly平臺的RK3288相關配置的.config文件,然后再執行命令make menuconfig來選擇一些模塊代碼進行編譯,這樣做的好處就是減少了很多工作量。當然,存在一個問題是,假設需要編譯的內核不支持現有的開發板怎么辦?比如從官網下載下來的原始Linux內核源碼,只支持RK3288這顆SOC,但是并沒有急于Firefly開發的這款RK3288的開發板,怎么辦呢?這涉及到了Linux內核的移植,在此不作過多的敘述,提醒一點的是,可以找一塊基于RK3288這顆SOC的開發板的配置(在官方發布的初始Linux內核源碼中,每支持一款SOC,基本上都會對于的支持一款官方開發板作為參考),進行移植和模擬,后生成一個類似于firefly-rk3288-linux_defconfig的配置文件,用它來生成基本的.config文件。
清楚了以上的關系,在根據問題1的原因,就不難理解了。沒錯,就是基于firefly-rk3288-linux_defconfig文件生成了基礎的,默認的.config配置文件,此文件的內容就包含了架構相關的東西,所以在進行Linux內核源碼的編譯時,根據.config文件的基本配置,尋找架構相關的代碼進行編譯和設備相關的代碼進行編譯。總之一起而已.config這個控制臺為準。
問題3.在編譯內核前,執行命令:make menuconfig的意義為何?
其實這個問題在前兩個問題中基本上都回答了,make menuconfig就是以菜單的形式打開內核源碼的樹形結構,然后程序員在默認配置的基礎上自行配置和選擇需要編譯的模塊代碼。
其實能做這個工作的命令有很多,比如:
•make config:基于文本的為傳統的配置界面,太復雜,不直觀,不推薦使用。
•#make xconfig:基于圖形窗口模式的配置界面,直觀明了,Xwindow界面下推薦使用。
•make oldconfig:如果只想在原來內核配置的基礎上修改一些小地方,會省去不少麻煩,可以使用。
•make menuconfig:基于文本選單的配置界面,直觀明了,字符終端下推薦使用。
大概好像這幾種吧,其實還有一種就是,手動修改.config文件,呵呵!相信基本上沒人會去干這種事的。
問題4.編譯內核時,執行命令:make zImage的意義為何?
通常在編譯時,都是執行這個命令或者執行make uImage命令進行編譯的。意義為何呢?其實是生成名為zImage或uImage的內核鏡像。當然,有人在疑問了,在編譯Firefly的RK3288開發板時,執行的命令是make firefly-rk3288.img ,而不是上面的任何一個,其實這是Firefly修改過的方法了,其實說白了也就是一個名字而已。這個不要太過糾結,想具體了解的話,答案就在Firefly提供的內核源碼內。
執行編譯命令后,通過.config文件、Kconfig文件和Makefile文件,就可以有規律有選擇的去編譯源代碼了。
問題5.內核中各層目錄下的Makefile文件和Kconfig文件是如何編寫的。
其實前面的4個問題只是基礎的鋪墊,這才是重點,但是這里必須依賴于前面的原理。
首先我們先確定一點,在Linux內核源碼的各層目錄下。都存在一個Kconfig文件和一個Makefile文件,.config文件存在頂層目錄。如下圖:
上圖基本上可以證明一切了。
為了更好的詮釋,我在drivers目錄下創建了一個my_dr目錄,主要存放我自己編寫的內核驅動代碼,此目錄下的其他目錄都是我編寫的驅動代碼,現在需要將它們連接起來,當執行make menuconfig命令時,能夠找到我自己編寫的內核驅動代碼。在這里以一個hello程序舉例。
那么到底該如何做呢?•••••••••兩字:“模仿”!
如下圖:
上圖中就是我要舉例的目錄層了,hello目錄里為hello.c程序;my_dr目錄里為我想把我自己寫的驅動程序存放的目錄;drivers目錄為Linux內核驅動目錄;firefly-rk3288-kernel目錄為Linux內核源碼頂層目錄。所以按照前文的推斷,在每一層目錄下都會存在一個Makefile文件和一個Kconfig文件。下面從底層hello到drivers目錄各層Makefile文件和Kconfig文件分析。
(1)hello目錄
如上圖,左邊為Makefile文件,右邊為Kconfig文件。對于Makefile文件,如果變量CONFIG_HELLO為真或假,則判斷這是否將目錄下的hello.c文件編譯為hello.o文件。CONFIG_HELLO變量的值來自于.config文件的配置。.config的配置又來自于通過Kconfig文件的顯式選擇(就是通過菜單選擇)。再看Kconfig文件,如下圖:
Config為配置關鍵字;HELLO為配置項;tristate為三態選擇器,此關鍵字在為上層提供菜單配置時,有三種選擇,分別是不編譯、編譯成內核鏡像和編譯成模塊驅動,除了這個關鍵字,還有另一個bool,這個不難理解,即為布爾類型,西游兩種選擇,編譯或者不編譯;help為幫助提示。這基本上是比較經典的格式了。所以通常在進行編寫這樣的配置時,通常的做法是參考Linux內核源碼中存在的Kconfig文件和Makefile文件,然后模仿他們的格式進行編寫。
(2)my_dr目錄
如上圖,即為mu_dr目錄下的Makefile文件和Kconfig文件的內容了。對于Makefile文件,一句話指引找到hello目錄。而對于Kconfig文件,因為my_dr目錄下可能要存在很多驅動程序,所以必須要建立好一個菜單,進行對各個驅動程序的選擇,所以第1行代碼就算建立名為my Drivers的菜單;第5行代表可以在相對路徑drivers/my_dr/hello/Kconfig找到Kconfig資源(注意,這里的路徑是相對于Linux內核源碼的頂層目錄的)。后的第7行表示使用關鍵字endmenu結束文件。
(3)drivers目錄
如上圖,即為drivers目錄下的Makefile文件和Kconfig文件了。還是一樣的,在Makefile文件中添加洗衣歌資源的菜單目錄路徑;而在Kconfig文件中,還是添加此目錄下的資源的Kconfig文件的路徑。就這樣配合使用。而且,基本上在這兩個文檔中,可以直接看到很多的參考示例,直接參考即可!其實有一個疑問是,在Makefile文件中,比如第158行,如下圖:
存在變量CONFIG_GATOR,這是為啥嘞?其實是這樣的,這表示如果想想在菜單中顯示選擇關于gator的目錄菜單,就必須依賴于變量CONFIG_GATOR的值為y,這樣才能在使用make menuconfig命令時,才能看到gator目錄下的菜單,而CONFIG_GATOR變量則是在gator目錄的上層,依賴于某些代碼,選擇后,其值為y。而筆者自己寫的直接使用了語句obj-y,表示變量值為y(其實就是yes),所以直接就可見了,不依賴于其他的代碼。
那么整一個過程就如上三步了,現在執行make menuconfig命令查看是否能看到前面建立的菜單。如下圖:
如上圖可以看到我自己創建的名為my Dribers的菜單了。
如上兩圖即為所使用的tristate關鍵字所表現出的三態結果了,即就是三種狀態?梢园凑招枰M行合適的選擇即可。
總結:本文主要根據個人的理解,記錄了Linux內核源碼中的Makefile、Kconfig和.config文件的編寫和使用,從某個角度來講,其貫穿了整一個Linux內核源碼,有以圖形菜單的形式體現給開發者,編譯開發和選擇。
聲明:以上內容純屬為個人理解,如若有誤,還望大神賜教!