從 2.4 到 2.6:Linux 核心可裝載模組機制的改變對裝置驅動的影響

來源:互聯網
上載者:User

本文轉自http://www.ibm.com/developerworks/cn/linux/l-module26/

層級: 初級

婷 周 (moting9@hotmail.com), 軟體工程師

2006 年 2 月 09 日

從 2.4 到 2.6,Linux 核心在可裝載模組機制、裝置模型、一些核心 API 等方面發生較大改變,裝置驅動開發人員面臨著將驅動從 2.4 移植到 2.6 核心,或是使驅動同時支援2.4 與 2.6 核心的任務。站在裝置驅動開發人員的角度,驅動由一個或幾個外部可載入核心模組組成,本文針對 2.6 核心裡模組機制的改變對編寫裝置驅動程式的影響,從核心模組的編譯、裝載時的版本檢查、初始化與退出、模組使用計數、輸出核心符號、命令列輸入參數、許可證聲明等方面比較了 2.4 與 2.6 核心的區別;並總結了使裝置驅動同時支援 2.4 與 2.6 核心的一系列模板。

1. 擷取核心版本

當裝置驅動需要同時支援不同版本核心時,在編譯階段,核心模組需要知道當前使用的核心源碼的版本,從而使用相應的核心 API。2.4 與 2.6 核心下,源碼標頭檔 linux/version.h 定義有:

LINUX_VERSION_CODE ― 核心版本的二進位表示,主、從、修訂版本號碼各對應一個位元組;

KERNEL_VERSION(major, minor, release) - 由主、從、修訂版本號碼構造二進位版本號碼。

在同時支援2.4與2.6 核心的裝置驅動程式中,經常可以看到以下程式碼片段:

清單1:判斷核心版本的程式碼片段。

#include <linux/version.h>#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0)#define LINUX26#endif#ifdef LINUX26/*code in 2.6 kernel*/#else/*code in 2.4 kernel */#endif 


回頁首

2.核心模組機制的改變

2.1模組編譯

從2.4到2.6,外部可裝載核心模組的編譯、串連過程以及Makefile的書寫都發生了改變。

2.4核心中,模組的編譯只需核心源碼標頭檔;需要在包含linux/modules.h之前定義MODULE;編譯、串連後產生的核心模組尾碼為.o。

2.6核心中,模組的編譯需要配置過的核心源碼;編譯、串連後產生的核心模組尾碼為.ko;編譯過程首先會到核心源碼目錄下,讀取頂層的Makefile檔案,然後再返回模組源碼所在目錄。

清單2:2.4 核心模組的Makefile模板

#Makefile2.4KVER=$(shell uname -r)KDIR=/lib/modules/$(KVER)/buildOBJS=mymodule.oCFLAGS=-D__KERNEL__ -I$(KDIR)/include -DMODULE -D__KERNEL_SYSCALLS__ -DEXPORT_SYMTAB  -O2 -fomit-frame-pointer  -Wall  -DMODVERSIONS -include $(KDIR)/include/linux/modversions.hall: $(OBJS)mymodule.o: file1.o file2.old -r -o $@ $^clean:rm -f *.o

 

在2.4 核心下,核心模組的Makefile與普通使用者程式的Makefile在結構和文法上都相同,但是必須在CFLAGS中定義-D__KERNEL__-DMODULE,指定核心標頭檔目錄-I$(KDIR)/include。 有一點需注意,之所以在CFLAGS中定義變數,而不是在模組源碼檔案中定義,一方面這些預定義變數可以被模組中所有源碼檔案可見,另一方面等價於將這些預定義變數定義在源碼檔案的起始位置。在模組編譯中,對於這些全域的預定義變數,一般在CFLAGS中定義。

清單3:2.6 核心模組的Makefile模板

# Makefile2.6ifneq ($(KERNELRELEASE),)#kbuild syntax. dependency relationshsip of files and target modules are listed here.mymodule-objs := file1.o file2.oobj-m := mymodule.o elsePWD  := $(shell pwd)KVER ?= $(shell uname -r)KDIR := /lib/modules/$(KVER)/buildall:$(MAKE) -C $(KDIR) M=$(PWD) clean:rm -rf .*.cmd *.o *.mod.c *.ko .tmp_versionsendif

 

