linux驅動的入口函數module_init的加載和釋放【轉】

本文轉載自:http://blog.csdn.net/zhandoushi1982/article/details/4927579linux

  就像你寫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) 中)。沒錯,驅動的加載就靠它。爲何須要這樣一個宏?緣由是按照通常的編程想法,各部分的初始化函數會在一個固定的函數裏調用好比:post

void init(void)spa

{.net

    init_a();線程

    init_b();指針

}rest

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

void init_a(void)

{

}

__initlist(init_a, 1);

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

[cpp]  view plain  copy
 
  1. #define __init __attribute__((unused, __section__(".initlist")))  
  2.   
  3. #define __initlist(fn, lvl) /  
  4. static initlist_t __init_##fn __init = { /  
  5.  magic:    INIT_MAGIC, /  
  6.  callback: fn, /  
  7.  level:   lvl }  


請注意:__section__(".initlist"),這個屬性起什麼做用呢?它告訴鏈接器這個變量存放在.initlist區段,若是全部的初始化函數都是用這個宏,那麼每一個函數會有對應的一個initlist_t結構體變量存放在.initlist區段,也就是說咱們能夠在.initlist區段找到全部初始化函數的指針。怎麼找到.initlist區段的地址呢?

extern u32 __initlist_start;
extern u32 __initlist_end;

這兩個變量起做用了,__initlist_start是.initlist區段的開始,__initlist_end是結束,經過這兩個變量咱們就能夠訪問到全部的初始化函數了。這兩個變量在那定義的呢?在一個鏈接器腳本文件裏

[cpp]  view plain  copy
 
  1. . = ALIGN(4);  
  2. .initlist : {  
  3.  __initlist_start = .;  
  4.  *(.initlist)  
  5.  __initlist_end = .;  
  6. }  


這兩個變量的值正好定義在.initlist區段的開始和結束地址,因此咱們能經過這兩個變量訪問到全部的初始化函數。

 

      與此相似,內核中也是用到這種方法,因此咱們寫驅動的時候比較獨立,不用咱們本身添加代碼在一個固定的地方來調用咱們本身的初始化函數和退出函數,鏈接器已經爲咱們作好了。先來分析一下module_init。定義以下:

[cpp]  view plain  copy
 
  1. #define module_init(x)     __initcall(x);              //include/linux/init.h  
  2.   
  3. #define __initcall(fn) device_initcall(fn)  
  4.   
  5. #define device_initcall(fn)                 __define_initcall("6",fn,6)  
  6.   
  7. #define __define_initcall(level,fn,id) /  
  8.   
  9.          static initcall_t __initcall_##fn##id __used /  
  10.   
  11.          __attribute__((__section__(".initcall" level ".init"))) = fn  


       若是某驅動想以func做爲該驅動的入口,則能夠以下聲明:module_init(func);被上面的宏處理事後,變成__initcall_func6 __used加入到內核映像的".initcall"區。內核的加載的時候,會搜索".initcall"中的全部條目,並按優先級加載它們,普通驅動程序的優先級是6。其它模塊優先級列出以下:值越小,越先加載。

[cpp]  view plain  copy
 
  1. #define pure_initcall(fn)           __define_initcall("0",fn,0)  
  2.   
  3. #define core_initcall(fn)            __define_initcall("1",fn,1)  
  4.   
  5. #define core_initcall_sync(fn)          __define_initcall("1s",fn,1s)  
  6.   
  7. #define postcore_initcall(fn)             __define_initcall("2",fn,2)  
  8.   
  9. #define postcore_initcall_sync(fn)  __define_initcall("2s",fn,2s)  
  10.   
  11. #define arch_initcall(fn)            __define_initcall("3",fn,3)  
  12.   
  13. #define arch_initcall_sync(fn)          __define_initcall("3s",fn,3s)  
  14.   
  15. #define subsys_initcall(fn)                 __define_initcall("4",fn,4)  
  16.   
  17. #define subsys_initcall_sync(fn)      __define_initcall("4s",fn,4s)  
  18.   
  19. #define fs_initcall(fn)                          __define_initcall("5",fn,5)  
  20.   
  21. #define fs_initcall_sync(fn)               __define_initcall("5s",fn,5s)  
  22.   
  23. #define rootfs_initcall(fn)                  __define_initcall("rootfs",fn,rootfs)  
  24.   
  25. #define device_initcall(fn)                 __define_initcall("6",fn,6)  
  26.   
  27. #define device_initcall_sync(fn)       __define_initcall("6s",fn,6s)  
  28.   
  29. #define late_initcall(fn)             __define_initcall("7",fn,7)  
  30.   
  31. #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末尾有個函數:

[cpp]  view plain  copy
 
  1. /* 
  2.  * Ok, we have completed the initial bootup, and 
  3.  * we're essentially up and running. Get rid of the 
  4.  * initmem segments and start the user-mode stuff.. 
  5.  */  
  6. init_post();  


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

[cpp]  view plain  copy
 
  1. void free_initmem(void)  
  2. {  
  3.     if (!machine_is_integrator() && !machine_is_cintegrator()) {  
  4.     free_area((unsigned long)(&__init_begin),  
  5.      (unsigned long)(&__init_end),  
  6.      "init");  
  7.      }  
  8. }  

接下來就是kernel內存管理的事了。

相關文章
相關標籤/搜索