一般來講,在驅動模塊的開發階段,通常是將模塊編譯成.ko文件,再使用html
sudo insmod module.ko
或者linux
depmod -a modprobe module
將模塊加載到內核,相對而言,modprobe要比insmod更加智能,它會檢查並自動處理模塊的依賴,而insmod出現依賴問題時僅僅是告訴你安裝失敗,本身想辦法吧。網絡
這一章節咱們並不關注模塊的運行時加載,咱們要討論的是將模塊編譯進內核。架構
在學習內核的Makefile規則的時候就能夠知道,將驅動程序編譯成模塊時,只須要使用:分佈式
obj-m += module.o
指定相應的源代碼(源代碼爲module.c)便可,因此不少朋友就簡單地得出結論:若是要將模塊編譯進內核,只要執行下面的的指令就能夠了:工具
obj-y += module.o
事實上,這樣是行不通的,要明白怎麼將驅動程序編譯進內核,咱們仍是得先了解linux源碼的編譯規則。性能
關於linux源碼的編譯規則和部分細節能夠查看個人另外一篇博客linux內核Makefile概覽
本篇博客的全部實驗基於arm平臺,beagle bone開發板,內核版本爲4.14.79學習
注:在如下的討論中,目標主機和本機指加載運行驅動程序的機器,是開發的對象。而開發機指只負責編譯的機器,通常指PC機。測試
在對驅動程序進行編譯時,通常會有兩種不一樣的作法:code
直接在目標主機上編譯是比較方便的作法,本機編譯本機運行。
一般,本機系統中通常不會自帶linux內核源碼的頭文件,咱們須要作的就是在系統中安裝頭文件:
sudo apt-get install linux-headers-$(uname -r)
$(uname -r)獲取當前主機運行的linux版本號。
有了頭文件,那麼源代碼從哪裏來呢?答案是並不須要源代碼,或者說是並不須要C文件形式的源代碼,而是直接引用當前運行的鏡像,在編譯時,將/boot/vmlinuz-$(version)鏡像當成庫文件進行連接便可。
值得注意的是,/boot/vmlinuz-$(version)是linux啓動時讀取的鏡像,可是在本機中進行驅動程序編譯的時候並不會影響到這個鏡像,換句話說,即便是指定了obj-y,驅動程序也不會編譯到/boot/vmlinuz-$(version)鏡像中,天然達不到將驅動編譯進內核的效果。
本機(目標機)編譯是比較方便的,可是沒法改變生成的鏡像,固然也能夠將源碼下載到本機(目標機)中進行編譯,就能夠生成相應的linux鏡像。
可是通常狀況下,在嵌入式開發中,不管是網絡、內存仍是執行速率,目標主機的性能通常不會過高,若是須要編譯完整的源碼時,用戶會更傾向於在PC端構建編譯環境以獲取更好的編譯性能。
選擇在開發機上編譯而不是本機編譯時,須要注意的一點就是:一般嵌入式開發都是基於arm、mips等嵌入式架構,而PC經常使用X86架構,在編譯時就不能使用開發機上的gcc編譯器,由於開發機上編譯器是針對開發平臺(X86),而非運行平臺(arm、mips),因此須要使用交叉編譯工具鏈,同時在編譯時指定運行的主機平臺。
指令是這樣的:
make arch=arm CROSS_COMPILE=$COMPILE_PATH/$COMPILE_TOOL
也能夠在makefile中給相應的arch和CROSS_COMPILE變量賦值,直接執行make指令便可。
顯然,這種交叉編譯方式是對linux內核源碼的完整編譯,主要生成這一些目標文件:
在上文中有提到,目標主機中,linux的啓動鏡像放置在/boot目錄下,因此若是咱們須要替換linux的鏡像,須要替換/boot目錄下的如下兩個文件:
在主機中,模塊通常被放置在/lib/modules目錄中,若是交叉編譯出的版本與本機中模塊版本不一致,將沒法識別,因此編譯出的模塊也須要替換。
根據上文,能夠得出的結論是:在試圖將驅動程序編譯進內核時,咱們須要編譯完整的linux內核源碼以生成相應的鏡像文件,而後將其替換到目標主機的/boot目錄下便可。
那麼,怎樣將驅動的源碼C文件編譯進內核呢?
這個問題得從makefile的執行流程提及:
首先,若是你有基本的linux內核編譯經驗,就知道在編譯linux源碼前,須要進行config(配置),以決定哪些部分編譯進內核、哪些部分編譯成模塊,
一般使用make menuconfig,不一樣的config方式一般只是選擇界面的不一樣,其中稍微特殊的make oldconfig則是沿用以前的配置。
在配置完成以後將生成一個.config文件,makefile根據.config文件選擇性地進入子目錄中執行編譯工做。
流程基本如上所說,可是咱們須要知道更多的細節:
答案是這樣的:
整個linux內核的編譯都是採用一種分佈式的思想,須要添加一個驅動到模塊中,咱們須要作的事情就是:
將驅動源文件放在內核對應目錄中,通常的驅動文件放在drivers目錄下,字符設備放在drivers/char中,塊設備就放在drivers/blok中,文件的位置遵循這個規律,若是是單個的字符設備源文件,就直接放在drivers/char目錄下,若是內容較多足以構成一個模塊,則新建一個文件夾。
若是是新建文件夾,須要修改上級目錄的Makefile和Kconfig,以將文件夾添加到整個源碼編譯樹中。
執行make menuconfig,執行make
將生成的新鏡像以及相應boot文件拷貝到目標主機中,測試。
beagle bone的啓動文件包括:vmlinuz、System.map,編譯出的模塊文件爲modules
上文中提到,在添加源碼時,通常會須要一個Kconfig文件,這樣就能夠在make menuconfig時對其進行配置選擇,在對一個源文件進行描述時,遵循相應的語法。
在這裏介紹一些經常使用的語法選項:
source:至關於C語言中的include,表示包含並引用其餘Kconfig文件
新建一個條目,用法:
source drivers/xxx/Kconfig config TEST bool "item name" depends on NET select NET help just for test
config是最經常使用的關鍵詞了,它負責新建一個條目,對應linux中的編譯模塊,條目前帶有選項。
config TEST:
config後面跟的標識會被當成名稱寫入到.config文件中,好比:當此項被選擇爲[y],即編譯進內核時,最後會在.config文件中添加這樣一個條目:
CONFIG_TEST=y
CONFIG_TEST變量被傳遞給makefile進行編譯工做。
***
bool "item name":
其中bool表示選項支持的種類,bool表示兩種,編譯進內核或者是忽略,還有另外一種選項就是tristate,它更經常使用,表示支持三種配置選項:編譯進內核、編譯成可加載模塊、忽略。而item name就是顯示在menu中的名稱。
**
depends on:*
表示當前模塊須要依賴另外一個選項,若是另外一個選項沒有沒選擇編譯,當前條目選項不會出如今窗口中
select:*
一樣是依賴相關的選項,表示當前選項須要另外其餘選項的支持,若是選擇了當前選項,那麼須要支持的那些選項就會被強制選擇編譯。
help:
容許添加一些提示信息
**
用法:
menu "Test option" ... endmenu
這一對關鍵詞建立一個選項目錄,該選項目錄不能被配置,選項目錄中能夠包含多個選項
至關於menu+config,此選項建立一個選項目錄,並且當前選項目錄可配置。
梳理了整個添加的流程,接下來就以一個具體的示例來進行詳細的說明。
背景以下:
鑑因而字符設備,因此將源文件目錄放置在$KERNEL_ROOT/drivers/char/下面。
若是是塊設備,就會被放置在block下面,可是這並非絕對的,相似USB爲字符設備,可是獨立了一個文件出來。
放置後目標文件位置爲:$KERNEL_ROOT/drivers/char/cdev_test
在$KERNEL_ROOT/drivers/char/cdev_test目錄下建立一個Kconfig文件,並修改文件以下:
menu "cdev test dir" config CDEV_TEST bool "cdev test support" default n help just for test ,hehe endmenu
根據上文中對Kconfig文件的語法描述,能夠看出,這個Kconfig文件的做用就是:
在上文中還提到,Kconfig分佈式地存在於子目錄下,同時須要注意的是,在編譯時,配置工具並不是無差異地進入每一個子目錄,收集全部的Kconfig信息,而是遵循必定的規則遞歸進入。
那麼,既然是新建的目錄,怎麼讓編譯器知道要進入到這個子目錄下呢?答案是,在上級目錄的Kconfig中包含當前路徑下的Kconfig文件。
打開char目錄下的Kconfig文件,而且在文件的靠後位置添加:
source "drivers/char/xillybus/Kconfig"
就把新的Kconfig文件包含到系統中檢索目錄中了,那麼drivers/char/又是怎麼被檢索到的呢?
就是在drivers的Kconfig中添加drivers/char/目錄下的Kconfig索引,以此類推。
在$KERNEL_ROOT/drivers/char/cdev_test目錄下建立一個Makefile文件,而且編譯Makefile文件以下:
obj-$(CONFIG_CDEV_TEST) += cdev_test.o
表示當前子目錄下Makefile的做用就是將cdev_test.c源文件編譯成cdev_test.o目標文件。
值得注意的是,這裏的編譯選項中使用的是:
obj-$(CONFIG_CDEV_TEST)
而非
obj-y
若是肯定要將驅動程序編譯進內核永遠不變,那麼能夠直接寫死,使用obj-y,若是須要進行靈活的定製,仍是須要選擇第一種作法。
CONFIG_CDEV_TEST是怎麼被配置的呢?在上文提到的Kconfig文件編寫時,有這麼一行:
config CDEV_TEST ...
在Kconfig被添加到配置菜單中,且被選中編譯進內核時,就會在$KERNEL_ROOT/.config文件中添加一個變量:
CONFIG_CDEV_TEST=y
自動添加CONFIG_前綴,而名稱CDEV_TEST則是由Kconfig指定,看到這裏,我想你應該明白了這是怎麼回事了。
是否是這樣就已經將當前子目錄添加到內核編譯樹中了呢?其實並無,就像Kconfig同樣,Makefile也是分佈式存在於整個源碼樹中,頂層makefile根據配置遞歸地進入到子目錄中,調用子目錄中的Makefile進行編譯。
一樣地,須要修改drivers/char/目錄下的Makefile文件,添加一行:
obj-$(CONFIG_CDEV_TEST) += cdev_test/
在編譯時,若是CONFIG_CDEV_TEST變量爲y,cdev_test/Makefile就會被調用。
生成配置的部分完成,就須要在menuconfig菜單中進行配置,執行:
make menuconfig
進入目錄選項Device Driver --> Character devices--->cdev test dir.
而後按'y'選中模塊cdev test support
保存退出,而後執行編譯:
make
將vmlinuz(zImage)、System.map拷貝到目標主機的/boot目錄下。
在編譯生成的modules拷貝到目標主機的/lib/modules目錄下。
須要注意的是:啓動文件也好,模塊也好,在目標板上極可能文件名爲諸如vmlinuz-$version,會包含版本信息,須要將文件名修改爲一致,否則沒法啓動。對於模塊而言,就是相應模塊沒法加載。
最後一步就是驗證本身的驅動程序是否被編譯進內核,若是被編譯進內核,驅動程序中的module_init()程序將被系統調用,完成一些開發者指定的操做。
這一部分的驗證操做就是各顯身手了。
***
好了,關於linux將驅動程序編譯進內核的討論就到此爲止啦,若是朋友們對於這個有什麼疑問或者發現有文章中有什麼錯誤,歡迎留言
原創博客,轉載請註明出處!
祝各位早日實現項目叢中過,bug不沾身.