99re热视频这里只精品,久久久天堂国产精品女人,国产av一区二区三区,久久久精品成人免费看片,99久久精品免费看国产一区二区三区

2.7. 初始化和關(guān)停

2018-02-24 15:49 更新

2.7.?初始化和關(guān)停

如已提到的, 模塊初始化函數(shù)注冊(cè)模塊提供的任何功能. 這些功能, 我們指的是新功能, 可以由應(yīng)用程序存取的或者一整個(gè)驅(qū)動(dòng)或者一個(gè)新軟件抽象. 實(shí)際的初始化函數(shù)定義常常如:


static int __init initialization_function(void)
{

 /* Initialization code here */
}
module_init(initialization_function);

初始化函數(shù)應(yīng)當(dāng)聲明成靜態(tài)的, 因?yàn)樗鼈儾粫?huì)在特定文件之外可見(jiàn); 沒(méi)有硬性規(guī)定這個(gè), 然而, 因?yàn)闆](méi)有函數(shù)能輸出給內(nèi)核其他部分, 除非明確請(qǐng)求. 聲明中的 init 標(biāo)志可能看起來(lái)有點(diǎn)怪; 它是一個(gè)給內(nèi)核的暗示, 給定的函數(shù)只是在初始化使用. 模塊加載者在模塊加載后會(huì)丟掉這個(gè)初始化函數(shù), 使它的內(nèi)存可做其他用途. 一個(gè)類(lèi)似的標(biāo)簽 (initdata) 給只在初始化時(shí)用的數(shù)據(jù). 使用 init 和 initdata 是可選的, 但是它帶來(lái)的麻煩是值得的. 只是要確認(rèn)不要用在那些在初始化完成后還使用的函數(shù)(或者數(shù)據(jù)結(jié)構(gòu))上. 你可能還會(huì)遇到 devinit 和 devinitdata 在內(nèi)核源碼里; 這些只在內(nèi)核沒(méi)有配置支持 hotplug 設(shè)備時(shí)轉(zhuǎn)換成 __init 和 _initdata. 我們會(huì)在 14 章談?wù)?hotplug 支持.

使用 moudle_init 是強(qiáng)制的. 這個(gè)宏定義增加了特別的段到模塊目標(biāo)代碼中, 表明在哪里找到模塊的初始化函數(shù). 沒(méi)有這個(gè)定義, 你的初始化函數(shù)不會(huì)被調(diào)用.

模塊可以注冊(cè)許多的不同設(shè)施, 包括不同類(lèi)型的設(shè)備, 文件系統(tǒng), 加密轉(zhuǎn)換, 以及更多. 對(duì)每一個(gè)設(shè)施, 有一個(gè)特定的內(nèi)核函數(shù)來(lái)完成這個(gè)注冊(cè). 傳給內(nèi)核注冊(cè)函數(shù)的參數(shù)常常是一些數(shù)據(jù)結(jié)構(gòu)的指針, 描述新設(shè)施以及要注冊(cè)的新設(shè)施的名子. 數(shù)據(jù)結(jié)構(gòu)常常包含模塊函數(shù)指針, 模塊中的函數(shù)就是這樣被調(diào)用的.

能夠注冊(cè)的項(xiàng)目遠(yuǎn)遠(yuǎn)超出第 1 章中提到的設(shè)備類(lèi)型列表. 它們包括, 其他的, 串口, 多樣設(shè)備, sysfs 入口, /proc 文件, 執(zhí)行域, 鏈路規(guī)程. 這些可注冊(cè)項(xiàng)的大部分都支持不直接和硬件相關(guān)的函數(shù), 但是處于"軟件抽象"區(qū)域里. 這些項(xiàng)可以注冊(cè), 是因?yàn)樗鼈円愿鞣N方式(例如象 /proc 文件和鏈路規(guī)程)集成在驅(qū)動(dòng)的功能中.

對(duì)某些驅(qū)動(dòng)有其他的設(shè)施可以注冊(cè)作為補(bǔ)充, 但它們的使用太特別, 所以不值得討論它們. 它們使用堆疊技術(shù), 在"內(nèi)核符號(hào)表"一節(jié)中講過(guò). 如果你想深入探求, 你可以在內(nèi)核源碼里查找 EXPORTSYMBOL , 找到由不同驅(qū)動(dòng)提供的入口點(diǎn). 大部分注冊(cè)函數(shù)以 register 做前綴, 因此找到它們的另外一個(gè)方法是在內(nèi)核源碼里查找 register_ .

2.7.1.?清理函數(shù)

