Linux設(shè)備驅(qū)動(dòng)程序?qū)W習(xí)----5.模塊的初始化和關(guān)閉

模塊的初始化和關(guān)閉

更多內(nèi)容請(qǐng)參考Linux設(shè)備驅(qū)動(dòng)程序?qū)W習(xí)----目錄

1. 初始化函數(shù)

??模塊的初始化函數(shù)負(fù)責(zé)注冊(cè)模塊所提供的任何設(shè)施,即可以被應(yīng)用程序訪問的新功能,可能是一個(gè)完整的驅(qū)動(dòng)程序或者僅僅是一個(gè)新的軟件抽象。初始化函數(shù)的定義通常如下所示:

static int __init initialization_function(void)
{
    // 初始化代碼
    
    return 0;
}
module_init(initialization_function);

??初始化函數(shù)被聲明為static,因?yàn)槌跏蓟瘮?shù)在特定文件之外沒有其他意義。__init標(biāo)記表明該函數(shù)僅在初始化期間使用。在模塊被裝載之后,模塊裝載器就會(huì)將初始化函數(shù)扔掉,可將該函數(shù)占用的內(nèi)存釋放出來。

??注意:不要在結(jié)束初始化之后仍要使用的函數(shù)或數(shù)據(jù)結(jié)構(gòu)上使用__init和__initdata標(biāo)記。對(duì)于__devinit和__devinitdata,只有在內(nèi)核未被配置為支持熱插拔設(shè)備的情況下,才會(huì)被翻譯為__init和__initdata。

??module_init()宏的使用是強(qiáng)制性的,會(huì)在模塊的目標(biāo)代碼中增加一個(gè)特殊的段,用于說明內(nèi)核初始化函數(shù)所在的位置。如果沒有這個(gè)定義,初始化函數(shù)永遠(yuǎn)不會(huì)被調(diào)用。

2. 清除函數(shù)

??每個(gè)模塊都需要一個(gè)清除函數(shù),在模塊被移除前注銷接口并向系統(tǒng)中返回所有資源。該函數(shù)定義如下:

static void __exit cleanup_function(void)
{
    // 清除代碼
}
module_exit(cleanup_function);

??清除函數(shù)沒有返回值,__exit修飾詞標(biāo)記該代碼僅用于模塊卸載,編譯器會(huì)把該函數(shù)放在特殊的ELF段中。如果模塊被直接編譯到內(nèi)核中,或者內(nèi)核配置不允許卸載模塊,則被標(biāo)記為__exit的函數(shù)將被直接丟棄。所以被標(biāo)記為__exit的函數(shù)只能在模塊被卸載或者系統(tǒng)關(guān)閉時(shí)被調(diào)用,其他任何用法都是錯(cuò)的。module_exit()聲明對(duì)于內(nèi)核找到模塊的清除函數(shù)是必需的。如果一個(gè)模塊未定義清除函數(shù),則內(nèi)核不允許卸載該模塊。

3. 初始化過程中的錯(cuò)誤處理

??在內(nèi)核中注冊(cè)設(shè)施時(shí),注冊(cè)可能會(huì)失敗。即使最簡(jiǎn)單的動(dòng)作,都需要內(nèi)存分配,而所需的內(nèi)存可能無法獲得。因此模塊代碼必須始終檢查返回值,并確保所請(qǐng)求的操作已真正執(zhí)行成功。如果在注冊(cè)設(shè)施時(shí)遇到錯(cuò)誤,首先要判斷模塊是否可以繼續(xù)初始化,只要可能,模塊應(yīng)該繼續(xù)向前并盡可能提供其功能。

??如果在發(fā)生了某個(gè)特定類型的錯(cuò)誤之后無法繼續(xù)裝載模塊,則要將出錯(cuò)之前的所有注冊(cè)工作都撤銷掉。即當(dāng)模塊的初始化出現(xiàn)錯(cuò)誤之后,模塊必須自行撤銷已注冊(cè)的設(shè)施。如果未能撤銷已注冊(cè)的設(shè)施,則內(nèi)核會(huì)處于一種不穩(wěn)定狀態(tài),這時(shí),唯一有效的解決辦法就是重新引導(dǎo)系統(tǒng)。所以必須在初始化過程出現(xiàn)錯(cuò)誤時(shí)認(rèn)真完成正確的工作。

