Username: Password:

Linux内核链接器学习
来源: ChinaUnix博客  作者: 发布时间:2008-04-25 02:50:48

  通过对Linux内核的不断学习, 越来越发现其复杂性,需要研发人员掌控N多知识,操作系统的知识我想是最基本的了,其次包括汇编,C语言,Make file,bash script等等知识,最近又发现了,在编译连接Linux内核的时候,还涉及到了连接器(LD)的一些知识。
  通常在编译C文档的时候,编译器会把文档转换成汇编语言,默认目标文档是包含text,data,bss三个段,然后链接器会把任何的目标程式连接成一个文档,链接过程包括多个文档不同段的整合,也就是不同文档的text段整合在一个text段里面,不同文档的data段整合在一个data段里面....
  在Linux内核编译连接后,会生成很多段(不止上面默认的三个)。那个怎样定义新段呢?

  [init.h]
  #define __init __attribute__ ((__section__ (".text.init")))
  [8139cp.c]
  static int __init cp_init (void)

  上面的cp_init的代码就被放置在一个新段".text.init"中,__attribute__ ((__section__ (".text.init")))就是通知编译器在编译时,把此函数体代码放在".text.init"中。其实Linux module方式加载驱动的init代码也都位于此段,把初始化代码都放在此段的原因主要是节约内存,当初始化这些模块后,这个".text.init"区域会被内核收回,linux内核程式员的确都珍惜内存。

  假如单单只在内核代码中做上面的事情,还是不够的,上面只是解决了文档编译分块的问题,真正链接的问题还没有解决,那么怎样把任何目标文档同样的段集合在一起呢?段位置怎样确定呢?这就是链接器的事情了!

[Linux内核的Makefile]
vmlinux: include/linux/version.h $(CONFIGURATION) init/main.o init/version.o linuxsubdirs $(LD) $(LINKFLAGS) $(HEAD) init/main.o init/version.o \ --start-group \ $(CORE_FILES) \ $(DRIVERS) \ $(NETWORKS) \ $(LIBS) \ --end-group \ -o vmlinux
LINKFLAGS =-T $(TOPDIR)/arch/i386/vmlinux.lds $(LDFLAGS)
vmlinux.lds
/* ld script to make i386 Linux kernel
* Written by Martin Mares ;
*/
OUTPUT_FORMAT("elf32-i386", "elf32-i386", "elf32-i386")
OUTPUT_ARCH(i386)
ENTRY(_start)
SECTIONS
{
  . = 0xC0000000 + 0x100000;
  _text = .;   /* Text and read-only data */
  .text : {
*(.text)
*(.fixup)
*(.gnu.warning)
} = 0x9090
  _etext = .;   /* End of text section */
  .rodata : { *(.rodata) *(.rodata.*) }
  .kstrtab : { *(.kstrtab) }
  . = ALIGN(16);  /* Exception table */
  __start___ex_table = .;
  __ex_table : { *(__ex_table) }
  __stop___ex_table = .;
  __start___ksymtab = .; /* Kernel symbol table */
  __ksymtab : { *(__ksymtab) }
  __stop___ksymtab = .;
  .data : {   /* Data */
*(.data)
CONSTRUCTORS
}
  _edata = .;   /* End of data section */
  . = ALIGN(8192);  /* init_task */
  .data.init_task : { *(.data.init_task) }
  . = ALIGN(4096);  /* Init code and data */
  __init_begin = .;
  .text.init : { *(.text.init) }
  .data.init : { *(.data.init) }
  . = ALIGN(16);
  __setup_start = .;
  .setup.init : { *(.setup.init) }
  __setup_end = .;
  __initcall_start = .;
  .initcall.init : { *(.initcall.init) }
  __initcall_end = .;
  . = ALIGN(4096);
  __init_end = .;
  . = ALIGN(4096);
  .data.page_aligned : { *(.data.idt) }
  . = ALIGN(32);
  .data.cacheline_aligned : { *(.data.cacheline_aligned) }
  __bss_start = .;  /* BSS */
  .bss : {
*(.bss)
}
  _end = . ;
  /* Sections to be discarded */
  /DISCARD/ : {
*(.text.exit)
*(.data.exit)
*(.exitcall.exit)
}
  /* Stabs debugging sections.  */
  .stab 0 : { *(.stab) }
  .stabstr 0 : { *(.stabstr) }
  .stab.excl 0 : { *(.stab.excl) }
  .stab.exclstr 0 : { *(.stab.exclstr) }
  .stab.index 0 : { *(.stab.index) }
  .stab.indexstr 0 : { *(.stab.indexstr) }
  .comment 0 : { *(.comment) }
}
上面是Linux ld(链接器)所使用的脚本,也就是在链接linux内核时所参照的文档,ENTRY界定了程式的入口点,这里面时__start,也就是bootsect.S中的其实地址,执行copy内核到指定内存功能,然后用SECTION定义了生成内核文档的分块结构,__setup_start = .;这句话很有意思,在linux内核里面定义了__setup_start这个变量,但就是没有赋值过,嘿嘿,其实赋值在这里呢,"."代表输出文档(内核image)当前链接位置,之前我还疑惑,linux为何有时候定一变量,却没看到初始化,果不其然,原来在这里。通过链接文档的配合,一个符合研发者所规划的linux内核image就产生了。





喜欢本文,那就收藏到:

    Del.icio.us Google书签 Digg Live Bookmark Technorati Furl Yahoo书签 Facebook 百度搜藏 新浪ViVi 365Key网摘 天极网摘 和讯网摘 博拉网 POCO网摘 添加到饭否 QQ书签 Digbuzz我挖网
相关评论  我也要评论
还没有关于此文章的相关评论!
  • 昵称: (为空则显示guest)
  • 评论分数: ★ ★ ★★★ ★★★★ ★★★★★
  • 评论内容:(不能超过250字,需审核后才会公布,请自觉遵守互联网相关政策法规。
  • 导航
    赞助商
    文章类别
    订阅