大家好,從本篇起,一口君將手把手教大家如何來學習Linux驅動,預計會有20篇關於驅動初級部分知識點。本專題會一直更新,有任何疑問,可以留言或者加我微信。
一、什么是模塊化編程?
Linux的開發者,遍布世界各地,他們相互之間覺大數估計都不認識。如果真的是對這些開發者進行統一管理,那是很難做到的。所以大牛們,在設計Linux內核的時候,融入了模塊化的思想。也就是說,現在大家已經有一個現成的Linux操作系統了,所有的開發者寫的代碼對於這個Linux操作系統而言都是一個模塊,開發者可以模塊的形式將自己的代碼添加到內核,也可以從操作系統中卸載自己的模塊。這種思想,在實際的開發中特別別有用。
例如:在你的設備上已經運行了一個成熟的Limux操作系統,由於客戶的需求變化,你需要向這個操作系統上添加一些功能。現在你有兩種做法:
第一種:獲得Linux源代碼,然后修改,添加功能,貌似挺牛,但是如果你寫的代碼不能一次性到達效果,你就必須去修改,這樣就每次必須重新編譯內核,是不是很麻煩。最可怕的是你一不小心,把內核源碼給修改錯了,那該怎么辦呀?
第二種:快速編寫自己的功能代碼,然后以模塊的形式添加到Linux操作系統中,然后測試,發現不行,卸載模塊,繼續修改代碼,添加模塊(高富帥的干活方式),。。是不是比使用第一種方法的苦逼程序員要輕松很多呀!
大家需要注意的是,一般我們都是通過模塊化的方法向Linux操作系統添加驅動程序,那些Linux核心的代碼,我個人覺得沒有幾個人會覺得不好,需要重新修改。
Linux 內核模塊主要由以下幾個部分組成:
模塊加載函數(必須):當通過insmod命令加載內核模塊時,模塊的加載函數會自動被內核執行,完成本模塊相關初始化工作;
模塊卸載函數(必須):當通過rmmod命令卸載模塊時,模塊的卸載函數會自動被內核執行,完成與模塊加載函數相反的功能;
模塊許可證聲明(必須):模塊許可證(LICENCE)聲明描述內核模塊的許可權限,如果不聲明LICENCE,模塊被加載時將收到內核被污染的警告。大多數
模塊參數(可選):模塊參數是模塊被加載的時候可以被傳遞給他的值,它本身對應模塊內部的全局變量;
模塊導出符號(可選):內核模塊可以導出符號(symbol,對應於函數或變量),這樣其他模塊可以使用本模塊中的變量或函數;
模塊作者等信息聲明(可選)。
二、進行模塊化編程
直接看代碼,no code no bb!
看完上面的代碼,請相信,你已經對模塊化編程有了一個基本的認識。上面這段代碼雖然很簡單,但是他包含了Linux內核模塊化編程需要的所有信息。
我們來一起總結一下Linux內核模塊化編程必備的步驟:
第一步:
包含linux/init.h和inux/module.h這兩個頭文件;
通過MODULE LICENSE("GPL"),告訴內核你的模塊遵從"GPL"協議,這個事情必須得做。如果不知道GPL的讀者自己去查找相關資料;Linux能夠成功一個關鍵因素就是遵循了GPL,從而一發不可收拾,在全球蔓延開來。
MODULE_AUTHOR("yikoulinux")用來指定編寫這個模塊的作者,可以不寫。
第二步:
編寫功能代碼,注意這里沒有main函數,這里叫模塊的入口函數,函數名一般叫“xxx_init”,這里叫"hello_init"。名字是不是一定要好了,你可以把模塊的入口函數當做你的main兩數,你的代碼就從這個地方起步吧!
那這個函數什么時候被調用呢?
在模塊加載到Linux內核的時候,Linux內核會調用這個函數模塊的退出函數,這個函數的名字一般叫“xxx_exit”,這里叫“hello_exit”。這個函數里,我們一般會做些資源的釋放。在模塊卸載的時候會被調用到。當一個模塊卸載的時候,我們肯定要把它占用的資源釋放掉,不然不就造成資源浪費了。
第三步:
告訴內核,你的模塊入入口和模塊出口。Linux內核提供了兩個宏,分別是:module_init 和 module_exit.
下面我們就來詳細說一下printk函數。
三、Linux內核打印函數printk
printk的用法和printf類似,print用於用戶空間,printk用於內核空間。用printk函數時,內核會根據日志級別,可能把消息打印到當前控制台上,這個控制台通常是一個字符模式的終端、一個串口打印機或是一個並口打印機。
這些消息正常輸出的前提是:日志輸出級別小於console_loglevel(在內核中數字越小優先級越高)。
日志級別一共有8個級別,printk的日志級別定義如下(在include/linux/kemel.h中);
沒有指定日志級別的printk語句默認采用的級別是DEFAULT_MESSAGE_LOGLEVEL(這個默認級別一般為<4>,即與KERN_WARNING在一個級別上)。
我們可以通過cat/proc/sys/kemel/printk這個文件,查看系統默認的日志級別
printk,其實不用想那么復雜,你就把它當做printf使用也可以的,在這里我們還不能測試printk輸出的消息,是否能到控制台上,因為我們不知道如何編譯我們的模塊代碼、如何加載我們的模塊、如何卸載我們的模塊。
好,接下來我們來看看,如何編譯我們的模塊。
四、模塊的編譯
這里,先給出Linux模塊化編譯的流程:
模塊的編譯分兩步:
第一步:調用linux源碼樹的Makefile進行收集編譯一個模塊所需要的信息
第二步:linux源碼樹的Makefile在收集完信息后,調用模塊的Makefile。獲取需要編譯成模塊的“.c”文件,最后生成模塊文件
明白了模塊的編譯流程,接下來我們就來看具體如何編寫模塊的Makefile
# KERNELRELEASE :在內核源碼樹的Makefile中定義,在當前的Makefile中,它的值為空#$(shell uname-r) :獲得當系統的Linux內核版本
#KDIR :制定當前Linux操作系統源代碼路徑,即編譯生成的模塊是在當前系統中使用#如果想將你寫的模塊,用在你的開發板上運行的Linux系統中,
只需在KDIR變量中指定你開發板Linux系統源碼樹的路徑
#PWD:=$(shell pwd)獲得當前路徑
我們來分析Makefile的執行過程
1. 在模塊的源代碼目錄下執行make,此時,宏“KERNELRELEASE”【內核源碼樹的Makefile會定義】沒有定義,因此進入else;
2. 記錄內核路徑KDIR和當前工作目錄PWD;
3. 由於make 后面沒有目標,所以make會在Makefile中的第一個不是以.開頭的目標作為默認的目標執行,於是all成為make的目標;
all:的第一個命令
類似於printf函數,編譯經過此處會打印提示信息。
4. make的第二條命令會執行 make -C $(KDIR) M=$(PWD) modules
翻譯下就是:
make -C /lib/modules/3.2.0-29-generic-pae/build M=/home/peng/driver/1/module modules
-C 表示到存放內核的目錄執行其Makefile,
M=$(PWD)表示返回到當前目錄,
modules表示編譯成模塊的意思
之所以這么寫是由內核源碼樹的頂層Makefile告訴我們的,當我們調用Linux內核源碼樹頂層的Makefile時,找到的是頂層Makefile的“modules”目標。我們來看下頂層Makefile的modules目標寫了什么:
【截取了部分內容,我們沒有必要全部了解,只需要關心紅色部分即可,特別是對應的英文注釋】
5. 找到modules目標后,接下來Linux源碼樹的頂層Makeflle就需要知道是將那些".c"文件編譯成模塊。誰告訴它呢?是的,模塊的Makefile文件。所以接下來就會回調模塊的Makefile。需要注意的是,此時KERNELRELEASE已經在Linux內核源碼樹的頂層Makefile中定義過了,所以此時它獲得信息是:
obj-m:=hello.o
obj-m表示會將hello.o目標編譯成.ko模塊;它告訴linux源碼樹頂層Makefile是動態編譯(編譯成模塊)而不是編譯進內核(obj-y),linux源碼樹頂層Makefile會根據hello.o找到hello.c文件
6. 將模塊文件hello.c編譯為.o,然后再將多個目標鏈接為.ko。
最終編譯結果如下:
由執行結果可知,Makefile最終被調用了三次
1) 執行命令make調用
2) 被linux內核源碼樹的頂層Makefile調用,產生.o文件
3) 被linux源碼樹頂層Makefile調用,將.o文件鏈接生成.ko文件
五、模塊的加載、卸載
如何將編譯好的模塊添加到Linux內核?如何從Linux內核將我們的模塊卸載下來?
1.模塊的加載命令
insmod xxx.ko
例如:在ubuntu系統中添加自己寫的模塊
sudo insmod hello.ko
注意:在Linux系統中只有超級用戶權限才可以添加模塊到內核。
2.查看系統中的模塊命令
lsmod
例如:在系統中搜索自己添加的hello模塊
sudo lsmod | grep hello
3.卸載模塊命令
sudo rmmod 模塊名
例如:卸載系統中的hello模塊
sudo mmod hello
4.查看加載模塊和卸載模塊通過printk打印的信息命令
dmesg或dmesg|tail
這個命令主要是從Linux內核的ring buffer(環形緩沖區)中讀取信息的。
那什么是ring buffer呢?
在Limux系統中,所有通過printk打印出來的信息都會送到ring buffer中。我們知道,我們打印出來的信息是需要在控制台設備上顯示的。在Linux內核初始化的時候,控制台設備並沒有初始化的時候,使用printk會不會有問題
控制台設備,因為此時printk只是把信息輸送到ring buffer中,等控制台設備初始化好后,在根據ring buffer中消息的優先級決定是否需要輸送到控制台設備上。
如何清空ring buffer呢?
sudo dmesg -c
操作結果如下:
一口君操作全部在特權模式下,如果在普通用戶權限下前面加sudo。
更多信息請關注公眾號:一口Linux