注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

学习笔记

正确的方法如同学习书法,开始的时候要临摹,临摹好了然后创造自己的风格。

 
 
 

日志

 
 

[Linux笔记]subsys_initcall含义及相关知识  

2014-05-05 21:27:39|  分类: Linux |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |
       在linux内核代码里, 到处充满了subsys_initcall,这个调用是用来干吗的呢?有人回答是系统启动时候用来初始化某些系统的,具体怎么初始化的呢,说起来还是有点复杂。以linux-2.6.37内核作为例子。
       在linux/init.h里,有这样一段代码:
#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_initcall又被定义为:
#define __define_initcall(level,fn,id) /
 static initcall_t __initcall_##fn##id __attribute_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/<target>/目录中的内核链接器脚本,它负责链接内核的各个节并将它们装入内存中特定偏移量处。

       在启动过程中,do_basic_setup--->do_initcalls里有以下代码:

static void __init do_initcalls(void)
{
initcall_t *fn;
for (fn = __early_initcall_end; fn < __initcall_end; fn++)

do_one_initcall(*fn);
/* Make sure there is no pending stuff from the initcall sequence */
flush_scheduled_work();
}
       这个__early_initcall_end是在文件include/asm-generic/vmlinux.lds.h定义的:
#define INITCALLS \
*(.initcallearly.init) \
VMLINUX_SYMBOL(__early_initcall_end) = .; \
   *(.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)

#define INIT_CALLS \
VMLINUX_SYMBOL(__initcall_start) = .; \
INITCALLS \
VMLINUX_SYMBOL(__initcall_end) = .;
       在文件arch/arm/kernel/vmlinux.lds.S中指定了.init段的内容,其中就会调用INIT_CALLS。代码如下
.init : { /* Init code and data */
_stext = .;
_sinittext = .;
HEAD_TEXT
INIT_TEXT
_einittext = .;
ARM_CPU_DISCARD(PROC_INFO)
__arch_info_begin = .;
*(.arch.info.init)
__arch_info_end = .;
__tagtable_begin = .;
*(.taglist.init)
__tagtable_end = .;
#ifdef CONFIG_SMP_ON_UP
__smpalt_begin = .;
*(.alt.smp.init)
__smpalt_end = .;
#endif
__pv_table_begin = .;
*(.pv_table)
__pv_table_end = .;

INIT_SETUP(16)
INIT_CALLS
CON_INITCALL
SECURITY_INITCALL
INIT_RAM_FS

#ifndef CONFIG_XIP_KERNEL
__init_begin = _stext;
INIT_DATA
#endif
}       

       在linux/init.h里,还有这样一段代码:
#define core_initcall(fn)  module_init(fn)
#define postcore_initcall(fn)  module_init(fn)
#define arch_initcall(fn)  module_init(fn)
#define subsys_initcall(fn)  module_init(fn)
#define fs_initcall(fn)   module_init(fn)
#define device_initcall(fn)  module_init(fn)
#define late_initcall(fn)  module_init(fn)

       这是在定义MODULE变量的情况下对subsys_initcall的定义,就是说对于驱动模块,使用subsys_initcall等价于使用module_init。

内核文件由链接器指定
       内核可执行文件由许多链接在一起的对象文件组成。对象文件有许多节,如文本、数据、init数据、bass等等。这些对象文件都是由一个称为链接器脚本的文件链接并装入的。这个链接器脚本的功能是将输入对象文件的各节映射到输出文件中;换句话说,它将所有输入对象文件都链接到单一的可执行文件中,将该可执行文件的各节装入到指定地址处。

       vmlinux.lds是存在于arch/arm/kernel/vmlinux.lds的内核链接器脚本,它负责链接内核的各个节并将它们装入内存中特定偏移量处。在vmlinux.lds文件里查找initcallearly.init就可以看到下面的内容。

 __initcall_start = .; *(.initcallearly.init) __early_initcall_end = .; *(.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) __initcall_end = .;
       其中的__early_initcall_end指向.initcall.init节的开始,__initcall_end指向.initcall.init节的结尾。两者之间的数据即为.initcall.init数据。而.initcall.init节又被分为了8个子节,分别是:
.initcall0.init
.initcall1.init
.initcall2.init
.initcall3.init
.initcall4.init
.initcall5.init
.initcall6.init
.initcall7.init

       subsys_initcall宏即是将指定的函数指针放在了.initcall4.init子节。其他类似的宏,比如core_initcall将函数指针放在.initcall1.init子节,device_initcall将函数指针放在了.initcall6.init子节等。

       各个子节的顺序是确定的,即先调用.initcall1.init中的函数指针,再调用.initcall2.init中的函数指针。__init修饰的初始化函数在内核初始化过程中调用的顺序和.initcall.init节里函数指针的顺序有关,不同的初始化函数被放在不同的子节中,因此也就决定了它们的调用顺序。

       因为属于内核的初始化,所以实际执行函数调用的位置在/init/main.c文件,其中的do_initcalls函数会直接用到这里的__early_initcall_end、__initcall_end并进行判断。

Reference:
http://book.51cto.com/art/201007/213593.htm
http://blog.csdn.net/heyunqi/article/details/1897108
http://blog.csdn.net/muge0913/article/details/7252107
  评论这张
 
阅读(1335)| 评论(0)
推荐 转载

历史上的今天

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017