对应内核版本 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)被依次调用,依次进入到各个驱动子模块的入口函数中