#1 設置測試系統linux
& 想要在運行的內核當中擴展模塊,就必須先準備好一個內核源代碼樹(能夠是「主線」內核,也能夠是發行版內核),構造一個新的內核,而後安裝到本身的系統中,做爲測試系統;
#2 Hello World 模塊
& 構造好內核樹以後,就能夠開始編寫模塊了。咱們先從簡單的 「Hello World 模塊」入手:
1 #include <linux/init.h>
2 #include <linux/module.h>
3 MODULE_LICENSE("Dual BSD/GPL");
4
5 static int hello_init(void)
6 {
7 printk(KERN_ALERT "Hello, world\n");
8 return 0;
9 }
10
11 static int hello_exit(void)
12 {
13 printk(KERN_ALERT "Goodbye, cruel world\n");
14 }
15
16 module_init(hello_init);
17 module_exit(hello_exit);
上述模塊定義了 hello_init 和 hello_exit 兩個函數,hello_init 在模塊被裝載到內核的時候調用,而 hello_exit 在模塊被移除內核時調用;
module_init 和 module_exit 則是使用了內核的特殊宏來表示 hello_init 和 hello_exit 所要實現的功能;
此外,特殊宏 MODULE_LICENSE 則聲明瞭該模塊的許可權限(若是沒有這個聲明,內核在裝載該模塊時會產生內核被污染警告),可用的 LICENSE 包括:GPL 、 GPL v2 、 GPL and additional rights 、 Dual BSD/GPL 、Dual MPL/GPL 、Proprietary;
函數 printk 是在內核中定義的,這是由於
內核在運行時不能依賴於 C 庫,模塊能夠調用 printk 是由於在 insmod 函數裝入模塊以後,模塊就鏈接到了內核,於是能夠訪問內核的公用符號;
KERN_ALERT 定義了消息的優先級,
消息的優先級降序排列爲:[0] KERN_EMERG 、[1] KERN_ALERT 、[2] KERN_CRIT 、[3] KERN_ERR 、[4] KERN_WARNING 、[5] KERN_NOTICE 、[6] KERN_INFO 和 [7] KERN_DEBUG;新的優先級被指定爲1~8之間的整數值,
打印規則是:顯示設定的優先級到最高優先級之間的信息,即若是值爲1,則只有優先級爲0的消息才能到達控制檯,若是值爲8,則0~7優先級的全部信息都會顯示出來;
& 模塊編譯和測試:
make:根據 makefile 規定的編譯規則將源程序編譯成模塊 .ko 文件
[關於 makefile 會在以後的筆記當中單獨編輯];
su:切換到超級用戶模式,只有超級用戶才能加載和卸載模塊
[熟悉Linux 的同窗應該都知道用戶權限的問題,這裏就不說了];
insmod ./hello.ko :加載模塊;
rmmod ./hello.ko :移除模塊;
#3 用戶空間和內核空間
& 模塊運行在內核空間裏,應用程序運行在用戶空間中;
在內核空間中,處理器能夠進行全部的操做;而在用戶空間中,處理器控制着對硬件的直接訪問以及對內存的非受權訪問;
當且僅當進程執行系統調用或者被硬件中斷掛起的時候,用戶空間能夠切換到內核空間;
#4 當前進程
&
內核代碼經過全局變量 current 來得到當前進程,current 是一個指向 struct task_struct 的指針;
在 open 、read 等系統調用的執行過程當中,當前進程指的是調用這些系統調用的進程;
須要提到的一點是,
在2.6 內核中,引入一種新的機制,即current 再也不是一個全局變量,而是將 task_struct 結構的指針隱藏在內核棧中,這樣,設備驅動只要包含<linux/sched.h>頭文件便可引用當前進程;並且
新的機制支持SMP(對稱多處理器);
#5 裝載和卸載模塊
&
insmod 和 ld 的區別:
insmod 將模塊的代碼和數據裝入內核,而後使用內核符號表解析模塊中任何未解析的符號;
連接器 ld 的工做是解析未定義的符號引用,將目標文件中的佔位符替換爲符號的地址,同時完成程序中各目標文件的地址空間的組織;
與
ld 不一樣,使用 insmod,內核不會修改模塊的磁盤文件,而是僅僅修改內存中的模塊副本;
&
insmod 的裝載過程:
&
insmod 和 modprobe 的區別:
modprobe 也是用來裝載模塊的,與 insmod 不一樣的是,modprobe 會考慮要裝載的模塊是否引用了當前內核當中不存在的符號;若是有這類引用,modprobe 會在當前模塊搜索路徑中查找定義了這些符號的其餘模塊,並將這些模塊一塊兒裝入內核,也即:
modprobe 在裝載的時候起到檢查並修正符號依賴性的做用;而insmod 明顯不具有這種功能,若在這種狀況下使用 insmod,則會返回「unresolve symbol」(未解析的符號)錯誤;
& rmmod 用於從內核中移除模塊;
當內核認爲模塊忙時和內核配置被禁止移除模塊時,rmmod 沒法移除模塊,這時可使用強制移除,但更爲保險的方式是從新引導系統;
& lsmod 用以顯示當前裝載到內核的全部模塊,實際上,
lsmod 經過讀取 /proc/modules 虛擬文件來得到當前已裝載的模塊的信息;
#6 內核符號表
& insmod 使用公用內核符號表來解析模塊中未定義的符號,這是由於
公用內核符號表包含了全部的全局內核項(函數和變量)的地址;當模塊被裝入內核後,它導出的任何符號都會變成內核符號表的一部分;
這些導出的符號能夠被新模塊使用,這就是模塊層疊技術;
& 若是一個模塊須要向其餘模塊導出符號,應使用下面兩個宏:
EXPORT_SYMBOL(name);
EXPORT_SYMBOL_GPL(name);
後者使得導出的版本只能被GPL 許可證下的模塊使用;
& 須要注意的是,
符號必須在模塊文件的全局部分導出,而不能在函數中導出,這是由於符號的導出由宏 EXPORT_SYMBOL 和 EXPORT_SYMBOL_GPL 控制,而這兩個宏將被擴展爲一個特殊變量的聲明,而該變量必須是全局的;
#7 版本依賴
& 在缺乏 modversions 的狀況下,模塊代碼必須針對要連接的每一個版本的內核從新編譯,也就是說,
不一樣的內核版本可能具備不一樣的數據結構和函數原型,模塊的構造嚴重依賴於內核的版本;
咱們在構造模塊時,
能夠將模塊與當前內核樹中的 vermagic.o 目標文件進行連接,經過 vermagic.o
[包含了大量有關內核的信息]
來檢查模塊和正在運行內核的兼容性,若是不匹配,就不會裝載該模塊;
#8 模塊頭文件
& 大部份內核代碼中都包含一堆頭文件,以便得到函數、數據類型和變量的定義;其中,有兩個頭文件是模塊當中必需要包含的,它們是:module.h 和 init.h;
module.h 包含可裝載模塊須要的大量符號和函數的定義;
init.h 指定了模塊初始化和清除函數;
此外,也可能包含 moduleparam.h 頭文件,
moduleparam.h 用來在裝載模塊時向模塊傳遞參數;
#9 初始化函數
&
模塊初始化函數註冊模塊提供的任何功能;
初始化函數實際定義一般以下:
1 static int __init initialization_function(void)
2 {
3 /* Initialization code here */
4 }
5
6 module_init(initialization_function);
初始化函數應聲明爲 st
atic,
它們不會在特定文件以外可見;
__init 標誌告訴內核給定的函數只是在初始化使用;模塊加載後會丟掉這個初始化函數,使它的內存可作其餘用途;
相似的標籤 :__initdate / __devinit / __devinitdata
__initdata 給只在初始化時用的數據;__init 和 __initdata 是可選的;
__devinit 和 __devinitdata 在內核源碼裏,只有在內核未被配置爲支持 hotplug 設備時它們才被翻譯爲 __init 和 _initdata;
module_init 的使用是強制的;這個宏會在模塊的目標代碼中增長一個特殊的段,用於說明初始化函數所在的位置;沒有這個定義,初始化函數永遠不會被調用;
模塊能夠註冊許多不一樣類型的設施,對每種設施對應有具體的內核函數用來完成註冊;大部分的模塊註冊函數使用 register_ 做爲前綴,可在內核源代碼中使用 grep register_ 找到它;
#10 清除函數
& 清除函數用於在模塊被移除前註銷接口並向系統中返回全部資源;
清除函數的定義以下:
static void __exit cleanup_function(void)
{
/* Cleanup code here */
}
module_exit(cleanup_function);
|
__exit 標記該代碼僅用於模塊卸載,
當且僅當模塊被卸載或系統關閉時被調用,其餘用法都是錯誤的;
若是模塊被直接嵌入到內核中或內核標記不容許卸載模塊時,__exit 標記的函數將被拋棄;