模塊

   Linux是單塊內核(monolithic)的操作系統,整個系統內核都運行與一個單獨的包含域中。Linux內核是模塊化組成的,它允許內核在運行時動態地向其中插入或從中刪除代碼。這些代碼包括相關的子例程,數據,函數入口和函數出口,被一併組合在一個單獨的二進制鏡像中,即所謂的可裝載內核模塊中,或簡稱爲模塊。

   支持模塊的好處就是基本內核鏡像可以儘可能的小,因爲可選的功能和驅動程序可以利用模塊形式再提供。模塊允許我們方便的刪除和重新載入內核代碼,也方便了調試工作。而且當熱插拔新設備時,可通過命令載入新的驅動程序。

  • Hello world!
  1. /*
  2.  *hello.c--hello world 簡單內核模塊程序
  3.  */
  4. #include<linux/init.h>
  5. #include<linux/module.h>
  6. #include<linux/kernel.h>
  7. static int hello_init()
  8. {
  9.   printk(KERN_ALERT"I bear a charmed life./n");
  10.   return 0;
  11. }
  12. static void hello_exit(void)
  13. {
  14.   printk(KERN_ALERT"out,out,brief candle!/n");
  15. }
  16. module_init(hello_init);
  17. module_exit(hello_exit);
  18. MODULE_LICENSE("GPL");
  19. MODULE_AUTHOR("Shakespeare");

   與大多數內核子系統不同,模塊開發更接近編寫新的應用系統,因爲至少要在模塊文件中具有入口點和出口點。

   模塊的初始化函數(入口點)必須符合下面的形式:

  1. int my_init(void);       //如果初始化順利完成,返回0,否則返回非零

   因爲init函數通常不會被外部函數直接調用,所以不必導出該函數,它可以被標記爲static類型。

   模塊的初始化函數通過module_init()例程註冊到系統中,在模塊裝載時被調用。調用module_init()實際上是一個宏調用,它惟一的參數是模塊的初始化函數。

   模塊的出口函數偶module_exit()例程註冊到系統。在模塊從內存卸載時,內核便會調用模塊的出口函數。該函數可能

在返回前負責清理資源,以保證硬件處於一致狀態,或者其他別的操作。在退出函數返回後,模塊就被卸載了。退出函數的形式如下:

 

  1. void my_exit(void);

  如果上述文件被靜態地編譯到內核映像中,那麼退出函數將不被包含,而且用於都不會被調用(如果不是編譯成模塊的話,代碼就永遠不需要從內核中卸載)。

  MODULE_LICENSE()宏用於指定模塊的版權。如果載入非GPL模塊到系統內存,則會在內核中設置被污染標誌,這個標誌起到記錄信息的作用。不過如果開發者提交的bug報告中包含有被污染的標誌,那麼報告的信用會被減低。非GPL模塊不能調用GPL-only符號。

    MODULE_AUTHOR()宏指定了代碼作者,它完全是用於信息記錄。

  • 構建模塊

   在2.6內核中,採用了新的“kbuild”構建系統,構建的第一步是決定在哪裏管理模塊源碼。可以把模塊源碼加入到內核源代碼樹中,或者作爲一個補丁,或者最終把代碼把代碼最終合併到正式的內核代碼樹中;另一種可行的方式是在內核源代碼樹之外維護和構建模塊源碼。

 

