【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)
{

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