對於核心中各種xxx_initcall呼叫的分析———linux子系統初始化
對應核心版本 linux-2.6.32.63, 架構arm(版本較老,但已經屬於2.6以後的版本了)。
一、
xxx_initcall是一系列子系統的初始化入口函式
對應檔案include/linux/init.h
#define early_initcall(fn) __define_initcall("early",fn,early)
#define pure_initcall(fn) __define_initcall("0",fn,0)
#define core_initcall(fn) __define_initcall("1",fn,1)
#define core_initcall_sync(fn) __define_initcall("1s",fn,1s)
#define postcore_initcall(fn) __define_initcall("2",fn,2)
#define postcore_initcall_sync(fn) __define_initcall("2s",fn,2s)
#define arch_initcall(fn) __define_initcall("3",fn,3)
#define arch_initcall_sync(fn) __define_initcall("3s",fn,3s)
#define subsys_initcall(fn) __define_initcall("4",fn,4)
#define subsys_initcall_sync(fn) __define_initcall("4s",fn,4s)
#define fs_initcall(fn) __define_initcall("5",fn,5)
#define fs_initcall_sync(fn) __define_initcall("5s",fn,5s)
#define rootfs_initcall(fn) __define_initcall("rootfs",fn,rootfs)
#define device_initcall(fn) __define_initcall("6",fn,6)
#define device_initcall_sync(fn) __define_initcall("6s",fn,6s)
#define late_initcall(fn) __define_initcall("7",fn,7)
#define late_initcall_sync(fn) __define_initcall("7s",fn,7s)
對於我們在驅動中常見的module_init入口函式
#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
typedef int (*initcall_t)(void);
所以對於一個module_init,其展開後即為
module_init(xxxx_init) 展開為
__define_initcall(“6",xxxx_init,6)
__define_initcall(“6",xxxx_init,6)繼續展開
static initcall_t __initcall_xxxx_init6 __used __attribute__((__section__(".initcall" “6"".init"))) = xxxx_init
結論一:所以最後可以看到 module_init(xxxx_init)展開後是一個 initcall_t 型別指向xxxx_init的函式指標,並且存放在__section__(".initcall" “6"".init"))段中,其實對於其他的xxx_initcall也是相同的道理,最後解析出來都是一個存放在相應段中的函式指標。這些段中的一系列指標有一定的執行順序,就是前面的一系列early,0,1,1s,2 ..etc數字了。後續來分析下他們是如何被按照順序執行的
二、
include/asm-generic/vmlinux.lds.h
#define INITCALLS \
*(.initcallearly.init) \ 對應__define_initcall("early",fn,early)
VMLINUX_SYMBOL(__early_initcall_end)= .; \
*(.initcall0.init) \ 對應__define_initcall("n",fn,n)
*(.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)
arch/arm/kernel/vmlinux.lds.S 段分佈
SECTIONS
{
#ifdef CONFIG_XIP_KERNEL
.= XIP_VIRT_ADDR(CONFIG_XIP_PHYS_ADDR);
#else
.= PAGE_OFFSET + TEXT_OFFSET;
#endif
.text.head: {
_stext= .;
_sinittext= .;
*(.text.head)
}
.init: { /* Init codeand data */
INIT_TEXT
_einittext= .;
__proc_info_begin= .;
*(.proc.info.init)
__proc_info_end= .;
__arch_info_begin= .;
*(.arch.info.init)
__arch_info_end= .;
__tagtable_begin= .; //uboot列表段,在parse_tag中解析uboot向kernel傳遞的引數。
*(.taglist.init)
__tagtable_end= .;
.= ALIGN(16);
__setup_start = .;
*(.init.setup)
__setup_end= .;
__early_begin= .;
*(.early_param.init)
__early_end= .;
__initcall_start= .; //init段,就是前面 INITCALLS ,在具體核心中所在的段
INITCALLS
__initcall_end= .;
__con_initcall_start= .;
*(.con_initcall.init)
__con_initcall_end= .;
__security_initcall_start= .;
*(.security_initcall.init)
__security_initcall_end= .;
#ifdef CONFIG_BLK_DEV_INITRD,//cpio相關的段, 若核心支援cpio型別的檔案系統,則會把cpio型別的rootfs放在該段中
.= ALIGN(32);
__initramfs_start= .;
usr/built-in.o(.init.ramfs)
__initramfs_end= .;
#endif
.= ALIGN(PAGE_SIZE);
__per_cpu_load= .;
__per_cpu_start= .;
*(.data.percpu.page_aligned)
*(.data.percpu)
*(.data.percpu.shared_aligned)
__per_cpu_end= .;
#ifndef CONFIG_XIP_KERNEL
__init_begin= _stext;
INIT_DATA
.= ALIGN(PAGE_SIZE);
__init_end= .;
#endif
}
。。。。。。。。
}
結論二:由上面的分析可知, module_init等一系列子系統入口函式,在編譯後會存放在.init段中的__initcall_start到__initcall_end地址中,且其順序是按照early,0,1,1s,2 ..依次存放.
三、
關於這些子系統入口函式在核心中的呼叫
init/main.c
kernel_init-->do_basic_setup-->do_initcalls
static void __init do_initcalls(void)
{
initcall_t *call;
/*正好是所有xxx_initcall所在段的起始地址到結束地址*/
for (call = __early_initcall_end; call <__initcall_end; call++)
do_one_initcall(*call);
/* Make sure there is no pending stuff from the initcall sequence */
flush_scheduled_work();
}
總結
1、由結論一可知,這一系列xxx_initcall其實是一系列函式指標.
2、由結論二可以,這系列函式指標在編譯後按照一定的順序存放在.init段中的__initcall_start到__initcall_end地址中
3、這一系列指標會在do_initcalls(void)被依次呼叫,依次進入到各個驅動子模組的入口函式中