LINUX设备驱动程序的注意事项(两)建设和执行模块
? ? ? ? ?<一>:設置測試系統
? ? ? ? ?首先準備好一個內核源代碼樹,構造一個新內核,然后安裝到自己的系統中。
? ? ? ? ? <二>:HelloWorld模塊
#include <linux/init.h> //定義了驅動的初始化和退出相關的函數 #include <linux/module.h> //定義了內核模塊相關的函數、變量及宏MODULE_LICENSE("Dual BSD/GPL"); //該宏告訴內核,該模塊採用自由許可證static int hello_init(void) //初始化函數僅在模塊載入時調用 {printk(KERN_ALERT"Helloworld\n");return 0; }static void hello_exit(void) //卸載函數僅在模塊卸載時調用 {printk(KERN_ALERT"Goodbye,cruelworld\n"); }module_init(hello_init); module_exit(hello_exit);
?? ?<三>:核心模塊與應用程序的對照
?? ? ? ?1.內核模塊與應用程序之間的種種不同:
? ? ? ?a.大多數小規模及中規模應用程序是從頭到尾運行單個任務。而模塊卻僅僅是預先注冊自己以便服務于將來的某個請求。然后它的初始化函數就馬上結束。
? ? ? ?b.事件驅動的應用程序和內核代碼之間的還有一個不同是:應用程序在退出時。能夠無論資源的釋放或者其它的清除工作,但模塊的退出函數卻必須細致撤銷初始化函數所做的一切,否則。在系統又一次引導之前某些東西就會殘留在系統中。
? ? ? ?c.應用程序可以調用它并沒有定義的函數,這是由于連接過程可以解析外部引用從而使用適當的函數庫。而模塊只被鏈接到內核,因此它能調用的函數不過內核導出的那些函數。而不存在任何可鏈接的函數庫。
? ? ? ?d.內核編程和用用編程的另外一點重要不同在于各環境下處理錯誤的方式不同:應用程序開發過程中的段錯誤是無害的,而且總是能夠使用調試器跟蹤到源碼中的問題所在。而一個內核錯誤即使不影響整個系統,也至少會殺死當前進程。
?
? ? ? ?2.用戶空間和內核空間
? ? ? ?模塊運行在所謂的內核空間里,而應用程序運行在所謂的用戶空間里。
操作系統的作用是為應用程序提供一個對計算機硬件的一致視圖。同一時候操作系統必須負責程序的獨立操作并保護資源不受非法訪問。這個任務僅僅有在CPU可以保護系統軟件不受應用程序破換時才干完畢。人們選擇的方法是在CPU中實現不同操作模式(或者級別)。每當應用程序運行系統調用或者被硬件中斷掛起時,Unix將運行模式從用戶空間切換到內核空間。
模塊化代碼在內核空間中運行。用于擴展內核的功能。
通常一個驅動程序要運行兩個任務:模塊中的某些函數作為系統調用的一部分而運行。而其它函數則負責中斷處理。
?
? ? ? ? 3.內核中的并發
? ? ? ?有幾方面的原因促使內核編程必須考慮并發問題:
? ? ? ?a.Linux系統中通常正執行多個并發進程。而且可能有多個進程同一時候使用我們的驅動程序。
? ? ? ?b.大多數設備可以中斷處理器。而中斷處理程序異步執行,并且可能在驅動程序正試圖處理其它任務時被調用
? ? ? ?c.有一些軟件抽象也在異步執行著。
? ? ? ?d.Linux還能夠執行在對稱多處理器系統上。因此可能同一時候有不止一個CPU執行我們的驅動程序。
? ? ? ?e.在2.6中內核代碼已經是搶占式的,這意味著即使在單處理器系統上也存在很多類似多處理器系統的并發問題。
?
? ? ? ?4.當前進程
? ? ? ?內核代碼可通過訪問全局項current來獲得當前進程。
current在<asm/current.h>中定義。是一個指向struct task_struct的指針,而task_struct結構在<linux/sched.h>文件里定義。current指針指向當前正執行的進程。在open/read等系統調用的執行過程中。當前進程指的是調用這些系統調用的進程。
? ? ? ?假設須要。內核代碼能夠通過current獲得與當前進程相關的信息。
? ? ? ?設備驅動程序僅僅要包括<linux/sched.h>頭文件就可以引用當前進程。比如,以下的語句通過訪問struct task_struct
的某些成員來打印當前進程的ID和命令名:
printk(KERN_INFO"The process is\"%s\"(pid%i)\n,current->comm,current->pid);
?
? ? ? ?5.其它一些細節
? ? ? ?應用程序在虛擬內存中布局,并具有一塊非常大的棧空間。當然,棧是用來保存函數調用歷史以及當前活動函數中的自己主動變量的。而相反的是,內核具有非常小的棧,它可能僅僅和一個4096字節大小的頁那樣小。
? ? ? ?常常會在內核API中看到具有兩個下劃線前綴(__)的函數名稱。
具有這樣的名稱的函數一般是接口的底層組件。應該慎重使用。
? ? ? ?<四>:編譯和裝載
? ? ? ?1.編譯模塊
? ? ? ? 首先來看看模塊時怎樣構造的,詳細細節參考內核源碼中Documentation/kbuild文件夾下的文件。
在構造內核模塊前。應確保具有正確版本號的編譯器、模塊工具盒其它必要的工具。
內核文檔文件夾中Documentation/Changes文件列出了須要的工具版本號。makefile里的一些規則:
? ? ? ? ?假設要構造的模塊名稱為module.ko。并由兩個源文件生成(比方file1.c和file2.c),則正確的makefile可例如以下編寫:
obj-m := module.o module-objs := file1.o file2.o? ? ? ?2.裝載和卸載模塊
? ? ? ?裝載模塊一般使用insmod程序,它和ld有些類似,它將模塊的代碼和數據裝入內核,然后使用內核的符號表解析模塊中不論什么為解析的符號。
然而,與鏈接器不同,內核不會改動模塊的磁盤文件,而只改動內存中的副本。insmod能夠接受一些命令行選項,而且能夠在模塊鏈接到內核之前給模塊中的整型和字符串型變量賦值。
與insmod類似的是modprobe工具,它不僅裝載該模塊。還裝載該模塊所一欄的模塊。
? ? ? ? 卸載模塊用rmmod工具。可從內核中移除模塊。假設內核覺得模塊仍然在使用狀態。或者內核被配置為禁止移除模塊,則無法移除該模塊。
? ? ? ? lsmod程序列出當前裝載到內核中的全部模塊,還提供了其它一些信息,比方其它模塊是不是在使用某個特定模塊等。
? ? ? 3.版本號依賴
? ? ? 4.平臺依賴
?
? ? ? <五>:內核符號表
? ? ? ?insmod使用公共內核符號表來解析模塊中沒有定義的符號。公共內核符號表中包括了全部全局內核項的地址,這是實現模塊驅動程序所必須的。當模塊被裝入內核后。它所導出的不論什么符號都會變成內核符號表的一部分。
新模塊能夠使用由我們自己的模塊導出的符號,這樣。我們能夠在其它模塊上層疊新的模塊。
? ? ? ?modprobe是處理層疊模塊的一個使用工具。它的功能在非常大程度上和insmod類似,可是它除了裝入指定模塊外還同一時候裝入指定模塊所依賴的其它模塊。通過層疊技術。我們能夠將模塊劃分為多個層,通過簡化每一個層可縮短開發時間。
? ? ? ?Linux內核頭文件提供了一個方便的方法來管理符號對模塊外部的可見性。從而降低了可能造成的名字空間污染。而且適當隱藏信息。假設一個模塊須要向其它模塊導出符號,則應該使用以下的宏。
EXPORT_SYMBOL(name);
EXPORT_SYMBOL_GPL(name);
? ? ? ?GPL版本號使得要導出的模塊僅僅能僅僅能被GPL許可證下的模塊使用。
符號必須在模塊文件的全局部分導出。不能在函數中導出,這是由于上面這兩個宏將被擴展為一個特殊變量的聲明,而該變量必須是全局的。該變量將在模塊可運行文件的特殊部分(即一個“ELF段”)中保存,在裝載時,內核通過這個段來尋找模塊導出的變量。
?
? ? ? ?<六>:預備知識
? ? ? ?大部分內核代碼中都要包括相當數量的頭文件,以便獲得函數、數據類型和變量的定義。有幾個頭文件時專門用于模塊的,因此必須在出如今每一個可裝載的模塊中。
因此。全部的模塊代碼中都包括以下兩行代碼:
#include <linux/module.h>? /*module.h包括有可裝載模塊須要的大量符號和函數的定義*/
#include <linux/init.h>??? /*包括init.h的目的是指定初始化和清除函數*/
大部分模塊還包含moduleparam.h頭文件,這樣就能夠在裝載模塊時向模塊傳遞參數。
MODULE_LICENSE("GPL");內核可以識別的該許可證。
假設一個模塊沒有顯示地標記為上述內核可識別的許可證,則會假定是專有的。而內核裝載這樣的模塊就會被“污染”。
? ? ? ?可在模塊中包括的其它描寫敘述性定義為包括MODULE_AUTHOR(描寫敘述模塊作者)、MODULE_DESCRIPTION(用來說明模塊用途的簡短描寫敘述)、MODULE_VERSION(代碼修訂號)等。
?
? ? ? ? ?<七>:初始化和關閉
?? ? ? 1.模塊的初始化函數負責注冊模塊所提供的不論什么設施。
這里的設施指的是一個能夠被應用程序訪問的新功能,它可能是一個完整的驅動程序或者不過一個新的軟件抽象。初始化函數的實際定義通常例如以下所看到的:
static int __init initialization_function(void)
{
??? /*這里時初始化代碼*/
}
module_init(initialization_function);
初始化函數應該被聲明為static,由于這樣的函數在特定文件之外沒有其它意義。__init標記對內核來講是一種暗示,表明該函數僅在初始化期間使用。
在模塊被裝載之后,模塊裝載器就會將初始化函數扔掉,這樣可將該函數占用的內存釋放出來。以作他用注意,不要在結束初始化之后仍要使用的函數上使用這兩個標記。
module_initd的使用是強制的。這個宏在模塊的目標代碼中添加一個特殊的段。用于說明內核初始化函數所在的位置。沒有這個定義,初始化函數永遠不會被調用。模塊能夠注冊很多不同類型的設施。包含不同類型的設備、文件系統、password交換等。對于每種設施,相應有詳細的內核函數用來完畢注冊。大部分注冊函數名字帶有register_前綴。
?
? ? ? ?2.清除函數
? ? ? ?每一個重要的模塊都須要一個清除函數,該函數在模塊被移除前注銷接口并向系統中返回全部資源。該函數定義例如以下:
static void __exit cleanup_function(void)
{
??? /*這里是清除代碼*/
}
module_exit(cleanup_function);
__exit修飾詞標記該代碼僅用于模塊卸載(編譯器將把該函數放在特殊的ELF段中)。假設模塊被直接內嵌到內核中,或者內核的配置不同意卸載模塊,則被標記為__exit的函數將被簡單地丟棄。
出于以上原因,被標記為__exit的函數僅僅能在模塊被卸載或者系統關閉時被調用。其它的不論什么使用方法都是錯誤的。module_exit聲明為對于幫助內核找到模塊的清除函數式必須的。
假設一個模塊沒有定義清除函數。則內核不同意卸載該模塊。
?
? ? ? ? 3.初始化過程中的錯誤處理
?
? ? ? ? 4.模塊裝載競爭
? ? ? ?a.在注冊完畢之后,內核的某些部分可能會馬上使用我們剛剛注冊的不論什么設施。即。在初始化函數還在執行的時候。內核就全然可能會調用我們的模塊。因此,在首次注冊完畢之后。代碼就應該準備好被內核的其它部分調用;在用來支持某個設施的全部內部初始化完畢之前。不要注冊不論什么設施。
? ? ? ?b.當初始化失敗而內核的某些部分已經使用了模塊所注冊的某個設施時應該怎樣處理。
假設這樣的情況可能發生在我們的模塊上,則根本不應該出現初始化失敗的情況,畢竟模塊已經成功導出了可用的功能及符號。假設初始化一定要失敗。則應該細致處理內核其它部分正在進行的操作,而且要等待這些操作的完畢。
?
? ? ? ?<八>:模塊參數
? ? ? 因為系統的不同,驅動程序須要的參數或許會發生變化。
這包含設備編號以及其它一些用來控制驅動程序操作方式的參數。內核同意對驅動程序指定參數,而這些參數可在裝載驅動程序模塊時改變。
這些參數的值可在執行insmod或modprobe命令裝載模塊時賦值,而modprobe還能夠從它的配置文件(/etc/modprob.conf)中讀取參數值。
? ? ? 在insmod改變模塊參數之前,模塊必須讓這些參數對insmod命令可見。
參數必須使用module_param宏來聲明,這個宏在moduleparam.h中定義。module_param須要三個參數:變量的名稱、類型以及用于sysfs入口項的訪問許可掩碼。這個宏必須放在不論什么函數之外,一般是在源文件的頭部。比如:
static char *whom = "world";
static int howmany = 1;
module_param(howmany, int, S_IRUGO);
module_param(whom, charp, S_IRUGO);
內核支持的模塊參數類型有:?bool invbool charp int long short uint ulongushort
模塊裝載器也支持數組參數,在提供數組值時用逗號劃分個數組成員。要聲明數組參數,須要使用以下的宏:
module_param_arry(name, type, num, perm);
當中name是數組名稱。type是數組元素類型,num是一個整型變量。而perm它是一種常見的訪問許可證值。
版權聲明:本文博主原創文章,博客,未經同意不得轉載。
總結
以上是生活随笔為你收集整理的LINUX设备驱动程序的注意事项(两)建设和执行模块的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 升级OS10.11系统后 Xcode6.
- 下一篇: STM32F0308开发环境的选择--C