Linux内核链接文件分析(zz)

实际上讲到linux的启动部分不得不会讲到linux的链接脚本部分,链接脚本指定了linux怎么链接程序并将特定的代码放到专门的段区间,因此我在这里再讲下vmlinux的链接过程的一些注意问题,以下均是基于SEP4020 linux2.6内核的分析:
首先看一下顶层Makefile生成的vmlinux以及arch/arm/boot/compressed/makefile生成的vmlinux的起始地址。

1.1 arch/arm/kernel/vmlinux.lds文件的生成

通过顶层Makefile中的规则生成vmlinux是根据arch/arm/kernel/vmlinux.lds这个脚本链接生成的。arch/arm/kernel/vmlinux.lds是由arch/arm/kernel/vmlinux.lds.S生成的,其生成规则在scripts/Makefile.build的第236行开始定义

quiet_cmd_cpp_lds_S = LDS     $@
      cmd_cpp_lds_S = $(CPP) $(cpp_flags) -D__ASSEMBLY__ -o $@ $<
 
%.lds: %.lds.S FORCE
       $(call if_changed_dep,cpp_lds_S)

1.2顶层vmlinux的起始地址

在arch/arm/kernel/vmlinux.lds.S的开始处有
 
#ifdef CONFIG_XIP_KERNEL
       . = XIP_VIRT_ADDR(CONFIG_XIP_PHYS_ADDR);
#else
       . = PAGE_OFFSET + TEXT_OFFSET; (即0xc0008000)
#endif
 
我们这里的起始地址就是PAGE_OFFSET + TEXT_OFFSET。
 
在include/asm-arm/memory.h的49行开始有
 
#ifndef PAGE_OFFSET
#define PAGE_OFFSET        UL(0xc0000000)
#endif
 
而arch/arm/kernel/vmlinux.lds.S的开头有
 
#include <asm/memory.h>
 
asm是一个符号,链接到asm-arm上的
 
在arch/arm/Makefile第140行,有
 
TEXT_OFFSET := $(textofs-y)
 
第90行有
 
textofs-y := 0x00008000
 
所以TEXT_OFFSET := 0x00008000
 
在153行有export TEXT_OFFSET将此变量输出。这样arch/arm/kernel/vmlinux.lds.S也就获得了PAGE_OFFSET + TEXT_OFFSET的值。

1.3 SEP4020的虚实地址;

(1)在arch/arm/mach-sep4020/Makefile.boot文件定义了一个压缩内核镜像zImage的起始地址
   zreladdr-$(CONFIG_ARCH_4020)   := 0x30008000
这个地址在制作boot目录下面的zImage,uImage时候会用到的,这可以在arch/arm/boot/Makefile中的21行有定义
ZRELADDR    := $(zreladdr-y)
PARAMS_PHYS := $(params_phys-y)
INITRD_PHYS := $(initrd_phys-y)
(2)在/include/asm-arm/arch-sep4020/memory.h中定义了一个物理的页偏移地址,即sdram的地址
/*
* Page offset: 3GB
*/
#define PHYS_OFFSET        UL(0x30000000)
这个地址在启动代码arch/arm/kernel/head.s中会用到的:
#define KERNEL_RAM_ADDR (PAGE_OFFSET + TEXT_OFFSET)  @其中TEXT_OFFSET = 0x8000
//swapper_pg_dir是放启动时的临时页表的页表基址(虚地址)
.globl swapper_pg_dir
.equ swapper_pg_dir, KERNEL_RAM_ADDR - 0x4000
在这部分要建立页表的时候会用到这个地址的。

1.4压缩的自引导镜像arch/arm/boot/compressed/vmlinux的起始地址

现在看看arch/arm/boot/compressed/makeflie生成的vmlinux。它是根据arch/arm/boot/compressed/vmlinux.lds链接脚本生成的。这个脚本由arch/arm/boot/compressed/vmlinux.lds.in生成,在这个文件的开始处有
 
  . = TEXT_START;
 
现在看arch/arm/boot/compressed/Makefile,在110行有
 
$(obj)/vmlinux.lds: $(obj)/vmlinux.lds.in arch/arm/boot/Makefile .config
       @sed "$(SEDFLAGS)" < $ $@
 
这就是由vmlinux.lds.in生成vmlinux.lds的规则,在它的命令中有个变量SEDFLAGS,在74行定义
 
SEDFLAGS    = s/TEXT_START/$(ZTEXTADDR)/;s/BSS_START/$(ZBSSADDR)/
 
这里就把TEXT_START换成了ZTEXTADDR。再往上看从arch/arm/boot/compressed/makeflie的66行起
 
ifeq ($(CONFIG_ZBOOT_ROM),y)
ZTEXTADDR := $(CONFIG_ZBOOT_ROM_TEXT)
ZBSSADDR    := $(CONFIG_ZBOOT_ROM_BSS)
else
ZTEXTADDR := 0
ZBSSADDR    := ALIGN(4)
endif
 
如果zImage是从ram中启动ZTEXTADDR      := 0,否则从rom或flash启动时ZTEXTADDR := $(CONFIG_ZBOOT_ROM_TEXT),这里要在配置时设定CONFIG_ZBOOT_ROM_TEXT的值。

1.5 /arch/arm/kernel/vmlinux.lds.s链接文件的分析

下面先看下这个文件:
#include <asm-generic/vmlinux.lds.h>
#include <linux/config.h>
#include <asm/thread_info.h>
#include <asm/memory.h>
     
OUTPUT_ARCH(arm)                /*指定目标板体系结构 */
ENTRY(stext)                             /*代码段入口 */
jiffies = jiffies_64;                       /*/kernel/Timer.c中定义的*/
SECTIONS                                /*代码段各部分 */
{
       . = PAGE_OFFSET + TEXT_OFFSET;    /*代码段起始地址,SEP4020 Linux内核是0xC0008000,‘.’表示连接地址*/
       .init : {                  /*内核初始化的代码和数据 */
              _stext = .;     /*标号_stext表示的就是起始地址0xc0008000*/
                     _sinittext = .;
                     *(.init.text)
                     _einittext = .;
              __proc_info_begin = .;
                     *(.proc.info.init)
              __proc_info_end = .;
              __arch_info_begin = .;
                     *(.arch.info.init)
              __arch_info_end = .;
              __tagtable_begin = .;
                     *(.taglist.init)
              __tagtable_end = .;
              . = ALIGN(16);
              __setup_start = .;
                     *(.init.setup)
              __setup_end = .;
              __early_begin = .;
                     *(.early_param.init)
              __early_end = .;
              __initcall_start = .;
                     *(.initcall1.init)
                     *(.initcall2.init)
                     *(.initcall3.init)
                     *(.initcall4.init)
                     *(.initcall5.init)
                     *(.initcall6.init)
                     *(.initcall7.init)
              __initcall_end = .;
              __con_initcall_start = .;
                     *(.con_initcall.init)
              __con_initcall_end = .;
              __security_initcall_start = .;
                     *(.security_initcall.init)
              __security_initcall_end = .;
              . = ALIGN(32);
              __initramfs_start = .;
                     usr/built-in.o(.init.ramfs)
              __initramfs_end = .;
              . = ALIGN(64);
              __per_cpu_start = .;
                     *(.data.percpu)
              __per_cpu_end = .;
       }
       /DISCARD/ : {                     /*内核退出的代码和数据 */
              *(.exit.text)
              *(.exit.data)
              *(.exitcall.exit)
       }
       .text : {                 /*真正的代码段部分 */
              _text = .;        /*代码和只读数据 */
                     *(.text)
                     SCHED_TEXT
                     LOCK_TEXT
                     *(.fixup)
                     *(.gnu.warning)
                     *(.rodata)
                     *(.rodata.*)
                     *(.glue_7)
                     *(.glue_7t)
              *(.got)                  /* Global offset table             */
       }
       RODATA
       _etext = .;                     /*代码段和只读数据结束 */
       . = ALIGN(THREAD_SIZE);
       __data_loc = .;
       .data : AT(__data_loc) { /*数据段起始 */
              __data_start = .;    /*内存中的地址 */
              /*
               * first, the init task union, aligned
               * to an 8192 byte boundary.
               */
              *(.init.task)
              . = ALIGN(4096);
              __nosave_begin = .;
              *(.data.nosave)
              . = ALIGN(4096);
              __nosave_end = .;
              /*
               * then the cacheline aligned data
               */
              . = ALIGN(32);
              *(.data.cacheline_aligned)
              /*
                /*例外修正表(可能需要在运行时修正) */
               */
              . = ALIGN(32);
              __start___ex_table = .;
              *(__ex_table)
              __stop___ex_table = .;
              /*
                /*普通的数据段 */
               */
              *(.data)
              CONSTRUCTORS
              _edata = .;
       }
       .bss : {                                /*未初始化的全局变量 */
              __bss_start = .;      /* BSS                         */
              *(.bss)
              *(COMMON)
              _end = .;
       }
                            /*调试信息和数据段.*/
       .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) }
}
/*
* These must never be empty
* If you have to comment these two assert statements out, your
* binutils is too old (for other reasons as well)
*/
ASSERT((__proc_info_end - __proc_info_begin), "missing CPU support")
ASSERT((__arch_info_end - __arch_info_begin), "no machine record defined")
This entry was posted in Linux内核. Bookmark the permalink.
实际上讲到linux的启动部分不得不会讲到linux的链接脚本部分,链接脚本指定了linux怎么链接程序并将特定的代码放到专门的段区间,因此我在这里再讲下vmlinux的链接过程的一些注意问题,以下均是基于SEP4020 linux2.6内核的分析:
首先看一下顶层Makefile生成的vmlinux以及arch/arm/boot/compressed/makefile生成的vmlinux的起始地址。

