module_init的加載和釋放


轉自:http://blog.csdn.net/dysh1985/article/details/7597105

 

像你寫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末尾有個函數: 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內存管理的事了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM