LDD3筆記:第二章 構造和運行模塊

在正式進行驅動開發前,需要了解有關模塊編程和內核編程的一些基本概念。在本節中將會構造幾個完整的(但絕對沒啥功用的)模塊。設置測試系統一般的發行版本都會裝好內核代碼樹的,用過的Red Hat Enterprise Linux 5.x, Fedora 15/16, CentOS5.x系統中,其在/usr/src/kernels/$(shelluname -r)/目錄下.你最好使用的內核源碼樹是和運行時的內核一個版本,起碼初學時不會遇到微妙的問題。

2.6版本的 Linux 內核提供了用戶工具來管理模塊:
     lsmod: 查看mod
     modprobe:insmod 和 rmmod 的包裝
     depmod:用於創建模塊依賴項
     modinfo:用於爲模塊宏查找值,查看模塊信息

讓我們從老熟人:Hello World 模塊說起:

/* hello.c */
#include <linux/init.h>
#include <linux/module.h>
MODULE_AUTHOR(" anonyWoo <[email protected]>");
MODULE_DESCRIPTION("To say Hello to the word");
MODULE_LICENSE("Dual BSD/GPL");
 
static __init int hello_init(void)
{
          printk(KERN_ALERT "Hello, world!\n");
       return 0;
}
 
static __exit void hello_exit(void)
{
       printk(KERN_ALERT "Seeyou than, cruel world!\n");
}
 
module_init(hello_init);
module_exit(hello_exit);
在module.h中定義了大量的MODULE_前綴宏,當然也包含了用於聲明模塊初始化及模塊註銷時清除函數的宏:
     module_init();
     module_exit();
模塊初始化函數:
static __init int hello_init(void)

   其中的__init就是告訴內核該函數只在初始化時使用一次,隨後其佔據的內存就被內核釋放了。所以如果你的初始化函數還爲其他的功能模塊提供功能,那就別加上__init了。

   與其相似的,__exit就是當模塊移除時,內核會調用來移除模塊。如果模塊被編譯進了內核或者模塊不允許移移除,內核2.6用宏CONFIG_MODULE_UNLOAD來配置模塊不允許被移除。那麼__exit函數只是誒簡單的丟棄。如果一個模塊未定義一個清除函數,則模塊時不可移除的。

   關於__init還得說倆句,內核時怎麼知道當hello_init函數初始化完成後,在哪釋放她佔的內存呢?這就有必要了解一下ELF(Executable and Linking Format, 可執行可鏈接格式)。


圖 1

    看見上面的objdump輸出了吧:其中的.init.text和.exit.text代碼段。任何加了__init和__exit前綴的函數都會被鏈接到相應的段中去,到時內核會自動銷燬他們。
    另外,你也可以進入/sys/module/hello/ sections 目錄中,也會看到各個節。
且看init.h中的:

 /* These macros are used to mark some functions or
 * initialized data (doesn't apply to uninitialized data)
 * as `initialization' functions. The kernel can take this
 * as hint that the function is used only during the initialization
 * phase and free up usedmemory resources after
 *
 * Usage:
 * For functions:
 *
 * You should add __init immediately before the functionname, like:
 *
 * static void __init initme(int x, int y)
 * {
 *    extern int z; z= x * y;
 * }

#define     __init         __attribute__ ((__section__((".init.text")))
        __attribute__是GNU C的一個擴展,主要用來聲明一些特殊的屬性,而section就是其中之一。上面的意思是由__init修飾的代碼會被鏈接到到.init.text段中(section, 節)
在init.h中你會看到如下定義:

#define       __define_initcall(level,fn,id) \
                           static initcall_t__initcall_##fn##id__used\
                           __attribute__((__section__(".initcall" level ".init"))) = fn
#define        device_initcall(fn)             __define_initcall("6",fn,6)
#define       __initcall(fn)           device_initcall(fn)
#define       module_init(x)           __initcall(x)
static __init int hello_init(void)
        module_init(hello_init) /* 就是將hello_init指針放到到 .initcall6.init 子節中了
                                             * module_init會被do_initcall調用來初始化 */

模塊到底是如何裝載與卸載的呢?
我們在用戶空間中:
[wang2@wuhz hello_world]$ insmod hello.ko
insmod 會調用用戶空間的系統調用init_module將hello.ko二進制文件複製到內核空間中去,接着交給內核;然後進入內核到達內核函數sys_init_module(2.6.32中在linux/sysycalls.h中)。這是加載模塊的主要函數,它利用非常多的其他函數完成工作。
rmmod則會使delete_module執行system call調用,而delete_module最終會進入內核,並調用sys_delete_module將hello.ko模塊從內核刪除。

裝載細節:

init_module->sys_init_module->capable(CAP_SYS_MODULE)
                  ->load_module->layout_and_allocate->copy_and_check
                  ->setup_load_info
                  ->check_modinfo