??錯(cuò)誤恢復(fù)的處理有時(shí)使用goto語句非常有效。正常情況下,很少使用goto,但是唯一在錯(cuò)誤處理時(shí)卻非常有效。內(nèi)核經(jīng)常使用goto來處理錯(cuò)誤。如下例子所示:

int __init my_init_function(void)
{
    int err;
    
    // 使用指針和名稱注冊(cè)
    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; // 返回錯(cuò)誤
}

在出錯(cuò)的時(shí)候使用goto語句,將只撤銷出錯(cuò)時(shí)刻以前所成功注冊(cè)的那些設(shè)施。

??另一種方法是,記錄任何成功注冊(cè)的設(shè)施,在出錯(cuò)的時(shí)候調(diào)用模塊的清除函數(shù)。清除函數(shù)將僅僅回滾已成功完成的步驟。這種方法需要更多的代碼和CPU時(shí)間,因此在追求效率的代碼中使用goto語句是最好的錯(cuò)誤恢復(fù)機(jī)制。

??在Linux內(nèi)核中錯(cuò)誤編碼是定義在<linux/errno.h>頭文件中的負(fù)整數(shù),如果不想使用其他函數(shù)返回的錯(cuò)誤碼,應(yīng)該包含<linux/errno.h>頭文件,以使用如:-ENODEV、-ENOMEM之類的符號(hào)值。每次返回核時(shí)的錯(cuò)誤編碼是個(gè)好習(xí)慣,因?yàn)橛脩舫绦蚩梢酝ㄟ^perror()函數(shù)或類似途徑將錯(cuò)誤符號(hào)轉(zhuǎn)換為有意義的字符串。

??模塊的清除函數(shù)需要撤銷初始化函數(shù)所注冊(cè)的所有設(shè)施,并且習(xí)慣上以相反于注冊(cè)的順序撤銷設(shè)施,如下所示:

void __exit my_cleanup_function(void)
{
    unregister_those(ptr3, "skull");
    unregister_that(ptr2, "skull");
    unregister_this(ptr1, "skull");
    
    return;
}

??如果初始化和清除工作涉及很多設(shè)施,則goto方法可能難以管理,因?yàn)樗杏糜谇宄O(shè)施的代碼在初始化函數(shù)中給重復(fù),同時(shí)一些標(biāo)號(hào)交織在一起。

??每次發(fā)生錯(cuò)誤時(shí)從初始化函數(shù)中調(diào)用清除函數(shù),將減少代碼的重復(fù)并且時(shí)代碼更清晰、更有條理。清除函數(shù)必須在撤銷每項(xiàng)設(shè)施的注冊(cè)之前檢查它的狀態(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; // 返回錯(cuò)誤
}

??如上代碼所示,根據(jù)調(diào)用的注冊(cè)/分配函數(shù)的語義,可以使用或不使用外部標(biāo)記來標(biāo)記每個(gè)初始化步驟的成功。這種方式的初始化能很好地?cái)U(kuò)展到對(duì)大量設(shè)施的支持。注意:因?yàn)榍宄瘮?shù)被非退出代碼調(diào)用,因此不能將清除函數(shù)標(biāo)記為__exit;

4. 模塊裝載競(jìng)爭(zhēng)

??模塊裝載中也存在競(jìng)態(tài)。在模塊注冊(cè)完成之前,內(nèi)核的某些部分可能會(huì)立即使用我們剛剛注冊(cè)的任何設(shè)施,即在初始化函數(shù)還在運(yùn)行的時(shí)候,內(nèi)核就完全可能會(huì)調(diào)用我們的模塊。因此,在首次注冊(cè)完成之后,代碼就應(yīng)該準(zhǔn)備好被內(nèi)核的其他部分調(diào)用;在支持某個(gè)設(shè)施的所有內(nèi)部初始化完成之前,不要注冊(cè)任何設(shè)施。

??當(dāng)模塊初始化失敗而內(nèi)核的某些部分已經(jīng)使用了模塊所注冊(cè)的某個(gè)設(shè)施時(shí),此時(shí)根本不應(yīng)該出現(xiàn)模塊初始化失敗,因?yàn)槟K已經(jīng)成功導(dǎo)出了可用的功能及符號(hào)。如果初始化一定要失敗,則應(yīng)該仔細(xì)處理內(nèi)核其他部分正在進(jìn)行的操作,并且要等待這些操作的完成。

更多內(nèi)容請(qǐng)參考Linux設(shè)備驅(qū)動(dòng)程序?qū)W習(xí)----目錄

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容