(提示:看到哪儿写到哪儿,看一半写一半,看累了歇,歇好了看,有空就写 。)
【第一次编辑 @tonyfield 2013.08.24 】
【参考android内核goldfish,内核版本 linux 3.4.0 】
所有驱动模块的初始化函数定义形式都是一个无参数返回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);
所有驱动模块的退出函数定义形式都为
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__)) 表示该函数或变量可能不使用,这个属性可以避免编译器产生警告信息。
模块初始化函数在何处被调用是关键问题,如果是使用如下形式
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 函数。
内核初始化模块分两个阶段,第一个阶段初始化从__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,处于第二个阶段。
/* This is where the real work happens */
SYSCALL_DEFINE3(init_module, void __user *, umod,
unsigned long, len, const char __user *, uargs)
{