linux内核__define_initcall分析

Posted kerneler_

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了linux内核__define_initcall分析相关的知识,希望对你有一定的参考价值。

 在阅读linux内核代码的过程中经常会看到驱动代码中在最后调用arch_initcall subsys_initcall module_init等函数,虽然知道这些函数是来确定驱动在内核启动过程中的加载顺序的,但是没有仔细看过实现原理,借清明节空闲来看一下具体实现,以mips处理器2.6.21内核为例。


  在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 __attribute_used__ \\
    __attribute__((__section__(".initcall" level ".init"))) = fn

/*
 * A "pure" initcall has no dependencies on anything else, and purely
 * initializes variables that couldn't be statically initialized.
 *
 * This only exists for built-in code, not for modules.
 */
#define pure_initcall(fn)       __define_initcall("0",fn,1)

#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)
#define late_initcall_sync(fn)      __define_initcall("7s",fn,7s)

#define __initcall(fn) device_initcall(fn)

#define __exitcall(fn) \\
    static exitcall_t __exitcall_##fn __exit_call = fn

#define console_initcall(fn) \\
    static initcall_t __initcall_##fn \\
    __attribute_used__ __attribute__((__section__(".con_initcall.init")))=fn

#define security_initcall(fn) \\
    static initcall_t __initcall_##fn \\
    __attribute_used__ __attribute__((__section__(".security_initcall.init"))) = fn


可以看到在驱动中对*_initcall的调用实际都是对__define_initcall的调用,按照id从0到7s共分了17种,来看__define_initcall

#define __define_initcall(level,fn,id) \\
    static initcall_t __initcall_##fn##id __attribute_used__ \\
    __attribute__((__section__(".initcall" level ".init"))) = fn


initcall_t是一个函数指针类型,定义也在init.h中 typedef int (*initcall_t)(void);

第一句的意思就是定义函数指针 static int *__initcall_fnid(void),将fn赋值给它。

而函数属性__attribute__((__section__()))表示将函数指针放到section所表示的代码段中。

第二句意思是将定义的函数指针放到.initcalllevel.init段中。


例如pci.c中的pcibios_init是subsys_initcall(pcibios_init),也就是__define_initcall("4",pcibios_init,4)

这个函数作用就是定义函数指针static init * __initcall_pcibios_init4 = pcibios_init,将函数指针pcibios_init放到.initcall4.init段中。

这么看来.initcall0.init~.initcall7s.init的定义也就决定了内核启动这写驱动的调用顺序,关于initcall*.init的定义来自如下过程。

内核启动调用过程start_kernel ==> rest_init ==> init ==>init_post进入控制台

在init函数调用init_post之前调用了函数do_basic_setup,do_basic_setup调用了do_initcalls,这个函数中

for (call = __initcall_start; call < __initcall_end; call++)
。。。

result = (*call)();

。。。

察看__initcall_start和__initcall_end的定义,在arch/mips/kernel/vmlinux.lds.S也就是链接脚本中找到

__initcall_start = .;
  .initcall.init :
    INITCALLS
 
  __initcall_end = .;
察看INITCALLS定义

#define INITCALLS                           \\
    *(.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函数中就会遍历initcalls段的每一个函数指针,执行这个函数指针。也就是说编译过程中链接脚本就将各个函数指针链接到了指定的位置。

对于编写外设驱动时调用的module_init,在init.h中可以看到

#define module_init(x)  __initcall(x);

#define __initcall(fn) device_initcall(fn)

#define device_initcall(fn)     __define_initcall("6",fn,6)

module_init就是level6的initcalls段,是比较靠后调用的。

很多外设驱动都调用module_init宏,这些函数指针会按照编译先后顺序插入到initcall6.init段中,等待do_initcalls函数的调用。

以上是关于linux内核__define_initcall分析的主要内容,如果未能解决你的问题,请参考以下文章

内核中 subsys_initcall 以及初始化标号

xxx_initcall 的调用

Linux 内核 内存管理物理分配页 ② ( __alloc_pages_nodemask 函数参数分析 | __alloc_pages_nodemask 函数分配物理页流程 )

Linux 内核 内存管理物理分配页 ① ( 分区伙伴分配器物理分配页核心函数 __alloc_pages_nodemask | __alloc_pages_nodemask 函数完整源码 )

Linux 内核 内存管理物理分配页 ⑨ ( __alloc_pages_slowpath 慢速路径调用函数源码分析 | retry 标号代码分析 )

Linux 内核 内存管理物理分配页 ② ( __alloc_pages_nodemask 函数参数分析 | __alloc_pages_nodemask 函数分配物理页流程 )