KERNELRELEASE是在核心源碼的頂層Makefile中定義的一個變數,在第一次讀取執行此Makefile時,KERNELRELEASE沒有被定義, 所以make將讀取執行else之後的內容。如果make的目標是clean,直接執行clean操作,然後結束。當make的目標為all時,-C $(KDIR) 指明跳轉到核心源碼目錄下讀取那裡的Makefile;M=$(PWD) 表明然後返回到目前的目錄繼續讀入、執行當前的Makefile。當從核心源碼目錄返回時,KERNELRELEASE已被被定義,kbuild也被啟動去解析kbuild文法的語句,make將繼續讀取else之前的內容。else之前的內容為kbuild文法的語句, 指明模組源碼中各檔案的依賴關係,以及要產生的目標模組名。mymodule-objs := file1.o file2.o表示mymoudule.o 由file1.o與file2.o 串連產生。obj-m := mymodule.o表示編譯串連後將產生mymodule.o模組。

補充一點,"$(MAKE) -C $(KDIR) M=$(PWD)"與"$(MAKE) -C $(KDIR) SUBDIRS =$(PWD)"的作用是等效的,後者是較老的使用方法。推薦使用M而不是SUBDIRS,前者更明確。

通過以上比較可以看到,從Makefile編寫來看,在2.6核心下,核心模組編譯不必定義複雜的CFLAGS,而且模組中各檔案依賴關係的表示簡潔清晰。

清單4: 可同時在2.4 與 2.6 核心下工作的Makefile

#Makefile for 2.4 & 2.6VERS26=$(findstring 2.6,$(shell uname -r))MAKEDIR?=$(shell pwd)ifeq ($(VERS26),2.6)include $(MAKEDIR)/Makefile2.6elseinclude $(MAKEDIR)/Makefile2.4endif  

 

2.2模組裝載時的版本檢查

Linux核心一直在更新、完善,在a版本核心源碼下編譯的模組在b版本核心下通常不能運行,所以必須有一種機制,限制在a版本核心下編譯產生的模組在b版本核心下被載入。

2.4與2.6核心在可裝載核心模組的版本檢查機制方面發生了根本性的改變,不過這些改變對裝置驅動開發人員而言基本是透明的。為了使模組裝載時的版本檢查機制生效,2.4 核心下,只需在CFLAGS中定義

 

-DMODVERSIONS -include $(KDIR)/include/linux/modversions.h; 

 

2.6核心下,開發人員無須採用任何操作。

不過,在此仍有必要闡明2.4與2.6核心對可載入模組的版本檢查機制。

2.4核心下, 執行`cat /proc/ksyms`可看到核心符號在名字後還跟隨著一串校正字串,此校正字串與核心版本有關。在核心源碼標頭檔linux/modules 目錄下存在許多*.ver檔案,這些檔案起著為核心符號添加校正尾碼的作用,如ksyms.ver 檔案裡有一行 #define printk _set_ver(printk)。linux/modversions.h 檔案會包含全部的 ver檔案 。所以當模組包含linux/modversions.h檔案後,編譯時間,模組裡使用的核心符號實質是帶有校正尾碼的核心符號。在載入模組時,如果模組中所使用核心符號的校正字串與當前運行核心所匯出的相應的核心符號的校正字串不一致,即當前核心空間並不存在模組所使用的核心符號,就會出現"Invalid module format "的錯誤。

為核心符號添加校正字串來驗證模組的版本與核心的版本是否匹配是繁雜和浪費核心空間的;而且隨著SMP(對稱式多處理器)、PREEMPT(可搶佔核心)等機制在2.6核心的引入和完善,模組運行時對核心的依賴不僅取決於核心版本,還取決於核心的配置,此時核心符號的校正碼是否一致不能成為判斷模組可否被載入的充分條件。2.6 核心下,在linux/vermagic.h中定義有VERMAGIC_STRING,VERMAGIC_STRING不僅包含核心版本號碼,還包含有核心使用的gcc版本,SMP與PREEMPT等配置資訊。模組在編譯時間,我們可以看到螢幕上會顯示"MODPOST"。在此階段, VERMAGIC_STRING會添加到模組的modinfo段。 在核心源碼目錄下scripts\mod\modpost.c檔案中可以看到模組後續處理部分的代碼。模組編譯產生後,通過`modinfo mymodule.ko`命令可以查看此模組的vermagic等資訊。2.6 核心下的模組裝載器裡儲存有核心的版本資訊,在裝載模組時,裝載器會比較所儲存的核心vermagic與此模組的modinfo段裡儲存的vermagic資訊是否一致,兩者一致時,模組才能被裝載。譬如Fedora core 4 與core 2 使用的都是2.6 版本核心, 在Fedore Core 2下去載入Fedora Core4下編譯產生的hello.ko,會出現"invalid module format" 錯誤。

 

#insmod hello.koInvalid module format hello: version magic '2.6.11-1.1369_FC4 686 REGPARM 4KSTACKS gcc-4.0' should be '2.6.5-1.358 686 REGPARM 4KSTACKS gcc-3.3'

 

