02 構造和運行模塊

#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 標記的函數將被拋棄;
相關文章
相關標籤/搜索