国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

Linux驅(qū)動(dòng)開發(fā)——內(nèi)核模塊

這篇具有很好參考價(jià)值的文章主要介紹了Linux驅(qū)動(dòng)開發(fā)——內(nèi)核模塊。希望對(duì)大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請(qǐng)大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問。

目錄

內(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é)了

linux內(nèi)核開發(fā),驅(qū)動(dòng)開發(fā),Linux,內(nèi)核,嵌入式

先來看第二章,內(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
else

obj-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)

指示安裝模塊的路徑,也就是生成的模塊放到哪里

  1. obj-y 編譯到內(nèi)核
  2. obj-m 編譯成模塊

linux內(nèi)核開發(fā),驅(qū)動(dòng)開發(fā),Linux,內(nèi)核,嵌入式

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).?

linux內(nèi)核開發(fā),驅(qū)動(dòng)開發(fā),Linux,內(nèi)核,嵌入式

內(nèi)核模塊工具?

?sudo insmod ./vser.ko

dmesg

linux內(nèi)核開發(fā),驅(qū)動(dòng)開發(fā),Linux,內(nèi)核,嵌入式

modprobe可以自動(dòng)加載模塊到內(nèi)核,但是我用著不好使不知道為什么,在運(yùn)行這個(gè)前要更新依賴,運(yùn)行depmod命令。等我學(xué)一學(xué)在看這個(gè)問題。

modinfo可以智能查找模塊

rmmod卸載模塊

linux內(nèi)核開發(fā),驅(qū)動(dòng)開發(fā),Linux,內(nèi)核,嵌入式?

?linux內(nèi)核開發(fā),驅(qū)動(dòng)開發(fā),Linux,內(nèi)核,嵌入式

?

內(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有問題。

linux內(nèi)核開發(fā),驅(qū)動(dòng)開發(fā),Linux,內(nèi)核,嵌入式

將多個(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");
}

linux內(nèi)核開發(fā),驅(qū)動(dòng)開發(fā),Linux,內(nèi)核,嵌入式

內(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)核的打印信息如下。
linux內(nèi)核開發(fā),驅(qū)動(dòng)開發(fā),Linux,內(nèi)核,嵌入式
可見打印的值都是代碼中的默認(rèn)值。如果需要指定模塊參數(shù)的值,可以使用下面的命令。
modprobe vser baudrate-115200 port-1,2,3,4 name-"virtual-serial”

dmesg

(我的modprobe不太好使我就用的insmod)

linux內(nèi)核開發(fā),驅(qū)動(dòng)開發(fā),Linux,內(nèi)核,嵌入式

?


參看 sysfs文件系統(tǒng)下的內(nèi)容,可以發(fā)現(xiàn)和模塊參數(shù)對(duì)應(yīng)的文件及相應(yīng)的權(quán)限。
linux內(nèi)核開發(fā),驅(qū)動(dòng)開發(fā),Linux,內(nèi)核,嵌入式

?

?雖然在代碼中增加模塊參數(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é)信息。

linux內(nèi)核開發(fā),驅(qū)動(dòng)開發(fā),Linux,內(nèi)核,嵌入式
使用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)。
linux內(nèi)核開發(fā),驅(qū)動(dòng)開發(fā),Linux,內(nèi)核,嵌入式

/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>");

?

linux內(nèi)核開發(fā),驅(qū)動(dòng)開發(fā),Linux,內(nèi)核,嵌入式

?linux內(nèi)核開發(fā),驅(qū)動(dòng)開發(fā),Linux,內(nèi)核,嵌入式

?喵喵的在開發(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.dep

extra/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-generic

insmod veer.ko
lnsmod: error inserting 'veoriko’! -i Invalid module format

dmesg
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]不能

?這題有歧義,只能是一定范圍內(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)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場(chǎng)。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請(qǐng)注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請(qǐng)點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • 嵌入式linux之iMX6ULL驅(qū)動(dòng)開發(fā) | 移遠(yuǎn)4G模塊EC800驅(qū)動(dòng)移植指南

    嵌入式linux之iMX6ULL驅(qū)動(dòng)開發(fā) | 移遠(yuǎn)4G模塊EC800驅(qū)動(dòng)移植指南

    回顧下移遠(yuǎn)4G模塊移植過程, 還是蠻簡(jiǎn)單的。一通百通,無論是其他4G模塊都是一樣的。這里記錄下過程,分享給有需要的人。環(huán)境使用正點(diǎn)原子的imax6ul開發(fā)板,板子默認(rèn)支持中興和移遠(yuǎn)EC20的驅(qū)動(dòng),這里要移植使用的是移遠(yuǎn)4G模塊EC800。 imax6ul開發(fā)板 虛擬機(jī)(Ubuntu18.04) 交叉編譯

    2024年02月12日
    瀏覽(26)
  • 【嵌入式Linux內(nèi)核驅(qū)動(dòng)】SPI子系統(tǒng) | 硬件原理 | 應(yīng)用編程 | 內(nèi)核驅(qū)動(dòng) | 總體框架

    【嵌入式Linux內(nèi)核驅(qū)動(dòng)】SPI子系統(tǒng) | 硬件原理 | 應(yīng)用編程 | 內(nèi)核驅(qū)動(dòng) | 總體框架

    1.1 SPI通信協(xié)議 SPI(Serial Peripheral Interface)是由Motorola公司開發(fā)的一種通用數(shù)據(jù)總線 四根通信線:SCK(Serial Clock)、MOSI(Master Output Slave Input)、MISO(Master Input Slave Output)、SS(Slave Select) 同步,全雙工 支持總線掛載多設(shè)備(一主多從) 1.2 硬件連接 多NSS獨(dú)立片選方式 菊花

    2024年02月16日
    瀏覽(28)
  • 【嵌入式環(huán)境下linux內(nèi)核及驅(qū)動(dòng)學(xué)習(xí)筆記-(5-驅(qū)動(dòng)的并發(fā)控制機(jī)制)】

    【嵌入式環(huán)境下linux內(nèi)核及驅(qū)動(dòng)學(xué)習(xí)筆記-(5-驅(qū)動(dòng)的并發(fā)控制機(jī)制)】

    在討論并發(fā)前,先要了解以下幾個(gè)概念:執(zhí)行流,上下文,共享與臨界等。 什么叫執(zhí)行流: 【執(zhí)行流】:有開始有結(jié)束總體順序執(zhí)行的一段代碼 又稱 上下文 。 上下文分類: 【任務(wù)上下文】:普通的,具有五種狀態(tài)(就緒態(tài)、運(yùn)行態(tài)、睡眠態(tài)、暫停態(tài)、僵死態(tài)),可被阻塞

    2023年04月21日
    瀏覽(29)
  • 【嵌入式Linux】編譯應(yīng)用和ko內(nèi)核模塊Makefile使用記錄

    【嵌入式Linux】編譯應(yīng)用和ko內(nèi)核模塊Makefile使用記錄

    在Makefile中,變量的賦值可以使用以下幾種方式: = :最基本的賦值符號(hào),表示簡(jiǎn)單的延遲展開(lazy expansion)方式。變量的值將會(huì)在使用變量的時(shí)候進(jìn)行展開。 := :立即展開(immediate expansion)的賦值方式。變量的值在賦值的時(shí)候立即展開,并且在后續(xù)的使用中不再改變。

    2024年02月08日
    瀏覽(24)
  • 嵌入式開發(fā)之linux內(nèi)核移植

    嵌入式開發(fā)之linux內(nèi)核移植

    目錄 ?前言 一、下載內(nèi)核源碼 1.1 下載linux-3.0.1 1.2 解壓源碼文件 二、 內(nèi)核添加yaffs2文件系統(tǒng)支持 2.1 下載yaffs2 2.2 內(nèi)核添加yaffs2文件補(bǔ)丁 三、配置開發(fā)板 3.1 修改機(jī)器ID 3.2 添加開發(fā)板初始化文件 3.3 配置NandFalsh 3.3.1 添加NandFlash設(shè)備 3.3.2 添加NandFlash驅(qū)動(dòng) 3.3 修改Kconfig(支持

    2024年02月07日
    瀏覽(99)
  • 【嵌入式Linux內(nèi)核驅(qū)動(dòng)】05_IIC子系統(tǒng) | 硬件原理與常見面試問題 | 應(yīng)用編程 | 內(nèi)核驅(qū)動(dòng) | 總體框架

    【嵌入式Linux內(nèi)核驅(qū)動(dòng)】05_IIC子系統(tǒng) | 硬件原理與常見面試問題 | 應(yīng)用編程 | 內(nèi)核驅(qū)動(dòng) | 總體框架

    1.1 IIC 基礎(chǔ) IIC協(xié)議簡(jiǎn)介—學(xué)習(xí)筆記_iic標(biāo)準(zhǔn)協(xié)議_越吃越胖的黃的博客-CSDN博客 I2C(Inter-Integrated Circuit)是一種串行通信協(xié)議,用于連接微控制器、傳感器、存儲(chǔ)器和其他外設(shè)。 I2C使用兩條線(SDA和SCL)進(jìn)行通信,可以連接多個(gè)設(shè)備,每個(gè)設(shè)備都有一個(gè)唯一的地址。I2C總線上的

    2024年02月09日
    瀏覽(89)
  • 嵌入式Linux底層系統(tǒng)開發(fā) +系統(tǒng)移植+內(nèi)核文件系統(tǒng)(基礎(chǔ))

    嵌入式Linux底層系統(tǒng)開發(fā) +系統(tǒng)移植+內(nèi)核文件系統(tǒng)(基礎(chǔ))

    搭建交叉編譯開發(fā)環(huán)境 bootloader的選擇和移植 kernel的配置、編譯、移植和調(diào)試 根文件系統(tǒng)的制作 前兩個(gè)要點(diǎn)通常芯片廠家提供。后邊兩個(gè)要點(diǎn)是公司的工作重點(diǎn)。 學(xué)習(xí)方法:先整體后局部,層層推進(jìn) 如何編譯—如何添加命令和功能—如何定義自己的開發(fā)板。 移植的基本步

    2024年02月03日
    瀏覽(98)
  • 正點(diǎn)原子嵌入式linux驅(qū)動(dòng)開發(fā)——Linux WIFI驅(qū)動(dòng)

    正點(diǎn)原子嵌入式linux驅(qū)動(dòng)開發(fā)——Linux WIFI驅(qū)動(dòng)

    WIFI的使用已經(jīng)很常見了,手機(jī)、平板、汽車等等,雖然可以使用有線網(wǎng)絡(luò),但是有時(shí)候很多設(shè)備存在布線困難的情況,此時(shí)WIFI就是一個(gè)不錯(cuò)的選擇。 正點(diǎn)原子STM32MP1開發(fā)板支持USB和SDIO這兩種接口的WIFI ,本章就來學(xué)習(xí)一下如何在STM32MP1開發(fā)板上使用USB和SDIO這兩種WIFI。 正點(diǎn)原

    2024年02月05日
    瀏覽(29)
  • 正點(diǎn)原子嵌入式linux驅(qū)動(dòng)開發(fā)——Linux CAN驅(qū)動(dòng)

    正點(diǎn)原子嵌入式linux驅(qū)動(dòng)開發(fā)——Linux CAN驅(qū)動(dòng)

    CAN是目前應(yīng)用非常廣泛的現(xiàn)場(chǎng)總線之一,主要應(yīng)用于汽車電子和工業(yè)領(lǐng)域 ,尤其是汽車領(lǐng)域,汽車上大量的傳感器與模塊都是通過CAN總線連接起來的。CAN總線目前是自動(dòng)化領(lǐng)域發(fā)展的熱點(diǎn)技術(shù)之一,由于其高可靠性,CAN總線目前廣泛的應(yīng)用于工業(yè)自動(dòng)化、船舶、汽車、醫(yī)療和

    2024年02月06日
    瀏覽(38)
  • 嵌入式Linux開發(fā)-USB驅(qū)動(dòng)

    嵌入式Linux開發(fā)-USB驅(qū)動(dòng)

    哥們馬上就要被裁了,總得整理一下技術(shù)方面的積累,準(zhǔn)備開始下一輪的面試和找工作之旅了。。。。 通用串行總線(USB)是主機(jī)和外圍設(shè)備之間的一種連接。 從拓?fù)渖蟻砜矗且活w由幾個(gè)點(diǎn)對(duì)點(diǎn)的連接構(gòu)建而成的樹。這些連接是連接設(shè)備和集線器(hub)的四線電纜(底線、電源線

    2024年02月20日
    瀏覽(25)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請(qǐng)作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包