arm架构Linux5.0内核连接脚本文件vmlinux.lds.S分析

发布于:2024-04-27 ⋅ 阅读:(27) ⋅ 点赞:(0)


vmlinux.lds.S 是 Linux 内核中用于链接的脚本文件,用于定义内核对象文件的内存布局,即它告诉链接器如何将不同的内核代码段和数据段组合成一个最终的内核映像 vmlinux。编译内核源码生成内核文件的过程分两步,一个是“编译”,另一个是“链接”的过程,vmlinux.lds.S要做的就是告诉编译器如何链接编译好的各个内核.o文件,从而生成vmlinux文件。

准备工作

我们需要准备好vmlinux.lds.S文件,文件的位置是arch/arm64/kernel/vmlinux.lds.S。我们还可以查看该文件预处理后的连接文件,在编译完linux内核会自动生成文件是arch/arm64/kernel/vmlinux.lds。我们还可以使用下面的命令读取 vmlinux 文件中的各段地址,协助我们分析:

readelf -S vmlinux > vmlinux_elf.txt

一、 参数

我们先直接查看预处理好的vmlinux.lds文件,可以看到开头就是下面这几行代码,这些是连接文件的参数,在vmlinux.lds.S中也可以找到一模一样的,只是放在了不同的行中而已:

OUTPUT_ARCH(aarch64)
ENTRY(_text)
jiffies = jiffies_64;
PECOFF_FILE_ALIGNMENT = 0x200;
  1. OUTPUT_ARCH(aarch64):这行指定输出文件的目标架构是 aarch64,即 64 位 ARM 架构。这是告诉链接器生成适用于 ARM 64 位处理器的内核映像。

  2. ENTRY(_text):这行指定了内核的入口点是 _text 符号。_text 通常是一个在内核代码中定义的标签,指向内核启动时第一条要执行的指令。这意味着当内核加载到内存并跳转到它的入口点时,它将开始执行 _text 标签指定的代码。

  3. jiffies = jiffies_64;: 在标准的 Linux 内核构建系统中,jiffies 作为变量通常是在 C 代码中定义和使用的。这行代码看起来像是一个赋值操作,但是在文件中没有使用到,个人猜测这是表示使用64位的jiffies。

  4. PECOFF_FILE_ALIGNMENT = 0x200;:这行设置了 PECOFF(Portable Executable for Console File Format)文件的对齐字节。PECOFF 是一种常用于 Windows 系统的文件格式,但在某些交叉编译场景下,Linux 内核可能需要以这种格式生成,以便于在某些环境中加载和执行。0x200 表示每个文件节(section)在磁盘上以 512 字节对齐,这是一个常见的对齐设置,用于优化文件的加载和内存访问性能。

二、 SECTIONS段

2.1 DISCARD节

	/DISCARD/ : {
		ARM_EXIT_DISCARD(EXIT_TEXT)
		ARM_EXIT_DISCARD(EXIT_DATA)
		EXIT_CALL
		*(.discard)
		*(.discard.*)
		*(.interp .dynamic)
		*(.dynsym .dynstr .hash .gnu.hash)
		*(.eh_frame)
	}

预处理后变成:

 /DISCARD/ : {
 
 
  *(.exitcall.exit)
  *(.discard)
  *(.discard.*)
  *(.interp .dynamic)
  *(.dynsym .dynstr .hash .gnu.hash)
  *(.eh_frame)
 }

结合英文注释和网上的一些言论,个人猜测/DISCARD/ 段是一个特殊的指令,用于指示链接器在生成最终的内核映像时应该忽略或丢弃某些段。这样子gcc可以通过配置来确定是否丢弃这些段,可以减小最终映像的大小或移除调试信息从而优化内核的加载时间和内存占用。如果确定丢弃,这意味着这些段不会出现在最终的程序或内核的内存映像中。

2.2 .head.text节

	. = KIMAGE_VADDR + TEXT_OFFSET;

	.head.text : {
		_text = .;
		HEAD_TEXT
	}

预处理后变成:

 . = ((((((((0xffffffffffffffff)) - (((1)) << (48)) + 1) + (0)) + (0x08000000))) + (0x08000000))) + 0x00080000;
 .head.text : {
  _text = .;
  KEEP(*(.head.text))
 }

