Linux驅(qū)動(dòng)實(shí)踐:帶你一步一步編譯內(nèi)核驅(qū)動(dòng)程序
作 者:道哥,10 年嵌入式開發(fā)老兵,專注于:C/C 、嵌入式、Linux。目錄關(guān)注下方公眾號(hào),回復(fù)【書籍】,獲取 Linux、嵌入式領(lǐng)域經(jīng)典書籍;回復(fù)【PDF】,獲取所有原創(chuàng)文章( PDF 格式)。
學(xué)習(xí)的困惑
記得以前我在開始學(xué)習(xí)驅(qū)動(dòng)開發(fā)的時(shí)候,找來很多文章、資料來學(xué)習(xí),但是總是覺得缺少了點(diǎn)全局視角。
因此,這幾篇文章我們就從最簡單的驅(qū)動(dòng)模塊編譯開始,然后介紹字符設(shè)備驅(qū)動(dòng)程序。
- 每一篇文章的介紹都是正確的,但是如果把很多文章放在一起看,就會(huì)發(fā)現(xiàn)怎么說的都不一樣啊?
- 有些文章注重函數(shù)的介紹,但是缺乏一個(gè)全局的視角,從整體上來觀察驅(qū)動(dòng)程序的結(jié)構(gòu);
- 對(duì)于一個(gè)新手來說,能夠邊學(xué)習(xí)、邊實(shí)踐,這是最好的學(xué)習(xí)方式,但是很多文章不會(huì)注意這方面。雖然文章內(nèi)容很漂亮,但是不知道怎么去實(shí)踐、驗(yàn)證。
- 編譯進(jìn)內(nèi)核;
- 編譯為一個(gè)獨(dú)立的驅(qū)動(dòng)模塊;
實(shí)踐環(huán)境
為了便于測試,以下操作都是在 Ubuntu16.04 操作系統(tǒng)里完成的。編譯Linux驅(qū)動(dòng)程序,肯定需要內(nèi)核源碼,這里選擇的是 linux-4.15 版本,可以在官網(wǎng)下載。
文末有下載方式。下載之后,把linux-4.15.tar.gz解壓到Ubuntu中任意目錄即可,例如:解壓到~/tmp/目錄下:
編譯進(jìn)內(nèi)核
創(chuàng)建驅(qū)動(dòng)程序目錄
linux中的驅(qū)動(dòng),一般都放在 linux-4.15/drivers/ 目錄下,因此在這個(gè)目錄中創(chuàng)建一個(gè)hello文件夾。
對(duì)于一個(gè)驅(qū)動(dòng)來說,最重要的就是3個(gè)文件:
只要按照固定的格式來編寫這3個(gè)文件,linux內(nèi)核的編譯腳本就可以確保把我們的驅(qū)動(dòng)程序編譯進(jìn)去。
- 源代碼
- Kconfig
- Makefile
創(chuàng)建源文件
首先是源碼,在hello文件夾中創(chuàng)建源文件hello.c:
$ touch hello.c
源文件hello.c的內(nèi)容是:
#include
// 當(dāng)驅(qū)動(dòng)被加載的時(shí)候,執(zhí)行此函數(shù)
static int __init hello_init(void)
{
printk(KERN_ALERT "welcome, hello"\n");
return 0;
}
// 當(dāng)驅(qū)動(dòng)被卸載的時(shí)候,執(zhí)行此函數(shù)
static void __exit hello_exit(void)
{
printk(KERN_ALERT "bye, hello\n");
}
// 版權(quán)聲明
MODULE_LICENSE("GPL");
// 以下兩個(gè)函數(shù)屬于 Linux 的驅(qū)動(dòng)框架,只要把驅(qū)動(dòng)兩個(gè)函數(shù)地址注冊(cè)進(jìn)去即可。
module_init(hello_init);
module_exit(hello_exit);
有兩個(gè)小地方注意一下:
- 在內(nèi)核中,打印函數(shù)是 printk,而不是 printf;
- 打印信息的級(jí)別有好幾個(gè),從 DEBUG 到 EMERG,這里使用的是 KERN_ALERT,方便查看打印信息。
創(chuàng)建 Kconfig 文件
這個(gè)文件是用來對(duì)內(nèi)核進(jìn)行配置的,當(dāng)執(zhí)行 make menuconfig 指令的時(shí)候,這個(gè)文件就被解析。
$ touch Kconfig
添加如下內(nèi)容:
tristate "hello driver"
help
just a simplest driver.
default y
第一行內(nèi)容 config HELLO ,在執(zhí)行配置的時(shí)候,將會(huì)生成一個(gè)變量 CONFIG_HELLO ,而這個(gè)變量,將會(huì)在編譯的時(shí)候,被 Makefile 引用。
endmenu // 加在這一句的上面
現(xiàn)在,可以來執(zhí)行下面指令,看一下具體的配置界面:
$ make distclean
$ make ARCH=x86_64 defconfig
$ make ARCH=x86_64 menuconfig
第2條指令,是用來把默認(rèn)的配置保存到當(dāng)前目錄下的 .config 配置文件,也就是把一個(gè)默認(rèn)的配置文件復(fù)制過來,作為我們自己的配置文件。
創(chuàng)建 Makefile 文件
Makefile文件是make工具的腳本,首先創(chuàng)建它:
$ touch Makefile
其中的內(nèi)容只有一行:
現(xiàn)在,hello驅(qū)動(dòng)程序的Makefile已經(jīng)創(chuàng)建好了,我們還要讓linux內(nèi)核的編譯框架知道這個(gè)文件才行。
- CONFIG_HELLO 可以看做一個(gè)變量,在編譯的時(shí)候,這個(gè)變量的值可能是:y, n 或者 m。
- 在剛才的 Kconfig 參數(shù)配置中,CONFIG_HELLO 被設(shè)置為 y,于是這句話就被翻譯成:obj-y = hello,表示把 hello 驅(qū)動(dòng)編譯進(jìn)內(nèi)核。
編譯
萬事俱備,只欠編譯!依次執(zhí)行如下指令:
$ make -j4
make指令執(zhí)行結(jié)束之后,編譯得到的內(nèi)核中(vmlinux)就包含了我們的hello驅(qū)動(dòng)。
編譯為驅(qū)動(dòng)模塊
編譯為驅(qū)動(dòng)模塊,也有兩種 操作方式:
編譯所有的驅(qū)動(dòng)模塊
編譯成功之后,就可以得到文件: linux-4.15/drivers/hello/hello.ko。
- 在執(zhí)行 make ARCH=x86_64 menuconfig 指令的時(shí)候,把 hello 配置成 M;
- 然后在 linux-4.15 中執(zhí)行編譯模塊指令:make -j4 modules。
只編譯 hello 這一個(gè)驅(qū)動(dòng)模塊
另外一種編譯驅(qū)動(dòng)模塊的方式是:進(jìn)入hello目錄,只編譯這一個(gè)驅(qū)動(dòng)模塊。
可以把 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文件夾中執(zhí)行make指令,即可得到驅(qū)動(dòng)模塊 hello.ko 。
驗(yàn)證一下
加載驅(qū)動(dòng):
$ sudo insmod ./hello.ko
此時(shí)終端窗口是沒有任何輸出的,需要輸入指令 dmesg | tail ,可以看到 hello_init 函數(shù)的輸出內(nèi)容:
再次輸入 dmesg | tail ,可以看到 hello_exit 函數(shù)的輸出內(nèi)容:
資料下載
在公眾號(hào)【IOT物聯(lián)網(wǎng)小鎮(zhèn)】的后臺(tái)回復(fù)關(guān)鍵字:1112,獲取下列文件的網(wǎng)盤地址:
linux-4.15.tar.gzhello文件夾壓縮包
------ End ------