驱动 __init修饰解析

来源:互联网 发布:js获取服务上下文 编辑:程序博客网 时间:2024/06/03 16:35
在driver文件中会经常看见“__init“修饰的代码。
      在GCC拓展的特殊属性中section时会提及这个__init,他所修饰的所有代码都会放到.init.text节中,当初始化结束后就可以释放这部分内存,这样就减少了内存的占用空间。

   

什么时候调用到该函数?

      首先要先讲解这个宏:subsys_initcall,在文件kernel/include/linux/init.h中。定义方式如下:
        #define subsys_initcall(fn)             __define_initcall("4",fn,4)

        又带来了一个新的宏__define_initcall,定义方式如下: 
        #define __define_initcall(level,fn,id) \
                 static initcall_t __initcall_##fn##id __used \
                 __attribute__((__section__(".initcall" level ".init"))) = fn
       那么 __define_initcall是用来修饰将指定的函数指针fn存放到".initcall.init"节中。因此可以断定subsys_initcall宏将fn存放到“.initcall.init”节中的“..initcall4.init”里。

        我们需要理解一下.initcall.init和.init.text和".initcall4.init"之类的符号的含义,这就需要我们了解和内核可执行文件的相关概念。
什么是内核可执行文件?
        可执行文件映像中包含了进程执行的代码和数据,同时也包含了操作系统用来将映像正确装入内存并执行的信息。
        这些信息包含了如下文本段、数据段、init数据段、bass段等。这些数据都是由一个称为“链接器脚本”的文件链接并装入的,这个文件的功能时间这些输入信息的各段装入到指定的地址处。vmlinux.lds就是存在"arch/xxx/"目录中哦你的内核连接其脚本,他负责链接内核的各个段并将他们装入到内存中特定偏移量处。
      看一下这个链接器脚本文件,路径是"arch/arm/kernel",名称为vmlinux.lds.
          找到关键程序:INIT_SETUP(16)--->init_main.c的__setup_start指向的.init.setup节的开始
                                       INIT_CALLS---->init_main.c的__initcall_start指向的.initcallearly.init节的开始
                                      CON_INITCALL---->./drivers/char/tty_io.c的__con_initcall_start指向的.con_initcall.init节的开始。
                                      其他的宏就不讲解了。
其中INIT_CALL是主要的,分为了九段:
                 INIT_CALLS_LEVEL(0)                                     \
                 INIT_CALLS_LEVEL(1)                                     \
                 INIT_CALLS_LEVEL(2)                                     \
                 INIT_CALLS_LEVEL(3)                                     \
                 INIT_CALLS_LEVEL(4)                                     \
                 INIT_CALLS_LEVEL(5)                                     \
                 INIT_CALLS_LEVEL(rootfs)                                \
                 INIT_CALLS_LEVEL(6)                                     \
                 INIT_CALLS_LEVEL(7) 
而INIT_CALL_LEVEL宏定义如下:
#define INIT_CALLS_LEVEL(level)                                         \
                 VMLINUX_SYMBOL(__initcall##level##_start) = .;          \
                 *(.initcall##level##.init)                              \
                 *(.initcall##level##s.init)
实际就是.initcall0.init~.initcall7.init。那么subsys_initcall将值定的话函数指针放在了".initcall4.init"子段中。例如:device_initcall将函数指针存放到了".initcall6.init"中;core_initcall将函数指针存放到了".initcall1.init"子段中。
        各个子段的顺序是确定的,是先调用".initcall4.init"中的函数指针再去执行“.initcall5.init”中的函数指针。


        那么__init修饰的初始化函数在内核初始化过程中调用的顺序和.initcall.init里面的函数指针的顺序有关,因此不同的初始化函数是被放在不同的子段中,因此就决定了他们的调用顺序。
原创粉丝点击