每個(gè)非試驗(yàn)性的模塊也要求有一個(gè)清理函數(shù), 它注銷(xiāo)接口, 在模塊被去除之前返回所有資源給系統(tǒng). 這個(gè)函數(shù)定義為:


static void __exit cleanup_function(void)
{
 /* Cleanup code here */
}

module_exit(cleanup_function);

清理函數(shù)沒(méi)有返回值, 因此它被聲明為 void. exit 修飾符標(biāo)識(shí)這個(gè)代碼是只用于模塊卸載( 通過(guò)使編譯器把它放在特殊的 ELF 段). 如果你的模塊直接建立在內(nèi)核里, 或者如果你的內(nèi)核配置成不允許模塊卸載, 標(biāo)識(shí)為 exit 的函數(shù)被簡(jiǎn)單地丟棄. 因?yàn)檫@個(gè)原因, 一個(gè)標(biāo)識(shí) __exit 的函數(shù)只在模塊卸載或者系統(tǒng)停止時(shí)調(diào)用; 任何別的使用是錯(cuò)的. 再一次, moudle_exit 聲明對(duì)于使得內(nèi)核能夠找到你的清理函數(shù)是必要的.

如果你的模塊沒(méi)有定義一個(gè)清理函數(shù), 內(nèi)核不會(huì)允許它被卸載.

2.7.2.?初始化中的錯(cuò)誤處理

你必須記住一件事, 在注冊(cè)內(nèi)核設(shè)施時(shí), 注冊(cè)可能失敗. 即便最簡(jiǎn)單的動(dòng)作常常需要內(nèi)存分配, 分配的內(nèi)存可能不可用. 因此模塊代碼必須一直檢查返回值, 并且確認(rèn)要求的操作實(shí)際上已經(jīng)成功.

如果在你注冊(cè)工具時(shí)發(fā)生任何錯(cuò)誤, 首先第一的事情是決定模塊是否能夠無(wú)論如何繼續(xù)初始化它自己. 常常, 在一個(gè)注冊(cè)失敗后模塊可以繼續(xù)操作, 如果需要可以功能降級(jí). 在任何可能的時(shí)候, 你的模塊應(yīng)當(dāng)盡力向前, 并提供事情失敗后具備的能力.

如果證實(shí)你的模塊在一個(gè)特別類(lèi)型的失敗后完全不能加載, 你必須取消任何在失敗前注冊(cè)的動(dòng)作. 內(nèi)核不保留已經(jīng)注冊(cè)的設(shè)施的每模塊注冊(cè), 因此如果初始化在某個(gè)點(diǎn)失敗, 模塊必須能自己退回所有東西. 如果你無(wú)法注銷(xiāo)你獲取的東西, 內(nèi)核就被置于一個(gè)不穩(wěn)定狀態(tài); 它包含了不存在的代碼的內(nèi)部指針. 這種情況下, 經(jīng)常地, 唯一的方法就是重啟系統(tǒng). 在初始化錯(cuò)誤發(fā)生時(shí), 你確實(shí)要小心地將事情做正確.

錯(cuò)誤恢復(fù)有時(shí)用 goto 語(yǔ)句處理是最好的. 我們通常不愿使用 goto, 但是在我們的觀念里, 這是一個(gè)它有用的地方. 在錯(cuò)誤情形下小心使用 goto 可以去掉大量的復(fù)雜, 過(guò)度對(duì)齊的, "結(jié)構(gòu)形" 的邏輯. 因此, 在內(nèi)核里, goto 是處理錯(cuò)誤經(jīng)常用到, 如這里顯示的.

下面例子代碼( 使用設(shè)施注冊(cè)和注銷(xiāo)函數(shù))在初始化在任何點(diǎn)失敗時(shí)做得正確:


int __init my_init_function(void)
{
        int err;
        /* registration takes a pointer and a name */
        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; /* success */
fail_those:
        unregister_that(ptr2, "skull");
fail_that:
        unregister_this(ptr1, "skull");
fail_this:
        return err; /* propagate the error */

}

這段代碼試圖注冊(cè) 3 個(gè)(虛構(gòu)的)設(shè)施. goto 語(yǔ)句在失敗情況下使用, 在事情變壞之前只對(duì)之前已經(jīng)成功注冊(cè)的設(shè)施進(jìn)行注銷(xiāo).

另一個(gè)選項(xiàng), 不需要繁多的 goto 語(yǔ)句, 是跟蹤已經(jīng)成功注冊(cè)的, 并且在任何出錯(cuò)情況下調(diào)用你的模塊的清理函數(shù). 清理函數(shù)只回卷那些已經(jīng)成功完成的步驟. 然而這種選擇, 需要更多代碼和更多 CPU 時(shí)間, 因此在快速途徑下, 你仍然依賴(lài)于 goto 作為最好的錯(cuò)誤恢復(fù)工具.