2.3模組的初始化與退出

在2.6核心中,核心模組必須調用宏module_init 與module_exit() 去註冊初始化與退出函數。在2.4 核心中,如果初始化函數命名為init_module()、退出函數命名為cleanup_module(),可以不必使用module_init 與module_exit 宏。推薦使用module_init 與module_exit宏,使代碼在2.4與2.6核心中都能工作。

清單5:適用於2.4與2.6核心的模組的初始化與退出模板

#include <linux/module.h>  /* Needed by all modules */#include <linux/init.h>    /* Needed for init&exit macros */static int mod_init_func(void){/*code here*/return 0;}static void mod_exit_func(void){/*code here*/}module_init(mod_init_func);module_exit(mod_exit_func);

 

需要注意的是初始化與退出函數必須在宏module_init和module_exit使用前定義,否則會出現編譯錯誤。

2.4 模組使用計數

模組在被使用時,是不允許被卸載的。2.4核心中,模組自身通過MOD_INC_USE_COUNT、MOD_DEC_USE_COUNT宏來管理自己被使用的計數。2.6核心提供了更健壯、靈活的模組計數管理介面try_module_get(&module)及module_put(&module)取代2.4中的模組使用計數管理宏;模組的使用計數不必由自身管理,而且在管理模組使用計數時考慮到SMP與PREEMPT機制的影響。

int try_module_get(struct module *module):用於增加模組使用計數;若返回為0,表示調用失敗,希望使用的模組沒有被載入或正在被卸載中。

void module_put(struct module *module):減少模組使用計數。

try_module_get 與module_put的引入與使用與2.6核心下的裝置模型密切相關。模組是用來管理硬體裝置的,2.6 核心為不同類型的裝置定義了struct module *owner 域,用來指向管理此裝置的模組。如字元裝置的定義:

 

struct cdev {struct kobject kobj;struct module *owner;struct file_operations *ops;struct list_head list;dev_t dev;unsigned int count;};

 

從裝置使用的角度出發,當需要開啟、開始使用某個裝置時,使用try_module_get(dev->owner)去增加管理此裝置的owner模組的使用計數;當關閉、不再使用此裝置時,使用module_put(dev->owner)減少對管理此裝置的owner模組的使用計數。這樣,當裝置在使用時,管理此裝置的模組就不能被卸載;只有裝置不再使用時模組才能被卸載。

2.6核心下,對於為具體裝置寫驅動的開發人員而言,基本無需使用try_module_get與module_put,因為此時開發人員所寫的驅動通常為支援某具體裝置的owner模組,對此裝置owner模組的計數管理由核心裡更底層的代碼如匯流排驅動或是此類裝置共用的核心模組來實現,從而簡化了裝置驅動開發。

 



回頁首

2.5 模組輸出的核心符號

2.4 核心下,預設情況時模組中的非靜態全域變數及函數在模組載入後會輸出到核心空間。

2.6 核心下,預設情況時模組中的非靜態全域變數及函數在模組載入後不會輸出到核心空間,需要顯式調用宏EXPORT_SYMBOL才能輸出。所以在2.6 核心的模組下,EXPORT_NO_SYMBOLS宏的調用沒有意義,是空操作。在同時支援2.4與2.6核心的裝置驅動中,可以通過以下程式碼片段來輸出模組的核心符號

清單6: 同時支援2.4與2.6的輸出核心符號程式碼片段

#include <linux/module.h>#ifndef LINUX26EXPORT_NO_SYMBOLS;#endifEXPORT_SYMBOL(var);EXPORT_SYMBOL(func);

 

需要注意的是如需在2.4核心下使用 EXPORT_SYMBOL,必須在 CFLAGS中定義 EXPORT_SYMTAB,否則編譯將會失敗。

從良好的代碼風格角度出發,模組中不需要輸出到核心空間且不需為模組中其它檔案所用的全域變數及函數最好顯式申明為static類型,需要輸出的核心符號以模組名為首碼。

模組載入後,2.4核心下可通過 /proc/ksyms、 2.6 核心下可通過/proc/kallsyms查看模組輸出的核心符號

 



回頁首

2.6 模組的命令列輸入參數

在裝載核心模組時,使用者可以向模組傳遞一些參數,如`modprobe modname var=value`,否則,var將使用模組內定義的預設值。

2.4核心下,linux/module.h中定義有宏MODULE_PARM(var,type) 用於向模組傳遞命令列參數。var為接受參數值的變數名,type為採取如下格式的字串[min[-max]]{b,h,i,l,s}。min及max用於表示當參數為數群組類型時,允許輸入的數組元素的個數範圍;b:byte;h:short;i:int;l:long;s:string。