**.head.text 😗*表示节的名字,后面的大括号表示节中包括的内容。
.表示当前位置,通过宏查找,在arch/arm64/include/asm/memory.h文件中,我们计算出KIMAGE_VADDR 大小为0xFFFF000010000000。还知道了一些小知识,在高端虚拟内存的最低地址开始是0xFFFF000000000000,从0xFFFF000000000000到0xFFFF000008000000是预留出来存放BPF JIT的,大小是128M。从0xFFFF000008000000到0xFFFF000010000000是预留出来存放各种ko模块的,大小也是128M。TEXT_OFFSET是512K,TEXT_OFFSET是arm64端口早期的历史产物,当时引导协议基本上是“将此映像复制到内存+ 512k的基础”,为我们提供512 KB的保证BSS空间来放置交换器页表。在linuxv3.10版本已经携带了TEXT_OFFSET的实际值,以允许引导加载程序动态地发现它,而不是将其硬编码为512 KB。在linux内核v5.7版本已经默认设置位0了。在linux内核v5.7版本已经把TEXT_OFFSET移除了。所以我们计算得到当前位置是0xFFFF000010080000。这跟我们读取vmlinux各段地址的vmlinux_elf.txt文件记录是一致的。

在链接脚本中,KEEP 是一个宏,它用于告诉链接器保留匹配的节(sections)内容,在这个例子中,KEEP 宏确保了所有标记为 .head.text 的节都会被保留在最终的内核映像中。linux内核是这样子标记.head.text段的,在arch/x86/kernel/head64.c文件中:

#define __head  __section(.head.text)

__head 在arch/arm64/kernel/head.S文件就用上了,表示下面这一段代码都是标记为 .head.text 的节,会放到linux内核映像的0xFFFF000010080000这个位置。

	__HEAD
_head:
	/*
	 * DO NOT MODIFY. Image header expected by Linux boot-loaders.
	 */
#ifdef CONFIG_EFI
	/*
	 * This add instruction has no meaningful effect except that
	 * its opcode forms the magic "MZ" signature required by UEFI.
	 */
	add	x13, x18, #0x16
	b	stext
#else
	b	stext				// branch to kernel start, magic
	.long	0				// reserved
#endif
	le64sym	_kernel_offset_le		// Image load offset from start of RAM, little-endian
	le64sym	_kernel_size_le			// Effective size of kernel image, little-endian
	le64sym	_kernel_flags_le		// Informative flags, little-endian
	.quad	0				// reserved
	.quad	0				// reserved
	.quad	0				// reserved
	.ascii	ARM64_IMAGE_MAGIC		// Magic number
#ifdef CONFIG_EFI
	.long	pe_header - _head		// Offset to the PE header.

关于表示当前位置的"."和各个节的内容,后面就不再详细说明了。

2.3 .text节

	.text : {			/* Real text segment		*/
		_stext = .;		/* Text and read-only data	*/
			__exception_text_start = .;
			*(.exception.text)
			__exception_text_end = .;
			IRQENTRY_TEXT
			SOFTIRQENTRY_TEXT
			ENTRY_TEXT
			TEXT_TEXT
			SCHED_TEXT
			CPUIDLE_TEXT
			LOCK_TEXT
			KPROBES_TEXT
			HYPERVISOR_TEXT
			IDMAP_TEXT
			HIBERNATE_TEXT
			TRAMP_TEXT
			*(.fixup)
			*(.gnu.warning)
		. = ALIGN(16);
		*(.got)			/* Global offset table		*/
	}
	. = ALIGN(SEGMENT_ALIGN);
	_etext = .;			/* End of text section */

