ARM架构内核启动分析-head.S(1.1、vmlinux.lds 链接脚本分析)

ARM架构内核启动分析

一、start kernel之前

首先需要明确的是,内核镜像在被解压之后执行,是执行哪段代码,这是个重要的问题,平时在编译生成应用程序或内核模块时,我们无需考虑链接的具体细节,如代码和数据放在哪里、代码执行入口在哪等等,但在编译生成内核镜像时就不能不考虑这些了,对于arm架构,下面是它的编译内核时的arm交叉链接器命令:

arm-linux-ld -EL -p --no-undefined -X --build-id -ovmlinux -T arch/arm/kernel/vmlinux.lds

可以通过man –ld命令查看,-T的意思是:为链接器ld指定一个链接脚本linker script,就是让链接器根据这个链接脚本的内容来生成最终二进制镜像。

以我手中的arm芯片(marvell 88F6500)为例,它对应的linux源码的arch/arm/kernel目录下都会有对应的vmlinux.lds文件,这个文件是由同一目录下的vmlinux.lds.S文件生成的,所以这个vmlinux.lds.S文件需要重点分析;

1.1、  链接脚本分析:

1.1.1、任何可执行程序的大致结构:

对于任何一种可执行程序,不论是ELF/EXE、so/dll、ko,应该都听说过代码段text、数据段data、未初始化数据段bss等等的说法,其实这些都是一个个的段(section);每个编译好的.o文件,都有该文件的text/data/bss段;事实上每个.o文件不仅仅这三个段,还有一些别的段,但不是分析重点;

链接脚本最终要把这一大堆.o文件生成最终的二进制可执行文件,也就是把每一个.o文件整合到一个大文件中,这个大文件有一个总的text/data/bss段,分别囊括每个.o文件的text/data/bss段;那具体是怎么使用的呢,举例如下:

SECTIONS

 {

       . = 0x10000;

       .text : { *(.text) }

       . = 0x8000000;

       .data : { *(.data) }

       .bss : { *(.bss) }

 }

第一行指示,链接地址为0x100000;即指定了后面的text段的链接地址

第二行指示:输出文件的text段内容由所有输入文件(*,理解为所有的.o文件,*.o)的text段组成;

第三行指示:链接地址变了,变为0x8000000;即重新指定了后面的data段的链接地址;

第四行指示:输出文件的data端由所有输入文件的data段组成;

第五行指示:输出文件的bss端由所有输入文件的bss段组成;

个人认为理解到此就差不多可以了,下面分析vmlinux.lds.S文件:

1.1.2、vmlinux.lds.S文件:

OUTPUT_ARCH(arm):表示输出文件基于ARM架构;

 

ENTRY(stext):ENTRY用来设置入口点。这里表示入口点是stext。这就是内核代码的入口!入口点的意思就是程序运行的第一条指令,内核本身也是一个程序,同样需要设置入口;

jiffies = jiffies_64:

/*下面开始正式定义输出文件的段!!!*/

SECTIONS

