現代內核派系 巨集內核 關鍵功能(基本功能,不可裁剪、擴展)和服務功能(如文件系統、設備驅動、網路服務等,可裁剪、擴展)均在內核空間提供。運行效率高。擴展性較差。system call(系統調用)能夠先入內核態來使用內核提供的服務。 微內核 內核空間只提供關鍵功能,服務功能在用戶空間提供。運行效率較低 ...
現代內核派系
巨集內核
關鍵功能(基本功能,不可裁剪、擴展)和服務功能(如文件系統、設備驅動、網路服務等,可裁剪、擴展)均在內核空間提供。運行效率高。擴展性較差。system call(系統調用)能夠先入內核態來使用內核提供的服務。
微內核
內核空間只提供關鍵功能,服務功能在用戶空間提供。運行效率較低。安全性、擴展性較高。
在Linux內核源碼中有超過50%的代碼都與設備驅動相關。Linux為巨集內核架構(windows、鴻蒙等為微內核架構),如果開啟所有的功能,內核就會變得十分臃腫。內核模塊實現了某個功能的一段內核代碼,在內核運行過程,可以載入這部分代碼到內核,從而動態地增加了內核的功能。基於這種特性,進行設備驅動開發時,以內核模塊的形式編寫設備驅動,只需要編譯相關的驅動代碼即可,無需對整個內核進行編譯。
在設備驅動開發過程中,用戶可以隨意將正在測試的驅動程式添加到內核或從內核中移除,每次修改內核模塊的代碼不需要重新啟動內核。
在開發板上,用戶也不需要將內核模塊程式,或設備驅動程式的ELF文件存放在開發板,免去占用不必要的存儲空間。當需要載入內核模塊時,可以掛在NFS伺服器,將存放在其它設備的內核模塊載入到開發板上。
內核模塊
為解決linux內核可擴展性和可維護性相對較差的缺陷。我們編寫的內核模塊,經過編譯,最終形成.ko為尾碼的ELF文件。ko文件是elf格式,是一種普通的可重定位目標文件。這類文件包含了代碼和數據,可以被用來鏈接成可執行文件或共用目標文件,靜態鏈接庫也可以歸為這一類。
內核模塊頭文件
#include <linux/init.h> /*包含module_init()和module_exit()函數的聲明*/ #include <linux/module.h> /*包含內核模塊信息聲明的相關函數*/ #include <linux/kernel.h> /*包含內核提供的各種函數,如printk*/
內核模塊載入與卸載
載入內核模塊:insmod
卸載內核模塊:rmmod
內核模塊出入口
module_init():載入模塊時該函數自動執行,進行初始化操作
module_exit():卸載模塊時該函數自動執行,進行清理操作
內核模塊信息聲明
MODULE_LICENSE():表示模塊代碼接受的軟體許可協議,Linux內核遵循GPL V2開源協議,內核模塊與linux內核保持一致即可。
MODULE_AUTHOR():描述模塊的作者信息。
MODULE_DESCRIPTION():對模塊的簡單介紹。
MODULE_ALIAS():給模塊設置一個別名。
列印函數printk
printf:glibc庫實現的列印函數,工作於用戶空間。
printk:內核模塊無法使用glibc庫函數,內核自身實現的一個類printf函數,但是需要指定列印等級。
#include <linux/kernel.h> /*printk列印等級,數字越小,等級越高*/ #define KERN_EMERG "<0>" /*通常是系統崩潰前的信息*/ #define KERN_ALERT "<1>" /*需要立即處理的消息*/ #define KERN_CRIT "<2>" /*嚴重情況*/ #define KERN_ERR "<3>" /*錯誤情況*/ #define KERN_WARNING "<4>" /*有問題的情況*/ #define KERN_NOTICE "<5>" /*註意信息*/ #define KERN_INFO "<6>" /*普通消息*/ #define KERN_DEBUG "<7>" /*調試信息*/
cat /proc/sys/kernel/printk:查看當前系統printk列印等級。
終端輸出:
當前控制台日誌級別(小於該等級才能列印在當前控制台)
預設消息日誌級別
最小的控制台級別(當前控制台日誌級別的最小值)
預設控制台日誌級別(沒指定等級時預設級別)
dmesg:列印內核所有列印信息。
lsmod:查看當前系統載入內核情況。
實驗環境(野火為例)
開發板燒錄好Debian鏡像。啟動開發板,搭建好nfs客戶端,掛載共用文件夾。獲取Debian鏡像的內核源碼並編譯。(選擇4.19.71版本內核,內核模塊的功能需要依賴內核提供的各種底層介面。)
註:cat /etc/issue查看鏡像日期。新版內核是4.19.35版本(22年之後),看驅動文檔的“驅動章節實驗環境搭建”。
下載linux內核源碼(基於野火linux開發板,ebf-buster-linux存放著內核相關)
git clone https://github.com/Embedfire/ebf-buster-linux.git
git clone https://gitee.com/Embedfire/ebf-buster-linux.git
安裝必要環境工具庫(make工具、gcc交叉編譯鏈、gcc編譯工具、bison語法分析器、flex詞法分析器、libssl-dev OpenSSL通用庫、lzop LZO壓縮庫的壓縮軟體)
sudo apt install make gcc-arm-linux-gnueabihf gcc bison flex libssl-dev dpkg-dev lzop
前往ebf-buster-linux目錄,執行腳本,一鍵編譯內核
sudo ./make_deb.sh
獲取編譯出來的內核相關文件(make_deb.sh腳本指定存放路徑)
/home/couvrir/桌面/ebf-buster-linux/build_image/build
make_deb.sh如下:
deb_distro=bionic DISTRO=stable build_opts="-j 6" build_opts="${build_opts} O=build_image/build" //O執行編譯文件的存放路徑 build_opts="${build_opts} ARCH=arm" build_opts="${build_opts} KBUILD_DEBARCH=${DEBARCH}" build_opts="${build_opts} LOCALVERSION=-imx-rl" build_opts="${build_opts} KDEB_CHANGELOG_DIST=${deb_distro}" build_opts="${build_opts} KDEB_PKGVERSION=l${DISTRO}" build_opts="${build_opts} CROSS_COMPILE=arm-linux-gnueabihf-" build_opts="${build_opts} KDEB_SOURCENAME=linux-upstream" make ${build_opts} npi_v7+defconfig make ${build_opts} make ${build_opts} bindeb-pkg
實驗一
helloworld.c文件
#include <linux/init.h> #include <linux/module.h> #include <linux/kernel.h> static int __init hello_init(void) //__init指將這段函數指定保存在__init段記憶體 { printk(KERN_EMERG "[ KERN_EMERG ] Hello World Module Init\n"); //指定等級 printk("[ default ] Hello World Module Init\n"); //預設等級 return 0; } static int __exit hello_exit(void) //__exit指將這段函數指定保存在__exit段記憶體 { printk("[ default ] goodbye\n"); //預設等級 } module_init(hello_init); //hello_init作為模塊函數入口 module_exit(hello_exit); //hello_exit作為模塊函數出口 MODULE_LICENSE("GPL2"); //表示模塊代碼接受的軟體許可協議 MODULE_AUTHOR("couvrir"); //描述模塊的作者信息 MODULE_DESCRIPTION("hello world module"); //對模塊的簡單介紹 MODULE_ALIAS("test module"); //給模塊設置一個別名
Makefile文件
KERNEL_DIR=/home/couvrir/桌面/ebf-buster-linux/build_image/build //將內核源碼編譯生成的內核文件存放在該路徑 ARCH=arm //交叉編譯的目標架構 CROSS_COMPILE=arm-linux-gnueabihf- //交叉編譯的編譯工具鏈 export ARCH CROSS_COMPILE //將架構和編譯鏈導出到子Makefile,使其在當前shell環境中生效 obj-m:=helloworld.o all: //預設目標,用於編譯所有模塊 $(MAKE) -C $(KERNEL_DIR) M=$(CURDIR) modules //執行內核源碼目錄的Makefile文件,並將當前目錄作為模塊的源碼目錄 //通過執行Makefile文件,可以將helloworld.c文件編譯成一個內核模塊 .PHONY:clean copy clean: $(MAKE) -C $(KERNEL_DIR) M=$(CURDIR) clean //執行內核源碼目錄的Makefile文件的clean目標,清理編譯生成的文件 copy: sudo cp *.ko /home/couvrir/workdir //將ko文件存到共用文件夾
obj-m:=<模塊名>.o:指定要編譯的模塊文件。
-C指定子Makefile的路徑,M=描述內核關於內核模塊的源碼路徑。CURDIR為當前目錄路徑。
make modules:執行linux頂層Makefile的偽目標,它實現內核模塊的源碼讀取並編譯為.ko文件。
執行make、make copy。
然後開發板上sudo insmod xxx.ko。
dmesg:列印內核所有列印信息。
lsmod:查看當前系統載入內核情況。