這篇文章做爲驅動的入門很不錯:http://greenlinux.blogcn.com/diary,103232026.shtml,如下是引用的內容!html
就整個kernel運行在單個保護域而言,Linux kernel 成爲了「單內核」,可是Linux kernel是組件模式的,在運行時容許「代碼」動態地插入到kernel和從kernel中移除。模塊就是相關的一些子程序,數據,入口點和出口點共同組合成的一個單一的二進制映像,即一個可裝載的kernel目標文件。模塊的支持使得系統能夠擁有一個最小的基本的內核映像,而且經過模塊的方式支持一些可選的特徵和驅動程序。模塊對內核代碼動態地插入到kernel中和從kernel中移除提供了一種簡便方法;它有助於調試內核程序(例如,當咱們在板子上調試一個驅動程序時,能夠採用模塊的方式,也能夠採用靜態的編譯到內核當中,若是採用後者,每次修改驅動程序的時候,都必須從新燒寫內核;若是採用前者,只需把驅動程序下載到板子,而後動態插入到內核);當有新的設備「熱插」到的系統時,能夠經過模塊方式按需裝載新的驅動程序。在這章中,咱們將學習模塊的基本知識和實現模塊的基本原理以及如何寫本身的模塊。
1. Hello World
模塊的開發很像寫一個應用程序,它有本身的入口點,出口點和本身的「生活空間」。下面咱們一塊兒來寫「Hello World」內核模塊,藉此學習寫內核模塊的通常步驟。
/*
* hello.c Hello, World! As a Kernel Module
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
/*
* hello_init the init function, called when the module is loaded.
* Returns zero if successfully loaded, nonzero otherwise.
*/
static int hello_init(void)
{
printk(KERN_ALERT "I bear a charmed life.\n" ) ;
return 0;
}
/*
* hello_exit the exit function, called when the module is removed.
*/
static void hello_exit(void)
{
printk(KERN_ALERT "Out, out, brief candle!\n" ) ;
}
module_init(hello_init) ;
module_exit(hello_exit) ;
MODULE_LICENSE("GPL" ) ;
MODULE_AUTHOR("Shakespeare" ) ;
經過module_init()把hello_init()函數註冊爲這個模塊的入口點。當裝載模塊的時候,內核調用hello_init()函數。module_init()是一個宏,它的任務是把它的唯一參數做爲相應模塊的初始化函數。全部初始化函數必須有下列形式:int my_init(void);
因爲初始化函數不會被外部的代碼直接調用,所以你沒必要export這個函數,若是把這個函數標誌爲static,將會更加合理。初始化函數返回一個int型整數,若是初始化成功,返回零,若是初始化失敗,返回非零。
這裏的初始化函數僅僅打印了一句話,而後返回零。在實際開發的模塊裏,初始化函數通常是註冊資源,爲數據結構分配內存等等。若是咱們選擇把這個文件靜態地編譯到內核的p_w_picpath內,那麼也會保留初始化函數,而且在內核啓動的時候運行。
經過module_exit()把hello_exit()函數註冊爲這個模塊的出口點。當模塊從內存中移除的時候,內核調用hello_exit()。退出函數在返回以前,必須清除模塊佔用的資源,確保硬件處於一致狀態等等。退出函數返回以後,模塊從內核中移除。
退出函數必須有下列形式:void my_exit(void); 似於初始化函數,你把這個函數標誌爲static,將會更加合理。
若是這個文件編譯到靜態的內核p_w_picpath內,退出函數不會包含在內核p_w_picpath以內,它也不會被調用。由於,若是不是以模塊方式插入內核,代碼永遠不會從內存中刪除。
宏MODULE_LICENSE()用於指定這個文件的copyright license。若是裝載一個non-GPL模塊到內存,從而致使在內核中設置了tainted flag。這個flag僅僅爲開發者提供說明信息的目的。若是設置了tainted flag,許多內核開發者會給出bug報告。進一步說,non-GPL模塊不能調用GPL-only 符號。
最後,宏MDULE_AUTHOR()指定了這個文件的做者。宏的值徹底是爲了提供說明信息的目的。
2. Building Modules
在編譯模塊以前,咱們必須決定模塊的源碼放在哪裏。你能夠把模塊的源碼增長到內核源碼的一個合適的地方,既能夠把你的文件做爲一個」patch」,最終也能夠把你的代碼合到官方的源碼樹內。另一種選擇是在源碼樹以外維護和編譯你本身的模塊。
2.1 At Home in the Source Tree
從理想的角度出發,你的模塊是官方Linux的一部分,這樣,模塊的代碼能夠生存在內核的源碼樹內。若是你的工做完全地放入內核之中,那麼首先你須要對模塊代碼要更多的維護觀念,這也是一般首選的方案。
第一步是決定你的模塊將要生活在內核源碼樹的哪一個地方。假設咱們要寫一個經過USB線把手機看成PC機的一臺聯機小電腦的驅動程序,那麼這個驅動程序放在內核源碼樹的哪一個地方比較合適呢?咱們進入到內核源碼樹根目錄下的drivers/目錄下看一看,各個子目錄的組織是按照驅動程序的class, type來實現的。咱們很容易發現有個usb/子目錄,在usb/子目錄以內,咱們又能夠找到gadget/子目錄,在gadget/子目錄內能夠發現許多驅動程序,有的用來實現經過USB線把手機看成u盤,有的用來實現經過USB線把手機看成modem,等等,物以類聚,把手機看成PC機的一臺聯機小電腦的驅動程序生活在gadget/子目錄裏看來很合適,可是因爲實現這個驅動程序須要許多文件,所以,咱們想在gadget/子目錄下再建立online/子目錄,讓咱們新建立的相關文件存放這個目錄下。
在gadget/子目錄下建立online/子目錄以後,咱們必須在gadget/的Makefile文件中添加一行:
obj-m += online/
目的在於編譯模塊的時候,告訴build系統找到online/子目錄。通常狀況下,爲了控制是否是須要編譯驅動程序,咱們的使用一個特定的配置選項來達到這個目的,例如,使用CONFIG_USB_GADGET_ONLINE(在第五節會講述如何添加一個新的配置選項)。所以咱們能夠把上面添加的一行修改成:
obj-$(CONFIG_USB_GADGET_ONLINE) += online/
最後,咱們在online/下建立新文件Makefile,而且添加下面一行到其內。
obj-m += netmeeting.o
到這一步,build系統可以沿着源碼樹往下找到online/子目錄,而且根據netmeeting.c文件創建netmeeting.ko模塊。雖然在Makefile文件中netmeeting.o的擴展名是.o,可是編譯後模塊的擴展名是.ko。
通常狀況下,爲了控制是否是須要編譯該驅動程序,咱們的使用一個特定的配置選項來達到這個目的,例如:
obj-$(CONFIG_USB_GADGET_ONLINE) += netmeeting.o
固然,實現手機看成PC機的一臺聯機小電腦的驅動程序須要許多文件,那麼Makefile又該如何寫呢?
obj-$(CONFIG_USB_GADGET_ONLINE) += netmeeting.o
netmeeting-objs := one.o two.o three.o four.o
這樣,netmeeting.ko 由one.c, two.c, three.c 和four.c四個文件編譯鏈接而成。
最後,若是你想爲這些文件指定額外的gcc編譯選項,在Makefile文件中添加相似於如下一行:
EXTRA_CFLAGS += -ONLINE_NETMEETING
若是咱們不須要建立一個子目錄來存放新添加的文件,而是放新添加的文件在gadget/目錄下,那麼咱們把在online/下的Makefile內的全部內容移到gadget/目錄下的Makefile文件中。
若是模塊是否編譯由配置選項來控制,然而你又想編譯此模塊,那麼在編譯以前,必須把配置選項打開。在源代碼樹kernel/目錄下,運行make menuconfig,在drivers/usb/gadget下並無發現咱們添加的online目錄,這究竟什麼回事?咱們學了第五節如何添加一個新的配置選項後,問題就會迎刃而解。
2.2 Living Externally
若是咱們在源碼樹以外維護和創建模塊,那麼在本身的源碼目錄下建立Makefile文件,而且添加:
obj-m := netmeeting.o
這樣會把netmeeting.c編譯成netmeeting.ko。 若是有多個源文件,那麼在Makefile文件中添加:
obj-m := netmeeting.o
netmeeting-objs := one.o two.o three.o four.o
這樣,one.c, two.c, three.c, four.c都編譯到netmeeting.ko以內。
與在內核源碼樹內添加新文件相比,主要的區別在於build過程。在內核源碼樹以外編譯模塊,須要使用命令make 去找到內核源碼文件和基本的Makefile文件。按照下面的方法作就好了。
make –C /kernel/source/location SUBDIRS=$PWD modules
這裏/kernel/source/location是已經配置過的內核源碼樹的位置。你最好不要把內核源碼樹放在/usr/src/linux之下,這個目錄下存放的是系統安裝時的內核源碼樹,把你的內核源碼樹放到容易找的地方,好比home目錄中。
2.2 Installing Modules
編譯後的模塊要放在/lib/modules/version/kernel/。下面的命令把全部編譯後的模塊放到相應的目錄之下 (須要root權限)。
make modules_install
2.3 Generating Module Dependencies
Linux模塊實用工具可以理解模塊間的依賴性。這意味着,若是模塊chum依賴於模塊bait,那麼當裝載模塊chum時,模塊bait會自動裝載。這種依賴性信息不是憑空存在的,而是事先要創建起來。大多數發行的Linux可以自動產生這種映射關係,而且在每次啓動的時候,創建最新的依賴關係。爲了創建模塊間的依賴信息,在root權限下運行以下命令:
depmod
爲了快速更新模塊間的依賴信息,即若是有新模塊,從新創建依賴信息;若是沒有新模塊,會保留原來的依賴信息。在root權限下運行以下命令:
depmod –A
模塊間的依賴信息存放在文件/lib/modules/version/modules.dep之中。
2.4 Loading Modules
用insmod裝載模塊是一種最簡單的方法。它請求kernel裝載指定的模塊。insmod不會檢查模塊間的依賴關係,也不會執行是否有錯誤的檢查。用法很簡單。在root權限下,運行下列命令:
insmod module
這裏module是模塊名。
一樣,移除模塊,使用rmmod。在root權限下,運行下列命令:
rmmod module
這兩個實用工具,雖然用法簡單,可是缺少智能性。實用工具modprobe提供了依賴關係的解決方案,智能的錯誤檢查和彙報,等等。裝載模塊的時候,是咱們的首選。
用modprobe裝載模塊,在root權限下,運行下列命令:
modprobe module { module parameters }
這裏module是模塊名。模塊的參數(請看第七節)是要傳遞到模塊的參數。有點像DOS下執行程序的時候附帶幾個參數。
modprobe命令不只試圖裝載寫在其後的模塊,還試圖裝載它依賴的全部模塊。所以,咱們要首選它。
modprobe也能夠用於從kernel中移除模塊,在root權限下,運行下列命令:
modprobe r modules
這裏modules指的是要移除的模塊名,能夠是多個模塊名。不像rmmod僅移除指定的模塊,modprobe還移除它依賴的而且不在使用中的其它模塊。
2.5 Managing Configuration Options
在2.6 kernel中,因爲有了新的 「kbuild」系統,所以增添一項新的配置是至關容易的。就是在Kconfig文件中增添一項新的配置內容。Kconfig文件用於銜接整個kernel源碼樹。對於驅動程序而言,Kconfig文件在驅動程序源碼的同級目錄。若是你的驅動程序在drivers/usb/gadget/下,那麼你用的是driversusb/gadget/Kconfig。
若是你建立了一個新目錄,那麼要在其下建立新的Kconfig文件,而且從一個已存在的Kconfig中「source」它。即在已存在的Kconfig(例如,drivers/usb/gadget/Kconfig)中添加相似下面的一行:
source 「drivers/usb/gadget/online/Kconfig」
而後在新建的Kconfig中添加相似如下的內容:
config USB_GADGET_ONLINE
tristate 「 Gadget Netmeeting support」
default n
help
If you say Y here, netmeeting driver will be compiled into the kernel. You can also say M here and the driver will be built as a module named netmeeting.ko
If unsure, say N.
第一行定義了配置選項。事先已經假定存在有CONFIG_前綴,所以用不着咱們寫。
第二行說明了這個配置選項是三態的,即有三種選擇方式,第一種是選擇Y,表示相對應的程序編譯到kernel以內;第二種是選擇M,表示相對應的程序編譯成模塊,第一種是選擇N,表示不編譯相對應的程序.若是沒有編譯成模塊這個選項,能夠用bool代替tristate。指令tristate後帶引號的文本是配置選項名,用於各類配置實用程序的選項顯示。
第三行爲這個配置選項指定一個默認值,這裏是表示選擇了n。
第四行help指令表示其後面是幫助文本。有助於用戶和開發者理解相應的程序和創建本身的內核。
還有其餘另一些指令。好比depends指令用於指定要使這個配置選項有效,則必須先要設置其它配置選項有效。若是這種依賴關係不能知足,那麼配置選項就會失效。例如,若是你添加了以下的指令:
depends on PXA27X
要使CONFIG_GADGET_NETMEETING這個配置選項有效,必須首先要使CONFIG_PXA27X這個配置選項有效。
指令select與depends有些相似,不一樣點在於:若是選了當前配置選項,那麼select後的配置選項也會選中。因爲指令select會自動「打開」其餘配置選項,所以它不如depends經常使用。用法以下:
select PXA27X
若是打開CONFIG_GADGET_NETMEETING配置選項,那麼自動打開CONFIG_PXA27X配置選項。
對於select和depends指令,使用&&,||和!組合多個配置選項。例如:
depends on DUMB_DRIVERS && !ONE_DRIVER
意思是僅當CONFIG_DUMB_DRIVERS配置選項打開而CONFIG_ONE_DRIVER配置選項關閉時,CONFIG_GADGET_NETMEETING配置選項纔打開。
指令if能夠跟在指令tristate和bool以後,這樣爲配置選項設置了一個條件選項。若是條件不符合,不只關閉了配置選項甚至在配置實用工具內也不會出現這個配置選項。例如:
bool 「Deep Sea Mode」 if OCEAN
其表示只有配置選項CONFIG_OCEAN打開以後,配置實用工具纔會顯示配置選項名Deep Sea Mode,並且也會打開CONFIG_GADGET_NETMEETING配置選項。
指令if也能夠跟在指令default以後,其意思是僅當條件成立時,纔會賦默認值。
爲了更容易地創建配置,配置系統提供了幾個meta-options。當且僅當用戶但願打開設計用來禁止關鍵特性(好比在嵌入式系統上保留精確的內存)的選項時,那麼打開配置選項CONFIG_ EMBEDDED( This option allows certain base kernel options and settings to be disabled or tweaked. This is for specialized environments which can tolerate a 「non-standard」 kernel. Only use this if you really know what you are doing)。配置系統CONFIG_BROKEN_ON_SMP是用於指定一個驅動程序不是SMP-safe。一般這個選項是沒有打開的,這樣強制用戶清晰地認識到一個驅動程序在SMP環境下具備「破壞性」。固然,新開發的驅動程序,不要使用這個選項。
最後,CONFIG_EXPERIMENTAL配置選項用於標誌一個驅動程序是處於試驗階段或者beta版本階段,這個選項默認是關閉的,這使得用戶在使用驅動程序以前清晰地認識到其中的風險性。
2.6 Module Parameters
對於如何向模塊傳遞參數,Linux kernel 提供了一個簡單的框架。其容許驅動程序聲明參數,而且用戶在系統啓動或模塊裝載時爲參數指定相應值,在驅動程序裏,參數的用法如同全局變量。這些模塊參數也可以在sysfs中顯示出來。結果,有許許多多的方法用來建立和管理模塊參數。
經過宏module_param()定義一個模塊參數:
module_param(name, type, perm);
這裏,name既是用戶看到的參數名,又是模塊內接受參數的變量; type表示參數的數據類型,是下列之一:byte, short, ushort, int, uint, long, ulong, charp, bool, invbool。這些類型分別是:a byte, a short integer, an unsigned short integer, an integer, an unsigned integer, a long integer, an unsigned long integer, a pointer to a char, a Boolean, a Boolean whose value is inverted from what the user specifies. The byte type is stored in a single char and the Boolean types are stored in variables of type int. The rest are stored in the corresponding primitive C types. 最後,perm指定了在sysfs中相應文件的訪問權限。訪問權限用一般的八進制格式來表示,例如,用0644(表示ower具備讀寫權限,group和everyone只讀權限), 或者用一般的S_Ifoo定義,例如,S_IRUGO | S_IWUSR (表示everyone具備讀權限,用戶具備寫權限)。用0表示徹底關閉在sysfs中相對應的項。
其實宏不會聲明變量,所以在使用宏以前,必須聲明變量。因此,典型地用法以下:
static unsigned int use_acm = 0;
module_param(use_acm, uint, S_IRUGO);
這些必須寫在模塊源文件的開頭部分。即use_acm是全局的。
咱們也可使模塊源文件內部的變量名與外部的參數名有不一樣的名字。這經過宏module_param_named()定義。
module_param_named(name, variable, type, perm);
這裏name是外部可見的參數名,variable是源文件內部的全局變量名。例如:
static unsigned int max_test = 9;
module_param_name(maximum_line_test, max_test, int, 0);
若是模塊參數是一個字符串時,一般使用charp類型定義這個模塊參數。內核複製用戶提供的字符串到內存,而且相對應的變量指向這個字符串。例如:
static char *name;
module_param(name, charp, 0);
另外一種方法是經過宏module_param_string()讓內核把字符串直接複製到程序中的字符數組內。
module_param_string(name, string, len, perm);
這裏,name是外部的參數名,string是內部的變量名,len是以string命名的buffer大小(能夠小於buffer的大小,可是沒有意義),perm表示sysfs的訪問權限(或者perm是零,表示徹底關閉相對應的sysfs項)。例如:
static char species[BUF_LEN];
module_param_string(specifies, species, BUF_LEN, 0);
上面說得只是給模塊傳入一個參數的狀況,若是給模塊傳入多個參數,那該怎麼辦呢?能夠經過宏module_param_array()給模塊傳入多個參數。 用法以下:
module_param_array(name, type, nump, perm);
這裏,name既是外部模塊的參數名又是程序內部的變量名,type是數據類型,perm是sysfs的訪問權限。指針nump指向一個整數,其值表示有多少個參數存放在數組name中。值得注意是name數組必須靜態分配。例如:
static int finsh[MAX_FISH];
static int nr_fish;
module_param_array(fish, int, &nr_fish, 0444);
經過宏module_param_array_named()使得內部的數組名與外部的參數名有不一樣的名字。例如:
module_param_array_named(name, array, type, nump, perm);
這裏的參數意義與其它宏同樣。
最後,經過宏MODULE_PARM_DESC()對參數進行說明:
static unsigned short size = 1;
module_param(size, ushort, 0644);
MODULE_PARM_DESC(size, 「The size in inches of the fishing pole」 \
「connected to this computer.」 );
使用這些宏時須要包含頭文件<linux/moduleparam.h>。
2.7 Exported Symbols
當裝載模塊的時候,模塊動態地連接入內核之中。動態連接的二進制代碼只能調用外部函數,然而,外部函數必須明確地輸出,在內核中,經過EXPORT_SYMBOL()和EXPORT_SYMBOL_GPL來達到這個目的。
輸出的函數能夠被其它模塊調用。沒有輸出過的函數不能被其它模塊調用。模塊比核心內核映像代碼具備更嚴格的連接和調用規則。由於全部核心源文件連接成一個單一的做爲基礎的映像,所以在內核中核心代碼能夠調用任何非靜態的接口。固然,輸出符號也必須是非靜態屬性。
一套輸出的內核符號稱之爲輸出的內核接口,甚至稱之爲kernel API。
輸出一個內核符號是舉手之勞之事。當函數聲明之時,在其後用EXPORT_SYMBOL()把函數輸出。
例如:
/* it will receive control requests including set_configuration(), which enables non-control requests.
*/
int usb_gadget_register_driver(struct usb_gadget_driver *driver)
{
…
}
EXPORT_SYMBOL(usb_gadget_register_driver) ;
今後之後,任何模塊均可以調用函數usb_gadget_register_driver(),只要在源文件中包含聲明這個函數的頭文件,或者extern這個函數的聲明。
有些開發者但願他們的接口只讓聽從GPL的模塊調用。經過MODULE_LICENSE()的使用,內核連接器可以強制保證作到這點。若是你但願前面的函數僅被標有GPL許可證的模塊訪問,那麼你能夠用以下方式輸出符號:
EXPORT_SYMBOL_GPL(usb_gadget_register_driver);
若是你的代碼配置爲模塊方式,那麼必須確保:源文件中使用的全部接口必須是已經輸出的符號,不然致使在裝載時連接錯誤。
2.8 Wrapping Up Modules
這章咱們學習瞭如何寫模塊,創建模塊,裝載和卸載模塊。咱們討論了什麼是模塊,Linux如何動態裝載模塊代碼,並且咱們還討論了模塊的參數和輸出符號。linux