{

    /*1、设置链接地址(也即虚拟地址)为0xc0008000,这就是内核代码的虚拟地址为0xc0008000的由来值的依据就是PAGE_OFFSET和TEXT_OFFSET*/

#ifdef CONFIG_XIP_KERNEL

                  . = XIP_VIRT_ADDR(CONFIG_XIP_PHYS_ADDR);

#else

                  . = PAGE_OFFSET + TEXT_OFFSET;

#endif

/*2、定义一个.text.head段,由输入文件中所有.text.head段组成,并定义了两个地址变量下面描述链接脚本中变量的意思: 如

            start_of_ROM   = .ROM;

            end_of_ROM     = .ROM + sizeof (.ROM) - 1;

            start_of_FLASH = .FLASH;

     这三个变量分别指向ROM段的开始和结尾、FLASH段的开始,那么在C代码中是这样使用这些变量:

            extern char start_of_ROM, end_of_ROM, start_of_FLASH;

     注意,C代码这些变量指示的都是指针变量,比如希望在C代码中把ROM段的内容拷贝到FLASH段中:

            memcpy (&start_of_FLASH, &start_of_ROM, &end_of_ROM - &start_of_ROM);

     可见,注意其中的取地址符号&,C代码中只能通过这种方式来使用LS中定义的变量。start_of_ROM这个值本身是没有意义的,只有它的地址才有意义

     说白了,链接脚本中定义的变量其实就是地址,即_stext=0x100就是C代码中的一个地址:int *_stext=0x100 */

.text.head : {

                   _stext = .;     @@定义变量_stext存储当前地址(PAGE_OFFSET + TEXT_OFFSET)

                   _sinittext = .; @@定义变量_sinittext存储当前地址(PAGE_OFFSET + TEXT_OFFSET)

                   *(.text.head)   @@所有输入文件中所有.text.head段在此

}

/*3、定义一个.init段*/

.init : {                         /* Init code and data                 */

         /*INIT_TEXT在include/asm-generic/vmlinux.lds.h文件中定义,意为所有的.init.text/.cpuinit.text/.meminit.text在此/

                            INIT_TEXT

         /*定义变量_einittext,它其实是INIT_TEXT的结尾标识*/

                   _einittext = .;

         /*这是一种典型的用法,后面大量使用该方法,前面已知可以在链接脚本中定义变量存储地址,然后由C代码使用,这里就用这种方式,定义两个变量,两个变量之间是输出文件.proc.info.init,用这两个变量就把中间的内容牢牢卡住,供C代码使用*/

__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 = .;

                            INITCALLS

                   __initcall_end = .;

                   __con_initcall_start = .;

                            *(.con_initcall.init)

                   __con_initcall_end = .;

                   __security_initcall_start = .;

                            *(.security_initcall.init)

                   __security_initcall_end = .;

/*

#ifdef CONFIG_BLK_DEV_INITRD

                   . = ALIGN(32);

                   __initramfs_start = .;

                            usr/built-in.o(.init.ramfs)

                   __initramfs_end = .;

#endif

*/

                   . = ALIGN(PAGE_SIZE);

                   __per_cpu_load = .;

                   __per_cpu_start = .;

                            *(.data.percpu.page_aligned)

                            *(.data.percpu)

                            *(.data.percpu.shared_aligned)

                   __per_cpu_end = .;

#ifndef CONFIG_XIP_KERNEL

                   __init_begin = _stext;

                   INIT_DATA

                   . = ALIGN(PAGE_SIZE);

                   __init_end = .;

#endif

         }

/*4、DISACARD是一种特殊的段,表示符合这个条件的输入段都不会写到输出段中,也就是输出文件中不包含下列段,不是分析重点*/

/DISCARD/ : {                     /* Exit code and data                */

                   EXIT_TEXT

                   EXIT_DATA

                   *(.exitcall.exit)

                   *(.discard)

                   *(.ARM.exidx.exit.text)

                   *(.ARM.extab.exit.text)

#ifndef CONFIG_HOTPLUG_CPU

                   *(.ARM.exidx.cpuexit.text)

                   *(.ARM.extab.cpuexit.text)

#endif

#ifndef CONFIG_HOTPLUG

                   *(.ARM.exidx.devexit.text)

                   *(.ARM.extab.devexit.text)

#endif

#ifndef CONFIG_MMU

                   *(.fixup)

                   *(__ex_table)

#endif

         }

/*5、这是text段*/

         .text : {                        /* Real text segment                */

                   _text = .;           /* Text and read-only data       */

                            __exception_text_start = .;

                            *(.exception.text)

                            __exception_text_end = .;

/* Then all the functions that are "hot" in profiles, to group them onto the same hugetlb entry */

#include "functionlist"

 /* Then the rest */

                            TEXT_TEXT

                            SCHED_TEXT

                            LOCK_TEXT

                            KPROBES_TEXT

#ifdef CONFIG_MMU

                            *(.fixup)

#endif

                            *(.gnu.warning)

                            *(.rodata)

                            *(.rodata.*)

                            *(.glue_7)

                            *(.glue_7t)

                   *(.got)                         /* Global offset table                */

         }

O_DATA(PAGE_SIZE)

_etext = .;                           /* End of text and rodata section */

. = ALIGN(THREAD_SIZE);

__data_loc = .;

/*6、这是data段*/

         .data : AT(__data_loc) {

                   _data = .;          /* address in memory */

                   _sdata = .;

                   /*

                    * first, the init task union, aligned

                    * to an 8192 byte boundary.

                    */

                   *(.data.init_task)

                   . = ALIGN(PAGE_SIZE);

                   __nosave_begin = .;

                   *(.data.nosave)

                   . = ALIGN(PAGE_SIZE);

                   __nosave_end = .;

                   /*

                    * then the cacheline aligned data

                    */

                   . = ALIGN(32);

                   *(.data.cacheline_aligned)

                   /*

                    * The exception fixup table (might need resorting at runtime)

                    */

                   . = ALIGN(32);

                   __start___ex_table = .;

                   *(__ex_table)

                   __stop___ex_table = .;

                   /*

                    * and the usual data section

                    */

                   DATA_DATA

                   CONSTRUCTORS

                   _edata = .;

         }

/*7、这是bss段*/

         .bss : {

                   __bss_start = .;        /* BSS                                   */

                   *(.bss)

                   *(COMMON)

                   __bss_stop = .;

                   _end = .;

         }

                                               /* 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) }

}

上面罗列也描述了很多内容,重点把握以下几点:

1、  内核代码入口为stext;并且它应该是属于输入文件的.text.head段;并且stext的实现在arch/arm/kernel/head.S文件中,可找到.section ".text.head", "ax"声明;

2、  内核代码的链接地址从0xc0008000开始,不断增加,这体现了PAGE_OFFSET 、TEXT_OFFSET两个宏的作用何在;

3、  注意init段中的类似用__proc_info_begin、__proc_info_end来卡住中间内容的方式的原理,这在后面频繁使用;

其余的关于链接脚本的内容,可暂时不用分析的很细,后面会逐渐的理解。

你可能感兴趣的:(linux,ARM,启动分析,vmlin)