linux驅動的入口函數module_init的載入和釋放

來源:互聯網
上載者:User

      就像你寫C程式需要包含C庫的標頭檔那樣,Linux核心編程也需要包含Kernel標頭檔,大多的Linux驅動程式需要包含下面三個標頭檔:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
其中,init.h 定義了驅動的初始化和退出相關的函數,kernel.h 定義了經常用到的函數原型及宏定義,module.h 定義了核心模組相關的函數、變數及宏。

      幾乎每個linux驅動都有個module_init(與module_exit的定義在Init.h (/include/linux) 中)。沒錯,驅動的載入就靠它。為什麼需要這樣一個宏?原因是按照一般的編程想法,各部分的初始化函數會在一個固定的函數裡調用比如:

void init(void)

{

    init_a();

    init_b();

}

如果再加入一個初始化函數呢,那麼在init_b()後面再加一行:init_c();這樣確實能完成我們的功能,但這樣有一定的問題,就是不能獨立的添加初始化函數,每次添加一個新的函數都要修改init函數。可以採用另一種方式來處理這個問題,只要用一個宏來修飾一下:

void init_a(void)

{

}

__initlist(init_a, 1);

它是怎麼樣通過這個宏來實現初始化函數列表的呢?先來看__initlist的定義:

#define __init __attribute__((unused, __section__(".initlist")))#define __initlist(fn, lvl) /static initlist_t __init_##fn __init = { / magic:    INIT_MAGIC, / callback: fn, / level:   lvl }

請注意:__section__(".initlist"),這個屬性起什麼作用呢?它告訴連接器這個變數存放在.initlist區段,如果所有的初始化函數都是用這個宏,那麼每個函數會有對應的一個initlist_t結構體變數存放在.initlist區段,也就是說我們可以在.initlist區段找到所有初始化函數的指標。怎麼找到.initlist區段的地址呢?

extern u32 __initlist_start;
extern u32 __initlist_end;

這兩個變數起作用了,__initlist_start是.initlist區段的開始,__initlist_end是結束,通過這兩個變數我們就可以訪問到所有的初始化函數了。這兩個變數在那定義的呢?在一個連接器指令檔裡

 . = ALIGN(4); .initlist : {  __initlist_start = .;  *(.initlist)  __initlist_end = .; }

這兩個變數的值正好定義在.initlist區段的開始和結束位址,所以我們能通過這兩個變數訪問到所有的初始化函數。

 

      與此類似,核心中也是用到這種方法,所以我們寫驅動的時候比較獨立,不用我們自己添加代碼在一個固定的地方來調用我們自己的初始化函數和退出函數,連接器已經為我們做好了。先來分析一下module_init。定義如下:

#define module_init(x)     __initcall(x);              //include/linux/init.h#define __initcall(fn) device_initcall(fn)#define device_initcall(fn)                 __define_initcall("6",fn,6)#define __define_initcall(level,fn,id) /         static initcall_t __initcall_##fn##id __used /         __attribute__((__section__(".initcall" level ".init"))) = fn

       如果某驅動想以func作為該驅動的入口,則可以如下聲明:module_init(func);被上面的宏處理過後,變成__initcall_func6 __used加入到核心映像的".initcall"區。核心的載入的時候,會搜尋".initcall"中的所有條目,並按優先順序載入它們,普通驅動程式的優先順序是6。其它模組優先順序列出如下:值越小,越先載入。

#define pure_initcall(fn)           __define_initcall("0",fn,0)#define core_initcall(fn)            __define_initcall("1",fn,1)#define core_initcall_sync(fn)          __define_initcall("1s",fn,1s)#define postcore_initcall(fn)             __define_initcall("2",fn,2)#define postcore_initcall_sync(fn)  __define_initcall("2s",fn,2s)#define arch_initcall(fn)            __define_initcall("3",fn,3)#define arch_initcall_sync(fn)          __define_initcall("3s",fn,3s)#define subsys_initcall(fn)                 __define_initcall("4",fn,4)#define subsys_initcall_sync(fn)      __define_initcall("4s",fn,4s)#define fs_initcall(fn)                          __define_initcall("5",fn,5)#define fs_initcall_sync(fn)               __define_initcall("5s",fn,5s)#define rootfs_initcall(fn)                  __define_initcall("rootfs",fn,rootfs)#define device_initcall(fn)                 __define_initcall("6",fn,6)#define device_initcall_sync(fn)       __define_initcall("6s",fn,6s)#define late_initcall(fn)             __define_initcall("7",fn,7)#define late_initcall_sync(fn)           __define_initcall("7s",fn,7s)

可以看到,被聲明為pure_initcall的最先載入。

       module_init除了初始化載入之外,還有後期釋放記憶體的作用。linux kernel中有很大一部分代碼是裝置驅動代碼,這些驅動代碼都有初始化和反初始化函數,這些代碼一般都只執行一次,為了有更有效利用記憶體,這些代碼所佔用的記憶體可以釋放出來。

      linux就是這樣做的,對只需要初始化運行一次的函數都加上__init屬性,__init 宏告訴編譯器如果這個模組被編譯到核心則把這個函數放到(.init.text)段,module_exit的參數卸載時同__init類似,如果驅動被編譯進核心,則__exit宏會忽略清理函數,因為編譯進核心的模組不需要做清理工作,顯然__init和__exit對動態載入的模組是無效的,只支援完全編譯進核心。

      在kernel初始化後期,釋放所有這些函數代碼所佔的記憶體空間。連接器把帶__init屬性的函數放在同一個section裡,在用完以後,把整個section釋放掉。當函數初始化完成後這個地區可以被清除掉以節約系統記憶體。Kenrel啟動時看到的訊息“Freeing unused kernel memory: xxxk freed”同它有關。

      我們看源碼,init/main.c中start_kernel是進入kernel()的第一個c函數,在這個函數的最後一行是rest_init();

static void rest_init(void)
{
     .....

     kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);
     unlock_kernel();
     cpu_idle();

     .....
}
建立了一個核心線程,主函數kernel_init末尾有個函數:

 /*  * Ok, we have completed the initial bootup, and  * we're essentially up and running. Get rid of the  * initmem segments and start the user-mode stuff..  */ init_post();

這個init_post中的第一句就是free_initmem();就是用來釋放初始化代碼和資料的。

void free_initmem(void){    if (!machine_is_integrator() && !machine_is_cintegrator()) {    free_area((unsigned long)(&__init_begin),     (unsigned long)(&__init_end),     "init");     }}

接下來就是kernel記憶體管理的事了。
 

參考地址:http://blog.csdn.net/citytramper/archive/2006/02/16/600708.aspx

參考原文: http://blog.csdn.net/citytramper/archive/2006/04/15/664930.aspx

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.