2.7. 初始化和關閉
模組的初始化函式負責註冊模組所提供的任何設施。這裡的設施指的是乙個可以被應用程式訪問的新功能,它可能是乙個完整的驅動程式或者僅僅是乙個新的軟體抽象。初始化函式定義通常如下所示:
static int __init initialization_function(void)
module_init(initialization_function);
分析:初始化函式應該被宣告為static ,因為這種函式在特定檔案之外沒有其它意義。因為乙個模組函式如果要對核心其它部分可見,則必須被顯式匯出,這並不是什麼強制性規則。__init標記對核心來講是一種暗示,表明該函式僅在初始化期間使用。在模組被載入之後,模組載入器就會將初始化函式扔掉,可將函式占用的記憶體釋放出來。__init和__initdata的使用是可選的,雖然有點繁瑣,但是很值得使用。注意:不要在結束初始化之後仍要使用的函式或者資料結構上使用這兩個標記。在核心源**中可能還會遇到__devinit 和 __devinitdata,只有在核心未配置支援熱插拔的情況下,這兩個標記才會被翻譯為__init和__initdata。
module_init的使用是強制性的。這個巨集會在模組的目標**中增加乙個特殊的段,用於說明核心初始化函式所在的位置。沒有這個定義,初始化函式不會被呼叫。
模組可以註冊許多不同型別的設施,包括不同型別的裝置、檔案系統、密碼變換等。對每種設施,對應有具體的核心函式用來完成註冊。傳遞到核心註冊函式中的引數通常是指向用來描述新設施以及設施名稱的資料結構指標,而資料結構通常包含指向模組函式的指標,這樣,模組體中的函式就會在恰當的時間被核心呼叫。
能夠註冊的設施型別包括串列埠、雜項裝置、sysfs入口、/proc檔案、可執行域以及線路規程等。很多可註冊的設施所支援的功能屬於軟體抽象範疇,而不與任何硬體直接相關。這種型別的設施能夠被註冊,是因為它們能夠以某種方式整合到驅動程式功能當中。
還有其他一些設施可以註冊為特定驅動程式的附加功能,但它們的用途有限,它們使用核心符號表一節中提到的層疊技術。可以在核心原始碼中grep export_symbol ,並找出由不同驅動程式提供的入口點。大部分註冊函式以 register_做字首,因此找到它們的另一種方法是在核心原始碼中grep register_。
2.7.1. 清除函式
每個重要的模組都需要乙個清除函式,該函式在模組被移除前登出介面並向系統中返回所有資源。該函式的定義如下:
static void __exit cleanup_function(void)
module_exit(cleanup_function);
清除函式沒有返回值,因此被宣告為void。 __exit修飾詞標記該**僅用於模組解除安裝(編譯器將把該函式放在特殊的elf段)。如果模組被直接內嵌到核心中,或者核心的配置不允許解除安裝模組,則被標記為__exit的函式將被丟棄。出於以上原因,被標記為__exit的函式只能在模組被解除安裝或者系統被關閉時呼叫,其它的任何用法都是錯誤的。module_exit宣告用於幫助核心找到模組的清除函式是必需的。
如果乙個模組為定義清除函式,則核心不允許解除安裝該模組。
2.7.2. 初始化過程中的錯誤處理
當在核心中註冊設施時,要時刻銘記註冊可能會失敗。即使是最簡單的動作,都需要記憶體分配,而所需的記憶體可能無法獲得。因此模組**必須始終檢查返回值,並確保所請求的操作已真正成功。
如果在註冊設施時遇到任何錯誤,首先要判斷模組是否可以繼續初始化。通常,在某個註冊失敗後可以通過降低功能來繼續運轉。因此,只要可能,模組應該繼續向前並盡可能提供其功能。
如果在發生了某個特定型別的錯誤之後無法繼續裝載模組,則要將出錯之前的任何註冊工作撤銷掉。linux中沒有記錄每個模組都註冊了哪些設施,因此,當模組的初始化出現錯誤之後,模組必須自己撤銷已註冊的設施。如果由於某種原因未能撤銷已註冊的設施,則核心會處於一種不穩定狀態,這是因為核心中包含了一些指向並不存在的**的內部指標。在這種情況下,唯一有效的解決辦法是重新引導系統。因此,必須在初始化過程出現錯誤時認真完成正確的工作。錯誤恢復的處理有時使用goto語句比較有效。通常情況下很少使用goto,但在處理錯誤時(可能是唯一的情況)goto卻非常有用。錯誤情況下的goto的仔細使用可避免大量複雜的、高度縮排的結構化邏輯。因此,核心經常使用goto來處理錯誤。不管初始化過程在什麼時候失敗,下面的例子(使用了虛構的註冊和撤銷註冊函式)都能正確工作:
int __init my_init_function(void)
如果初始化和清除工作涉及很多設施,則goto方法可能變得難以管理,因為所有用於清除設施的**在初始化函式中重複,同時一些標號交織在一起。因此,有時需要考慮重新構思**的結構。
每當發生錯誤時從初始化函式中呼叫清除函式,這種方法將減少**的重複並且是**更清晰、更有條例。當然,清除函式必須在撤銷每項設施的註冊之前檢查它的狀態。
struct something *item1;
struct somethingelse *item2;
int stuff_ok;
void my_cleanup(void)
如這段**表示,根據呼叫的註冊、分配函式的語義,可以使用或不使用外部標誌來標記每個初始化步驟的成功。不管是否需要使用標誌,這種方式的初始化能夠很好地擴充套件到對大量設施的支援,因此比前面介紹的技術更具優越性。需要注意的是,因為清除函式被非退出**呼叫,不能將清除函式標記為__exit。
2.7.3. 模組裝載競爭
首先,在註冊完成之後,核心的某些部分可能會立即使用剛剛註冊的任何設施。在初始化函式還在執行時,核心完全可能會呼叫自己的模組。因此,在首次註冊完成之後,**就應該準備好被核心的其它部分呼叫;在用來支援某個設施的所有內部初始化完成之前,不要註冊任何設施。
還必須考慮,當初始化失敗而核心的某些部分已經使用了模組所註冊的某個設施時應該如何處理。如果這種情況可能發生在自己的模組上,則根本不應該出現初始化失敗的情況,畢竟模組已經成功匯出了可用的功能及符號。如果初始化一定要失敗,則應該仔細處理核心其它部分正在進行的操作,並且要等待這些操作的完成。
Linux裝置驅動之第 2 章 預備知識
2.6.預備知識 核心是乙個特定的環境,對需要和它介面的 有自己的一些要求。大部分核心 中都要包含相當數量的標頭檔案,以便獲得函式 資料型別和變數的定義。有幾個標頭檔案是用於模組的,必須出現在每個可裝載的模組中。所有的模組 中都包含下面兩行 include include linux module....
Linux裝置驅動第 2 章之 模組引數
2.8.模組引數 由於系統的不同,驅動程式需要的引數也許會發生變化。這包括裝置編號以及其它一些用來控制驅動程式操作方式的引數。例如,scsi介面卡的驅動程式經常要處理一些選項,這些選項用來控制標記命令佇列的使用,而整合裝置電路驅動程式允許使用者控制dma操作。如果驅動程式用來控制一些早期的硬體,也許...
第1章Linux裝置驅動簡介二
1.2 劃分核心 在 unix 系統中,幾個併發的程序專注於不同的任務.每個程序請求系統資源,比如計算能力,記憶體,網路連線,或者一些別的資源.核心是個大塊的可執行檔案,負責處理所有這樣的請求.儘管不同核心任務間的區別常常不是能清楚劃分,核心的角色可以劃分成下列幾個部分 如圖1.1 1 程序管理 核...