1.1 arch/arm/kernel/vmlinux.lds文件的生成

通过顶层Makefile中的规则生成vmlinux是根据arch/arm/kernel/vmlinux.lds这个脚本链接生成的。arch/arm/kernel/vmlinux.lds是由arch/arm/kernel/vmlinux.lds.S生成的,其生成规则在scripts/Makefile.build的第236行开始定义

quiet_cmd_cpp_lds_S = LDS     $@
      cmd_cpp_lds_S = $(CPP) $(cpp_flags) -D__ASSEMBLY__ -o $@ $<
 
%.lds: %.lds.S FORCE
       $(call if_changed_dep,cpp_lds_S)

1.2顶层vmlinux的起始地址

在arch/arm/kernel/vmlinux.lds.S的开始处有
 
#ifdef CONFIG_XIP_KERNEL
       . = XIP_VIRT_ADDR(CONFIG_XIP_PHYS_ADDR);
#else
       . = PAGE_OFFSET + TEXT_OFFSET; (即0xc0008000)
#endif
 
我们这里的起始地址就是PAGE_OFFSET + TEXT_OFFSET。
 
在include/asm-arm/memory.h的49行开始有
 
#ifndef PAGE_OFFSET
#define PAGE_OFFSET        UL(0xc0000000)
#endif
 
而arch/arm/kernel/vmlinux.lds.S的开头有
 
#include <asm/memory.h>
 
asm是一个符号,链接到asm-arm上的
 
在arch/arm/Makefile第140行,有
 
TEXT_OFFSET := $(textofs-y)
 
第90行有
 
textofs-y := 0x00008000
 
所以TEXT_OFFSET := 0x00008000
 
在153行有export TEXT_OFFSET将此变量输出。这样arch/arm/kernel/vmlinux.lds.S也就获得了PAGE_OFFSET + TEXT_OFFSET的值。

1.3 SEP4020的虚实地址;

(1)在arch/arm/mach-sep4020/Makefile.boot文件定义了一个压缩内核镜像zImage的起始地址
   zreladdr-$(CONFIG_ARCH_4020)   := 0x30008000
这个地址在制作boot目录下面的zImage,uImage时候会用到的,这可以在arch/arm/boot/Makefile中的21行有定义
ZRELADDR    := $(zreladdr-y)
PARAMS_PHYS := $(params_phys-y)
INITRD_PHYS := $(initrd_phys-y)
(2)在/include/asm-arm/arch-sep4020/memory.h中定义了一个物理的页偏移地址,即sdram的地址
/*
* Page offset: 3GB
*/
#define PHYS_OFFSET        UL(0x30000000)
这个地址在启动代码arch/arm/kernel/head.s中会用到的:
#define KERNEL_RAM_ADDR (PAGE_OFFSET + TEXT_OFFSET)  @其中TEXT_OFFSET = 0x8000
//swapper_pg_dir是放启动时的临时页表的页表基址(虚地址)
.globl swapper_pg_dir
.equ swapper_pg_dir, KERNEL_RAM_ADDR - 0x4000
在这部分要建立页表的时候会用到这个地址的。

1.4压缩的自引导镜像arch/arm/boot/compressed/vmlinux的起始地址

现在看看arch/arm/boot/compressed/makeflie生成的vmlinux。它是根据arch/arm/boot/compressed/vmlinux.lds链接脚本生成的。这个脚本由arch/arm/boot/compressed/vmlinux.lds.in生成,在这个文件的开始处有
 
  . = TEXT_START;
 
现在看arch/arm/boot/compressed/Makefile,在110行有
 
$(obj)/vmlinux.lds: $(obj)/vmlinux.lds.in arch/arm/boot/Makefile .config
       @sed "$(SEDFLAGS)" < $ $@
 
这就是由vmlinux.lds.in生成vmlinux.lds的规则,在它的命令中有个变量SEDFLAGS,在74行定义
 
SEDFLAGS    = s/TEXT_START/$(ZTEXTADDR)/;s/BSS_START/$(ZBSSADDR)/
 
这里就把TEXT_START换成了ZTEXTADDR。再往上看从arch/arm/boot/compressed/makeflie的66行起
 
ifeq ($(CONFIG_ZBOOT_ROM),y)
ZTEXTADDR := $(CONFIG_ZBOOT_ROM_TEXT)
ZBSSADDR    := $(CONFIG_ZBOOT_ROM_BSS)
else
ZTEXTADDR := 0
ZBSSADDR    := ALIGN(4)
endif
 
