Linux驅動實踐:帶你一步一步編譯核心驅動程式

IOT物聯網小鎮發表於2021-11-17

作 者:道哥,10+年嵌入式開發老兵,專注於:C/C++、嵌入式、Linux

關注下方公眾號,回覆【書籍】,獲取 Linux、嵌入式領域經典書籍;回覆【PDF】,獲取所有原創文章( PDF 格式)。

目錄

別人的經驗,我們的階梯!

大家好,我是道哥。今天給大家分享一些筆記本里的一些存貨: Linux 系統中的驅動和中斷相關。

大概會用 6~7 篇的文章,由淺入深的為大家介紹Linux 中驅動程式的編寫方法。

文章的順序,也是我之前自己學習時的順序。

以前的學習記錄比較零散,現在只是把它們按照一定的順序重新梳理一下。

這幾篇文章,理論知識會少一些,更注重實際的操作

我會把操作用引導的程式碼,全部上傳到網盤上,在文末有下載說明

只要根據文中介紹的步驟進行操作,就一定可以操作成功。

學習的困惑

記得以前我在開始學習驅動開發的時候,找來很多文章、資料來學習,但是總是覺得缺少了點全域性視角。

就好像:我想看清一座山的全貌,但總是被困在一個、又一個山谷中一樣。

主要的困惑有 3 點:

  1. 每一篇文章的介紹都是正確的,但是如果把很多文章放在一起看,就會發現怎麼說的都不一樣啊?

  2. 有些文章注重函式的介紹,但是缺乏一個全域性的視角,從整體上來觀察驅動程式的結構;

  3. 對於一個新手來說,能夠邊學習、邊實踐,這是最好的學習方式,但是很多文章不會注意這方面。雖然文章內容很漂亮,但是不知道怎麼去實踐、驗證。

因此,這幾篇文章我們就從最簡單的驅動模組編譯開始,然後介紹字元裝置驅動程式。

在這部分,會以 GPIO 為例子,重點描述其中的關鍵節點。

最後再介紹在中斷處理程式中,如何利用訊號量、小任務、工作佇列,把核心事件傳遞到應用層來處理。

作為第一個開篇文章,從最簡單的核心編譯開始。

實際操作一下:如何把一個最簡單的驅動程式(hello),按照 2 種方式進行編譯:

  1. 編譯進核心;

  2. 編譯為一個獨立的驅動模組;

實踐環境

為了便於測試,以下操作都是在 Ubuntu16.04 作業系統裡完成的。

編譯Linux驅動程式,肯定需要核心原始碼,這裡選擇的是 linux-4.15 版本,可以在官網下載。

文末有下載方式。

下載之後,把linux-4.15.tar.gz解壓到Ubuntu中任意目錄即可,例如:解壓到~/tmp/目錄下:

$ tar -zxvf linux-4.15.tar.gz -C ~/tmp/

編譯進核心

建立驅動程式目錄

linux 中的驅動,一般都放在 linux-4.15/drivers/ 目錄下,因此在這個目錄中建立一個hello資料夾。

$ mkdir linux-4.15/drivers/hello

對於一個驅動來說,最重要的就是3個檔案:

  1. 原始碼

  2. Kconfig

  3. Makefile

只要按照固定的格式來編寫這3個檔案,linux核心的編譯指令碼就可以確保把我們的驅動程式編譯進去。

建立原始檔

首先是原始碼,在hello資料夾中建立原始檔 hello.c

$ cd linux-4.15/drivers/hello
$ touch hello.c

原始檔hello.c的內容是:

#include <linux module.h="">
#include <linux init.h="">

