(提示:看到哪兒寫到哪兒,看一半寫一半,看累了歇,歇好了看,有空就寫 。)
【第一次編輯 @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)
{