注:本文是《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