現在,我們看看加載模塊時的內部函數。
    當調用內核函數 sys_init_module 時,會開始一個許可檢查,只有特權用戶纔可以執行這個操作(通過 capable 函數完成)。
    然後,調用 load_module 函數,這個函數首先會爲將模塊分配ELF塊內存,接着調用copy_from_user將hello.ko模塊加載到內核並執行必要的檢查。圖片1中的各個節(section或叫段)共同組成了ELF;
    接着會進行ELF類型type檢查,架構arch檢查,size檢查,etc.
    然後創建一些快捷變量(Convenience variables),方便隨後的訪問,同時檢查ELF的區段頭部,ELF的start address 0x00000000,這樣就容易定位各個節的地址了;這期間還檢查了是否開啓了CONFIG_MODULE_UNLOAD標誌(文章開始說過了);
    還會檢查version;進行版本檢查,需要三個節的配合:
         6.modinfo
         9.__versions
        11.gnu.linkonce.this_module
    接受模塊參數,並且更新模塊狀態正在加載:
mod->state= MODULE_STATE_COMING 。如果需要 per-CPU 數據(這在檢查區段頭時確定),那麼就分配 per-CPU 塊;
    前面load_module分配的ELF內存時臨時的,現在知道誰可以留誰得走了,就爲模塊分配最終的位置,並移動相應的節;
    然後執行另一個分配,大小是模塊必要區段所需的大小。迭代臨時 ELF 塊中的每個區段,並將需要執行的區段複製到新的塊中。接下來要進行一些額外的維護。同時還進行符號解析,可以解析位於內核中的符號(被編譯成內核映象),或臨時的符號(從其他模塊導出);
    然後爲每個剩餘的區段迭代新的模塊並執行重新定位(do relocations);這個步驟與架構有關,因此依賴於爲架構(./linux/arch/<arch>/kernel/module.c)定義的 helper 函數;

    最後,刷新指令緩存(因爲使用了臨時.text 區段),執行一些額外的維護(釋放臨時模塊內存,設置系統文件,是/sys/module/hello入口項,通過mod_sysfs_init(mod);來創建的,這回在設備模型章節中我們好好學習學習),返回mod。

卸載細節:
    當調用內核函數 sys_delete_module(將要刪除的模塊的名稱作爲參數傳入)之後,第一步同樣是確保調用方具有權限;
    接下來會檢查一個列表,查看是否存在依賴於這個模塊的其他模塊。這裏有一個名爲 modules_which_use_me 的列表,它包含每個依賴模塊的一個元素。如果這個列表爲空,就不存在任何模塊依賴項,因此這個模塊就是要刪除的模塊(否則會返回一個錯誤);
    接下來還要測試模塊是否加載。用戶可以在當前安裝的模塊上調用 rmmod,因此這個檢查確保模塊已經加載;
在幾個維護檢查之後,倒數第二個步驟是調用模塊的exit 函數(模塊內部自帶);
    最後,調用 free_module 函數。
    調用 free_module 函數之後,您將發現模塊將被安全刪除。該模塊不存在依賴項,因此可以開始模塊的內核清理過程。
    首先,從安裝期間添加的各種列表中(系統文件、模塊列表等)刪除模塊;
    其次,調用一個與架構相關的清理例程(可以在 ./linux/arch/<arch>/kernel/module.c 中找到);
    然後,迭代具有依賴性的模塊,並將這個模塊從這些列表中刪除;
    最後,從內核的角度而言,清理已經完成,爲模塊分配的各種內存已被釋放,包括參數內存、per-CPU 內存和模塊的 ELF 內存(core 和 init)。
版本依賴:
如果出現下面情況:
[root@wuhz hello_world]# insmod hello.ko
Error inserting './hello.ko': -1 Invalidmodule format
可以使用命令
#dmesg | tail -n 20
查看一下具體的原因,一般是構建hello.ko模塊的環境(內核源碼樹)和當前的kernel不一致造成的。
內核符號表:
你應該知道,linux系統中有非常多的優秀的簡潔的小工具,一個優秀的管理元可以通過整合這些個小工具來完成超複雜的工作!就如上面的命令:
#dmesg | tail -n 20
tail接受dmesg的輸出,並顯示最後20行的信息!同樣的各個模塊之間也可以共享各自的功能,輸出接口就好了,準確點是符號(symbols)。insmod會使用公共內核符號表來解析模塊中未定義的符號。公共內核符號表中包含了所有的全局內核項(func和variables)的地址,這使得模塊化成爲可能。當然,當你的模塊裝載到內核的時候,他所導出的符號也會成爲內核符號表的一部分供其他模塊使用!
linux爲我們提供一些宏方便我們導出符號以供她用:
    EXPORT_SYMBOL(name);
    EXPORT_SYMBOL_GPL(name);/* 顯然由他導出的符號只能供GPL授權的模塊使用 */
    他們只能在模塊的全局部分導出,不能在函數中導出,因爲上面兩個宏會被擴展成一個特殊變量的申明,而改變量必須是全局的。

    用objdump看一下會多出倆個節__ksymtab_gpl和__kcrctab_gpl(checksum of somesymbols).在裝載時,內核從這個節(section或叫段)中來尋找模塊導出的變量。詳見<linux/module.h>文件。
預備知識:

所有的模塊代碼中都包含下面兩行代碼:
    #include <linux/module.h> /* 其包含了模塊需要的大量符號和函數定義*/
    #include <linux/init.h> /* 制定初始化和清除函數 */
也可能含有:
    #include <linux/moduleparam.h> /* 可參數化裝載模塊 */

指定模塊使用的許可證:

MODULE_LICENSE("GPL");
/*     "GPL"                          [GNU Public License v2 or later]
 *     "GPL v2"                       [GNU Public License v2]
 *     "GPL and additional rights"    [GNU Public License v2 rights and more]
 *     "Dual BSD/GPL"                 [GNU Public License v2
 *                                       or BSD license choice]
 *     "Dual MIT/GPL"                 [GNU Public License v2
 *                                       or MITlicense choice]
 *     "Dual MPL/GPL"                 [GNU Public License v2
 *                                       orMozilla license choice]
 *
 * Thefollowing other idents are available
 *
 *     "Proprietary"                  [Non free products]
 */
也有些其他以MODULE_爲前綴的宏()定義在<linux/module.h>:

            MODULE_AUTHOR("anonyWoo");
       MODULE_DESCRIPTION("helloa.kois to say to world: Hello!");
       MODULE_VERSION(_version);

模塊參數:

/*helloa.c  英語註釋的比較差,見諒哈 */
#include <linux/init.h>
#include <linux/module.h>
#include <linux/stat.h>         /* this header contains the accesspermission,e.g. S_IRUGO as showed bellow */
#include <linux/kernel.h>
#define MAXSIZE  5
MODULE_LICENSE("Dual BSD/GPL"); /*to state the lincense */
 
static int array[MAXSIZE] = {1, 2, 3, 4};
static char *whom = "anonyWoo";
static int howmany = 1;
 
/** #insmod helloa.ko howmany=25
 * */
module_param(howmany, int, S_IRUGO);
 
/* #insmod helloa.kowhom="Miss.wong" */
module_param(whom, charp, S_IRUGO);
 
/** passing the parameter from the commandline has this form:
   #insmod ./helloa.ko array=4,5,6 */
module_param_array(array, int, NULL,S_IRUGO);
 
/*static __init int hello_init(void)*/
static int hello_init(void)
{
       int i, j;
       for(i = 0; i < howmany; i++)
                printk(KERN_ALERT "Hello,world %d time(s), hello %s\n", i + 1, whom);
      for(j = 0; j <MAXSIZE; j++ )
                printk(KERN_ALERT "This is%d time(s) output\n", *(array + j));
       return 0;
}
static __exit void hello_exit(void)
{
       printk(KERN_ALERT "Goodbye, cruel world!\n");
}
EXPORT_SYMBOL_GPL(hello_init);
module_init(hello_init);
module_exit(hello_exit);
Makefile

#if we had define the KERNELRELEASE, for itcalls from the kernel tree,
#that we can use the buildin statement asbellow.
ifneq ($(KERNELRELEASE),)
       obj-m := helloa.o
#else, call from the command line directly,
#in this case, we have to call the kernel tree 
else
#這裏按照ldd3的似乎不行,遂改成如下形式了
       KERNELDIR ?= /usr/src/kernels/$(shell uname -r)/
default:
       $(MAKE) -C $(KERNELDIR) M=$(PWD) modules
clean:
       $(MAKE) -C $(KERNELDIR) M=$(PWD) clean
endif

#make

#insmod helloa.ko howmany=5 whom=”Miss.wong”array=7,8,9

#dmesg | tail -n 10

...

Hello, world 1 time(s), hello Miss.wong

Hello, world 2 time(s), hello Miss.wong

Hello, world 3 time(s), hello Miss.wong

Hello, world 4 time(s), hello Miss.wong

Hello, world 5 time(s), hello Miss.wong

This is 7 time(s) output

This is 8 time(s) output

This is 9 time(s) output

This is 4 time(s) output

#lsmod | grep helloa

helloa                  1314  0

#rmmod helloa

Goodbye, cruel world!


關於module_param(variable,type, perm); 

/* type 可以是bool, charp, int, invbool(inverse bool), long, short, ushort, uint,ulong, or intarray */

     module_param_array(array_name, array_item_type, array_nump, perm );

array_nump會是用戶提供的參數個數,且模塊會拒絕接受超過數組大小的值,sizeof(array[0]) , 零數組長度,動態獲得其長度,指定NULL就可以了。                                                         

       perm<linux/stat.h>中定義,用於sysfs中入口項的訪問掩碼:eg. umask=0700,file: rwx------則相減得:---------

結束:

    Ch02 is over...

                                                                                                                                                                              To be continued...

                                                             







發佈了29 篇原創文章 · 獲贊 55 · 訪問量 19萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章