W3Cschool
恭喜您成為首批注冊用戶
獲得88經(jīng)驗值獎勵
如已提到的, 模塊初始化函數(shù)注冊模塊提供的任何功能. 這些功能, 我們指的是新功能, 可以由應(yīng)用程序存取的或者一整個驅(qū)動或者一個新軟件抽象. 實際的初始化函數(shù)定義常常如:
static int __init initialization_function(void)
{
/* Initialization code here */
}
module_init(initialization_function);
初始化函數(shù)應(yīng)當聲明成靜態(tài)的, 因為它們不會在特定文件之外可見; 沒有硬性規(guī)定這個, 然而, 因為沒有函數(shù)能輸出給內(nèi)核其他部分, 除非明確請求. 聲明中的 init 標志可能看起來有點怪; 它是一個給內(nèi)核的暗示, 給定的函數(shù)只是在初始化使用. 模塊加載者在模塊加載后會丟掉這個初始化函數(shù), 使它的內(nèi)存可做其他用途. 一個類似的標簽 (initdata) 給只在初始化時用的數(shù)據(jù). 使用 init 和 initdata 是可選的, 但是它帶來的麻煩是值得的. 只是要確認不要用在那些在初始化完成后還使用的函數(shù)(或者數(shù)據(jù)結(jié)構(gòu))上. 你可能還會遇到 devinit 和 devinitdata 在內(nèi)核源碼里; 這些只在內(nèi)核沒有配置支持 hotplug 設(shè)備時轉(zhuǎn)換成 __init 和 _initdata. 我們會在 14 章談?wù)?hotplug 支持.
使用 moudle_init 是強制的. 這個宏定義增加了特別的段到模塊目標代碼中, 表明在哪里找到模塊的初始化函數(shù). 沒有這個定義, 你的初始化函數(shù)不會被調(diào)用.
模塊可以注冊許多的不同設(shè)施, 包括不同類型的設(shè)備, 文件系統(tǒng), 加密轉(zhuǎn)換, 以及更多. 對每一個設(shè)施, 有一個特定的內(nèi)核函數(shù)來完成這個注冊. 傳給內(nèi)核注冊函數(shù)的參數(shù)常常是一些數(shù)據(jù)結(jié)構(gòu)的指針, 描述新設(shè)施以及要注冊的新設(shè)施的名子. 數(shù)據(jù)結(jié)構(gòu)常常包含模塊函數(shù)指針, 模塊中的函數(shù)就是這樣被調(diào)用的.
能夠注冊的項目遠遠超出第 1 章中提到的設(shè)備類型列表. 它們包括, 其他的, 串口, 多樣設(shè)備, sysfs 入口, /proc 文件, 執(zhí)行域, 鏈路規(guī)程. 這些可注冊項的大部分都支持不直接和硬件相關(guān)的函數(shù), 但是處于"軟件抽象"區(qū)域里. 這些項可以注冊, 是因為它們以各種方式(例如象 /proc 文件和鏈路規(guī)程)集成在驅(qū)動的功能中.
對某些驅(qū)動有其他的設(shè)施可以注冊作為補充, 但它們的使用太特別, 所以不值得討論它們. 它們使用堆疊技術(shù), 在"內(nèi)核符號表"一節(jié)中講過. 如果你想深入探求, 你可以在內(nèi)核源碼里查找 EXPORTSYMBOL , 找到由不同驅(qū)動提供的入口點. 大部分注冊函數(shù)以 register 做前綴, 因此找到它們的另外一個方法是在內(nèi)核源碼里查找 register_ .
每個非試驗性的模塊也要求有一個清理函數(shù), 它注銷接口, 在模塊被去除之前返回所有資源給系統(tǒng). 這個函數(shù)定義為:
static void __exit cleanup_function(void)
{
/* Cleanup code here */
}
module_exit(cleanup_function);
清理函數(shù)沒有返回值, 因此它被聲明為 void. exit 修飾符標識這個代碼是只用于模塊卸載( 通過使編譯器把它放在特殊的 ELF 段). 如果你的模塊直接建立在內(nèi)核里, 或者如果你的內(nèi)核配置成不允許模塊卸載, 標識為 exit 的函數(shù)被簡單地丟棄. 因為這個原因, 一個標識 __exit 的函數(shù)只在模塊卸載或者系統(tǒng)停止時調(diào)用; 任何別的使用是錯的. 再一次, moudle_exit 聲明對于使得內(nèi)核能夠找到你的清理函數(shù)是必要的.
如果你的模塊沒有定義一個清理函數(shù), 內(nèi)核不會允許它被卸載.
你必須記住一件事, 在注冊內(nèi)核設(shè)施時, 注冊可能失敗. 即便最簡單的動作常常需要內(nèi)存分配, 分配的內(nèi)存可能不可用. 因此模塊代碼必須一直檢查返回值, 并且確認要求的操作實際上已經(jīng)成功.
如果在你注冊工具時發(fā)生任何錯誤, 首先第一的事情是決定模塊是否能夠無論如何繼續(xù)初始化它自己. 常常, 在一個注冊失敗后模塊可以繼續(xù)操作, 如果需要可以功能降級. 在任何可能的時候, 你的模塊應(yīng)當盡力向前, 并提供事情失敗后具備的能力.
如果證實你的模塊在一個特別類型的失敗后完全不能加載, 你必須取消任何在失敗前注冊的動作. 內(nèi)核不保留已經(jīng)注冊的設(shè)施的每模塊注冊, 因此如果初始化在某個點失敗, 模塊必須能自己退回所有東西. 如果你無法注銷你獲取的東西, 內(nèi)核就被置于一個不穩(wěn)定狀態(tài); 它包含了不存在的代碼的內(nèi)部指針. 這種情況下, 經(jīng)常地, 唯一的方法就是重啟系統(tǒng). 在初始化錯誤發(fā)生時, 你確實要小心地將事情做正確.
錯誤恢復(fù)有時用 goto 語句處理是最好的. 我們通常不愿使用 goto, 但是在我們的觀念里, 這是一個它有用的地方. 在錯誤情形下小心使用 goto 可以去掉大量的復(fù)雜, 過度對齊的, "結(jié)構(gòu)形" 的邏輯. 因此, 在內(nèi)核里, goto 是處理錯誤經(jīng)常用到, 如這里顯示的.
下面例子代碼( 使用設(shè)施注冊和注銷函數(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 */
}
這段代碼試圖注冊 3 個(虛構(gòu)的)設(shè)施. goto 語句在失敗情況下使用, 在事情變壞之前只對之前已經(jīng)成功注冊的設(shè)施進行注銷.
另一個選項, 不需要繁多的 goto 語句, 是跟蹤已經(jīng)成功注冊的, 并且在任何出錯情況下調(diào)用你的模塊的清理函數(shù). 清理函數(shù)只回卷那些已經(jīng)成功完成的步驟. 然而這種選擇, 需要更多代碼和更多 CPU 時間, 因此在快速途徑下, 你仍然依賴于 goto 作為最好的錯誤恢復(fù)工具.
my_init_function 的返回值, err, 是一個錯誤碼. 在 Linux 內(nèi)核里, 錯誤碼是負數(shù), 屬于定義于 <linux/errno.h> 的集合. 如果你需要產(chǎn)生你自己的錯誤碼代替你從其他函數(shù)得到的返回值, 你應(yīng)當包含 <linux/errno.h> 以便使用符號式的返回值, 例如 -ENODEV, -ENOMEM, 等等. 返回適當?shù)腻e誤碼總是一個好做法, 因為用戶程序能夠把它們轉(zhuǎn)變?yōu)橛幸饬x的字串, 使用 perror 或者類似的方法.
顯然, 模塊清理函數(shù)必須撤銷任何由初始化函數(shù)進行的注冊, 并且慣例(但常常不是要求的)是按照注冊時相反的順序注銷設(shè)施.
void __exit my_cleanup_function(void)
{
unregister_those(ptr3, "skull");
unregister_that(ptr2, "skull");
unregister_this(ptr1, "skull");
return;
}
如果你的初始化和清理比處理幾項復(fù)雜, goto 方法可能變得難于管理, 因為所有的清理代碼必須在初始化函數(shù)里重復(fù), 包括幾個混合的標號. 有時, 因此, 一種不同的代碼排布證明更成功.
使代碼重復(fù)最小和所有東西流線化, 你應(yīng)當做的是無論何時發(fā)生錯誤都從初始化里調(diào)用清理函數(shù). 清理函數(shù)接著必須在撤銷它的注冊前檢查每一項的狀態(tà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;
}
如這段代碼所示, 你也許需要, 也許不要外部的標志來標識初始化步驟的成功, 要依賴你調(diào)用的注冊/分配函數(shù)的語義. 不管要不要標志, 這種初始化會變得包含大量的項, 常常比之前展示的技術(shù)要好. 注意, 但是, 清理函數(shù)當由非退出代碼調(diào)用時不能標志為 __exit, 如同前面的例子.
到目前, 我們的討論已來到一個模塊加載的重要方面: 競爭情況. 如果你在如何編寫你的初始化函數(shù)上不小心, 你可能造成威脅到整個系統(tǒng)的穩(wěn)定的情形. 我們將在本書稍后討論競爭情況; 現(xiàn)在, 快速提幾點就足夠了:
首先時你應(yīng)該一直記住, 內(nèi)核的某些別的部分會在注冊完成之后馬上使用任何你注冊的設(shè)施. 這是完全可能的, 換句話說, 內(nèi)核將調(diào)用進你的模塊, 在你的初始化函數(shù)仍然在運行時. 所以你的代碼必須準備好被調(diào)用, 一旦它完成了它的第一個注冊. 不要注冊任何設(shè)施, 直到所有的需要支持那個設(shè)施的你的內(nèi)部初始化已經(jīng)完成.
你也必須考慮到如果你的初始化函數(shù)決定失敗會發(fā)生什么, 但是內(nèi)核的一部分已經(jīng)在使用你的模塊已注冊的設(shè)施. 如果這種情況對你的模塊是可能的, 你應(yīng)當認真考慮根本不要使初始化失敗. 畢竟, 模塊已清楚地成功輸出一些有用的東西. 如果初始化必須失敗, 必須小心地處理任何可能的在內(nèi)核別處發(fā)生的操作, 直到這些操作已完成.
Copyright©2021 w3cschool編程獅|閩ICP備15016281號-3|閩公網(wǎng)安備35020302033924號
違法和不良信息舉報電話:173-0602-2364|舉報郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號
聯(lián)系方式:
更多建議: