2011/08/19

Linux Kernel Initcall

Linux kernel module的module init function 如下,本文將以kernel module為例,說明他們是怎麼被kernel核心呼叫到。
static int __devinit tegra_kbc_init(void) {	
    pr_debug("KBC: tegra_kbc_init\n")
	return platform_driver_register(&tegra_kbc_driver)
}
module_init(tegra_kbc_init)

module_init

隨便找到有呼叫module_init的kernel module往上追,可以找到最後是呼叫到__define_inicall(level,fn,id)這個macro,如下
// File: include/linux/init.h
#define module_init(x)	__initcall(x)
#define __initcall(fn) device_initcall(fn)
#define device_initcall(fn)		__define_initcall("6",fn,6)
#define __define_initcall(level,fn,id) \
    static initcall_t __initcall_##fn##id __used \
    __attribute__((__section__(".initcall" level ".init"))) = fn
其中以module_init(tegra_kbc_init)會被展開成
static initcall_t __initcall_tegra_kbc_init6 __used 
__attribute__((__section__(".initcall6.init"))) = tegra_kbc_init 
由__attribute__得知,它最後會被放到initcall6.init這個section裡。而linker連結時會根據vmlinux.lds.S來將它放到指定的section。如下的INIT_CALLS。
// File: arch/arm/kernel/vmlinux.lds.S
SECTIONS {		
    INIT_SETUP(16)
    INIT_CALLS
    CON_INITCALL
    SECURITY_INITCALL
    INIT_RAM_FS
再往下追就看到了.initcall6.init。
// File: include/asm-generic/vmlinux.lds.h
#define INIT_CALLS							\
		VMLINUX_SYMBOL(__initcall_start) = .;			\
		INITCALLS						\
		VMLINUX_SYMBOL(__initcall_end) = .;

#define INITCALLS							\
	*(.initcallearly.init)						\
	VMLINUX_SYMBOL(__early_initcall_end) = .;			\
  	*(.initcall0.init)						\
  	*(.initcall0s.init)						\
  	*(.initcall1.init)						\
  	*(.initcall1s.init)						\
  	*(.initcall2.init)						\
  	*(.initcall2s.init)						\
  	*(.initcall3.init)						\
  	*(.initcall3s.init)						\
  	*(.initcall4.init)						\
  	*(.initcall4s.init)						\
  	*(.initcall5.init)						\
  	*(.initcall5s.init)						\
	*(.initcallrootfs.init)						\
  	*(.initcall6.init)						\
  	*(.initcall6s.init)						\
  	*(.initcall7.init)						\
  	*(.initcall7s.init)

do_initcalls

kernel是從那裡呼叫到init calls,可以從kernel的entry point,init/main.c,找到下面這個函式。
// File: init/main.c
extern initcall_t __initcall_start[], __initcall_end[], __early_initcall_end[];
static void __init do_initcalls(void) {	
    initcall_t *fn;
	for (fn = __early_initcall_end; fn < __initcall_end; fn++)
        do_one_initcall(*fn);
	/* Make sure there is no pending stuff from the initcall sequence */	
    flush_scheduled_work();
}
一路往上追可以知道do_initcalls()在start_kernel()的最後的函式裡面用一個kernel thread間接呼叫,這裡很容易就可以追到了,我就不再列出。

No comments: