vmlinux.lds.s文件分析

vmlinux.lds.S是如何组织内核的每个函数存放在内核镜像文件的位置,我们知道你在编译内核生成内核文件的时候,其实这个过程分两步,一个是“编译”,另一个是“链接”的过程,vmlinux.lds.S要做的就是告诉编译器如何链接编译好的各个内核.o文件。

  
  
    
    
    
    
  1. 小知识:链接器中的entry 
  2. 链接器 按以下优先顺序设入口点,找到即停止 
  3. 1 -e 命令行选项 
  4. 2 脚本中的entry(symbol)命令 
  5. 3如定义了start的值,取其值为入口点 
  6. 4.text的第一个字节的地址 
  7. 5地址0

所以你可以从vmlinux.lds.S下面的代码中看到:

  1. OUTPUT_ARCH(arm)
  2. ENTRY(stext)

所以你可以从vmlinux.lds.S下面的代码中看到:

  
  
    
    
    
    
  1. OUTPUT_ARCH(arm)
  2. ENTRY(stext)

表明我们指定stext作为,程序的入口点。

SECTIONS
{
#ifdef CONFIG_XIP_KERNEL
. = XIP_VIRT_ADDR(CONFIG_XIP_PHYS_ADDR);
#else
. = PAGE_OFFSET + TEXT_OFFSET;
#endif

.init : { /* Init code and data */
_stext = .;
_sinittext = .;
HEAD_TEXT
INIT_TEXT
ARM_EXIT_KEEP(EXIT_TEXT)
_einittext = .;
ARM_CPU_DISCARD(PROC_INFO)
__arch_info_begin = .;
*(.arch.info.init)
__arch_info_end = .;
__tagtable_begin = .;
*(.taglist.init)
__tagtable_end = .;
#ifdef CONFIG_SMP_ON_UP
__smpalt_begin = .;
*(.alt.smp.init)
__smpalt_end = .;
#endif

__pv_table_begin = .;
*(.pv_table)
__pv_table_end = .;

INIT_SETUP(16)

INIT_CALLS
CON_INITCALL
SECURITY_INITCALL
INIT_RAM_FS

#ifndef CONFIG_XIP_KERNEL
__init_begin = _stext;
INIT_DATA
ARM_EXIT_KEEP(EXIT_DATA)
#endif
}

PERCPU_SECTION(32)

#ifndef CONFIG_XIP_KERNEL
. = ALIGN(PAGE_SIZE);
__init_end = .;
#endif

/*
* unwind exit sections must be discarded before the rest of the
* unwind sections get included.
*/
/DISCARD/ : {
*(.ARM.exidx.exit.text)
*(.ARM.extab.exit.text)
ARM_CPU_DISCARD(*(.ARM.exidx.cpuexit.text))
ARM_CPU_DISCARD(*(.ARM.extab.cpuexit.text))
#ifndef CONFIG_HOTPLUG
*(.ARM.exidx.devexit.text)
*(.ARM.extab.devexit.text)
#endif
#ifndef CONFIG_MMU
*(.fixup)
*(__ex_table)
#endif
}

.text : { /* Real text segment */
_text = .; /* Text and read-only data */
__exception_text_start = .;
*(.exception.text)
__exception_text_end = .;
IRQENTRY_TEXT
TEXT_TEXT
SCHED_TEXT
LOCK_TEXT
KPROBES_TEXT
#ifdef CONFIG_MMU
*(.fixup)
#endif
*(.gnu.warning)
*(.rodata)
*(.rodata.*)
*(.glue_7)
*(.glue_7t)
. = ALIGN(4);
*(.got) /* Global offset table */
ARM_CPU_KEEP(PROC_INFO)
}

RO_DATA(PAGE_SIZE)

#ifdef CONFIG_ARM_UNWIND
/*
* Stack unwinding tables
*/
. = ALIGN(8);
.ARM.unwind_idx : {
__start_unwind_idx = .;
*(.ARM.exidx*)
__stop_unwind_idx = .;
}
.ARM.unwind_tab : {
__start_unwind_tab = .;
*(.ARM.extab*)
__stop_unwind_tab = .;
}
#endif

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

#ifdef CONFIG_XIP_KERNEL
__data_loc = ALIGN(4); /* location in binary */
. = PAGE_OFFSET + TEXT_OFFSET;
#else
. = ALIGN(THREAD_SIZE);
__data_loc = .;
#endif

.data : AT(__data_loc) {
_data = .; /* address in memory */
_sdata = .;

/*
* first, the init task union, aligned
* to an 8192 byte boundary.
*/
INIT_TASK_DATA(THREAD_SIZE)

#ifdef CONFIG_XIP_KERNEL
. = ALIGN(PAGE_SIZE);
__init_begin = .;
INIT_DATA
ARM_EXIT_KEEP(EXIT_DATA)
. = ALIGN(PAGE_SIZE);
__init_end = .;
#endif

NOSAVE_DATA
CACHELINE_ALIGNED_DATA(32)
READ_MOSTLY_DATA(32)

/*
* The exception fixup table (might need resorting at runtime)
*/
. = ALIGN(32);
__start___ex_table = .;
#ifdef CONFIG_MMU
*(__ex_table)
#endif
__stop___ex_table = .;

/*
* and the usual data section
*/
DATA_DATA
CONSTRUCTORS

_edata = .;
}
_edata_loc = __data_loc + SIZEOF(.data);

#ifdef CONFIG_HAVE_TCM
/*
* We align everything to a page boundary so we can
* free it after init has commenced and TCM contents have
* been copied to its destination.
*/
.tcm_start : {
. = ALIGN(PAGE_SIZE);
__tcm_start = .;
__itcm_start = .;
}

/*
* Link these to the ITCM RAM
* Put VMA to the TCM address and LMA to the common RAM
* and we'll upload the contents from RAM to TCM and free
* the used RAM after that.
*/
.text_itcm ITCM_OFFSET : AT(__itcm_start)
{
__sitcm_text = .;
*(.tcm.text)
*(.tcm.rodata)
. = ALIGN(4);
__eitcm_text = .;
}

/*
* Reset the dot pointer, this is needed to create the
* relative __dtcm_start below (to be used as extern in code).
*/
. = ADDR(.tcm_start) + SIZEOF(.tcm_start) + SIZEOF(.text_itcm);

.dtcm_start : {
__dtcm_start = .;
}

/* TODO: add remainder of ITCM as well, that can be used for data! */
.data_dtcm DTCM_OFFSET : AT(__dtcm_start)
{
. = ALIGN(4);
__sdtcm_data = .;
*(.tcm.data)
. = ALIGN(4);
__edtcm_data = .;
}

/* Reset the dot pointer or the linker gets confused */
. = ADDR(.dtcm_start) + SIZEOF(.data_dtcm);

/* End marker for freeing TCM copy in linked object */
.tcm_end : AT(ADDR(.dtcm_start) + SIZEOF(.data_dtcm)){
. = ALIGN(PAGE_SIZE);
__tcm_end = .;
}
#endif

NOTES

BSS_SECTION(0, 0, 0)
_end = .;

STABS_DEBUG
.comment 0 : { *(.comment) }

/* Default discards */
DISCARDS

#ifndef CONFIG_SMP_ON_UP
/DISCARD/ : {
*(.alt.smp.init)
}
#endif
}

先看第一个知识点:


(1). = PAGE_OFFSET + TEXT_OFFSET;


arch/arm/include/asm中的memory.h文件定义了:

#define PAGE_OFFSET UL(CONFIG_PAGE_OFFSET)

CONFIG_PAGE_OFFSET是在内核配置里面配置的,比如拿ok6410来说,我的配置:

#define CONFIG_PAGE_OFFSET 0xC0000000

PAGE_OFFSET代表是的内核image的的起始虚拟地址。
在arch/arm/Makefile中有定义:

textofs-y := 0x00008000
TEXT_OFFSET := $(textofs-y)

TEXT_OFFSET代表的是内核的image存放在内存中地址,注意这个地址为相对于内存的起始地址的偏移量,是个相对的偏移量不是实际的存放内存物理地址。而且这个偏移量取得有讲究,必须为:0xXXXX8000,xxx为任意值。
所以:. = PAGE_OFFSET + TEXT_OFFSET就变成为:.=c0008000,这个地址就是内核存放在内存的虚拟的起始地址。从脚本来看,也就是.stext的地址就是:c0008000,那么我们如何验证我们的这个说法呢,你可以打开Linux目录下的System.map文件查看:

00000020 A cpu_v6_suspend_size
c0004000 A swapper_pg_dir
c0008000 T __init_begin
c0008000 T _stext T _sinittext
c0008000 T _stext
c0008000 T stext
c000804c t __create_page_tables
c0008148 t __enable_mmu_loc
c0008154 t __enable_mmu
c0008180 t __turn_mmu_on
c0008198 t __enable_mmu_end
c0008198 t __vet_atags
c00081e0 t __mmap_switched
c0008228 t __mmap_switched_data
c000824c T lookup_processor_type
c0008260 t set_reset_devices
c0008284 t debug_kernel

从map文件可以知道_stext是内核的入口地址,这个地址就是c0008000,这也验证了我们在第一章讲搭建环境的时候,我为什么要用dnw c0008000,就是因为我们已经指定好了,内核的存放地址必须为c0008000,针对ok6410来说。
(二).init内核的初始化代码和数据段

_sinittext = .;
HEAD_TEXT
INIT_TEXT
ARM_EXIT_KEEP(EXIT_TEXT)
_einittext = .;

从上面的链接脚本可以知道所有:_sinittext开头的,_einittext结尾的HEAD_TEXT, INIT_TEXT,ARM_EXIT_KEEP(EXIT_TEXT)段都是从起始地址c0008000开始存放的。这些宏定义在:include/asm-generic/vmlinux.lds.h中。

__arch_info_begin = .;
*(.arch.info.init)
__arch_info_end = .;
__tagtable_begin = .;
*(.taglist.init)
__tagtable_end = .;
#ifdef CONFIG_SMP_ON_UP
__smpalt_begin = .;
*(.alt.smp.init)
__smpalt_end = .;
#endif

__pv_table_begin = .;
*(.pv_table)
__pv_table_end = .;

紧接着以此存放的是:*(.arch.info.init), *(.taglist.init),*(.alt.smp.init),*(.pv_table)段的代码,其中我们最熟悉的*(.arch.info.init)段的代码就是:

#define MACHINE_START(_type,_name) \
static const struct machine_desc __mach_desc_##_type \
__used \
__attribute__((__section__(".arch.info.init"))) = { \
.nr = MACH_TYPE_##_type, \
.name = _name,

#define MACHINE_END \
};

也就是说所有平台的下面这段代码都放到了*(.arch.info.init)段中:

MACHINE_START(MCUOS6410, "MCUOS6410")
/* Maintainer: Ben Dooks */
.boot_params = S3C64XX_PA_SDRAM + 0x100,

.init_irq = s3c6410_init_irq,
.map_io = mcuos6410_map_io,
.init_machine = mcuos6410_machine_init,
.timer = &s3c24xx_timer,
MACHINE_END

紧接着存放的是初始化数据段:

#ifndef CONFIG_XIP_KERNEL
__init_begin = _stext;
INIT_DATA
ARM_EXIT_KEEP(EXIT_DATA)
#endif
}

PERCPU_SECTION(32)

#ifndef CONFIG_XIP_KERNEL
. = ALIGN(PAGE_SIZE);
__init_end = .;
#endif

这个段存放的是所有*(.init.da ta),*(.init.rodata)段中的代码。

.init段中的代码段和数据段,在Linux初始化完成之后,这个段的内存都会被请空,被释放。因为他们只需要在初始化的时候使用一次,没有必要再驻留在内存中,浪费空间。为了验证我们以上的分析,我们可以查看system.map这个文件,下面我们看到的从_sinittext开头的,_einittext结尾的所有函数,都会在初始化完成之后被释放:

c0008000 T _sinittext
c0008000 T _stext
c0008000 T stext
c000804c t __create_page_tables
c0008148 t __enable_mmu_loc
c0008154 t __enable_mmu
c0008180 t __turn_mmu_on
c0008198 t __enable_mmu_end
c0008198 t __vet_atags
c00081e0 t __mmap_switched
c0008228 t __mmap_switched_data
c000824c T lookup_processor_type
c0008260 t set_reset_devices
c0008284 t debug_kernel
c00082a8 t quiet_kernel
c00082cc t init_setup
c0008308 t rdinit_setup
c0008344 W smp_setup_processor_id
c0008354 W thread_info_cache_init
c0008364 t loglevel
c0008398 T parse_early_options
c00083d4 t unknown_bootoption
c00085f8 T parse_early_param
c0008648 t do_early_param
c0008724 t kernel_init
c0008850 T start_kernel
c0008b9c t readonly
c0008bd0 t readwrite
c0008c04 t rootwait_setup
c0008c34 t root_data_setup
c0008c54 t fs_names_setup
c0008c74 t load_ramdisk
c0008ca4 t root_dev_setup
c0008ccc t root_delay_setup
c0008cf8 T change_floppy
c0008e20 T mount_block_root
c0009108 T mount_root
c0009174 T prepare_namespace
c0009364 t ramdisk_start_setup
c0009390 t prompt_ramdisk
c00093c0 t error
c00093fc t compr_fill
c0009464 t compr_flush
c00094d4 T rd_load_image
c0009b5c T rd_load_disk
c0009c10 t no_initrd
c0009c34 T initrd_load
c0009f74 t do_linuxrc
c0009fc8 t error
c0009fec t read_into
c000a0a8 t do_start
c000a0d4 t write_buffer
c000a12c t flush_buffer
c000a1d8 t retain_initrd_param
c000a208 t clean_path
c000a268 t do_utime
c000a2ac t do_symlink
c000a378 t unpack_to_rootfs
c000a6d4 t maybe_link
c000a840 t do_name
c000aad8 t do_header
c000adf0 t free_initrd

(3)真正的驻留在内存中的内核内代码段
从脚本上来看,所有.text开始,.etext结束区间的段都是内核代码段:

.text : { /* Real text segment */
_text = .; /* Text and read-only data */
__exception_text_start = .;
*(.exception.text)
__exception_text_end = .;
IRQENTRY_TEXT
TEXT_TEXT
SCHED_TEXT
LOCK_TEXT
KPROBES_TEXT
#ifdef CONFIG_MMU
*(.fixup)
#endif
*(.gnu.warning)
*(.rodata)
*(.rodata.*)
*(.glue_7)
*(.glue_7t)
. = ALIGN(4);
*(.got) /* Global offset table */
ARM_CPU_KEEP(PROC_INFO)
}

RO_DATA(PAGE_SIZE)

#ifdef CONFIG_ARM_UNWIND
/*
* Stack unwinding tables
*/
. = ALIGN(8);
.ARM.unwind_idx : {
__start_unwind_idx = .;
*(.ARM.exidx*)
__stop_unwind_idx = .;
}
.ARM.unwind_tab : {
__start_unwind_tab = .;
*(.ARM.extab*)
__stop_unwind_tab = .;
}
#endif

_etext = .; /* End of text and rodata section */
复制代码


这些段中的代码都是事实在在的内核函数。
(4)已经初始化的data数据段:
.data : AT(__data_loc) {
_data = .; /* address in memory */
_sdata = .;

/*
* first, the init task union, aligned
* to an 8192 byte boundary.
*/
INIT_TASK_DATA(THREAD_SIZE)

#ifdef CONFIG_XIP_KERNEL
. = ALIGN(PAGE_SIZE);
__init_begin = .;
INIT_DATA
ARM_EXIT_KEEP(EXIT_DATA)
. = ALIGN(PAGE_SIZE);
__init_end = .;
#endif

NOSAVE_DATA
CACHELINE_ALIGNED_DATA(32)
READ_MOSTLY_DATA(32)

/*
* The exception fixup table (might need resorting at runtime)
*/
. = ALIGN(32);
__start___ex_table = .;
#ifdef CONFIG_MMU
*(__ex_table)
#endif
__stop___ex_table = .;

/*
* and the usual data section
*/
DATA_DATA
CONSTRUCTORS

_edata = .;

(5)紧接着以初始化的.da ta段的是.bss,未经初始化的内核数据段。

BSS_SECTION(0, 0, 0)
_end = .;

最后我给出这些段的大致位置图,请参考我们上面所讲的来看:

你可能感兴趣的:(kernel学习)