1. 放在內核源碼樹中

    最理想的情況是你的模塊正式稱爲Linux內核的一部分,這樣就會被存放在內核源代碼樹中。

   設備驅動程序存放在內核源碼樹根目錄deriver/的子目錄下,在其內部設備驅動文件被進一步按照類別、類型和特殊驅動程序得呢個更有序的組織起來。字符設備存在與drivers/char/目錄下,塊設備存放在drivers/block/目錄下,USB設備則存放在drivers/usb/目錄下。

   假設我們的驅動程序名爲Foo XL,如果在目錄drivers/char/下建立了自己的代碼子目錄foo,要做的是向drivers/char/目錄中的Makefile文件添加代碼,不同的代碼產生不同的編譯效果:

  1. obj-m += foo/       //這行編譯指令告訴模塊構建系統在編譯模塊時需要進入foo/目錄中
  2. obj-$(CONFIG_FOO) += foo/     //如果驅動程序的編譯取決於一個特殊的配置選項,比如CONFIG_FISHING_POLE,這時要用這條編譯指令
  3. /*驅動程序智能化,可以自動檢測其他文件,如foo-xxx.c,這樣foo-main.c和foo-xxx.c就一起被編譯和連接到foo.ko模塊內*/
  4. obj-$(CONFIG_FOO) += foo.o
  5. foo-objs :=foo-main.o foo-xxx.o
  6. //注意,如果需要在構建文件時需要額外的編譯標記,添加如下指令
  7. EXTRA_CFLAGS += -DTITANIUM_POLE

 

     如果把源文件置於drivers/char/目錄下,不建立新目錄,就將原來處於drivers/char/foo/下你寫的makefile中的代碼都加入到drivers/char/下的Makefile中。

    編譯,運行內核構建過程。如果模塊編譯取決於配置選項,比如有CONFIG_FOO約束,那麼在編譯前首先要確保選項被允許。

 

2. 放在內核代碼外

   如要要脫離內核源代碼以此來維護和構建自己的模塊,要做的就是在自己的源代碼目錄中建立一個Makefile文件,它只需要一行指令:

  1. obj-m := foo.o

就可以把foo.c編譯成foo.ko。如果有多個源文件,那麼就用下列兩行 :

 

  1. obj-m := foo.o
  2. foo-objs := foo-main.o foo-xxx.o

這樣foo-main.c和foo-xxx.c就一起被編譯和連接到foo.ko模塊內。

   模塊在內核內或內核外構建的最大區別在於構建過程,當模塊在內核源碼外圍時,要告訴make如何找到內核源碼文件和基礎Makefile文件:

 

  1. /*  /kernel source location是已配置的內核源代碼樹。
  2.  *不要把要處理的內核源代碼樹放在/usr/src/linux下,而要移到你的home目錄下的某個方便訪問的地方
  3. */
  4. make -c /kernel source location  SUBDIRS-$PWD modules   
  •    安裝模塊

   編譯後的模塊將被裝入到目錄/lib/modules/version/kernel/下。比如,使用2.6.10版本內核,模塊的源代碼直接放在drivers/char/下,編譯後的驅動程序的存放路徑是:

/lib/modules/2.6.10/kernel/drivers/char/foo.ko

  1. //下面的構建命令用來安裝編譯的模塊到合適的目錄下,要以root權限運行
  2. make modules_install
  • 產生模塊依賴性

  Linux模塊之間存在依賴性。A模塊依賴於B模塊,那麼當載入A模塊時,B模塊也會被自動載入。這裏需要的依賴信息必須事先生成。若想產生內核依賴關係的信息,root用戶可以運行命令depmod

  爲了執行更快的更新操作,可以只爲新模塊生成依賴信息,而不是生成所有的依賴關係,這時root用戶可以運行命令:

depmod -A

模塊依賴關係信息存放在/lib/modules/version/modules.dep文件中。

  • 載入模塊

  載入模塊最簡單的方法是通過insmod命令。這是個功能很有限的命令,它的作用是請求內核載入指定的模塊。insmod程序不執行任何依賴性分析或進一步的錯誤檢查。以root身份運行命令:

insmod  foo

  卸載一個模塊:

rmmod foo

   系統提供了更先進的工具modprobe,它提供了模塊依賴性分析,錯誤智能檢查,錯誤報告以及其他功能和選項:

modprobe modulename [module parameter]

  參數將在模塊加載時傳入內核。

   modprobe命令不但會加載指定的模塊,還會自動加載任何它所依賴的有關模塊。它還可以用來從內核中卸載模塊,以root身份運行:

modprobe -r modulename

   與rmmod命令不同,modprobe也會卸載給定模塊所依賴的相關模塊。其前提是這些相關模塊沒有被使用。

  • 管理配置選項

  前面設置了CONFIG_FOO配置選項,foo模塊將被自動編譯,若要添加一個新的配置選項還可以怎麼做呢?

  向Kconfig文件中添加一項,用以對應內核源碼樹。

  對驅動程序而言,Kconfig一般與源代碼處於同一目錄,比如,foo程序在目錄drivers/char/下,那麼就存在drivers/char/Kconfig。如果新建了子目錄存放foo,而且系統Kconfit文件存在於該目錄,那麼必須在一個已經存在的Kconfig文件(即drivers/char/Kconfig)中將它引入:

source "drivers/char/foo/Kconfig"

我們以Foo XL模塊添加選項爲例,在Kconfig文件中加入一個配置選項如下:

  1. config FOO
  2.        tristate "Foo XL support"
  3.        default n
  4. help
  5.        If you say Y here, support for the Foo XL with computer interface will
  6.        be complied into the kernel and accessible via device node. You can also 
  7.        say N here and the driver will be built as a module named foo.ko.
  8.        If unsure, say N. 

配置選項第1行定義了該選項所代表的配置目標。注意CONFIG_前綴不需要寫上。

第2行聲明選項類型爲tristate,也就是說可以被編譯進內核(Y),也可作爲模塊編譯(M),或者乾脆不編譯它(N)。如果編譯選項代表的是一個系統功能,而不是一個模塊,那麼編譯選項將用bool指令代替tristate,這說明它不允許被編譯成模塊。處於指令之後的引號內的文字爲該選項指定的名稱。

第3行指定了該選項的默認選擇,這裏默認爲不編譯它。

第4行help指令的目的是爲該選項提供幫助文檔。

   除了上述選項,還有其他選項。

depends指令規定了在該選項被設置其,首先要設置的選項。假如依賴性不滿足,那麼該選項被禁止。比如:

  1. depends on FOO_DEPEND

加入到配置選項中,那麼就意味着CONFIG_FOO_DEPEND被選擇前,Foo XL模塊是不能被使用的。

select指令與depends指令類似,不同之處在於,select指定了誰,它就會強行將被指定的選項打開。不能濫用該指令,因爲它會自動的激活其他配置選項。比如:

  1. select BIT

 

意味着當CONFIG_FOO被激活時,配置選項CONFIG_BIT必然一起被激活。

   如果select和depends同時指定多個選項,需要通過&&指令來進行多選。使用depends還可以利用歎號!前綴來指明禁止某個選項。比如:

  1. depends on DUMB_DREVERS && !NO_FOO_ALLOWED
意味着指定驅動安裝程序要求打開DUMB_DREVERS 選項,同時禁止NO_FOO_ALLOWED選項。

bool選項和tristate往往會結合if指令一起使用,表示某個選項取決於另一個配置選項。如果條件不滿足,配置選項就會被禁止,甚至不會顯示在配置工具中。比如:

  1. bool “Deep sea mode”if OCEAN

意味着"Deep sea mode"只有在CONFIG_OCEAN選項打開時纔可見且有效。

If指令也可以與defualt指令結合使用,強制只有在條件滿足時default選項纔有效。

配置系統導出了一些元選項(meta-option)以簡化生成的配置文件。

CONFIG_EMBEDDED是用於關閉那些用戶想要禁止的關鍵功能。

CONFIG_BROKEN_ON_SMP用來表示驅動程序並非多處理器安全的。

CONFIG_EXPERIMENTAL用於說明某些功能尚在試驗或處於beta版本階段。

  • 模塊參數

  Linux語序驅動程序聲明參數,從而用戶可以在系統啓動或者模塊裝載時再指定參數值,這些參數對驅動程序來說屬於全局變量。模塊參數同時也將出現在sysfs文件系統中,這樣一來,無論是生成模塊參數,還是管理模塊參數的方式都變得靈活多樣了。

   定義一個模塊參數可以通過宏module_param()完成:

  1. 在include/linux/moduleparam.h中
