实际上讲到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是一个符号,链接到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
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")