模塊的初始化和關(guān)閉
更多內(nèi)容請參考Linux設(shè)備驅(qū)動程序?qū)W習(xí)----目錄
1. 初始化函數(shù)
??模塊的初始化函數(shù)負(fù)責(zé)注冊模塊所提供的任何設(shè)施,即可以被應(yīng)用程序訪問的新功能,可能是一個完整的驅(qū)動程序或者僅僅是一個新的軟件抽象。初始化函數(shù)的定義通常如下所示:
static int __init initialization_function(void)
{
// 初始化代碼
return 0;
}
module_init(initialization_function);
??初始化函數(shù)被聲明為static,因為初始化函數(shù)在特定文件之外沒有其他意義。__init標(biāo)記表明該函數(shù)僅在初始化期間使用。在模塊被裝載之后,模塊裝載器就會將初始化函數(shù)扔掉,可將該函數(shù)占用的內(nèi)存釋放出來。
??注意:不要在結(jié)束初始化之后仍要使用的函數(shù)或數(shù)據(jù)結(jié)構(gòu)上使用__init和__initdata標(biāo)記。對于__devinit和__devinitdata,只有在內(nèi)核未被配置為支持熱插拔設(shè)備的情況下,才會被翻譯為__init和__initdata。
??module_init()宏的使用是強制性的,會在模塊的目標(biāo)代碼中增加一個特殊的段,用于說明內(nèi)核初始化函數(shù)所在的位置。如果沒有這個定義,初始化函數(shù)永遠(yuǎn)不會被調(diào)用。
2. 清除函數(shù)
??每個模塊都需要一個清除函數(shù),在模塊被移除前注銷接口并向系統(tǒng)中返回所有資源。該函數(shù)定義如下:
static void __exit cleanup_function(void)
{
// 清除代碼
}
module_exit(cleanup_function);
??清除函數(shù)沒有返回值,__exit修飾詞標(biāo)記該代碼僅用于模塊卸載,編譯器會把該函數(shù)放在特殊的ELF段中。如果模塊被直接編譯到內(nèi)核中,或者內(nèi)核配置不允許卸載模塊,則被標(biāo)記為__exit的函數(shù)將被直接丟棄。所以被標(biāo)記為__exit的函數(shù)只能在模塊被卸載或者系統(tǒng)關(guān)閉時被調(diào)用,其他任何用法都是錯的。module_exit()聲明對于內(nèi)核找到模塊的清除函數(shù)是必需的。如果一個模塊未定義清除函數(shù),則內(nèi)核不允許卸載該模塊。
3. 初始化過程中的錯誤處理
??在內(nèi)核中注冊設(shè)施時,注冊可能會失敗。即使最簡單的動作,都需要內(nèi)存分配,而所需的內(nèi)存可能無法獲得。因此模塊代碼必須始終檢查返回值,并確保所請求的操作已真正執(zhí)行成功。如果在注冊設(shè)施時遇到錯誤,首先要判斷模塊是否可以繼續(xù)初始化,只要可能,模塊應(yīng)該繼續(xù)向前并盡可能提供其功能。
??如果在發(fā)生了某個特定類型的錯誤之后無法繼續(xù)裝載模塊,則要將出錯之前的所有注冊工作都撤銷掉。即當(dāng)模塊的初始化出現(xiàn)錯誤之后,模塊必須自行撤銷已注冊的設(shè)施。如果未能撤銷已注冊的設(shè)施,則內(nèi)核會處于一種不穩(wěn)定狀態(tài),這時,唯一有效的解決辦法就是重新引導(dǎo)系統(tǒng)。所以必須在初始化過程出現(xiàn)錯誤時認(rèn)真完成正確的工作。
??錯誤恢復(fù)的處理有時使用goto語句非常有效。正常情況下,很少使用goto,但是唯一在錯誤處理時卻非常有效。內(nèi)核經(jīng)常使用goto來處理錯誤。如下例子所示:
int __init my_init_function(void)
{
int err;
// 使用指針和名稱注冊
err = register_this(ptr1, "skull");
if (err)
goto fail_this;
err = register_that(ptr2, "skull");
if (err)
goto fail_that;
err = register_those(ptr3, "skull");
if (err)
goto fail_those;
return 0; // 成功
fail_those:
unregister_that(ptr2, "skull");
fail_that:
unregister_that(ptr1, "skull");
fail_this:
return err; // 返回錯誤
}
在出錯的時候使用goto語句,將只撤銷出錯時刻以前所成功注冊的那些設(shè)施。
??另一種方法是,記錄任何成功注冊的設(shè)施,在出錯的時候調(diào)用模塊的清除函數(shù)。清除函數(shù)將僅僅回滾已成功完成的步驟。這種方法需要更多的代碼和CPU時間,因此在追求效率的代碼中使用goto語句是最好的錯誤恢復(fù)機制。
??在Linux內(nèi)核中錯誤編碼是定義在<linux/errno.h>頭文件中的負(fù)整數(shù),如果不想使用其他函數(shù)返回的錯誤碼,應(yīng)該包含<linux/errno.h>頭文件,以使用如:-ENODEV、-ENOMEM之類的符號值。每次返回核時的錯誤編碼是個好習(xí)慣,因為用戶程序可以通過perror()函數(shù)或類似途徑將錯誤符號轉(zhuǎn)換為有意義的字符串。
??模塊的清除函數(shù)需要撤銷初始化函數(shù)所注冊的所有設(shè)施,并且習(xí)慣上以相反于注冊的順序撤銷設(shè)施,如下所示:
void __exit my_cleanup_function(void)
{
unregister_those(ptr3, "skull");
unregister_that(ptr2, "skull");
unregister_this(ptr1, "skull");
return;
}
??如果初始化和清除工作涉及很多設(shè)施,則goto方法可能難以管理,因為所有用于清除設(shè)施的代碼在初始化函數(shù)中給重復(fù),同時一些標(biāo)號交織在一起。
??每次發(fā)生錯誤時從初始化函數(shù)中調(diào)用清除函數(shù),將減少代碼的重復(fù)并且時代碼更清晰、更有條理。清除函數(shù)必須在撤銷每項設(shè)施的注冊之前檢查它的狀態(tài)。如下示例:
struct something *item1;
struct somethingelse *item2;
void my_cleanup(void)
{
if (item1)
release_thing(item1);
if (item2)
release_thing2(item2);
if (stuff_ok)
unregister_stuff();
return;
}
int __init my_init(void)
{
int err = -ENOMEM;
item1 = allocate_thing(arguments);
item2 = allocate_thing2(arguments2);
if (!item1 || !item2)
goto fail;
err = register_stuff(item1, item2);
if (!err)
stuff_ok = 1;
else
goto fail;
return 0; // 返回成功
fail:
my_cleanup();
return err; // 返回錯誤
}
??如上代碼所示,根據(jù)調(diào)用的注冊/分配函數(shù)的語義,可以使用或不使用外部標(biāo)記來標(biāo)記每個初始化步驟的成功。這種方式的初始化能很好地擴(kuò)展到對大量設(shè)施的支持。注意:因為清除函數(shù)被非退出代碼調(diào)用,因此不能將清除函數(shù)標(biāo)記為__exit;
4. 模塊裝載競爭
??模塊裝載中也存在競態(tài)。在模塊注冊完成之前,內(nèi)核的某些部分可能會立即使用我們剛剛注冊的任何設(shè)施,即在初始化函數(shù)還在運行的時候,內(nèi)核就完全可能會調(diào)用我們的模塊。因此,在首次注冊完成之后,代碼就應(yīng)該準(zhǔn)備好被內(nèi)核的其他部分調(diào)用;在支持某個設(shè)施的所有內(nèi)部初始化完成之前,不要注冊任何設(shè)施。
??當(dāng)模塊初始化失敗而內(nèi)核的某些部分已經(jīng)使用了模塊所注冊的某個設(shè)施時,此時根本不應(yīng)該出現(xiàn)模塊初始化失敗,因為模塊已經(jīng)成功導(dǎo)出了可用的功能及符號。如果初始化一定要失敗,則應(yīng)該仔細(xì)處理內(nèi)核其他部分正在進(jìn)行的操作,并且要等待這些操作的完成。
更多內(nèi)容請參考Linux設(shè)備驅(qū)動程序?qū)W習(xí)----目錄