2.6核心下,宏MODULE_PARM(var,type)不再被支援。在標頭檔linux/moduleparam.h裡定義了如下宏:

 

module_param(name, type, perm)module_param_array(name, type, nump, perm)

 

type 類型可以是byte、short,、ushort、 int、 uint、long、ulong、charp, bool or invbool, 不再採用2.4核心中的字串形式,而且在模組編譯時間會將此處申明的type與變數定義的類型進行比較,判斷是否一致。

perm表示此參數在sysfs檔案系統中所對應的檔案節點的屬性。2.6核心使用sysfs檔案系統,這是一個建立在記憶體中比proc更強大的檔案系統。sysfs檔案系統可以動態、即時,有組織層次地反應當前系統中的硬體、驅動等狀態。當perm為0時,表示此參數不存在sysfs檔案系統下對應的檔案節點。 模組被載入後,在/sys/module/ 目錄下將出現以此模組名命名的目錄。如果此模組存在perm不為0的命令列參數,在此模組的目錄下將出現parameters目錄,包含一系列以參數名命名的檔案節點,這些檔案的許可權值等於perm,檔案的內容為參數的值。

nump 為儲存輸入的數組元素個數的變數的指標。當不需儲存實際輸入的數組元素個數時,可以設為NULL。從2.6.0至2.6.10 版本,須將變數名賦給nump;從2.6.10 版本開始,須將變數的引用賦給nump,這更易為開發人員理解。載入模組時,使用逗號分隔輸入的數組元素。

清單7: 適用於2.4與2.6核心的模組輸入參數模板

#include <linux/module.h>#ifdef LINUX26#include <linux/moduleparam.h>#endif int debug = 0;char *mode = "800x600";int tuner[4] = {1, 1, 1, 1};#ifdef LINUX26int tuner_c = 1;  #endif #ifdef LINUX26MODULE_PARM(debug, "i");MODULE_PARM(mode, "s");MODULE_PARM(tuner,"1-4i");#elsemodule_param(debug, int, 0644);module_param(mode, charp, 0644);#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 10)module_param_array(tuner, int, &tuner_c, 0644);#elsemodule_param_array(tuner, int, tuner_c, 0644);#endif #endif

 

模組編譯產生後,載入模組時可以輸入:`modprobe my_module mode=1024x768 debug=1 tuner=22,33`。

在linux/moduleparam.h還定義有:

 

module_param_array_named(name, array, type, nump, perm)module_param_call(name, set, get, arg, perm)module_param_named(name, value, type, perm)

 

讀者可以參閱linux/moduleparam.h查看這些宏的詳細描述,有一點需注意,在2.6核心裡,module_param這一系列宏使用的都是小寫名字。

2.7 模組的許可證聲明

從2.4.10版本核心開始,模組必須通過MODULE_LICENSE宏聲明此模組的許可證,否則在載入此模組時,會收到核心被汙染"kernel tainted" 的警告。從linux/module.h檔案中可以看到,被核心接受的有意義的許可證有 "GPL","GPL v2","GPL and additional rights","Dual BSD/GPL","Dual MPL/GPL","Proprietary"。

在同時支援2.4與2.6核心的裝置驅動中,模組可按如下方式聲明自己的許可證。

清單8: 適用於2.4與2.6核心的模組許可證聲明模板

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,10)MODULE_LICENSE("GPL");#endif

 

2.8 小結

此外,2.6核心裡還有一些模組機制的改變,不常為驅動開發人員用到。如載入核心模組的介面request_module在2.4 下為request_module(const char * module_name);在2.6核心下為request_module(const char *fmt, ...)。在2.6 核心下,驅動開發人員可以通過調用

 

request_module("msp3400");request_module("char-major-%d-%d", MAJOR(dev), MINOR(dev));

 

這種更靈活的方式載入其它核心模組。

2.6核心在linux/module.h中還提供了MODULE_ALIAS(alias)宏,模組可以通過調用此宏為自己定義一或若干個別稱。而在2.4核心下,使用者只能在/etc/modules.conf中為模組定義別稱。

通過以上比較可以看到,從2.4到2.6核心,可裝載模組管理機制的改變使裝置驅動的開發變得更加簡潔、靈活、健壯。

參考資料

  • Porting device drivers to the 2.6 kernel

關於作者

 

作者:周婷,軟體工程師,S3 Graphics 上海研發中心,工作方向: 視頻解碼。email: moting9@hotmail.com。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.