预处理后变成:

 .text : {
  _stext = .;
   __exception_text_start = .;
   *(.exception.text)
   __exception_text_end = .;
   . = ALIGN(8); __irqentry_text_start = .; *(.irqentry.text) __irqentry_text_end = .;
   . = ALIGN(8); __softirqentry_text_start = .; *(.softirqentry.text) __softirqentry_text_end = .;
   . = ALIGN(8); __entry_text_start = .; *(.entry.text) __entry_text_end = .;
   . = ALIGN(8); *(.text.hot .text .text.fixup .text.unlikely) *(.text..refcount) *(.ref.text)
   . = ALIGN(8); __sched_text_start = .; *(.sched.text) __sched_text_end = .;
   . = ALIGN(8); __cpuidle_text_start = .; *(.cpuidle.text) __cpuidle_text_end = .;
   . = ALIGN(8); __lock_text_start = .; *(.spinlock.text) __lock_text_end = .;
   . = ALIGN(8); __kprobes_text_start = .; *(.kprobes.text) __kprobes_text_end = .;
   . = ALIGN(0x00001000); __hyp_idmap_text_start = .; *(.hyp.idmap.text) __hyp_idmap_text_end = .; __hyp_text_start = .; *(.hyp.text) __hyp_text_end = .;
   . = ALIGN(0x00001000); __idmap_text_start = .; *(.idmap.text) __idmap_text_end = .;
   . = ALIGN(0x00001000); __hibernate_exit_text_start = .; *(.hibernate_exit.text) __hibernate_exit_text_end = .;
   . = ALIGN((1 << 12)); __entry_tramp_text_start = .; *(.entry.tramp.text) . = ALIGN((1 << 12)); __entry_tramp_text_end = .;
   *(.fixup)
   *(.gnu.warning)
  . = ALIGN(16);
  *(.got)
 }
  . = ALIGN(0x00010000);
 _etext = .;

_stext记录了当前的内存地址,这里表示 .text是紧跟在 .head.text节后面。最后的_etext 是记录当前节的结束地址
**. = ALIGN(0x00010000);**表示当前地址进行64K的对齐。
我们可以把这个代码段分为4个部分看,第一部分是寻找合适的地址,一般是使用ALIGN宏进行内存对齐,也有一些是直接跟在上一个段后面;第二部分是记录段起始地址到某个标记中;第三部分是加载各个代码段;第四部分是记录段结束地址到标记中。我们看到 .text节中有好多段,第一个是arm64异常处理代码段,比如内存异常do_mem_abort、栈异常do_sp_pc_abort、用户态指令访问异常do_el0_ia_bp_hardening和未定义异常do_undefinstr等。其他自己看。查看方法是使用grep 搜索,具体如下:

rlk@rlk:runninglinuxkernel_5.0$ grep -nr exception.text
...
arch/arm64/include/asm/exception.h:25:#define __exception	__attribute__((section(".exception.text")))
...
rlk@rlk:runninglinuxkernel_5.0$ grep -nrw __exception
arch/arm64/mm/fault.c:741:asmlinkage void __exception do_mem_abort(unsigned long addr, unsigned int esr,
arch/arm64/mm/fault.c:759:asmlinkage void __exception do_el0_irq_bp_hardening(void)
arch/arm64/mm/fault.c:765:asmlinkage void __exception do_el0_ia_bp_hardening(unsigned long addr,
arch/arm64/mm/fault.c:782:asmlinkage void __exception do_sp_pc_abort(unsigned long addr,
arch/arm64/mm/fault.c:827:asmlinkage int __exception do_debug_exception(unsigned long addr,
arch/arm64/kernel/traps.c:413:asmlinkage void __exception do_undefinstr(struct pt_regs *regs)
arch/arm64/kernel/traps.c:684:asmlinkage void __exception do_cp15instr(unsigned int esr, struct pt_regs *regs)
arch/arm64/kernel/traps.c:724:asmlinkage void __exception do_sysinstr(unsigned int esr, struct pt_regs *regs)

2.4 .rodata节

RO_DATA(PAGE_SIZE)		/* everything from this point to     */

预处理后变成:

 . = ALIGN(((1 << 12))); 
 .rodata : AT(ADDR(.rodata) - 0) { __start_rodata = .; *(.rodata) *(.rodata.*) __start_ro_after_init = .; *(.data..ro_after_init) . = ALIGN(8); __start___jump_table = .; KEEP(*(__jump_table)) __stop___jump_table = .; __end_ro_after_init = .; KEEP(*(__vermagic)) . = ALIGN(8); __start___tracepoints_ptrs = .; KEEP(*(__tracepoints_ptrs)) __stop___tracepoints_ptrs = .; *(__tracepoints_strings) } 
 .rodata1 : AT(ADDR(.rodata1) - 0) { *(.rodata1) } 
 .pci_fixup : AT(ADDR(.pci_fixup) - 0) { __start_pci_fixups_early = .; KEEP(*(.pci_fixup_early)) __end_pci_fixups_early = .; __start_pci_fixups_header = .; KEEP(*(.pci_fixup_header)) __end_pci_fixups_header = .; __start_pci_fixups_final = .; KEEP(*(.pci_fixup_final)) __end_pci_fixups_final = .; __start_pci_fixups_enable = .; KEEP(*(.pci_fixup_enable)) __end_pci_fixups_enable = .; __start_pci_fixups_resume = .; KEEP(*(.pci_fixup_resume)) __end_pci_fixups_resume = .; __start_pci_fixups_resume_early = .; KEEP(*(.pci_fixup_resume_early)) __end_pci_fixups_resume_early = .; __start_pci_fixups_suspend = .; KEEP(*(.pci_fixup_suspend)) __end_pci_fixups_suspend = .; __start_pci_fixups_suspend_late = .; KEEP(*(.pci_fixup_suspend_late)) __end_pci_fixups_suspend_late = .; } 
 .builtin_fw : AT(ADDR(.builtin_fw) - 0) { __start_builtin_fw = .; KEEP(*(.builtin_fw)) __end_builtin_fw = .; } 
 __ksymtab : AT(ADDR(__ksymtab) - 0) { __start___ksymtab = .; KEEP(*(SORT(___ksymtab+*))) __stop___ksymtab = .; } 
 __ksymtab_gpl : AT(ADDR(__ksymtab_gpl) - 0) { __start___ksymtab_gpl = .; KEEP(*(SORT(___ksymtab_gpl+*))) __stop___ksymtab_gpl = .; } 
 __ksymtab_unused : AT(ADDR(__ksymtab_unused) - 0) { __start___ksymtab_unused = .; KEEP(*(SORT(___ksymtab_unused+*))) __stop___ksymtab_unused = .; }
 __ksymtab_unused_gpl : AT(ADDR(__ksymtab_unused_gpl) - 0) { __start___ksymtab_unused_gpl = .; KEEP(*(SORT(___ksymtab_unused_gpl+*))) __stop___ksymtab_unused_gpl = .; } 
 __ksymtab_gpl_future : AT(ADDR(__ksymtab_gpl_future) - 0) { __start___ksymtab_gpl_future = .; KEEP(*(SORT(___ksymtab_gpl_future+*))) __stop___ksymtab_gpl_future = .; } 
 __kcrctab : AT(ADDR(__kcrctab) - 0) { __start___kcrctab = .; KEEP(*(SORT(___kcrctab+*))) __stop___kcrctab = .; } 
 __kcrctab_gpl : AT(ADDR(__kcrctab_gpl) - 0) { __start___kcrctab_gpl = .; KEEP(*(SORT(___kcrctab_gpl+*))) __stop___kcrctab_gpl = .; }
 __kcrctab_unused : AT(ADDR(__kcrctab_unused) - 0) { __start___kcrctab_unused = .; KEEP(*(SORT(___kcrctab_unused+*))) __stop___kcrctab_unused = .; } 
 __kcrctab_unused_gpl : AT(ADDR(__kcrctab_unused_gpl) - 0) { __start___kcrctab_unused_gpl = .; KEEP(*(SORT(___kcrctab_unused_gpl+*))) __stop___kcrctab_unused_gpl = .; }
 __kcrctab_gpl_future : AT(ADDR(__kcrctab_gpl_future) - 0) { __start___kcrctab_gpl_future = .; KEEP(*(SORT(___kcrctab_gpl_future+*))) __stop___kcrctab_gpl_future = .; } 
 __ksymtab_strings : AT(ADDR(__ksymtab_strings) - 0) { *(__ksymtab_strings) } 
 __init_rodata : AT(ADDR(__init_rodata) - 0) { *(.ref.rodata) } 
 __param : AT(ADDR(__param) - 0) { __start___param = .; KEEP(*(__param)) __stop___param = .; } 
 __modver : AT(ADDR(__modver) - 0) { __start___modver = .; KEEP(*(__modver)) __stop___modver = .; . = ALIGN(((1 << 12))); __end_rodata = .; } 
 . = ALIGN(((1 << 12)));

这里是只读数据段集合。**. = ALIGN(((1 << 12))); 表示地址进行4K对齐。对齐后.rodata节的地址是ffff0000118f0000,这是使用readelf读取出来的信息。RO_DATA展开后是18个rodate相关的节,每一个节使用AT指定了加载地址, AT(ADDR(.rodata) - 0) **表示加载到当前.rodata节的地址,也就是跟当前地址是一样的。这里没有指定运行地址,默认是当前地址。所以这里运行地址和加载地址是相同的,不需要额外的重定向。前面的节中没有使用AT指定加载地址,说明是和运行地址一样的。每一个节里面的内容就不再具体展开分析了。

2.5 EXCEPTION_TABLE

预处理后变成:

. = ALIGN(8); __ex_table : AT(ADDR(__ex_table) - 0) { __start___ex_table = .; KEEP(*(__ex_table)) __stop___ex_table = .; }

这个是存放arm64系统调用表代码段的,也就是系统调用进来的处理代码。从elf来看内存地址为0xffff000011bf7000。

2.6 NOTES

预处理后变成:

.notes : AT(ADDR(.notes) - 0) { __start_notes = .; KEEP(*(.note.*)) __stop_notes = .; }

从elf来看内存地址为0xffff000011bf9120。不知道是干嘛用的。

2.7 idmap_pg_dir 地址

	. = ALIGN(PAGE_SIZE);
	idmap_pg_dir = .;
	. += IDMAP_DIR_SIZE;

idmap_pg_dir是Linux 内核中是一个用于建立恒等映射的页全局目录基地址,它的主要作用是在内核启动的早期阶段为物理地址和虚拟地址提供直接映射。这意味着通过这个页表,虚拟地址和物理地址是相等的,从而允许内核在启用 MMU(内存管理单元)之前和之后无差别地访问物理内存。
**. = ALIGN(PAGE_SIZE);**表示当前地址进行4K对齐。
**. += IDMAP_DIR_SIZE;**表示占用内存大小为IDMAP_DIR_SIZE,计算得出结果为3个页。

2.8 tramp_pg_dir 地址

	tramp_pg_dir = .;
	. += PAGE_SIZE;

tramp_pg_dir 在 Linux 内核中是用于解决某些特定安全问题(如漏洞)的页表目录。它提供了一种方法来创建一个安全的过渡或“跳板”执行环境,以便在处理某些潜在的安全风险时,内核能够安全地过渡到一个隔离的、受控的执行上下文。

2.9 swapper_pg_dir 地址

	swapper_pg_dir = .;
	. += PAGE_SIZE;
	swapper_pg_end = .;

swapper_pg_dir 在 Linux 内核中是一个非常重要的数据结构,它指的是内核使用的一组页表的起始位置,这些页表构成了内核的全局页目录。这里预留了页全局目录页表的内存。

2.10 .init相关的节

	. = ALIGN(SEGMENT_ALIGN);
	__init_begin = .;
	__inittext_begin = .;
...
	. = ALIGN(PAGE_SIZE);
	__inittext_end = .;
	__initdata_begin = .;
...
	. = ALIGN(SEGMENT_ALIGN);
	__initdata_end = .;
	__init_end = .;

从elf读取到.init.text的地址是0xffff000011c00000。可以分为代码段和数据段。

2.10.1 .init.text 节

这是init的第一个代码段:

INIT_TEXT_SECTION(8)

预处理后变成:

. = ALIGN(8); .init.text : AT(ADDR(.init.text) - 0) { _sinittext = .; *(.init.text .init.text.*) *(.text.startup) *(.meminit.text*) _einittext = .; }

.init.text的内存位置是0xffff000011c00000,这部分是存放内核启动相关代码的。

2.10.2 .exit.text 节

这是init的第二个代码段:

	.exit.text : {
		ARM_EXIT_KEEP(EXIT_TEXT)
	}

预处理后变成:

 .exit.text : {
  *(.exit.text) *(.text.exit) *(.memexit.text)
 }

.exit.text的内存位置是ffff000011cc37d0,这部分是存放内核退出相关代码的。

2.10.3 .altinstructions和.altinstr_replace

这是init的第三个代码段:

	. = ALIGN(4);
	.altinstructions : {
		__alt_instructions = .;
		*(.altinstructions)
		__alt_instructions_end = .;
	}
	.altinstr_replacement : {
		*(.altinstr_replacement)
	}

代码段放在arch/arm64/include/asm/alternative.h文件中,代码的使用具体看arch/arm64/kernel/alternative.c文件,作用是实现内核代码的动态指令替换功能。这个功能允许内核在运行时根据 CPU 的特性来优化或修改执行的指令,而不需要重启系统或更换内核。

2.10.4 .init.data

这是init的第一个数据段:

	.init.data : {
		INIT_DATA
		INIT_SETUP(16)
		INIT_CALLS
		CON_INITCALL
		INIT_RAM_FS
		*(.init.rodata.* .init.bss)	/* from the EFI stub */
	}

预处理后变成:

 .init.data : {
  KEEP(*(SORT(___kentry+*))) *(.init.data init.data.*) *(.meminit.data*) 
  . = ALIGN(8); __start_mcount_loc = .; KEEP(*(__mcount_loc)) KEEP(*(__patchable_function_entries)) __stop_mcount_loc = .; *(.init.rodata .init.rodata.*) 
  . = ALIGN(8); __start_ftrace_events = .; KEEP(*(_ftrace_events)) __stop_ftrace_events = .; __start_ftrace_eval_maps = .; KEEP(*(_ftrace_eval_map)) __stop_ftrace_eval_maps = .; 
  . = ALIGN(8); __start_syscalls_metadata = .; KEEP(*(__syscalls_metadata)) __stop_syscalls_metadata = .; 
  . = ALIGN(8); __start_kprobe_blacklist = .; KEEP(*(_kprobe_blacklist)) __stop_kprobe_blacklist = .; *(.meminit.rodata) 
  . = ALIGN(8); __clk_of_table = .; KEEP(*(__clk_of_table)) KEEP(*(__clk_of_table_end)) 
  . = ALIGN(8); __reservedmem_of_table = .; KEEP(*(__reservedmem_of_table)) KEEP(*(__reservedmem_of_table_end)) 
  . = ALIGN(8); __timer_of_table = .; KEEP(*(__timer_of_table)) KEEP(*(__timer_of_table_end)) 
  . = ALIGN(8); __cpu_method_of_table = .; KEEP(*(__cpu_method_of_table)) KEEP(*(__cpu_method_of_table_end)) 
  . = ALIGN(8); __cpuidle_method_of_table = .; KEEP(*(__cpuidle_method_of_table)) KEEP(*(__cpuidle_method_of_table_end)) 
  . = ALIGN(32); __dtb_start = .; KEEP(*(.dtb.init.rodata)) __dtb_end = .; 
  . = ALIGN(8); __irqchip_of_table = .; KEEP(*(__irqchip_of_table)) KEEP(*(__irqchip_of_table_end)) 
  . = ALIGN(8); __earlycon_table = .; KEEP(*(__earlycon_table)) __earlycon_table_end = .;
  . = ALIGN(16); __setup_start = .; KEEP(*(.init.setup)) __setup_end = .;
  __initcall_start = .; KEEP(*(.initcallearly.init)) __initcall0_start = .; KEEP(*(.initcall0.init)) KEEP(*(.initcall0s.init)) __initcall1_start = .; KEEP(*(.initcall1.init)) KEEP(*(.initcall1s.init)) __initcall2_start = .; KEEP(*(.initcall2.init)) KEEP(*(.initcall2s.init)) __initcall3_start = .; KEEP(*(.initcall3.init)) KEEP(*(.initcall3s.init)) __initcall4_start = .; KEEP(*(.initcall4.init)) KEEP(*(.initcall4s.init)) __initcall5_start = .; KEEP(*(.initcall5.init)) KEEP(*(.initcall5s.init)) __initcallrootfs_start = .; KEEP(*(.initcallrootfs.init)) KEEP(*(.initcallrootfss.init)) __initcall6_start = .; KEEP(*(.initcall6.init)) KEEP(*(.initcall6s.init)) __initcall7_start = .; KEEP(*(.initcall7.init)) KEEP(*(.initcall7s.init)) __initcall_end = .;
  __con_initcall_start = .; KEEP(*(.con_initcall.init)) __con_initcall_end = .;
 
  *(.init.rodata.* .init.bss)
 }

.init.data包括了init.data、mcount相关、ftrace的event相关、kprobe相关、设备树、irqchip相关、各个级别的initcall、启动使用的只读数据段和bss段。

2.10.5 .exit.data

这是init的第二个数据段:

 .exit.data : {
  *(.exit.data .exit.data.*) *(.fini_array .fini_array.*) *(.dtors .dtors.*) *(.memexit.data*) *(.memexit.rodata*)
 }

这里主要是存放了initcall的退出函数。

2.10.6 PERCPU_SECTION节

这是init的第三个数据段:

	PERCPU_SECTION(L1_CACHE_BYTES)

预处理后变成:

 . = ALIGN((1 << 12)); 
 .data..percpu : AT(ADDR(.data..percpu) - 0) { 
 __per_cpu_load = .; __per_cpu_start = .; *(.data..percpu..first) 
 . = ALIGN((1 << 12)); *(.data..percpu..page_aligned) 
 . = ALIGN((1 << (6))); *(.data..percpu..read_mostly) 
 . = ALIGN((1 << (6))); *(.data..percpu) *(.data..percpu..shared_aligned) 
 __per_cpu_end = .; }

这里预留了是存放每cpu数据的内存。放在0xffff000011ddf000上,占用了大约82K。

2.10.7 .rela.dyn节

这是init的第四个数据段:

	.rela.dyn : ALIGN(8) {
		*(.rela .rela*)
	}
	__rela_offset	= ABSOLUTE(ADDR(.rela.dyn) - KIMAGE_VADDR);
	__rela_size	= SIZEOF(.rela.dyn);

不知道干嘛用的。

2.11 .data

	_data = .;
...
	_edata = .;

这里是内核的数据段。放在ffff000012090000上。数据段有分为几个部分,包括:_sdata、.mmuoff.data.write和.mmuoff.data.read。

2.11.1 _sdata

这里是.data的第一个数据段:

	_sdata = .;
	RW_DATA_SECTION(L1_CACHE_BYTES, PAGE_SIZE, THREAD_ALIGN)

预处理后变成:

 _data = .;
 _sdata = .;
 . = ALIGN((1 << 12)); .data : AT(ADDR(.data) - 0) { 
 . = ALIGN((((1)) << (15 + 0))); __start_init_task = .; init_thread_union = .; init_stack = .; KEEP(*(.data..init_task)) KEEP(*(.data..init_thread_info)) . = __start_init_task + (((1)) << (15 + 0)); __end_init_task = .; 
 . = ALIGN((1 << 12)); __nosave_begin = .; *(.data..nosave) 
 . = ALIGN((1 << 12)); __nosave_end = .; 
 . = ALIGN((1 << 12)); *(.data..page_aligned) 
 . = ALIGN((1 << (6))); *(.data..cacheline_aligned) 
 . = ALIGN((1 << (6))); *(.data..read_mostly) 
 . = ALIGN((1 << (6))); *(.xiptext) *(.data) *(.ref.data) *(.data..shared_aligned) *(.data.unlikely) __start_once = .; *(.data.once) __end_once = .; 
 . = ALIGN(32); *(__tracepoints) 
 . = ALIGN(8); __start___verbose = .; KEEP(*(__verbose)) __stop___verbose = .; __start___trace_bprintk_fmt = .; KEEP(*(__trace_printk_fmt)) __stop___trace_bprintk_fmt = .; 
 . = ALIGN(32); __start__bpf_raw_tp = .; KEEP(*(__bpf_raw_tp_map)) __stop__bpf_raw_tp = .; __start___tracepoint_str = .; KEEP(*(__tracepoint_str)) __stop___tracepoint_str = .; CONSTRUCTORS } 

这里是内核的静态数据段。放在ffff000012090000上。

2.11.2 .mmuoff.data.write和.mmuoff.data.read

这里是.data的第二个数据段:

	.mmuoff.data.write : ALIGN(SZ_2K) {
		__mmuoff_data_start = .;
		*(.mmuoff.data.write)
	}
	. = ALIGN(SZ_2K);
	.mmuoff.data.read : {
		*(.mmuoff.data.read)
		__mmuoff_data_end = .;
	}

如果遇到了一种情况,就是在关闭MMU的情况下写入数据,但在打开MMU的情况下读取数据,则需要使缓存线路无效,从而丢弃缓存中的数据的cache回写颗粒。将需要这种类型维护的部分保持在其自己的缓存回写颗粒(CWG)区域中,这样缓存维护操作就不会干扰相邻的数据。这里就是预留了内存空间,从elf输出来看, .mmuoff.data.write预留12个bit,.mmuoff.data.read预留8个bit。

2.12 BSS_SECTION

BSS_SECTION(0, 0, 0)

预处理后变成:

 . = ALIGN(0); 
 __bss_start = .; 
 . = ALIGN(0); 
 .sbss : AT(ADDR(.sbss) - 0) { 
 	*(.dynsbss) *(.sbss) *(.scommon) 
 } 
 . = ALIGN(0); 
 .bss : AT(ADDR(.bss) - 0) { 
	 *(.bss..page_aligned) *(.dynbss) *(.bss) *(COMMON) 
 } 
 . = ALIGN(0); __bss_stop = .;

BSS段主要用于存储程序中未初始化的全局变量和静态变量。

2.13 init_pg_dir 地址

. = ALIGN(PAGE_SIZE);
init_pg_dir = .;
. += INIT_DIR_SIZE;
init_pg_end = .;

这是内核启动过程中建立粗粒度映射页表。init_pg_dir 是页全局目录页表基地址。INIT_DIR_SIZE是5个页大小。

2.14 STABS_DEBUG

预处理后变成:

 .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) }

2.15 HEAD_SYMBOLS

预处理后变成:

 _kernel_size_le_lo32 = (((_end - _text) & 0xffffffff) & 0xffffffff); 
 _kernel_size_le_hi32 = (((_end - _text) >> 32) & 0xffffffff); 
 _kernel_offset_le_lo32 = (((0x00080000) & 0xffffffff) & 0xffffffff); 
 _kernel_offset_le_hi32 = (((0x00080000) >> 32) & 0xffffffff); 
 _kernel_flags_le_lo32 = (((((0 << 0) | (((12 - 10) / 2) << (0 + 1)) | (1 << ((0 + 1) + 2)))) & 0xffffffff) & 0xffffffff); 
 _kernel_flags_le_hi32 = (((((0 << 0) | (((12 - 10) / 2) << (0 + 1)) | (1 << ((0 + 1) + 2)))) >> 32) & 0xffffffff);

这里仅仅是计算了一些变量的大小。

三、 断言

ASSERT(__hyp_idmap_text_end - (__hyp_idmap_text_start & ~(0x00001000 - 1)) <= 0x00001000,
 "HYP init code too big or misaligned")
ASSERT(__idmap_text_end - (__idmap_text_start & ~(0x00001000 - 1)) <= 0x00001000,
 "ID map text too big or misaligned")
ASSERT(__hibernate_exit_text_end - (__hibernate_exit_text_start & ~(0x00001000 - 1))
 <= 0x00001000, "Hibernate exit text too big or misaligned")
ASSERT((__entry_tramp_text_end - __entry_tramp_text_start) == (1 << 12),
 "Entry trampoline text too big")
ASSERT(_text == (((((((((0xffffffffffffffff)) - (((1)) << (48)) + 1) + (0)) + (0x08000000))) + (0x08000000))) + 0x00080000), "HEAD is misaligned")

这里的作用是避免一些代码或者数据越界了。