【android內核學習筆記】驅動模塊 - 初始化與釋放

(提示:看到哪兒寫到哪兒,看一半寫一半,看累了歇,歇好了看,有空就寫 。)

第一次編輯  @tonyfield  2013.08.24 】

【參考android內核goldfish,內核版本 linux 3.4.0 】

1. 驅動模塊初始化函數定義分析

  所有驅動模塊的初始化函數定義形式都是一個無參數返回int型的靜態函數,函數類型帶有 __init 前綴

static int __init devname_init(void);

  __init 的定義如下

#define __init        __section(.init.text) __cold notrace               // Line 44 @ include/linux/init.h

  其中,宏 __section,__cold,notrace 的定義如下,

#define __section(S) __attribute__ ((__section__(#S)))     // Line 278 @ include/linux/compiler.h

#define __cold                                             //  Line 273 @ include/linux/compiler.h

#define notrace __attribute__((no_instrument_function))    //  Line 51 @ include/linux/compiler.h
  注意,如果使用 GNUC,上述定義可能受compiler-gcc.h 影響這樣,模塊初始化函數宏展開爲:

static int  __attribute__((__section__(.init.text)))  __attribute__((no_instrument_function))  devname_init(void);

  __attribute__((__section__(.init.text)))前綴將使devname_init函數在鏈接過程中被放置在 .init.text 段

  __attribute__((no_instrument_function))前綴將阻止gcc在帶有–finstrument-functions 或 --gnu_instrument 編譯參數的情況下插入跟蹤函數。

  所謂跟蹤函數是gcc在函數入口和出口分別插入如下函數形式,如果你使用了–finstrument-functions 或 --gnu_instrument 編譯參數,這兩個函數需要你自己定義。

     void __cyg_profile_func_enter (void *this_fn,  void*call_site);

     void __cyg_profile_func_exit (void *this_fn, void*call_site);

2. 驅動模塊退出函數定義分析

  所有驅動模塊的退出函數定義形式都爲

static int __exit devname_exit(void);

#define __exit          __section(.exit.text) __exitused __cold notrace       // Line 83 @ include/linux/init.h

  其中只有 __exitused 是和 __init宏定義不同,其它都可以參考上一節。__exitused 定義如下,在模塊編譯的情況下定義爲空。

/* Line 77 - 81 @ include/linux/init.h  */

#ifdef MODULE
#define __exitused
#else
#define __exitused  __used
#endif

  再來看 __used 定義

/*  Line 9-13  @ include/linux/compile-gcc3.h   */

#if __GNUC_MINOR__ >= 3
# define __used            __attribute__((__used__))
#else
# define __used            __attribute__((__unused__))
#endif

 __attribute__((__unused__)) 表示該函數或變量可能不使用,這個屬性可以避免編譯器產生警告信息。

3. 驅動模塊初始化函數入口分析

  模塊初始化函數在何處被調用是關鍵問題,如果是使用如下形式

module_lvlx_init()
{
...
#ifdef MODULE_DEVNAME
   devname_init();
#endif
...
}

 那麼對於裁剪模塊的工程師來說工作發生錯誤的機會將大大提高,同時,維護不同的版本的成本也會隨代碼量的上升而提高。

  Linux 中只要用宏 module_init 聲明 devname_init 函數就可以了,具體技術細節被隱藏在module_init 中。這樣,驅動工程師就可以只關注驅動模塊,在對應模塊文件中定義module_init(...) 和 module_exit(...)宏就可以。

module_init(devname_init);

module_init 及相關宏定義如下:

#define module_init(x)    __initcall(x);                // Line 267 @ include/linux/init.h

#define __initcall(fn) device_initcall(fn)              // Line 213 @ include/linux/init.h

#define device_initcall(fn)        __define_initcall("6",fn,6)// Line 208 @ include/linux/init.h

/* Line 168 - 180 @ include/linux/init.h */

/* initcalls are now grouped by functionality into separate
 * subsections. Ordering inside the subsections is determined
 * by link order.
 * For backwards compatibility, initcall() puts the call in
 * the device init subsection.
 *
 * The `id' arg to __define_initcall() is needed so that multiple initcalls
 * can point at the same handler without causing duplicate-symbol build errors.
 */

#define __define_initcall(level,fn,id) \
    static initcall_t __initcall_##fn##id __used \
    __attribute__((__section__(".initcall" level ".init"))) = fn

typedef int (*initcall_t)(void);                                         // Line 139 @ include/linux/init.h

  這樣,module_init(devname_init); 展開後的樣子就是:

 static initcall_t __initcall_devname_init6 __used  __attribute__((__section__(".initcall6.init"))) = devname_init;

  它定義了一個initcall_t類型的函數指針__initcall_devname_init6,指向對應設備初始化函數 devname_init,這個函數指針在編譯的鏈接階段被指定放置在 .initcall6.init 段。

  這個函數指針僅在內核初始化階段或是動態加載模塊階段被調用,兩者只居其一。調用時一般通過 do_one_initcall 函數。

3.1  內核初始化階段的模塊初始化工作

  內核初始化模塊分兩個階段,第一個階段初始化從__initcall_start 到 __initcall0_start 的模塊初始化指針,第二個階段初始化從__initcall0_start 到 __initcall7_start 的模塊初始化指針。__initcall_start 到 __initcall7_start 的具體定義可以參考 include/Asm-generic/Vmlinux.lds.h 鏈接描述文件。調用起點可以追溯到kernel_init 爲止,調用層級關係如下。

static int __init kernel_init(void * unused)                             /* Line 839 @ init/main.c */

    |-->>static void __init do_pre_smp_initcalls(void)         /* Line 784 @ init/main.c */

                {
                   initcall_t *fn;
                   for (fn = __initcall_start; fn <__initcall0_start; fn++)
                       do_one_initcall(*fn);
               }

    |-->>static void __initdo_basic_setup(void)                 /* Line 772 @ init/main.c */

        |-->> static void __init do_initcalls(void)                                 /* Line 757 @ init/main.c */

            |--->> static void __init do_initcall_level(int level)                   /* Line 741 - 755 @ init/main.c */
                      {
                           ......
                           for (fn = initcall_levels[level]; fn < initcall_levels[level+1]; fn++)
                           do_one_initcall(*fn);
                       }

  第一個階段調用的模塊id最小(爲0),級別最高。而我們關心的設備驅動id是6,處於第二個階段。


3.2  動態載入模塊階段的模塊初始化工作

/* This is where the real work happens */
SYSCALL_DEFINE3(init_module, void __user *, umod,
        unsigned long, len, const char __user *, uargs)
{

       |-->>
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章