內(nèi)核(Kernel)在計(jì)算機(jī)科學(xué)中是操作系統(tǒng)最基本的部分,主要負(fù)責(zé)管理系統(tǒng)資源。它是為眾多應(yīng)用程序提供對(duì)計(jì)算機(jī)硬件的安全訪問(wèn)的一部分軟件,這種訪問(wèn)是有限的,并由內(nèi)核決定一個(gè)程序在什么時(shí)候?qū)δ巢糠钟布僮鞫嚅L(zhǎng)時(shí)間。直接對(duì)硬件操作是非常復(fù)雜的。所以?xún)?nèi)核通常提供一種硬件抽象的方法,來(lái)完成這些操作。通過(guò)進(jìn)程間通信機(jī)制及系統(tǒng)調(diào)用,應(yīng)用進(jìn)程可間接控制所需的硬件資源(特別是處理器及IO設(shè)備)。
內(nèi)核在設(shè)計(jì)上分為宏內(nèi)核與微內(nèi)核兩大架構(gòu)。
(資料圖片僅供參考)
宏內(nèi)核:簡(jiǎn)單來(lái)說(shuō),就是把很多東西都集成進(jìn)內(nèi)核,例如Linux內(nèi)核,除了最基本的進(jìn)程、線程管理、內(nèi)存管理外,文件系統(tǒng),驅(qū)動(dòng),網(wǎng)絡(luò)協(xié)議棧等都在內(nèi)核里面。優(yōu)點(diǎn)是效率高。缺點(diǎn)是穩(wěn)定性差,開(kāi)發(fā)過(guò)程中的bug經(jīng)常會(huì)導(dǎo)致整個(gè)系統(tǒng)掛掉。做驅(qū)動(dòng)開(kāi)發(fā)的應(yīng)該經(jīng)常有按電源鍵強(qiáng)行關(guān)機(jī)的經(jīng)歷。
微內(nèi)核:內(nèi)核中只有最基本的調(diào)度、內(nèi)存管理。驅(qū)動(dòng)、文件系統(tǒng)等都是用戶(hù)態(tài)的守護(hù)進(jìn)程去實(shí)現(xiàn)的。優(yōu)點(diǎn)是超級(jí)穩(wěn)定,驅(qū)動(dòng)等的錯(cuò)誤只會(huì)導(dǎo)致相應(yīng)進(jìn)程死掉,不會(huì)導(dǎo)致整個(gè)系統(tǒng)都崩潰,做驅(qū)動(dòng)開(kāi)發(fā)時(shí),發(fā)現(xiàn)錯(cuò)誤,只需要kill掉進(jìn)程,修正后重啟進(jìn)程就行了,比較方便。缺點(diǎn)是效率低。
Linux是一個(gè)宏內(nèi)核,運(yùn)行在單獨(dú)的內(nèi)核地址空間。不過(guò),Linux汲取了微內(nèi)核的精華:其引以為豪的是模塊化設(shè)計(jì)、搶占式內(nèi)核、支持內(nèi)核線程以及動(dòng)態(tài)裝載內(nèi)核模塊的能力。不僅如此,Linux還避免其微內(nèi)核設(shè)計(jì)上性能損失的缺陷,讓所有事情都運(yùn)行在內(nèi)核態(tài),直接調(diào)用函數(shù),無(wú)需消息傳遞。至今,Linux是模塊化的、多線程的以及內(nèi)核本身可調(diào)度的操作系統(tǒng),實(shí)用主義再次占了上風(fēng)。
模塊是具有獨(dú)立功能的程序,它可以被 單獨(dú)編譯,但 不能獨(dú)立運(yùn)行。它在運(yùn)行時(shí)被鏈接到內(nèi)核作為內(nèi)核的一部分在內(nèi)核空間運(yùn)行。模塊通常由一組函數(shù)和數(shù)據(jù)結(jié)構(gòu)組成,用來(lái)實(shí)現(xiàn)一種文件系統(tǒng)、一個(gè)驅(qū)動(dòng)程序或其他內(nèi)核上層的功能。
內(nèi)核模塊是Linux內(nèi)核向外部提供的一個(gè)插口,其全稱(chēng)為動(dòng)態(tài)可加載內(nèi)核模塊(Loadable Kernel Module,LKM),簡(jiǎn)稱(chēng)為模塊。
同時(shí)內(nèi)核模塊的這一特點(diǎn)也有助于減小內(nèi)核鏡像文件的體積,自然也就減少了內(nèi)核所占的內(nèi)存空間(因?yàn)檎麄€(gè)內(nèi)核鏡像將會(huì)被加載到內(nèi)存中運(yùn)行)。不必把所有的驅(qū)動(dòng)都編譯內(nèi)核,而是以模塊的形式單獨(dú)編譯驅(qū)動(dòng)程序,這是基于不是所有的驅(qū)動(dòng)都會(huì)同時(shí)工作原理。因?yàn)椴皇撬械挠布家瑫r(shí)接入系統(tǒng),比如一個(gè)無(wú)線網(wǎng)卡討論完內(nèi)核模塊的這些特性后,我們正式開(kāi)始編寫(xiě)模塊程序。
眾所周知,內(nèi)核模式下的編程和用戶(hù)模式下有所不同,會(huì)有如下限制條件:
不能使用用戶(hù)模式下的C標(biāo)準(zhǔn)庫(kù)。不能使用浮點(diǎn)運(yùn)算,因?yàn)閘inux內(nèi)核切換模式時(shí)不保存處理器的浮點(diǎn)狀態(tài)。盡可能保持代碼的清潔易懂,因?yàn)閮?nèi)核調(diào)試不方便。模塊編程和內(nèi)核版本密切相連,不同的內(nèi)核版本,某些函數(shù)的函數(shù)名會(huì)有變化。因此模塊編程也可以說(shuō)是內(nèi)核編程。只有超級(jí)用戶(hù)才可以運(yùn)行模塊 。應(yīng)用程序編程和內(nèi)核模塊編程的對(duì)比:
應(yīng)用程序 | 內(nèi)核模塊程序 | |
---|---|---|
使用函數(shù) | libc庫(kù) | 內(nèi)核函數(shù) |
運(yùn)行空間 | 用戶(hù)空間 | 內(nèi)核空間 |
運(yùn)行權(quán)限 | 普通用戶(hù) | 超級(jí)用戶(hù) |
入口函數(shù) | main() | module_init() |
出口函數(shù) | exit() | module_exit() |
編譯工具 | gcc | make |
鏈接工具 | gcc | insmod |
運(yùn)行方式 | 直接運(yùn)行 | insmod |
調(diào)試方法 | gdb | kdbug、kdb、kgdb |
#include < linux/module.h > #include < linux/kernel.h > #include < linux/init.h >
編寫(xiě)任何內(nèi)核模塊程序所必須引用的 3 個(gè)頭文件 :
module.h包含了對(duì)模塊結(jié)構(gòu)的定義及模塊版本的控制kernel.h包含了常用的內(nèi)核函數(shù)init.h包含了宏__init和__exit,以及一些其他初始化函數(shù)的調(diào)用宏。如宏module_init等。宏__init告訴編譯程序相關(guān)的函數(shù)僅用于初始化模塊的初始化的宏定義,宏__exit用于可加載模塊的卸載清理操作。1)xxx_init():注冊(cè)函數(shù)(名字xxx可任起) 或模塊的初始化函數(shù)。如:
/* 不加void在調(diào)試時(shí)會(huì)出現(xiàn)報(bào)警 */static int __init myfunc_init( void ) { printk("Hello, This is my own module…"); return 0;}
2)xxx_exit( ):卸載函數(shù)(名字xxx可任起) 或模塊的退出和清理函數(shù)。如:
/* 不加void會(huì)出現(xiàn)報(bào)警,若改為static int也會(huì)報(bào)錯(cuò) , 因?yàn)槌隹诤瘮?shù)是不能返回值的 */static void __exit myfunc_exit( void ) { printk("Goodbye, uninstall my own module…"); }
1) module_init():向內(nèi)核注冊(cè)模塊,提供新功能;告訴內(nèi)核你編寫(xiě)的模塊程序從哪里開(kāi)始執(zhí)行。
2) module_exit():注銷(xiāo)由模塊提供的功能;告訴內(nèi)核你編寫(xiě)的模塊程序從哪里離開(kāi)。
MODULE_LICENSE(“GPL”);
從內(nèi)核2.4.10開(kāi)始,動(dòng)態(tài)加載的模塊必須通過(guò)MODULE_LICENSE宏聲明此模塊的許可證。否則在動(dòng)態(tài)加載此模塊時(shí),會(huì)收到內(nèi)核被污染"module license’unspecified’ taints kernel."的警告。
從Linux內(nèi)核2.6開(kāi)始,內(nèi)核模塊的編譯采用Kbuild(kernel build)系統(tǒng)。Kbuild系統(tǒng)會(huì)兩次掃描Linux的Makefile:首先編譯系統(tǒng)會(huì)讀取Linux內(nèi)核頂層的Makefile,然后根據(jù)讀到的內(nèi)容第二次讀取Kbuild的Makefile來(lái)編譯Linux內(nèi)核或者模塊。
Kernel Makefile:Kernel Makefile位于Linux內(nèi)核源代碼的頂層錄/usr/src/kernels/xxx/,也叫Top Makefile。這個(gè)文件會(huì)被首先讀取,并根據(jù)讀到的內(nèi)容配置編譯環(huán)境變量。對(duì)于內(nèi)核或驅(qū)動(dòng)開(kāi)發(fā)人員來(lái)說(shuō),這個(gè)文件幾乎不用任何修改。
Kbuild Makefile:當(dāng)Kernel Makefile被解析完成后,Kbuild會(huì)讀取相關(guān)的Kbuild Makefile進(jìn)行內(nèi)核或模塊的編譯。內(nèi)核及驅(qū)動(dòng)開(kāi)發(fā)人員需要編寫(xiě)這個(gè)Kbuild Makefile文件。
myownfunc.c代碼:
/* 源文件myownfunc.c */#include < linux/module.h >#include < linux/kernel.h >#include < linux/init.h >static int __init myfunc_init(void){ printk("Hello,this is my own module!"); return 0;}static void __exit myfunc_exit(void){ printk("Goodbye,this is my own clean module!");}module_init(myfunc_init);module_exit(myfunc_exit);MODULE_DESCRIPTION("First Personel Module");MODULE_AUTHOR("Lebron James");MODULE_LICENSE("GPL");
Makefile代碼:
ifneq ($(KERNELRELEASE),)$(info "2nd")obj-m:=myownfunc.oelseKDIR :=/lib/modules/$(shell uname -r)/buildPWD :=$(shell pwd)all: $(info "1st") make -C $(KDIR) M=$(PWD) modulesclean: rm -f *.ko *.o *.mod.o *.symvers *.cmd *.mod.c *.order *.modendif
Makefile解析:
#KERNELRELEASE :在內(nèi)核源碼樹(shù)的Makefile中定義,在當(dāng)前的Makefile中,# 它的值為空。#$(shell uname-r) :獲得當(dāng)系統(tǒng)的Linux內(nèi)核版本#KDIR :指定當(dāng)前Linux操作系統(tǒng)源代碼路徑,即編譯生成的模塊是在當(dāng)前系統(tǒng)中使用# 如果想將你寫(xiě)的模塊,用在你的開(kāi)發(fā)板上運(yùn)行的Linux系統(tǒng)中,只需在KDIR變量中指定# 你開(kāi)發(fā)板Linux系統(tǒng)源碼樹(shù)的路徑#PWD:=$(shell pwd)獲得當(dāng)前待編譯模塊的源文件路徑
1)在模塊的源代碼目錄下執(zhí)行make,此時(shí),宏“KERNELRELEASE”沒(méi)有定義,因此進(jìn)入else分支;
2)記錄內(nèi)核路徑KDIR和當(dāng)前工作目錄PWD;
3)因?yàn)閙ake后面沒(méi)有目標(biāo),所以make會(huì)在Makefile中的第一個(gè)不是以.開(kāi)頭的目標(biāo)作為默認(rèn)的目標(biāo)執(zhí)行,于是all成為make的目標(biāo);all:之后的第一個(gè)命令$(info “1st”) 類(lèi)似于printf函數(shù),編譯經(jīng)過(guò)此處會(huì)打印提示信息。
4)make的第二條命令會(huì)執(zhí)行make -C $(KDIR) M=$(PWD) modules
,翻譯過(guò)來(lái)就是
make -C /lib/modules/6.1.0-rc4+/build M=/tmp/29 modules
-C 表示到存放內(nèi)核源碼的目錄執(zhí)行其Makefile
M=$(PWD) 表示返回到當(dāng)前待編譯模塊目錄
modules 表示編譯成模塊的意思
之所以這么寫(xiě)是由內(nèi)核源碼樹(shù)的頂層Makefile告訴我們的,當(dāng)我們調(diào)用Linux內(nèi)核源碼樹(shù)頂層的Makefile時(shí),找到的是頂層Makefile的“modules”目標(biāo)。
5)找到modules目標(biāo)后,接下來(lái)Linux源碼樹(shù)的頂層Makeflle就需要知道是將哪些".c"文件編譯成模塊。誰(shuí)告訴它呢?是的,待編譯模塊的Makefile文件。所以接下來(lái)就會(huì)回調(diào)模塊的Makefile。需要注意的是,此時(shí)KERNELRELEASE已經(jīng)在Linux內(nèi)核源碼樹(shù)的頂層Makefile中定義過(guò)了,所以此時(shí)它獲得信息是:
obj-m:=myownfunc.o
obj-m表示會(huì)將myownfunc.o目標(biāo)編譯成.ko模塊;它告訴Linux源碼樹(shù)頂層Makefile是動(dòng)態(tài)編譯(編譯成模塊)而不是編譯進(jìn)內(nèi)核(obj-y),Linux源碼樹(shù)頂層Makefile會(huì)根據(jù)myownfunc.o找到myownfunc.c文件。
6)將模塊文件myownfunc.c編譯為myownfunc.o,然后再將多個(gè)目標(biāo)鏈接為.ko
最終編譯結(jié)果如下:
[root@localhost 29]# make"1st"make -C /lib/modules/6.1.0-rc4+/build M=/tmp/29 modulesmake[1]: Entering directory `/usr/src/kernels/6.1.0-rc4+""2nd" CC [M] /tmp/29/myownfunc.o"2nd" MODPOST /tmp/29/Module.symvers CC [M] /tmp/29/myownfunc.mod.o LD [M] /tmp/29/myownfunc.komake[1]: Leaving directory `/usr/src/kernels/6.1.0-rc4+"
由執(zhí)行結(jié)果可知,待編譯模塊的Makefile最終被調(diào)用了三次
1) 執(zhí)行命令make調(diào)用
2) 被Linux內(nèi)核源碼樹(shù)的頂層Makefile調(diào)用,產(chǎn)生.o文件
3) 被Linux內(nèi)核源碼樹(shù)頂層Makefile調(diào)用,將.o文件鏈接生成.ko文件
綜上,可將Linux模塊編譯的流程總結(jié)如下圖:
編譯好了xxx.ko文件以后,接下來(lái)就要考慮如何將ko模塊加載到Linux內(nèi)核以及如何卸載ko模塊,讓我們學(xué)習(xí)Linux內(nèi)核模塊加載與卸載。
insmod /absolute-path/模塊名.ko
例如添加上文編譯的內(nèi)核模塊:
insmod ./myownfunc.ko
注意:Linux系統(tǒng)中只有超級(jí)用戶(hù)權(quán)限才可以添加模塊到內(nèi)核。
modprobe命令也可以實(shí)現(xiàn)模塊加載到內(nèi)核,具體差異本文不做詳細(xì)概述,后續(xù)會(huì)出專(zhuān)門(mén)的推文講解insmod和modprobe的區(qū)別。
lsmod 模塊名
例如在系統(tǒng)中搜索自己添加的myownfunc模塊:
[root@nj-rack01-06 29]# lsmod | grep myownfuncmyownfunc 16384 0
rmmod 模塊名
例如卸載系統(tǒng)中的myownfunc模塊:
rmmod myownfunc
1)查看模塊注冊(cè)的信息
modinfo 模塊名.ko
例如查看自己添加的myownfunc模塊的注冊(cè)信息:
[root@nj-rack01-06 29]# modinfo myownfunc.kofilename: /tmp/29/myownfunc.kolicense: GPLauthor: Lebron Jamesdescription: First Personel Modulesrcversion: 8748FD633F9276BD38A9934depends:retpoline: Yname: myownfuncvermagic: 6.1.0-rc4+ SMP preempt mod_unload modversions
如上結(jié)果所示,modinfo會(huì)顯示模塊的全路徑文件名,license信息,作者信息,描述信息,模塊名等。
2)查看模塊打印的信息
dmesg | tail
例如查看自己添加的myownfunc模塊打印信息:
dmesg主要是從Linux內(nèi)核的ring buffer(環(huán)形緩沖區(qū))中讀取信息的。
在Linux系統(tǒng)中,所有通過(guò)printk打印出來(lái)的信息都會(huì)送到ring buffer中。我們知道,我們打印出來(lái)的信息是需要在控制臺(tái)設(shè)備上顯示的。因?yàn)榇藭r(shí)printk只是把信息輸送到ring buffer中,等控制臺(tái)設(shè)備初始化好后,在根據(jù)ring buffer中消息的優(yōu)先級(jí)決定是否需要輸送到控制臺(tái)設(shè)備上。
如何清空ring buffer呢?
dmesg -c
到此,本文即成功實(shí)現(xiàn)了自定義內(nèi)核模塊的加載、卸載以及打印信息的查看。
紙上得來(lái)終覺(jué)淺,絕知此事要躬行,想學(xué)習(xí)Linux驅(qū)動(dòng)的朋友趕緊親自動(dòng)手試一試吧。
標(biāo)簽: