对于内核中各种xxx_initcall调用的分析———linux子系统初始化

时间:2021-09-23 19:43:38

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