vmlinux.lds.S用于对ld的输出进行组版,这个文件的格式在ld.info手册中有详细的说明。vmlinu
x.lds.S的主要目的是对输出文件中段进行排序,并定义相关的符号名,以下是简要注释。
/* ld script to make i386 Linux kernel
* Written by Martin Mares ;
*/
OUTPUT_FORMAT("elf32-i386", "elf32-i386","elf32-i386")
OUTPUT_ARCH(i386) /*输出格式*/
ENTRY(_start) /*定义_start作为入口点*/
SECTIONS
{
. = PAGE_OFFSET_RAW + 0x100000; /* 定义当前段的偏移量(.代表当前计数器)*/
_text =.; /*定义符号_text为当前位置 */
.text :{ /*定义段.text (": {"是段定义符)*/
*(.text) /*将所有输入文件中.text段合并到这里*/
*(.fixup) /*将所有输入文件中的.fixup段合并到这里*/
*(.gnu.warning) /* 将所有输入文件中的.gnu.warning段合并到这里*/
} =0x9090 /* 合并中的空隙用0x9090填充*/
/*以下的语法含义可以类推 */
.text.lock : { *(.text.lock)} /* out-of-line lock text*/
.rodata : { *(.rodata) }
.kstrtab : { *(.kstrtab) }
. =ALIGN(16); /*Exception table */
__start___ex_table =.; /* 定义__start_ex_table符号为当前位置*/
__ex_table : { *(__ex_table)}
__stop___ex_table = .;
__start___ksymtab= .; /* Kernel symbol table*/
__ksymtab : { *(__ksymtab) }
__stop___ksymtab = .;
_etext =.; /*End of text section */
.data :{ /*Data*/
*(.data)
CONSTRUCTORS /*将C++的构造函数指针段合并到这里*/
}
_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(4096); /*输出计数器在页边界上对齐 */
__init_end = .;
. =ALIGN(32);
.data.cacheline_aligned : {*(.data.cacheline_aligned) }
. =ALIGN(4096);
.data.page_aligned : { *(.data.idt) }
__bss_start=.; /*BSS */
.bss :{
*(.bss)
}
_end = . ;
/* Stabs debugging sections. */
.stab 0 : { *(.stab)} /* 0 是段属性,代表段的起始地址*/
.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) }
}
以下是用"objdump --headersvmlinux"得到的组版结果:
vmlinux: file format elf32-i386
Sections:
Idx Name Size VMA LMA File off Algn
0 .text 0009ccb8 c0100000 c0100000 00001000 2**4
CONTENTS, ALLOC, LOAD,READONLY, CODE
1 .text.lock 00000622 c019ccc0 c019ccc0 0009dcc0 2**4
CONTENTS, ALLOC, LOAD,READONLY, CODE
2 .rodata 0000f4ab c019d2e4 c019d2e4 0009e2e4 2**2
CONTENTS, ALLOC, LOAD,READONLY, DATA
3 .kstrtab 00002d9c c01ac78f c01ac78f 000ad78f 2**0
CONTENTS, ALLOC, LOAD,READONLY, DATA
4 __ex_table 00000ba8 c01af530 c01af530 000b0530 2**2
CONTENTS, ALLOC, LOAD,READONLY, DATA
5 __ksymtab 00001870 c01b00d8 c01b00d8 000b10d8 2**2
CONTENTS, ALLOC, LOAD,READONLY, DATA
6 .data 000133c0 c01b1950 c01b1950 000b2950 2**4
CONTENTS, ALLOC, LOAD,DATA
7 .data.init_task 00002000 c01c6000 c01c6000 000c6000 2**2
CONTENTS, ALLOC, LOAD,DATA
8 .text.init 0000868e c01c8000 c01c8000 000c8000 2**2
CONTENTS, ALLOC, LOAD,READONLY, CODE
9 .data.init 00003220 c01d0690 c01d0690 000d0690 2**2
CONTENTS, ALLOC, LOAD,DATA
10 .data.cacheline_aligned 00001c20 c01d4000 c01d4000 000d4000 2**2
CONTENTS, ALLOC, LOAD,DATA
11 .data.page_aligned 00000800 c01d6000 c01d6000 000d6000 2**2
CONTENTS, ALLOC, LOAD,DATA
12 .bss 0001f324 c01d6800 c01d6800 000d6800 2**4
ALLOC
13 .comment 000011d0 00000000 00000000 000d6800 2**0
CONTENTS,READONLY
14 .note 000011d0 000011d0 000011d0 000d79d0 2**0
CONTENTS, READONLY
更多0
vmlinuz自然就是内核了,initrd.img是一个小的映象,包含一个最小的linux系统。通常的步骤是先启动内核,然后内核挂载initrd.img,并执行里面的脚本来进一步挂载各种各样的模块,然后发现真正的root分区,挂载并执行/sbin/init... ...。
initrd.img当然是可选的了,如果没有initrd.img,内核就试图直接挂载root分区。
之所以要有initrd,那是为了启动的时候有更大的灵活性。比如,你把ext3支持编译成模块了。偏偏你的root分区又是ext3的。这下就麻烦了。因为内核需要挂载root分区之后才能加载ext3支持。但是没有ext3支持就没法挂载root分区。initrd就是用来解决这个问题的。
类似的用这个东西还可以做其他的事情,比如从usb盘启动linux也会面临上面类似的问题。用initrd就能搞定了。
甚至,我想在有些嵌入式设备里面都不需要真正的root分区,用initrd就足够搞定一切了。
一位网友写得一篇文章,写得很好,加深了对Linux启动的认识,贴在这里:
内核编译链接过程是依靠vmlinux.lds文件,以arm为例vmlinux.lds文件位于kernel/arch/arm/vmlinux.lds,但是该文件是由vmlinux-armv.lds.in生成的,根据编译选项的不同源文件还可以是vmlinux-armo.lds.in,vmlinux-armv-xip.lds.in。
vmlinux-armv.lds的生成过程在kernel/arch/arm/Makefile中
LDSCRIPT = arch/arm/vmlinux-armv.lds.in
arch/arm/vmlinux.lds:arch/arm/Makefile $(LDSCRIPT) /
$(wildcard include/config/cpu/32.h) /
$(wildcard include/config/cpu/26.h) /
$(wildcard include/config/arch
_stext = .;
__init_begin = .;
*(.text.init)
__proc_info_begin = .;
*(.proc.info)
__proc_info_end = .;
__arch_info_begin = .;
*(.arch.info)
__arch_info_end = .;
__tagtable_begin = .;
*(.taglist)
__tagtable_end = .;
*(.data.init)
. = ALIGN(16);
__setup_start = .;
*(.setup.init)
__setup_end = .;
__initcall_start = .;
*(.initcall.init)
__initcall_end = .;
. = ALIGN(4096);
__init_end = .;
}
其中TEXTADDR就是内核启动的虚拟地址,定义在kernel/arch/arm/Makefile中:
ifeq ($(CONFIG_CPU_32),y)
PROCESSOR = armv
TEXTADDR = 0xC0008000
LDSCRIPT = arch/arm/vmlinux-armv.lds.in
endif
需要注意的是这里是虚拟地址而不是物理地址。
一般情况下都在生成vmlinux后,再对内核进行压缩成为zImage,压缩的目录是kernel/arch/arm/boot。
下载到flash中的是压缩后的zImage文件,zImage是由压缩后的vmlinux和解压缩程序组成,如下图所示:
|-----------------|/ |-----------------|
| | / | |
| | / | decompress code |
| vmlinux | /|-----------------| zImage
| | /| |
| | | |
| | | |
| | | |
| | /|-----------------|
| | /
| | /
| | /
|-----------------|/
zImage链接脚本也叫做vmlinux.lds,位于kernel/arch/arm/boot/compressed。
是由同一目录下的vmlinux.lds.in文件生成的,内容如下:
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
{
. = LOAD_ADDR;
_load_addr = .;
. = TEXT_START;
_text = .;
.text : {
_start = .;
其中LOAD_ADDR就是zImage中解压缩代码的ram偏移地址,TEXT_START是内核ram启动的偏移地址,这个地址是物理地址。
在kernel/arch/arm/boot/Makefile文件中定义了:
ZTEXTADDR =0
ZRELADDR = 0xa0008000
ZTEXTADDR就是解压缩代码的ram偏移地址,ZRELADDR是内核ram启动的偏移地址,这里看到指定ZTEXTADDR的地址为0,
明显是不正确的,因为我的平台上的ram起始地址是0xa0000000,在Makefile文件中看到了对该地址设置的几行注释:
# We now have a PICdecompressor implementation. Decompressors running
# from RAM should notdefine ZTEXTADDR. Decompressors running directly
# from ROM or Flash must defineZTEXTADDR (preferably via the config)
他的意识是如果是在ram中进行解压缩时,不用指定它在ram中的运行地址,如果是在flash中就必须指定他的地址。所以这里将ZTEXTADDR指定为0,也就是没有真正指定地址。
在kernel/arch/arm/boot/compressed/Makefile文件有一行脚本:
SEDFLAGS =s/TEXT_START/$(ZTEXTADDR)/;s/LOAD_ADDR/$(ZRELADDR)/;s/BSS_START/$(ZBSSADDR)/
使得TEXT_START =ZTEXTADDR,LOAD_ADDR = ZRELADDR。
这样vmlinux.lds的生成过程如下:
vmlinux.lds: vmlinux.lds.inMakefile $(TOPDIR)/arch/$(ARCH)/boot/Makefile $(TOPDIR)/.config
@sed "$(SEDFLAGS)" <vmlinux.lds.in > $@
以上就是我对内核启动地址的分析,总结一下内核启动地址的设置:
1、设置kernel/arch/arm/Makefile文件中的
TEXTADDR = 0xC0008000
内核启动的虚拟地址
2、设置kernel/arch/arm/boot/Makefile文件中的
ZRELADDR = 0xa0008000
内核启动的物理地址
如果需要从flash中启动还需要设置
ZTEXTADDR地址。
源文档 <http://blog.csdn.net/zhoujiaxq/article/details/24347061>