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