目錄
內(nèi)核模塊的由來
第一個(gè)內(nèi)核模塊程序?
內(nèi)核模塊工具?
將多個(gè)源文件編譯生成一個(gè)內(nèi)核模塊?
內(nèi)核模塊參數(shù)
內(nèi)核模塊依賴
關(guān)于內(nèi)核模塊的進(jìn)一步討論?
習(xí)題
內(nèi)核模塊的由來
最近一直在玩那些其它的技術(shù),眼看快暑假了,我決定夯實(shí)一下我的驅(qū)動(dòng)方面的技能,迎接我的實(shí)習(xí),找了一本書,接下來就跟著這本書學(xué)了
先來看第二章,內(nèi)核模塊
????????Linux是宏內(nèi)核(或單內(nèi)核)的操作系統(tǒng)的典型代表,它和微內(nèi)核(典型的代表是 Windows操作系統(tǒng))的最大區(qū)別在于所有的內(nèi)核功能都被整體編譯在一起,形成一個(gè)單獨(dú)的內(nèi)核鏡像文件。其顯著的優(yōu)點(diǎn)就是效率非常高,內(nèi)核中各功能模塊的交互是通過直接的函數(shù)調(diào)用來進(jìn)行的。而微內(nèi)核則只實(shí)現(xiàn)內(nèi)核中相當(dāng)關(guān)鍵和核心的一部分,其他功能模塊被單獨(dú)編譯,功能模塊之間的交互需要通過微內(nèi)核提供的某種通信機(jī)制來建立。對(duì)于像 Linux 這類的宏內(nèi)核而言,其缺點(diǎn)也是不言而喻的,如果要增加、刪除、修改內(nèi)核的某個(gè)功能,不得不重新編譯整個(gè)內(nèi)核,然后重新啟動(dòng)整個(gè)系統(tǒng)。這對(duì)驅(qū)動(dòng)開發(fā)者來說基本上是不可接受的,因?yàn)轵?qū)動(dòng)程序的特殊性,在驅(qū)動(dòng)開發(fā)初期,需要經(jīng)常修改驅(qū)動(dòng)的代碼,即便是經(jīng)驗(yàn)豐富的驅(qū)動(dòng)開發(fā)者也是如此。
????????為了彌補(bǔ)這一缺點(diǎn),Linux引入了內(nèi)核模塊(后面在不引起混淆的情況下將其簡(jiǎn)稱為“模塊”)。簡(jiǎn)單地說,內(nèi)核模塊就是被單獨(dú)編譯的一段內(nèi)核代碼,它可以在需要的時(shí)候動(dòng)態(tài)地加載到內(nèi)核,從而動(dòng)態(tài)地增加內(nèi)核的功能。在不需要的時(shí)候,可以動(dòng)態(tài)地卸載,從而減少內(nèi)核的功能,并節(jié)約一部分內(nèi)存(這要求內(nèi)核配置了模塊可卸載的選項(xiàng)才行)。而不論是加載還是卸載,都不需要重新啟動(dòng)整個(gè)系統(tǒng)。這種特性使它非常適合于驅(qū)動(dòng)程序的開發(fā)(注意,內(nèi)核模塊不一定都是驅(qū)動(dòng)程序,驅(qū)動(dòng)程序也不一定都是模塊的形式)。驅(qū)動(dòng)開發(fā)者可以隨時(shí)修改驅(qū)動(dòng)的代碼,然后僅編譯驅(qū)動(dòng)代碼本身(而非整個(gè)內(nèi)核),并將新編譯的驅(qū)動(dòng)加載到內(nèi)核進(jìn)行測(cè)試。只要新加入的驅(qū)動(dòng)不會(huì)使內(nèi)核崩潰,就可以不重新啟動(dòng)系統(tǒng)。
????????內(nèi)核模塊的這一特點(diǎn)也有助于減小內(nèi)核鏡像文件的體積,自然也就減少了內(nèi)核所占用的內(nèi)存空間(因?yàn)檎麄€(gè)內(nèi)核鏡像將會(huì)被加載到內(nèi)存中運(yùn)行)。不必把所有的驅(qū)動(dòng)都編譯進(jìn)內(nèi)核,而是以模塊的形式單獨(dú)編譯驅(qū)動(dòng)程序,這是基于不是所有的驅(qū)動(dòng)都會(huì)同時(shí)工作的原理。因?yàn)椴皇撬械挠布家瑫r(shí)接入系統(tǒng),比如一個(gè)USB 無線網(wǎng)卡。
討論完內(nèi)核模塊的這些特性后,我們正式開始編寫模塊程序。
第一個(gè)內(nèi)核模塊程序? ?
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
int init_module()
{
printk("init_module\n");
return 0;
}
void cleanup_module()
{
printk("cleanup_module\n");
}
使用模塊init,清除模塊cleanup
編譯這個(gè)程序需要對(duì)應(yīng)的makefile,不然連這些頭文件都找不到,這些頭文件是內(nèi)核里的
ifeq就是邏輯與
ifneq就是邏輯或
ifeq ($(KERNELRELEASE),)
ifeq ($(ARCH),arm)
KERNELDIR ?= /home/book/Linux_4412/kernel/linux-3.14
ROOTFS ?= /home/book/nfs_rootfs
else
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
endif
PWD := $(shell pwd)modules:
?? ?$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
modules_install:
?? ?$(MAKE) -C $(KERNELDIR) M=$(PWD) INSTALL_MOD_PATH=$(ROOTFS) modules_install
clean:
?? ?rm -rf *.o *.ko .*.cmd *.mod.* modules.order Module.symvers .tmp_versions
elseobj-m += vser.o
endif
細(xì)節(jié)很重要,往往簡(jiǎn)單的地方才是最容易忽視的
“=”是最普通的等號(hào),然而在Makefile中確實(shí)最容易搞錯(cuò)的賦值等號(hào),使用”=”進(jìn)行賦值,變量的值是整個(gè)makefile中最后被指定的值。不太容易理解,舉個(gè)例子如下:
VIR_A = A
VIR_B = $(VIR_A) B
VIR_A = AA
經(jīng)過上面的賦值后,最后VIR_B的值是AA B,而不是A B。在make時(shí),會(huì)把整個(gè)makefile展開,拉通決定變量的值
相比于前面“最普通”的”=”,”:=”就容易理解多了?!?=”就表示直接賦值,賦予當(dāng)前位置的值。同樣舉個(gè)例子說明
VIR_A := A
VIR_B := $(VIR_A) B
VIR_A := AA
最后變量VIR_B的值是A B,即根據(jù)當(dāng)前位置進(jìn)行賦值。因此相比于”=”,”:=”才是真正意義上的直接賦值。
“?=”表示如果該變量沒有被賦值,則賦予等號(hào)后的值。舉例:VIR ?= new_value
如果VIR在之前沒有被賦值,那么VIR的值就為new_value.VIR := old_value
VIR ?= new_value
這種情況下,VIR的值就是old_value
“+=”和平時(shí)寫代碼的理解是一樣的,表示將等號(hào)后面的值添加到前面的變量上
$(MAKE)是make自定義的很多變量,在這里用來實(shí)現(xiàn)遞歸調(diào)用本身
???????? -C $(KERNELDIR) 指明跳轉(zhuǎn)到內(nèi)核源碼目錄下讀取那里的Makefile;讓內(nèi)核在編譯內(nèi)核外還編譯M指明的目錄也就是我們這個(gè)文件夾下的文件。
????????M=$(PWD) 表明然后返回到當(dāng)前目錄繼續(xù)讀入、執(zhí)行當(dāng)前的Makefile。
INSTALL_MOD_PATH=$(ROOTFS)
指示安裝模塊的路徑,也就是生成的模塊放到哪里
obj-y 編譯到內(nèi)核
obj-m 編譯成模塊
rm呢就是刪除這些文件
ifeq ($(KERNELRELEASE),)
第一次編譯時(shí)這個(gè)宏是空的KERNELRELEASE是一個(gè)變量,在Linux內(nèi)核源代碼的頂層Makefile中定義,用于指定當(dāng)前正在構(gòu)建的內(nèi)核版本。
也就是在不斷的make的第一次make我們可以執(zhí)行當(dāng)前目錄else前面的那些配置和工作。
modules為makefile的默認(rèn)目標(biāo)被執(zhí)行(因?yàn)槭堑谝粋€(gè)),而 modules_install 和clean則是偽目標(biāo),執(zhí)行make時(shí),只執(zhí)行modules 目標(biāo).?
內(nèi)核模塊工具?
?sudo insmod ./vser.ko
dmesg
modprobe可以自動(dòng)加載模塊到內(nèi)核,但是我用著不好使不知道為什么,在運(yùn)行這個(gè)前要更新依賴,運(yùn)行depmod命令。等我學(xué)一學(xué)在看這個(gè)問題。
modinfo可以智能查找模塊
rmmod卸載模塊
?
?
?
內(nèi)核模塊一般的形式
在前面的模塊加載實(shí)驗(yàn)中,我們看到內(nèi)核有以下打印信息的輸出。
[ 83.884417] vser: module license 'unspecified' taints kernel.
[83.884423] Disabling lock debugging due to kernel taint
????????其大概意思是因?yàn)榧虞d了 vser模塊而導(dǎo)致內(nèi)核被污染,并且因此禁止了鎖的調(diào)試功能。這是什么原因造成的呢?眾所周知,Linux 是一個(gè)開源的項(xiàng)目,為了使 Linux 在發(fā)展的過程中不成為一個(gè)閉源的項(xiàng)目,這就要求任何使用 Linux 內(nèi)核源碼的個(gè)人或組織在免費(fèi)獲得源碼并可針對(duì)源碼做任意的修改和再發(fā)布的同時(shí),必須將修改后的源碼發(fā)布。這就是所謂的GPL 許可證協(xié)議。在此并不討論該許可證協(xié)議的詳細(xì)內(nèi)容,而是討論在代碼中如何來反應(yīng)我們接受該許可證協(xié)議。在代碼中我們需要添加如下的代碼來表示該代碼接受相應(yīng)的許可證協(xié)議。
MODULE LICENSE("GPL");
????????MODULE_LICENSE是一個(gè)宏,里面的參數(shù)是一個(gè)字符串,代表相應(yīng)的許可證協(xié)議??梢允?GPL、GPL v2、GPL and additional rights、Dual BSD/GPL、Dua MIT/GPL、Dual MPL/GPL 等,詳細(xì)內(nèi)容請(qǐng)參見include/linux/module.h頭文件。這個(gè)宏將會(huì)生成一些模塊信息,放在ELF文件中的一個(gè)特殊的段中,模塊在加載時(shí)會(huì)將該信息復(fù)制到內(nèi)存中檢查該信息,可能讀者會(huì)認(rèn)為不加這行代碼,即不接受許可證協(xié)議只是導(dǎo)致內(nèi)核報(bào)案或關(guān)閉某些調(diào)試功能而已,對(duì)于可以不開源的這個(gè)結(jié)果,這個(gè)代價(jià)似乎是可以接受的但是正如本章的后面我們會(huì)講到的一樣,沒有這行代碼,內(nèi)核中的某些功能再數(shù)是不夠調(diào)用的,而我們?cè)陂_發(fā)驅(qū)動(dòng)時(shí)幾乎不可避免地要去使用內(nèi)核中的一些基礎(chǔ)設(shè)施,
用一些內(nèi)核的API函數(shù)。
除了MODULE_LICENSE之外,還有很多類似的描述模塊信息的宏,比如MODULE AUTHOR,:MODULE DESCRIPTION用于模塊的詳細(xì)信息說明,通常是該模塊的功能說明:MODULE_ALLA提供了給用戶空間使用的一個(gè)更合適的別名,也就是使用MODULE_ALIAS可以取一別名。
模塊的初始化函數(shù)和清除函數(shù)的名字是固定的,入口函數(shù)基本上都叫main。這對(duì)子追求個(gè)性化和更想表達(dá)函數(shù)真實(shí)意圖的我們來說顯得呆板了一些。幸虧內(nèi)核借助于GNU的函數(shù)別名機(jī)制,使得我們可以更靈活地指定模塊的初始化函數(shù)和清除函數(shù)的別名
module init(vser_init);module exit(vser exit);
????????module init 和module_exit是兩個(gè)宏,分別用于指定initmodule的函數(shù)別名是 vserinit,以及cleanup_module的別名是vser_exit。這樣我們的模塊初始化函數(shù)和清除承數(shù)就可以用別名來定義了。
????????函數(shù)名可以任意指定又帶來了一個(gè)新問題,那就是可能會(huì)和內(nèi)核中已有的函數(shù)重名因?yàn)槟K的代碼最終也屬于內(nèi)核代碼的一部分。C語言沒有類似于C++的命名空間的概念,為了避免因?yàn)橹孛鴰淼闹貜?fù)定義的問題,函數(shù)可以加static關(guān)鍵字修飾。經(jīng)過static修飾后的函數(shù)的鏈接屬性為內(nèi)部,從而解決了該問題。這就是幾乎所有的驅(qū)動(dòng)程序的函數(shù)前都要加static關(guān)鍵字修飾的原因。
????????Linux是節(jié)約內(nèi)存的操作系統(tǒng)的典范,任何可能節(jié)約下來的內(nèi)存都不會(huì)被它放過。上面的模塊代碼看上去已經(jīng)足夠簡(jiǎn)單了,但仔細(xì)思考,還是會(huì)發(fā)現(xiàn)可以優(yōu)化的地方。模塊的初始化函數(shù)會(huì)且僅會(huì)被調(diào)用一次,在調(diào)用完成后,該函數(shù)不應(yīng)該被再次調(diào)用。所以該函數(shù)所占用的內(nèi)存應(yīng)該被釋放掉,在函數(shù)名前加_init可以達(dá)到此目的。_init是把標(biāo)記的函數(shù)放到ELF文件的特定代碼段,在模塊加載這些段時(shí)將會(huì)單獨(dú)分配內(nèi)存,這些函數(shù)調(diào)用成功后,模塊的加載程序會(huì)釋放這部分內(nèi)存空間。_exit用于修飾清除函數(shù),和init的作用類似,但用于模塊的卸載,如果模塊不允許卸載,那么這段代碼完全就不用加載。
(其實(shí)不一定被污染,我的就沒有哈哈,書中的內(nèi)核版本是3.14我的是5.4.0)
(具體原因不詳)
?然后我們的程序就變成這樣了
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
static int __init vser_init(void)
{
printk("init_module\n");
return 0;
}
static void __exit vser_exit(void)
{
printk("cleanup_module\n");
}
module_init(vser_init);
module_exit(vser_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("姓名 <郵箱>");
MODULE_DESCRIPTION("A simple module");
MODULE_ALIAS("virtual-serial");
一定不要忘記函數(shù)的()里要寫void
之前那個(gè)modprobe能用了,但是要把ko文件復(fù)制到/lib/modules/5.4.0-137-generic下
感覺應(yīng)該是我的depmod有問題。
將多個(gè)源文件編譯生成一個(gè)內(nèi)核模塊?
對(duì)于一個(gè)比較復(fù)雜的驅(qū)動(dòng)程序,將所有的代碼寫在一個(gè)源文件中通常是不太現(xiàn)實(shí)的。我們通常會(huì)把程序的功能進(jìn)行拆分,由不同的源文件來實(shí)現(xiàn)對(duì)應(yīng)的功能,應(yīng)用程序是這樣的,驅(qū)動(dòng)程序也是如此。下面這個(gè)簡(jiǎn)單的例子演示了如何用多個(gè)源文件生成一個(gè)內(nèi)核模塊。
其實(shí)很簡(jiǎn)單就是修改一下makefile
#include <linux/kernel.h>
void bar(void)
{
printk("bar\n");
}
#include <linux/kernel.h>
void bar(void)
{
printk("bar\n");
}
book@100ask:~/makeru/driver/kernal$ cat foo.c
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
extern void bar(void);
static int __init vser_init(void)
{
printk("vser_init\n");
return 0;
}
static void __exit vser_exit(void)
{
printk("vser_exit\n");
}
內(nèi)核模塊參數(shù)
?????????通過前面的了解,我們知道模塊的初始化函數(shù)在模塊被加載時(shí)調(diào)用。但是該函數(shù)不接受參數(shù),如果我們想在模塊加載時(shí)對(duì)模塊的行為進(jìn)行控制,就不是很方便了。比如編寫了一個(gè)串口驅(qū)動(dòng),想要在串口驅(qū)動(dòng)加載時(shí)波特率由命令行參數(shù)設(shè)定,就像運(yùn)行普通的應(yīng)用程序時(shí),通過命令行參數(shù)來傳遞信息一樣。為此模塊提供了另外一種形式來支持這種行為,這就叫作模塊參數(shù)。
????????模塊參數(shù)允許用戶在加載模塊時(shí)通過命令行指定參數(shù)值,在模塊的加載過程中,加載程序會(huì)得到命令行參數(shù),并轉(zhuǎn)換成相應(yīng)類型的值,然后賦值給對(duì)應(yīng)的變量,這個(gè)過程發(fā)生在調(diào)用模塊初始化函數(shù)之前。內(nèi)核支持的參數(shù)類型有:bool、invbool(反轉(zhuǎn)值 bool類型)、charp(字符串指針)、short、int、long、ushort、uint、ulong。這些類型又可以復(fù)合成對(duì)應(yīng)的數(shù)組類型。為了說明模塊參數(shù)的用法,下面分別以整型、整型數(shù)組和字符串類型為例進(jìn)行說明
內(nèi)核中沒有字符類型(char),但有byte類型,使用時(shí)可以byte類型代替char類型,
但是在傳遞參數(shù)時(shí)不能直接傳遞字符,只能傳遞整形數(shù),否則會(huì)報(bào)錯(cuò)
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
static int baudrate = 9600;
static int port[4] = {0,1,2,3};
static char *name = "vser";
module_param(baudrate, int, S_IRUGO);
module_param_array(port, int, NULL, S_IRUGO);
module_param(name, charp, S_IRUGO);
static int __init vser_init(void)
{
int i;
printk("vser_init\n");
printk("baudrate: %d\n", baudrate);
printk("prot:");
for(i = 0; i < ARRAY_SIZE(port); i++)
printk("%d", port[i]);
printk("\n");
printk("name: %s\n", name);
return 0;
}
static void __exit vser_exit(void)
{
printk("vser_exit\n");
}
module_init(vser_init);
module_exit(vser_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("name <E-mail>");
MODULE_DESCRIPTION("A simple module");
MODULE_ALIAS("virtual-serial");
代碼第5行到第7行分別定義了一個(gè)整型變量、整型數(shù)組和字符串指針。代碼第9行到第11行將這三個(gè)類型的變量聲明為模塊參數(shù),分別用到了moduleparam和module param_array 兩個(gè)宏,兩者的參數(shù)說明如下。
module_param(name, type, perm)
module param array(name, type,nump; perm)name:變量的名字。
type:變量或數(shù)組元素的類型。
nump:數(shù)組元素個(gè)數(shù)的指針,可選。
perm:在sysfs文件系統(tǒng)中對(duì)應(yīng)文件的權(quán)限屬性。
權(quán)限的取值請(qǐng)參見<linux/stat.h>頭文件,含義和普通文件的權(quán)限是一樣的。但是如果perm為0,則在sysfs文件系統(tǒng)中將不會(huì)出現(xiàn)對(duì)應(yīng)的文件。
編譯、安裝模塊后,在加載模塊時(shí),如果不指定模塊參數(shù)的值,那么使用的命令和內(nèi)核的打印信息如下。
可見打印的值都是代碼中的默認(rèn)值。如果需要指定模塊參數(shù)的值,可以使用下面的命令。
modprobe vser baudrate-115200 port-1,2,3,4 name-"virtual-serial”dmesg
(我的modprobe不太好使我就用的insmod)
?
參看 sysfs文件系統(tǒng)下的內(nèi)容,可以發(fā)現(xiàn)和模塊參數(shù)對(duì)應(yīng)的文件及相應(yīng)的權(quán)限。?
?雖然在代碼中增加模塊參數(shù)的寫權(quán)限可以使用戶通過sysfs文件系統(tǒng)來修改模塊參數(shù)的值,但并不推薦這樣做。因?yàn)橥ㄟ^這種方式對(duì)模塊參數(shù)進(jìn)行的修改模塊本身是一無所知的。
內(nèi)核模塊依賴
????????在介紹模塊依賴之前,首先讓我們學(xué)習(xí)一下導(dǎo)出符號(hào)。在之前的模塊代碼中,都用到了printk函數(shù),很顯然,這個(gè)函數(shù)不是我們來實(shí)現(xiàn)的,它是內(nèi)核代碼的一部分。我們的模塊之所以能夠編譯通過,是因?yàn)閷?duì)模塊的編譯僅僅是編譯,并沒有鏈接。編譯出來的,ko文件是一個(gè)普通的ELF目標(biāo)文件,使用file命令和nm命令,可以得到相關(guān)的細(xì)節(jié)信息。
使用nm命令查看模塊目標(biāo)文件的符號(hào)信息時(shí),可以看到vser_exit和vser_init的符號(hào)類型是t,表示它們是函數(shù);而printk的符號(hào)類型是U,表示它是一個(gè)未決符號(hào)。這表示在編譯階段不知道這個(gè)符號(hào)的地址,因?yàn)樗欢x在其他文件中,沒有放在模塊代碼中一起編譯。那printk函數(shù)的地址問題怎么解決呢,讓我們來看看printk的實(shí)現(xiàn)代碼(位于內(nèi)核源碼kernel/printk/printk.c)。
/usr/src/linux-headers-5.4.0-137-generic/kernel
這是我的內(nèi)核版本其實(shí)這里都是軟連接
真正的源碼在makefile指定的那個(gè)路徑里
cat printk.c | head -n 1693 | tail -n +1674
asmlinkage int printk(const char *fmt, ...)
{
va_list args;
int r;
#ifdef CONFIG_KGDB_KDB
if (unlikely(kdb_trap_printk)) {
va_start(args, fmt);
r = vkdb_printf(fmt, args);
va_end(args);
return r;
}
#endif
va_start(args, fmt);
r = vprintk_emit(0, -1, NULL, 0, fmt, args);
va_end(args);
return r;
}
????????通過一個(gè)叫作EXPORT SYMBOL 的宏將printk導(dǎo)出,其目的是為動(dòng)態(tài)加載的模塊提供printk的地址信息。大致的工作原理是:利用EXPORT_SYMBOL 宏生成一個(gè)特定的結(jié)構(gòu)并放在 ELF 文件的一個(gè)特定段中,在內(nèi)核的啟動(dòng)過程中,公將符號(hào)的確切地場(chǎng)填充到這個(gè)結(jié)構(gòu)的特定成員中。模塊加載時(shí),加載程字將去處理未決符號(hào),在特殊段中2索符號(hào)的名字,如果找到,則將獲得的地址填充在被加載模塊的相應(yīng)段中,這樣符號(hào)的地址就可以確定。使用這種方式處理未決符號(hào),其實(shí)相當(dāng)于把鏈接的過程推后,進(jìn)行了動(dòng)態(tài)鏈接,和普通的應(yīng)用程序使用共享庫函數(shù)的道理是類似的??梢园l(fā)現(xiàn),內(nèi)核將會(huì)有大量的符號(hào)導(dǎo)出。為模塊提供了豐富的基礎(chǔ)設(shè)施,
????????通常情況下,一個(gè)模塊只使用內(nèi)核導(dǎo)出的符號(hào),自己不導(dǎo)出符號(hào)。但是如果一個(gè)初塊需要提供全局變量或函數(shù)給另外的模塊使用,那么就需要將這些符號(hào)導(dǎo)出。這在一個(gè)驅(qū)動(dòng)程序代碼調(diào)用另一個(gè)驅(qū)動(dòng)程序代碼時(shí)比較常見。這樣模塊和模塊之間就形成了依賴關(guān)系,使用導(dǎo)出符號(hào)的模塊將會(huì)依賴于導(dǎo)出符號(hào)的模塊,下面的代碼說明了這一點(diǎn)?
?
vser.c?
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
extern int expval;
extern void expfun(void);
static int __init vser_init(void)
{
printk("vser_init\n");
printk("expval: %d\n", expval);
expfun();
return 0;
}
static void __exit vser_exit(void)
{
printk("vser_exit\n");
}
module_init(vser_init);
module_exit(vser_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("name <E-mail>");
MODULE_DESCRIPTION("A simple module");
MODULE_ALIAS("virtual-serial");
?dep.c
#include <linux/kernel.h>
#include <linux/module.h>
static int expval = 5;
EXPORT_SYMBOL(expval);
static void expfun(void)
{
printk("expfun");
}
EXPORT_SYMBOL_GPL(expfun);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("name <E-mail>");
?
?
?喵喵的在開發(fā)板就好使,大概是我的ubuntu環(huán)境有問題。這幾個(gè)驅(qū)動(dòng)是之前做的開機(jī)自啟腳本加載的。
????????在上面的代碼中,dep.c里定義了一個(gè)全局變量expval,定義了一個(gè)函數(shù)expfun,并分別使用EXPORT SYMBOL 和EXPORT SYMBOL GPL 導(dǎo)出。在vser.c首先用extern聲明了這個(gè)變量和函數(shù),并打印了該變量的值和調(diào)用了該函數(shù)。在 Makefile中添加了第20行的代碼,增加了對(duì) dep模塊的編譯。編譯、安裝模塊后,使用下面的命令加載并查看內(nèi)核的打印信息。
$ modprobe vser$ dmesg
這里有幾點(diǎn)需要特別說明。
(1)如果使用insmod命令加載模塊,則必須先加載 dep 模塊,再加載vser模塊。因?yàn)関ser模塊使用了dep模塊導(dǎo)出的符號(hào),如果在dep模塊沒有加載的情況下加載vser模塊,那么將會(huì)在加載的過程中因?yàn)樘幚砦礇Q符號(hào)而失敗。從這里可以看出,modprobe令優(yōu)于 insmod 命令的地方在于其可以自動(dòng)加載被依賴的模塊。而這又要?dú)w功于depmod命令,depmod 命令將會(huì)生成模塊的依賴信息,保存在/1ib/modules/3.13.0-32-gene modules.dcp 文件中。其中,3.13.0-32-generic 是內(nèi)核源碼的版本,視版本的不同而不同查看該文件可以發(fā)現(xiàn)vser模塊所依賴的模塊。
$cat /lib/modules/3.13.0-32-generic/modules.depextra/vser.ko: extra/dep.ko
extra/dep.ko:
(我的ubuntu18.04和書中描述的不一致,我在我的開發(fā)板上也找不到depmod的文件,不過效果差不多,而且我的ubuntu里可能是ko文件太多了,還有大量的重名ko文件所以更新依賴失敗了。也不是失敗了,更新的不是我想要的。)
(2)兩個(gè)模塊存在依賴關(guān)系,如果分別編譯兩個(gè)模塊,將會(huì)出現(xiàn)類似于下面的警告
信息,并且即便加載順序正確,加載也不會(huì)成功。
NARNING:“expfun”【/home/farsight/fs4412/driver/module/ex5/vser.ko) undefined! WARNING:"expval”[/home/farsight/fs4412/driver/module/ex5/vser.kol)undefined!
$ sudo insmod dep.ko$ sudo insmod vser.ko
insmod: error inserting 'vser.ko': -1 Invalid parameters
這是因?yàn)樵诰幾gvser模塊時(shí)在內(nèi)核的符號(hào)表中找不到expval和expfun的項(xiàng),而vser模塊又完全不知道dep模塊的存在。解決這個(gè)問題的方法是將兩個(gè)模塊放在一起編譯或者將dep模塊放在內(nèi)核源碼中,先在內(nèi)核源碼下編譯完所有的模塊,再編譯 vser 模塊
(3)卸載模塊時(shí)要先卸載vser模塊,再卸載dep模塊,否則會(huì)因?yàn)閐ep 模塊被 vsa模塊使用而不能卸載。內(nèi)核將會(huì)創(chuàng)建模塊依賴關(guān)系的鏈表,只有當(dāng)依賴于這個(gè)模塊的表為空時(shí),模塊才能被卸載。
關(guān)于內(nèi)核模塊的進(jìn)一步討論?
????????Linux的內(nèi)核是由全世界的志愿者來開發(fā)的,這個(gè)組織中的內(nèi)核開發(fā)者會(huì)毫不顧慮地刪除不適合的接口或者對(duì)接口進(jìn)行修改,只要認(rèn)為這是必要的。所以,往往在前一個(gè)篇本這個(gè)接口函數(shù)以一種形式存在,而到了下一個(gè)版本函數(shù)的接口就發(fā)生了變化。這對(duì)內(nèi)核模塊的開發(fā)具有重要的影響,就是所謂的內(nèi)核模塊版本控制。在一個(gè)版本上編譯出來的內(nèi)核模塊,ko文件中詳細(xì)記錄了內(nèi)核源碼版本信息、體系結(jié)構(gòu)信息、函數(shù)接口信息(通過CRC校驗(yàn)實(shí)現(xiàn))等,在開啟了版本控制選項(xiàng)的內(nèi)核中加載一個(gè)模塊時(shí),內(nèi)核將核對(duì)這些信息,如果不一致,則會(huì)拒絕加載。下面就是把一個(gè)在3.13 內(nèi)核版本上編譯的內(nèi)核模塊放在 3.5 內(nèi)核版本的系統(tǒng)上加載的相關(guān)輸出信息。
modinfo vser,ko
filename: vser,ko?
alias: virtual-serial?
deneriptioni: A simple module?
authori : 手動(dòng)打碼
licese:?GPL?
sroveraloni BABBD66A92DF5D4C7VA3110?
depends:
vermagic:??3.13.0-32-generie BME mod unload modversionn 606?uname sr
3.5.0-23-genericinsmod veer.ko
lnsmod: error inserting 'veoriko’! -i Invalid module formatdmesg
vsert: disagreen about vernion of symbol module_layout最后再總結(jié)一下內(nèi)核模塊和普通應(yīng)用程序之間的差異。
(1)內(nèi)核模塊是操作系統(tǒng)內(nèi)核的一部分,運(yùn)行在內(nèi)核空間:而應(yīng)用程序運(yùn)行在用戶空間。
(2)內(nèi)核模塊中的函數(shù)是被動(dòng)地被調(diào)用的,比如初始化的數(shù)和清除函數(shù)分別是在內(nèi)核模塊被加載和被卸載的時(shí)候調(diào)用,模塊通常注冊(cè)一些服務(wù)性質(zhì)的函數(shù)供其他功能單元在之后調(diào)用,而應(yīng)用程序則是順序執(zhí)行,然后通常進(jìn)入一個(gè)循環(huán)反復(fù)調(diào)用某些函數(shù)。
(3)內(nèi)核模塊處于C函數(shù)庫之下,自然就不能調(diào)用C庫函數(shù)(內(nèi)核源碼中會(huì)實(shí)現(xiàn)類似的函數(shù));而應(yīng)用程序則可以隨意調(diào)用C庫函數(shù)。
(4)內(nèi)核模塊要做一些清除性的工作,比如在一個(gè)操作失敗后或者在內(nèi)核的清除函數(shù)中:而應(yīng)用程序有些工作通常不需要做,比如在程序退出前關(guān)閉所有已打開的文件。
(5)內(nèi)核模塊如果產(chǎn)生了非法訪問(比如對(duì)野指針的訪問),將很有可能導(dǎo)致整個(gè)系統(tǒng)的崩潰;而應(yīng)用程序通常只影響自己。
(6)內(nèi)核模塊中的并發(fā)更多,比如中斷、多處理器:而應(yīng)用程序一般只考慮多進(jìn)程或多線程。
(7)整個(gè)內(nèi)核空間的調(diào)用鏈上只有4KB或8KB的棧,相對(duì)于應(yīng)用程序來說非常的小。所以如果需要大的內(nèi)存空間,通常應(yīng)該動(dòng)態(tài)分配。
(8)雖然printk和printf的行為非常相似,但是通常printk不支持浮點(diǎn)數(shù),例如要打印一個(gè)浮點(diǎn)變量,在編譯時(shí)通常會(huì)出現(xiàn)如下警告,并且模塊也不會(huì)加載成功。
WARNING:" extendsfdf2"[/home/faraight/fe4412/driver/module/ex5/vser.ko)undefined!?
WARNING:" truncdfaf2"【/home/farsight/fs4412/driver/module/ex5/vser.ko)undetined! WARNING:“ divdf3”【/home/farsight/f84412/driver/module/ex5/vser.ko]undefined!
WARNING:"foatsidf”【/home/farsight/fs4412/driver/module/ex5/vser.ko] undefined!
習(xí)題
1,在默認(rèn)情況下,模塊初始化函數(shù)的名字是( A),模塊清除函數(shù)的名字是( B)。
[A]init_module[B]cleanup_module[C] mod_init [D] mod_exit?
2、加載模塊可以用哪個(gè)命令( AD)。
[A]insmod [B] rmmod [C] depmod [D] modprobe?
3、查看模塊信息用哪個(gè)命令(C)。
[A] insmod [B] rmmod [C] modinfo [D] modprobe?
4.內(nèi)核模塊參數(shù)的類型不包括(D)。
[A]布爾 [B]字符串指針 [C]數(shù)組 [D] 結(jié)構(gòu)?
b)?type:數(shù)據(jù)類型內(nèi)核支持模塊參數(shù)類型有:bool、invbool(bool的發(fā)轉(zhuǎn),true變?yōu)閒alse,false變?yōu)閠rue)、charp(char類型指針值)、int、long、short、uint、ulong、ushort
5.內(nèi)核模塊導(dǎo)出符號(hào)用哪個(gè)宏(C )。
[A]MODULE EXPORT [B]MODULE_PARAM?
[C]ENPORT SYMBOL [D] MODULE_LICENSE?
pararm是傳參數(shù)的
MODULE EXPORT這個(gè)我都沒找到好像沒有這個(gè)宏
6.內(nèi)核模塊能否調(diào)用C庫的函數(shù)接口(B )。
[A]能 [B] 不能?
7.在內(nèi)核模塊代碼中,我們能否定義任意大小的局部變量( B)。
[A] 能 [B]不能文章來源:http://www.zghlxwxcb.cn/news/detail-756628.html
?這題有歧義,只能是一定范圍內(nèi)的自由文章來源地址http://www.zghlxwxcb.cn/news/detail-756628.html
到了這里,關(guān)于Linux驅(qū)動(dòng)開發(fā)——內(nèi)核模塊的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!