my_init_function 的返回值, err, 是一個(gè)錯(cuò)誤碼. 在 Linux 內(nèi)核里, 錯(cuò)誤碼是負(fù)數(shù), 屬于定義于 <linux/errno.h> 的集合. 如果你需要產(chǎn)生你自己的錯(cuò)誤碼代替你從其他函數(shù)得到的返回值, 你應(yīng)當(dāng)包含 <linux/errno.h> 以便使用符號(hào)式的返回值, 例如 -ENODEV, -ENOMEM, 等等. 返回適當(dāng)?shù)腻e(cuò)誤碼總是一個(gè)好做法, 因?yàn)橛脩?hù)程序能夠把它們轉(zhuǎn)變?yōu)橛幸饬x的字串, 使用 perror 或者類(lèi)似的方法.

顯然, 模塊清理函數(shù)必須撤銷(xiāo)任何由初始化函數(shù)進(jìn)行的注冊(cè), 并且慣例(但常常不是要求的)是按照注冊(cè)時(shí)相反的順序注銷(xiāo)設(shè)施.


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

} 

如果你的初始化和清理比處理幾項(xiàng)復(fù)雜, goto 方法可能變得難于管理, 因?yàn)樗械那謇泶a必須在初始化函數(shù)里重復(fù), 包括幾個(gè)混合的標(biāo)號(hào). 有時(shí), 因此, 一種不同的代碼排布證明更成功.

使代碼重復(fù)最小和所有東西流線化, 你應(yīng)當(dāng)做的是無(wú)論何時(shí)發(fā)生錯(cuò)誤都從初始化里調(diào)用清理函數(shù). 清理函數(shù)接著必須在撤銷(xiāo)它的注冊(cè)前檢查每一項(xiàng)的狀態(tài). 以最簡(jiǎn)單的形式, 代碼看起來(lái)象這樣:


struct something *item1;
struct somethingelse *item2;
int stuff_ok;

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 (!item2 || !item2)
                goto fail;

        err = register_stuff(item1, item2);
        if (!err)
                stuff_ok = 1;
        else
                goto fail;
        return 0; /* success */

fail:
        my_cleanup();
        return err;
}

如這段代碼所示, 你也許需要, 也許不要外部的標(biāo)志來(lái)標(biāo)識(shí)初始化步驟的成功, 要依賴(lài)你調(diào)用的注冊(cè)/分配函數(shù)的語(yǔ)義. 不管要不要標(biāo)志, 這種初始化會(huì)變得包含大量的項(xiàng), 常常比之前展示的技術(shù)要好. 注意, 但是, 清理函數(shù)當(dāng)由非退出代碼調(diào)用時(shí)不能標(biāo)志為 __exit, 如同前面的例子.

2.7.3.?模塊加載競(jìng)爭(zhēng)

到目前, 我們的討論已來(lái)到一個(gè)模塊加載的重要方面: 競(jìng)爭(zhēng)情況. 如果你在如何編寫(xiě)你的初始化函數(shù)上不小心, 你可能造成威脅到整個(gè)系統(tǒng)的穩(wěn)定的情形. 我們將在本書(shū)稍后討論競(jìng)爭(zhēng)情況; 現(xiàn)在, 快速提幾點(diǎn)就足夠了:

首先時(shí)你應(yīng)該一直記住, 內(nèi)核的某些別的部分會(huì)在注冊(cè)完成之后馬上使用任何你注冊(cè)的設(shè)施. 這是完全可能的, 換句話(huà)說(shuō), 內(nèi)核將調(diào)用進(jìn)你的模塊, 在你的初始化函數(shù)仍然在運(yùn)行時(shí). 所以你的代碼必須準(zhǔn)備好被調(diào)用, 一旦它完成了它的第一個(gè)注冊(cè). 不要注冊(cè)任何設(shè)施, 直到所有的需要支持那個(gè)設(shè)施的你的內(nèi)部初始化已經(jīng)完成.

你也必須考慮到如果你的初始化函數(shù)決定失敗會(huì)發(fā)生什么, 但是內(nèi)核的一部分已經(jīng)在使用你的模塊已注冊(cè)的設(shè)施. 如果這種情況對(duì)你的模塊是可能的, 你應(yīng)當(dāng)認(rèn)真考慮根本不要使初始化失敗. 畢竟, 模塊已清楚地成功輸出一些有用的東西. 如果初始化必須失敗, 必須小心地處理任何可能的在內(nèi)核別處發(fā)生的操作, 直到這些操作已完成.

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)