Linux內核模組學習
- 2022 年 1 月 9 日
- 筆記
- 《Linux設備驅動開發詳解:基於最新的Linux 4.0內核 》學習筆記
註:本文是《Linux設備驅動開發詳解:基於最新的Linux 4.0內核 by 宋寶華 》一書學習的筆記,大部分內容為書籍中的內容。
書籍可直接在微信讀書中查看:Linux設備驅動開發詳解:基於最新的Linux4.0內核-宋寶華-微信讀書 (qq.com)
1 簡介
模組(Module)具有以下特點:
- 模組本身不編譯進內核映像
- 內核載入之後,和其它內核中的部分完全一樣。
一個簡單的示例:
#include <linux/init.h>
#include <linux/module.h>
static int __init hello_init(void)
{
printk(KERN_INFO "Hello world enter\n");
return 0;
}
module_init(hello_init); //內核載入函數
static void __exit hello_exit(void)
{
printk(KERN_INFO "Hello world exit\n");
}
module_exit(hello_exit); //內核卸載函數
MODULE_AUTHOR("Test Hello");
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("A simple Hello World Module");
MODULE_ALIAS("A simple module");
Makefile:
KVERS = $(shell uname -r)
# Kernel modules
obj-m += hello.o
# Specify flags for the module compilation.
#EXTRA_CFLAGS=-g -O0
build: kernel_modules
kernel_modules:
make -C /lib/modules/$(KVERS)/build M=$(CURDIR) modules
clean:
make -C /lib/modules/$(KVERS)/build M=$(CURDIR) clean
編譯,並且插入ko:
$ make
$ sudo insmod hello.ko
插入ko時,可能沒有列印資訊,可以使用dmesg命令查看:
可以通過lsmod命令查看當前系統插入了哪些ko,lsmod命令查看的結果對應/proc/modules文件,內核中已經載入模組資訊也存在於/sys/module目錄中:
$ lsmod | grep "hello"
Module Size Used by
hello 12425 0
$ cat /proc/modules | grep "hello"
hello 12425 0 - Live 0xffffffffc06fc000 (OE)
$ ls /sys/module/hello/
coresize initsize notes/ rhelversion srcversion uevent
holders/ initstate refcnt sections/ taint
modinfo命令可以獲得模組的資訊,包括模組作者、模組的說明、模組所支援的參數以及vermagic:
$ modinfo hello.ko
filename: /home/grace/driver_study/code/modules/hello.ko
alias: A simple module
description: A simple Hello World Module
license: GPL
author: Test Hello
rhelversion: 7.4
srcversion: E4D5379B55084D8ED2D94E8
depends:
vermagic: 3.10.0-693.el7.x86_64 SMP mod_unload modversions
卸載模組命令rmmod:
$ rmmod hello
2 模組程式結構
一個Linux內核模組主要由以下幾個部分組成:
(1)模組載入函數
通過insmod或者modprobe命令載入模組時,模組的載入函數會自動被內核執行,完成模組的初始化工作。
(2)模組卸載函數
通過rmmod命令卸載模組時,模組的卸載函數會自動被內核執行,完成模組相關的卸載功能。
(3)模組許可證聲明
許可證(LICENSE)聲明描述了內核模組的許可許可權,如果不申明LICENSE,模組載入時會收到內核被污染(Kernel Tainted)的警告。
[10688.585888] hello: module license 'unspecified' taints kernel.
在Linux內核模組中可接受的LICENSE包括:GPL、GPL v2等。大多數情況下,內核模組應遵循GPL兼容許可證。
(4)模組參數(可選)
模組參數是模組被載入時可以傳遞給它的值,它本身對應模組內部的全局變數。
(5)模組導出符號(可選)
內核模組可以導出的符號(symbol,對應於函數或變數),若導出,其它模組可以使用本模組的變數或函數。
(6)模組作者等資訊聲明(可選)
3 模組載入函數
Linux模組載入函數一般以__init標識聲明,典型的模組載入函數的形式如下:
static int __init init_func(void)
{
/* 初始化程式碼 */
}
module_init(init_func);
模組載入函數module_init(函數名)的方式指定,返回整型值,若初始化成功,返回0;初始化失敗,返回錯誤編碼。
Linux內核中,錯誤碼是一個接近0的負值,定義在<lnux/errno.h>中。
Linux內核中,可以使用request_module(const char *fmt, …)函數載入內核模組,使用方式:
request_module(module_name);
初始化數據可以定義為__initdata,對於只在初始化階段所需要的數據,內核在初始化完成之後會釋放它們佔用的記憶體。
static int hello_data __initdata = 1;
static int __init hello_init(void)
{
printk(KERN_INFO "Hello world enter %d\n", hello_data);
return 0;
}
module_init(hello_init); //內核載入函數
4 模組卸載函數
模組卸載函數一般以__exit標識聲明,常見的用法如下:
static void __exit clean_func(void)
{
/* 釋放程式碼 */
}
module_exit(clean_func); //內核卸載函數
模組卸載函數在模組卸載的時候執行,不返回任何值,且必須以module_exit(函數名)的方式使用。
5 模組參數
可使用“module_param(參數名, 參數類型, 參數讀/寫許可權)”為模組定義一個參數。
在載入模組時,用戶可以向模組傳遞參數,形式為:
insmod 模組名 參數名=參數值
如果不傳遞,參數使用模組定義的預設值。如果模組被內置無法insmod,在bootloader中可以通過bootargs設置”模組名.參數名=值”的形式給內核的模組傳遞參數。
參數的類型可以是:
byte short ushort int uint long ulong charp(字元指針) bool invbool(布爾的反)
在模組編譯時會將module_param中的聲明類型和變數定義的類型進行比較,判斷是否一致。
模組也可以有參數數組,形式為:
module_param_array(數組名, 數組類型, 數組長, 參數讀/寫許可權)
模組參數舉例:
#include <linux/init.h>
#include <linux/module.h>
static char *book_name = "dissection Linux Device Driver";
module_param(book_name, charp, S_IRUGO);
static int book_num = 400;
module_param(book_num, int, S_IRUGO);
static int __init hello_init(void)
{
printk(KERN_INFO "book name:%s\n", book_name);
printk(KERN_INFO "book num:%d\n", book_num);
return 0;
}
module_init(hello_init); //內核載入函數
static void __exit hello_exit(void)
{
printk(KERN_INFO "Hello world exit\n");
}
module_exit(hello_exit); //內核卸載函數
MODULE_AUTHOR("Test Hello");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("A simple Hello World Module");
MODULE_ALIAS("A simple module");
編譯並且載入不帶參數,可以看出輸出的是默認的參數值:
$ make
$ insmod para.ko
$ dmesg
[13186.787598] book name:dissection Linux Device Driver
[13186.787601] book num:400
載入時帶參數,列印的是輸入的參數:
$ insmod para.ko book_name="LDD3" book_num=500
$ dmesg
[13423.036831] book name:LDD3
[13423.036835] book num:500
在/sys/module/模組名/parameters目錄下也可以看到模組的參數:
$ ls /sys/module/para/parameters/
book_name book_num
$ cat /sys/module/para/parameters/book_num
500
$ cat /sys/module/para/parameters/book_name
LDD3
6 導出符號
Linux下內核符號表在/proc/kallsyms下,它記錄了符號以及符號所在的記憶體地址:
$ more /proc/kallsyms
ffffffff8109f320 T sys_kill
ffffffff8109f330 T SyS_tgkill
模組可以使用以下方式導出符號到符號表中,導出的符號可以被其它模組使用,使用前需要進行聲明:
EXPORT_SYMBOL(符號名);
EXPORT_SYMBOL_GPL(符號名); //只適用於包含GPL許可權的模組
測試用例:
#include <linux/init.h>
#include <linux/module.h>
int add_integer(int a, int b)
{
return a + b;
}
EXPORT_SYMBOL_GPL(add_integer);
int sub_integer(int a, int b)
{
return a - b;
}
EXPORT_SYMBOL_GPL(sub_integer);
MODULE_LICENSE("GPL v2");
載入並且查看相關資訊:
$ insmod export_symb.ko
ffffffffc0701000 t add_integer [export_symb]
ffffffffc0701010 t sub_integer [export_symb]
7 模組聲明與描述
Linux內核模組中,使用以下函數進行聲明:
MODULE_AUTHOR //作者
MODULE_DESCRIPTION //描述
MODULE_VERSION //版本
MODULE_DEVICE_TABLE //設備表,對於USB、PCI驅動,表明該驅動模組所支援的設備
MODULE_ALIAS //別名
8 模組的使用計數
Linux2.6之後的模組計數管理介面為:try_module_get(&module)和module_put(&module)。模組的使用計數不用模組自己管理。
/* 用於增加模組使用計數;返回為0,表示調用失敗,希望使用的模組沒有載入或正在卸載 */
int try_module_get(struct module *module);
/* 用於減少模組的使用計數 */
void module_put(struct module *module);
9 模組的編譯
簡單的Makefile:
KVERS = $(shell uname -r)
# Kernel modules
obj-m += hello.o
# Specify flags for the module compilation.
#EXTRA_CFLAGS=-g -O0 #是否使用調試資訊
build: kernel_modules
kernel_modules:
make -C /lib/modules/$(KVERS)/build M=$(CURDIR) modules
clean:
make -C /lib/modules/$(KVERS)/build M=$(CURDIR) clean
該makefile和源碼hello.c在同一個目錄,運行make命令得到模組hello.ko。
如果需要包括多個.c文件,需要更改:
obj-m += modulename.o
modulename-objs := file1.o file2.o