// 當驅動被載入的時候,執行此函式
static int __init hello_init(void)
{
    printk(KERN_ALERT "welcome, hello"\n");
    return 0;
}

// 當驅動被解除安裝的時候,執行此函式
static void __exit hello_exit(void)
{
    printk(KERN_ALERT "bye, hello\n");
}

// 版權宣告
MODULE_LICENSE("GPL");

// 以下兩個函式屬於 Linux 的驅動框架,只要把驅動兩個函式地址註冊進去即可。
module_init(hello_init);
module_exit(hello_exit);

有兩個小地方注意一下:

  1. 在核心中,列印函式是 printk,而不是 printf;

  2. 列印資訊的級別有好幾個,從 DEBUG 到 EMERG,這裡使用的是 KERN_ALERT,方便檢視列印資訊。

建立 Kconfig 檔案

這個檔案是用來對核心進行配置的,當執行 make menuconfig 指令的時候,這個檔案就被解析。

先建立檔案:

$ cd linux-4.15/drivers/hello
$ touch Kconfig

新增如下內容:

config HELLO
tristate "hello driver"
help
  just a simplest driver.
default y

第一行內容 config HELLO ,在執行配置的時候,將會生成一個變數 CONFIG_HELLO ,而這個變數,將會在編譯的時候,被 Makefile 引用。

最後一行的 default y ,就表示把 CONFIG_HELLO 的值設定成 y,從而讓這個驅動被編譯到核心中。

現在,hello驅動中的KConfig配置檔案已經準備好了,但是還需要這個配置檔案登記Linux 核心的整體配置檔案中。

也就是把它登記在 linux-4.15/drivers/Kconfig 檔案的末尾:

source "drivers/hello/Kconfig"

endmenu   // 加在這一句的上面

現在,可以來執行下面指令,看一下具體的配置介面:

$ cd linux-4.15/
$ make distclean
$ make ARCH=x86_64 defconfig
$ make ARCH=x86_64 menuconfig

2條指令,是用來把預設的配置儲存到當前目錄下的 .config 配置檔案,也就是把一個預設的配置檔案複製過來,作為我們自己的配置檔案。

以後再修改配置引數時,修改的內容就會儲存在 .config 檔案中,

3條指令,是用來配置核心的,可以進入 Device Drivers 選單,然後在最底層看到我們的 hello driver 被標記成星號,
這表示被編譯進核心。

按向下方向鍵,把高亮定位到 Device Drivers ---> ,然後敲Enter鍵,進入到 Device Drivers 的配置介面。

按向下方向鍵,一直到最後一個條目,就可以看到我們的 hello 驅動了,如下:

可以看到 hello driver 前面顯示的是型號 *,這表示:該驅動將會編譯進核心。

我們可以按下空格鍵試一下,會在三種標記中切換:型號,M,空值。M 標記意思是編譯成驅動模組。

我們這裡選擇星號(編譯進核心),然後按下右方向鍵,最下方的幾個按鍵的焦點移動到 按鈕上:

按下Enter鍵,就會彈出儲存對話方塊,選擇預設儲存檔案 .config 即可,然後在 按鈕高亮的時候,按下Enter鍵即可儲存。

此時,在彈出的確認視窗中,選擇 <exit>,按下Enter鍵即可:

此時,返回到 Device Drivers 的配置介面,在最下面的按鈕中,選擇讓<exit>高亮,然後一路退出即可。

建立 Makefile 檔案

Makefile 檔案是make工具的指令碼,首先建立它:

$ cd linux-4.15/drivers/hello
$ touch Makefile

其中的內容只有一行:

obj-$(CONFIG_HELLO) += hello.o
  1. CONFIG_HELLO 可以看做一個變數,在編譯的時候,這個變數的值可能是:y, n 或者 m。

  2. 在剛才的 Kconfig 引數配置中,CONFIG_HELLO 被設定為 y,於是這句話就被翻譯成: obj-y += hello,表示把 hello 驅動編譯進核心。

現在,hello驅動程式的Makefile已經建立好了,我們還要讓linux核心的編譯框架知道這個檔案才行。

在檔案 linux-4.15/drivers/Makefile 中的末尾,新增如下內容:

obj-$(CONFIG_HELLO)    += hello/

編譯

萬事俱備,只欠編譯!依次執行如下指令:

$ cd linux-4.15/
$ make -j4

make指令執行結束之後,編譯得到的核心中(vmlinux)就包含了我們的hello驅動。

編譯為驅動模組

編譯為驅動模組,也有兩種 操作方式:

編譯所有的驅動模組

  1. 在執行 make ARCH=x86_64 menuconfig 指令的時候,把 hello 配置成 M;

  2. 然後在 linux-4.15 中執行編譯模組指令:make -j4 modules。

編譯成功之後,就可以得到檔案: linux-4.15/drivers/hello/hello.ko

這樣的編譯指令,是把所有的模組都編譯了一次(在輸出資訊中,可以看到編譯了很多模組)。

只編譯 hello 這一個驅動模組

另外一種編譯驅動模組的方式是:進入hello目錄,只編譯這一個驅動模組。

這種編譯方法,就需要修改hello目錄下的Makefile檔案了,內容如下:

可以把 hello 目錄下的所有檔案刪除,只保留原始檔 hello.c,然後新建 Makefile 檔案。

ifneq ($(KERNELRELEASE),)
        obj-m := hello.o
else
        KERNELDIR ?= /lib/modules/$(shell uname -r)/build
        PWD := $(shell pwd)
default:
        $(MAKE) -C $(KERNELDIR) M=$(PWD) modules
clean:
        $(MAKE) -C $(KERNEL_PATH) M=$(PWD) clean
endif

然後,在hello資料夾中執行make指令,即可得到驅動模組 hello.ko

驗證一下

載入驅動:

$ cd linux-4.15/drivers/hello
$ sudo insmod ./hello.ko

此時終端視窗是沒有任何輸出的,需要輸入指令 dmesg | tail ,可以看到 hello_init 函式的輸出內容:

解除安裝驅動:

$ sudo rmmod hello

再次輸入 dmesg | tail ,可以看到 hello_exit 函式的輸出內容:

資料下載

在公眾號【IOT物聯網小鎮】的後臺回覆關鍵字:1112,獲取下列檔案的網盤地址:

linux-4.15.tar.gz

hello資料夾壓縮包


------ End ------

推薦閱讀

【1】《Linux 從頭學》系列文章

【2】C語言指標-從底層原理到花式技巧,用圖文和程式碼幫你講解透徹

【3】原來gdb的底層除錯原理這麼簡單

【4】內聯彙編很可怕嗎?看完這篇文章,終結它!

其他系列專輯:精選文章應用程式設計物聯網C語言

星標公眾號,第一時間看文章!

相關文章