/*第一個參數是用戶可見參數名,也是模塊中存放模塊參數的變量名。  *第二個參數存放了參數的類型  *第三個參數制定了模塊在sysfs文件系統下對應文件的權限,該值可以是八進制格式或是S_Ifoo的定義形式,比如S_IRUGO|S_IWUSR,如果是0則表示禁止所有的sysfs */ #define module_param(name, type, perm)              /     module_param_named(name, name, type, perm) /* Helper functions: type is byte, short, ushort, int, uint, long,    ulong, charp, bool or invbool, or XXX if you define param_get_XXX,    param_set_XXX and param_check_XXX. */ name是外部可見參數名,value是參數對應的內部全局變量名稱 #define module_param_named(name, value, type, perm)            /     param_check_##type(name, &(value));                /     module_param_call(name, param_set_##type, param_get_##type, &value, perm); /     __MODULE_PARM_TYPE(name, #type)

   宏module_param()並沒有定義變量,必須在使用該宏前進行變量定義。例如:

  1. static int allow_live_bait = 1;
  2. module_param(allow_live_bait,bool,0644);

  通常需要用一個charp類型來定義模塊參數(一個字符串),內核將用戶提供的這個字符串拷貝到內存,而且將你的變量指向該字符串。比如:

 

  1. static char *name;
  2. module_param(name,charp,0);

  有可能模塊的外部參數名稱與對應的內部參數名稱,這時使用宏module_param_named()。比如:

  1. static unsigned int max_test = DEFAULT_MAX_LINE_TEST;
  2. module_para_named(maximum_line_test,max_test,int,0);

其他相關的宏:

  1. /* Actually copy string: maxlen param is usually sizeof(string). */
  2. #define module_param_string(name, string, len, perm)            / 
  3.     static struct kparam_string __param_string_##name       /
  4.         = { len, string };                  /
  5.     module_param_call(name, param_set_copystring, param_get_string, /
  6.            &__param_string_##name, perm);           /
  7.     __MODULE_PARM_TYPE(name, "string")
  8. /* Comma-separated array: *nump is set to number they actually specified. */
  9. #define module_param_array_named(name, array, type, nump, perm)     / 
  10.     static struct kparam_array __param_arr_##name           /
  11.     = { ARRAY_SIZE(array), nump, param_set_##type, param_get_##type,/
  12.         sizeof(array[0]), array };                  /
  13.     module_param_call(name, param_array_set, param_array_get,   /
  14.               &__param_arr_##name, perm);           /
  15.     __MODULE_PARM_TYPE(name, "array of " #type) 
  16. #define module_param_array(name, type, nump, perm)      / 
  17.     module_param_array_named(name, name, type, nump, perm)
  • 導出符號表

  模塊被載入後,就會動態連接到內核。注意,它與用戶空間中的動態連接庫類型,只有當被顯示導出後的外部函數,纔可以被動態庫調用。在內核中,導出內核函數需要使用特殊的指令:EXPORT_SYMBOL()和EXPORT_SYMBOL_GPL()。

  導出的內核函數可以被模塊調用,而爲導出的函數模塊則無法被調用。模塊代碼的鏈接和調用規則相比核心內核鏡像中的代碼而言,更加嚴格。核心代碼在內核中可以調用任意非靜態接口,因爲所有的核心源碼文件都被鏈接成同一個鏡像。當然,被導出的符號表所包含的函數必須是非靜態的。

  導出的內核符號表被看做是導出的內核接口,甚至稱爲API。

  導出符號相對簡單,在聲明函數後,緊接着EXPORT_SYMBOL()指令就可以了。該函數如果定義在一個可訪問的頭文件中,那麼任何模塊現在都可以訪問它。

  如果系統自己的接口僅僅對GPL兼容的模塊科技,那麼內核連接器使用MODULE_LICENCE()宏滿足這個要求,如果系統函數僅僅對標記爲GPL協議的模塊可見,那麼就要用EXPORT_SYMBOL_GPL()指令。

  如果你的代碼被配置爲模塊,那麼就必須確保當它被編譯爲模塊時鎖用的全部接口都已經被導出,否則會產生鏈接錯誤,模塊不能成功編譯。

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