如果zImage是从ram中启动ZTEXTADDR      := 0,否则从rom或flash启动时ZTEXTADDR := $(CONFIG_ZBOOT_ROM_TEXT),这里要在配置时设定CONFIG_ZBOOT_ROM_TEXT的值。

1.5 /arch/arm/kernel/vmlinux.lds.s链接文件的分析

下面先看下这个文件:
#include <asm-generic/vmlinux.lds.h>
#include <linux/config.h>
#include <asm/thread_info.h>
#include <asm/memory.h>
     
OUTPUT_ARCH(arm)                /*指定目标板体系结构 */
ENTRY(stext)                             /*代码段入口 */
jiffies = jiffies_64;                       /*/kernel/Timer.c中定义的*/
SECTIONS                                /*代码段各部分 */
{
       . = PAGE_OFFSET + TEXT_OFFSET;    /*代码段起始地址,SEP4020 Linux内核是0xC0008000,‘.’表示连接地址*/
       .init : {                  /*内核初始化的代码和数据 */
              _stext = .;     /*标号_stext表示的就是起始地址0xc0008000*/
                     _sinittext = .;
                     *(.init.text)
                     _einittext = .;
              __proc_info_begin = .;
                     *(.proc.info.init)
              __proc_info_end = .;
              __arch_info_begin = .;
                     *(.arch.info.init)
              __arch_info_end = .;
              __tagtable_begin = .;
                     *(.taglist.init)
              __tagtable_end = .;
              . = ALIGN(16);
              __setup_start = .;
                     *(.init.setup)
              __setup_end = .;
              __early_begin = .;
                     *(.early_param.init)
              __early_end = .;
              __initcall_start = .;
                     *(.initcall1.init)
                     *(.initcall2.init)
                     *(.initcall3.init)
                     *(.initcall4.init)
                     *(.initcall5.init)
                     *(.initcall6.init)
                     *(.initcall7.init)
              __initcall_end = .;
              __con_initcall_start = .;
                     *(.con_initcall.init)
              __con_initcall_end = .;
              __security_initcall_start = .;
                     *(.security_initcall.init)
              __security_initcall_end = .;
              . = ALIGN(32);
              __initramfs_start = .;
                     usr/built-in.o(.init.ramfs)
              __initramfs_end = .;
              . = ALIGN(64);
              __per_cpu_start = .;
                     *(.data.percpu)
              __per_cpu_end = .;
       }
       /DISCARD/ : {                     /*内核退出的代码和数据 */
              *(.exit.text)
              *(.exit.data)
              *(.exitcall.exit)
       }
       .text : {                 /*真正的代码段部分 */
              _text = .;        /*代码和只读数据 */
                     *(.text)
                     SCHED_TEXT
                     LOCK_TEXT
                     *(.fixup)
                     *(.gnu.warning)
                     *(.rodata)
                     *(.rodata.*)
                     *(.glue_7)
                     *(.glue_7t)
              *(.got)                  /* Global offset table             */
       }
       RODATA
       _etext = .;                     /*代码段和只读数据结束 */
       . = ALIGN(THREAD_SIZE);
       __data_loc = .;
       .data : AT(__data_loc) { /*数据段起始 */
              __data_start = .;    /*内存中的地址 */
              /*
               * first, the init task union, aligned
               * to an 8192 byte boundary.
               */
              *(.init.task)
              . = ALIGN(4096);
              __nosave_begin = .;
              *(.data.nosave)
              . = ALIGN(4096);
              __nosave_end = .;
              /*
               * then the cacheline aligned data
               */
              . = ALIGN(32);
              *(.data.cacheline_aligned)
              /*
                /*例外修正表(可能需要在运行时修正) */
               */
              . = ALIGN(32);
              __start___ex_table = .;
              *(__ex_table)
              __stop___ex_table = .;
              /*
                /*普通的数据段 */
               */
              *(.data)
              CONSTRUCTORS
              _edata = .;
       }
       .bss : {                                /*未初始化的全局变量 */
              __bss_start = .;      /* BSS                         */
              *(.bss)
              *(COMMON)
              _end = .;
       }
                            /*调试信息和数据段.*/
       .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) }
}
/*
* These must never be empty
* If you have to comment these two assert statements out, your
* binutils is too old (for other reasons as well)
*/
ASSERT((__proc_info_end - __proc_info_begin), "missing CPU support")
ASSERT((__arch_info_end - __arch_info_begin), "no machine record defined")
This entry was posted in Linux内核. Bookmark the permalink.

猜你喜欢

转载自blog.csdn.net/zwz_home/article/details/7712018