linux設備驅動程序——將驅動程序編譯進內核

linux驅動程序——將驅動程序編譯進內核

模塊的加載

一般來講,在驅動模塊的開發階段,通常是將模塊編譯成.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

  • 直接在目標主機上編譯
  • 在其餘平臺上構建交叉編譯環境,通常是在PC機上編譯出可在目標板上運行的驅動程序

直接在目標主機上編譯是比較方便的作法,本機編譯本機運行。

一般,本機系統中通常不會自帶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的可啓動鏡像,一般是zImage或者vmlinuz,這是一個可boot執行的壓縮文件
  • 伴隨着的還有鏡像對應的map文件,這個文件對應鏡像中的編譯符號以及符號的地址信息
  • 未編譯進內核的模塊,也就是在配置時被選爲M的選項
  • linux內核頭文件等等

在上文中有提到,目標主機中,linux的啓動鏡像放置在/boot目錄下,因此若是咱們須要替換linux的鏡像,須要替換/boot目錄下的如下兩個文件:

  • linux的可啓動鏡像,也就是生成的zImage或者vmlinuz
  • .map文件

在主機中,模塊通常被放置在/lib/modules目錄中,若是交叉編譯出的版本與本機中模塊版本不一致,將沒法識別,因此編譯出的模塊也須要替換。

驅動程序編譯進內核

根據上文,能夠得出的結論是:在試圖將驅動程序編譯進內核時,咱們須要編譯完整的linux內核源碼以生成相應的鏡像文件,而後將其替換到目標主機的/boot目錄下便可。

那麼,怎樣將驅動的源碼C文件編譯進內核呢?

這個問題得從makefile的執行流程提及:

make的執行

首先,若是你有基本的linux內核編譯經驗,就知道在編譯linux源碼前,須要進行config(配置),以決定哪些部分編譯進內核、哪些部分編譯成模塊,

一般使用make menuconfig,不一樣的config方式一般只是選擇界面的不一樣,其中稍微特殊的make oldconfig則是沿用以前的配置。

在配置完成以後將生成一個.config文件,makefile根據.config文件選擇性地進入子目錄中執行編譯工做。

流程基本如上所說,可是咱們須要知道更多的細節:

  • make menuconfig執行的原理是什麼?
  • 頂層makefile是怎樣執行子目錄中的編譯工做的?

答案是這樣的:

  • make menuconfig確定是讀取某個配置文件來陳列出全部的配置選項,遞歸地進入到子目錄中,發現幾乎每一個子目錄中都有一個名爲Kconfig的文件,這個文件負責配置驅動在配置菜單中的顯示以及配置行爲,且Kconfig遵循某種語法,make menuconfig就是讀取這些文件來顯示配置項。
  • 遞歸地進入到每一個子目錄中,發現其中都有一個makefile中,能夠想到,makefile遞歸地進入子目錄,而後經過調用子目錄中的makefile來執行各級子目錄的編譯,最後統一連接。

整個linux內核的編譯都是採用一種分佈式的思想,須要添加一個驅動到模塊中,咱們須要作的事情就是:

  1. 將驅動源文件放在內核對應目錄中,通常的驅動文件放在drivers目錄下,字符設備放在drivers/char中,塊設備就放在drivers/blok中,文件的位置遵循這個規律,若是是單個的字符設備源文件,就直接放在drivers/char目錄下,若是內容較多足以構成一個模塊,則新建一個文件夾。

  2. 若是是新建文件夾,由於是分佈式編譯,須要在文件夾下添加一個Makefile文件和Kconfig文件並修改爲指定格式,若是是單個文件直接添加,則直接修改當前目錄下的Makefile和Kconfig文件將其添加進去便可。
  3. 若是是新建文件夾,須要修改上級目錄的Makefile和Kconfig,以將文件夾添加到整個源碼編譯樹中。

  4. 執行make menuconfig,執行make

  5. 將生成的新鏡像以及相應boot文件拷貝到目標主機中,測試。

beagle bone的啓動文件包括:vmlinuz、System.map,編譯出的模塊文件爲modules

Kconfig的語法簡述

上文中提到,在添加源碼時,通常會須要一個Kconfig文件,這樣就能夠在make menuconfig時對其進行配置選擇,在對一個源文件進行描述時,遵循相應的語法。

在這裏介紹一些經常使用的語法選項:

source

source:至關於C語言中的include,表示包含並引用其餘Kconfig文件

config

新建一個條目,用法:

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,此選項建立一個選項目錄,並且當前選項目錄可配置。

編譯示例

梳理了整個添加的流程,接下來就以一個具體的示例來進行詳細的說明。

背景以下:

  • 目標開發板爲開源平臺beagle bone,基於4.14.79內核版本,arm平臺
  • 須要添加的源代碼爲字符設備驅動,名爲cdev_test.c,新建一個目錄cdev_test
  • 字符設備驅動實現的功能:在/dev目錄下生成一個basic_demo文件,用來檢測是否成功將源代碼編譯進內核

將驅動編譯進內核

放置目錄

鑑因而字符設備,因此將源文件目錄放置在$KERNEL_ROOT/drivers/char/下面。

若是是塊設備,就會被放置在block下面,可是這並非絕對的,相似USB爲字符設備,可是獨立了一個文件出來。

放置後目標文件位置爲:$KERNEL_ROOT/drivers/char/cdev_test

Kconfig文件

在$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文件的做用就是:

  1. 在menuconfig的菜單中,在Device Driver(對應drivers目錄) ---> Character devices(對應char目錄)菜單下建立一個名爲"cdev test dir"的菜單選項,
    執行效果是這樣的

  2. 在"cdev test dir"的菜單選項下建立一個"cdev test support"的條目,這個條目的選項只有兩個,[*]表示編譯進內核和[]表示不進行編譯,默認選擇n,不進行編譯。
    執行效果是這樣的

  3. 選擇help能夠查看相應的提示信息。

在上文中還提到,Kconfig分佈式地存在於子目錄下,同時須要注意的是,在編譯時,配置工具並不是無差異地進入每一個子目錄,收集全部的Kconfig信息,而是遵循必定的規則遞歸進入。

那麼,既然是新建的目錄,怎麼讓編譯器知道要進入到這個子目錄下呢?答案是,在上級目錄的Kconfig中包含當前路徑下的Kconfig文件。
打開char目錄下的Kconfig文件,而且在文件的靠後位置添加:

source "drivers/char/xillybus/Kconfig"

就把新的Kconfig文件包含到系統中檢索目錄中了,那麼drivers/char/又是怎麼被檢索到的呢?

就是在drivers的Kconfig中添加drivers/char/目錄下的Kconfig索引,以此類推。

Makefile文件

在$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就會被調用。

在make menuconfig中選中

生成配置的部分完成,就須要在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不沾身.

相關文章
相關標籤/搜索