一、vmlinux.lds.S 的汇编分析
vmlinux.lds.S 的汇编分析,有利于对内核的理解
arch/arm64/kernel/vmlinux.lds.S
OUTPUT_ARCH(aarch64)
ENTRY(_text)jiffies = jiffies_64;
#ifdef CONFIG_KVM
#define HYPERVISOR_EXTABLE \
. = ALIGN(SZ_8); \
__start___kvm_ex_table = .; \
*(__kvm_ex_table) \
__stop___kvm_ex_table = .;#define HYPERVISOR_PERCPU_SECTION \
. = ALIGN(PAGE_SIZE); \
HYP_SECTION_NAME(.data..percpu) : { \
*(HYP_SECTION_NAME(.data..percpu)) \
}
#else /* CONFIG_KVM */
#define HYPERVISOR_EXTABLE
#define HYPERVISOR_PERCPU_SECTION
#endif#define HYPERVISOR_TEXT \
/* \
* Align to 4 KB so that \
* a) the HYP vector table is at its minimum \
* alignment of 2048 bytes \
* b) the HYP init code will not cross a page \
* boundary if its size does not exceed \
* 4 KB (see related ASSERT() below) \
*/ \
. = ALIGN(SZ_4K); \
__hyp_idmap_text_start = .; \
*(.hyp.idmap.text) \
__hyp_idmap_text_end = .; \
__hyp_text_start = .; \
*(.hyp.text) \
HYPERVISOR_EXTABLE \
__hyp_text_end = .;#define IDMAP_TEXT \
. = ALIGN(SZ_4K); \
__idmap_text_start = .; \
*(.idmap.text) \
__idmap_text_end = .;#ifdef CONFIG_HIBERNATION
#define HIBERNATE_TEXT \
. = ALIGN(SZ_4K); \
__hibernate_exit_text_start = .; \
*(.hibernate_exit.text) \
__hibernate_exit_text_end = .;
#else
#define HIBERNATE_TEXT
#endif#ifdef CONFIG_UNMAP_KERNEL_AT_EL0
#define TRAMP_TEXT \
. = ALIGN(PAGE_SIZE); \
__entry_tramp_text_start = .; \
*(.entry.tramp.text) \
. = ALIGN(PAGE_SIZE); \
__entry_tramp_text_end = .;
#else
#define TRAMP_TEXT
#endif/*
* The size of the PE/COFF section that covers the kernel image, which
* runs from _stext to _edata, must be a round multiple of the PE/COFF
* FileAlignment, which we set to its minimum value of 0x200. '_stext'
* itself is 4 KB aligned, so padding out _edata to a 0x200 aligned
* boundary should be sufficient.
*/
PECOFF_FILE_ALIGNMENT = 0x200;#ifdef CONFIG_EFI
#define PECOFF_EDATA_PADDING \
.pecoff_edata_padding : { BYTE(0); . = ALIGN(PECOFF_FILE_ALIGNMENT); }
#else
#define PECOFF_EDATA_PADDING
#endif
OUTPUT_ARCH(aarch64)说明最终编译的格式为aarch64。
ENTRY(_text)表示其入口地址是_text
jiffies
是一个32位无符号整数,它表示自系统启动以来的时钟滴答数。每个时钟滴答对应于一个时钟周期,其长度取决于系统的时钟频率。通过增加或减少jiffies
的值,可以表示时间的流逝或倒退。
jiffies_64
是一个64位无符号整数,它提供了更大的计数范围。在一些需要更长计时周期的场景下,可以使用jiffies_64
来获得更精确的时间跟踪。
在某些情况下,为了保持兼容性或简化代码,jiffies
和jiffies_64
可能会被设置为相同的值。这样,无论是使用32位还是64位的内核,都可以使用相同的变量名来引用系统时间。
配置KVM,会设置hypervisor 的对齐,这是关于虚拟化的功能。涉及到内核源码中的 kvm/ virt/ xen/ 等目录下的代码块。
SECTIONS
{
/*
* XXX: The linker does not define how output sections are
* assigned to input sections when there are multiple statements
* matching the same input section name. There is no documented
* order of matching.
*/
DISCARDS
/DISCARD/ : {
*(.interp .dynamic)
*(.dynsym .dynstr .hash .gnu.hash)
}. = KIMAGE_VADDR;
.head.text : {
_text = .;
HEAD_TEXT
}
.text : { /* Real text segment */
_stext = .; /* Text and read-only data */
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 */
}
section
SECTIONS { } 是链接脚本语法中的关键命令,用以描述输出文件的内存布局。
SECTIONS 命令告诉链接文件如何把输入文件的段映射到输出文件的各个段中,如何将输入段整合为输出段,如何把输出段放入程序地址空间和进程地址空间中。
/DISCARD/ 段
这是一个特殊的输出段,被该段引用的任何输入段,将不会出现在输出文件中。
DISCARDS
/DISCARD/ : {
*(.interp .dynamic)
*(.dynsym .dynstr .hash .gnu.hash)
}
.head.text 段
. = KIMAGE_VADDR;
.head.text : {
_text = .;
HEAD_TEXT
}
'.' 号是连接脚本中一个特殊的符号,用以表示当前位置计数器。
最开始将 KIMAGE_VADDR 赋值给 '.' 意思是把代码段的地址设置给当前位置;
KIMAGE_VADDR 在 memory.h 中定义
#define VA_BITS (CONFIG_ARM64_VA_BITS)
#define _PAGE_OFFSET(va) (-(UL(1) << (va)))
#define PAGE_OFFSET (_PAGE_OFFSET(VA_BITS))
#define KIMAGE_VADDR (MODULES_END)
#define BPF_JIT_REGION_START (KASAN_SHADOW_END)
#define BPF_JIT_REGION_SIZE (SZ_128M)
#define BPF_JIT_REGION_END (BPF_JIT_REGION_START + BPF_JIT_REGION_SIZE)
#define MODULES_END (MODULES_VADDR + MODULES_VSIZE)
#define MODULES_VADDR (BPF_JIT_REGION_END)
#define MODULES_VSIZE (SZ_128M)
#define VMEMMAP_START (-VMEMMAP_SIZE - SZ_2M)
#define VMEMMAP_END (VMEMMAP_START + VMEMMAP_SIZE)
#define PCI_IO_END (VMEMMAP_START - SZ_2M)
#define PCI_IO_START (PCI_IO_END - PCI_IO_SIZE)
#define FIXADDR_TOP (PCI_IO_START - SZ_2M)
".head.text" 表示输出段名称,对应的输入段位 HEAD_TEXT:
include/asm-generic/vmlinux.lds.h
#define HEAD_TEXT KEEP(*(.head.text))
text 段
.text : { /* Real text segment */
_stext = .; /* Text and read-only data */
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 */
}/*
* Make sure that the .got.plt is either completely empty or it
* contains only the lazy dispatch entries.
*/
.got.plt : { *(.got.plt) }
ASSERT(SIZEOF(.got.plt) == 0 || SIZEOF(.got.plt) == 0x18,
"Unexpected GOT/PLT entries detected!"). = ALIGN(SEGMENT_ALIGN);
_etext = .; /* End of text section */
定义文本段内容,区间为 [ _stext, _etext )
开始的时候,将当前位置存放在 _stext 和 __exception_text_start 中;
接着加入所有输入目标文件的 .exception.text 段到 .text 段中;
添加完 .exception.text 后,将当前位置存入 __exception_text_end 中;
接着是宏定义 IRQENTRY_TEXT,定义在 vmlinux.lds.h 中:
include/asm-generic/vmlinux.lds.h
#define IRQENTRY_TEXT \
ALIGN_FUNCTION(); \
__irqentry_text_start = .; \
*(.irqentry.text) \
__irqentry_text_end = .;
其他以此类推
SOFTIRQENTRY_TEXT
ENTRY_TEXT
TEXT_TEXT
SCHED_TEXT
CPUIDLE_TEXT
LOCK_TEXT
KPROBES_TEXT
HYPERVISOR_TEXT
IDMAP_TEXT
HIBERNATE_TEXT
TRAMP_TEXT
这些都是类似
RO_DATA(PAGE_SIZE)
只读区间
/* everything from this point to __init_begin will be marked RO NX */
RO_DATA(PAGE_SIZE)
这个宏定义在 vmlinux.lds.文件中
/*
* Read only Data
*/
#define RO_DATA(align) \
. = ALIGN((align)); \
.rodata : AT(ADDR(.rodata) - LOAD_OFFSET) { \
__start_rodata = .; \
*(.rodata) *(.rodata.*) \
SCHED_DATA \
RO_AFTER_INIT_DATA /* Read only after init */ \
. = ALIGN(8); \
__start___tracepoints_ptrs = .; \
KEEP(*(__tracepoints_ptrs)) /* Tracepoints: pointer array */ \
__stop___tracepoints_ptrs = .; \
*(__tracepoints_strings)/* Tracepoints: strings */ \
} \
\
.rodata1 : AT(ADDR(.rodata1) - LOAD_OFFSET) { \
*(.rodata1) \
} \
\
/* PCI quirks */ \
.pci_fixup : AT(ADDR(.pci_fixup) - LOAD_OFFSET) { \
__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 = .; \
} \
\
/* Built-in firmware blobs */ \
.builtin_fw : AT(ADDR(.builtin_fw) - LOAD_OFFSET) ALIGN(8) { \
__start_builtin_fw = .; \
KEEP(*(.builtin_fw)) \
__end_builtin_fw = .; \
} \
\
TRACEDATA \
\
/* Kernel symbol table: Normal symbols */ \
__ksymtab : AT(ADDR(__ksymtab) - LOAD_OFFSET) { \
__start___ksymtab = .; \
KEEP(*(SORT(___ksymtab+*))) \
__stop___ksymtab = .; \
} \
\
/* Kernel symbol table: GPL-only symbols */ \
__ksymtab_gpl : AT(ADDR(__ksymtab_gpl) - LOAD_OFFSET) { \
__start___ksymtab_gpl = .; \
KEEP(*(SORT(___ksymtab_gpl+*))) \
__stop___ksymtab_gpl = .; \
} \
\
/* Kernel symbol table: Normal unused symbols */ \
__ksymtab_unused : AT(ADDR(__ksymtab_unused) - LOAD_OFFSET) { \
__start___ksymtab_unused = .; \
KEEP(*(SORT(___ksymtab_unused+*))) \
__stop___ksymtab_unused = .; \
} \
\
/* Kernel symbol table: GPL-only unused symbols */ \
__ksymtab_unused_gpl : AT(ADDR(__ksymtab_unused_gpl) - LOAD_OFFSET) { \
__start___ksymtab_unused_gpl = .; \
KEEP(*(SORT(___ksymtab_unused_gpl+*))) \
__stop___ksymtab_unused_gpl = .; \
} \
\
/* Kernel symbol table: GPL-future-only symbols */ \
__ksymtab_gpl_future : AT(ADDR(__ksymtab_gpl_future) - LOAD_OFFSET) { \
__start___ksymtab_gpl_future = .; \
KEEP(*(SORT(___ksymtab_gpl_future+*))) \
__stop___ksymtab_gpl_future = .; \
} \
\
/* Kernel symbol table: Normal symbols */ \
__kcrctab : AT(ADDR(__kcrctab) - LOAD_OFFSET) { \
__start___kcrctab = .; \
KEEP(*(SORT(___kcrctab+*))) \
__stop___kcrctab = .; \
} \
\
/* Kernel symbol table: GPL-only symbols */ \
__kcrctab_gpl : AT(ADDR(__kcrctab_gpl) - LOAD_OFFSET) { \
__start___kcrctab_gpl = .; \
KEEP(*(SORT(___kcrctab_gpl+*))) \
__stop___kcrctab_gpl = .; \
}/* Kernel symbol table: Normal unused symbols */ \
__kcrctab_unused : AT(ADDR(__kcrctab_unused) - LOAD_OFFSET) { \
__start___kcrctab_unused = .; \
KEEP(*(SORT(___kcrctab_unused+*))) \
__stop___kcrctab_unused = .; \
} \
\
/* Kernel symbol table: GPL-only unused symbols */ \
__kcrctab_unused_gpl : AT(ADDR(__kcrctab_unused_gpl) - LOAD_OFFSET) { \
__start___kcrctab_unused_gpl = .; \
KEEP(*(SORT(___kcrctab_unused_gpl+*))) \
__stop___kcrctab_unused_gpl = .; \
} \
\
/* Kernel symbol table: GPL-future-only symbols */ \
__kcrctab_gpl_future : AT(ADDR(__kcrctab_gpl_future) - LOAD_OFFSET) { \
__start___kcrctab_gpl_future = .; \
KEEP(*(SORT(___kcrctab_gpl_future+*))) \
__stop___kcrctab_gpl_future = .; \
} \
\
/* Kernel symbol table: strings */ \
__ksymtab_strings : AT(ADDR(__ksymtab_strings) - LOAD_OFFSET) { \
*(__ksymtab_strings) \
} \
\
/* __*init sections */ \
__init_rodata : AT(ADDR(__init_rodata) - LOAD_OFFSET) { \
*(.ref.rodata) \
MEM_KEEP(init.rodata) \
MEM_KEEP(exit.rodata) \
} \
\
/* Built-in module parameters. */ \
__param : AT(ADDR(__param) - LOAD_OFFSET) { \
__start___param = .; \
KEEP(*(__param)) \
__stop___param = .; \
} \
\
/* Built-in module versions. */ \
__modver : AT(ADDR(__modver) - LOAD_OFFSET) { \
__start___modver = .; \
KEEP(*(__modver)) \
__stop___modver = .; \
} \
\
RO_EXCEPTION_TABLE \
NOTES \
BTF \
\
. = ALIGN((align)); \
__end_rodata = .;
页表地址段
idmap_pg_dir = .;
. += IDMAP_DIR_SIZE;
idmap_pg_end = .;#ifdef CONFIG_UNMAP_KERNEL_AT_EL0
tramp_pg_dir = .;
. += PAGE_SIZE;
#endifreserved_pg_dir = .;
. += PAGE_SIZE;swapper_pg_dir = .;
. += PAGE_SIZE;
在 .init.text 段之前,会预留一部分的空间给一些页表初始化使用。
例如,idmap_pg_dir、tramp_pg_dir、reserved_pg_dir、swapper_pg_dir 等。
idmap_pg_dir 是 identity mapping 用到的页表。
init段
. = ALIGN(SEGMENT_ALIGN);
__init_begin = .; //开始
__inittext_begin = .;INIT_TEXT_SECTION(8)
__exittext_begin = .;
.exit.text : {
EXIT_TEXT
}
__exittext_end = .;. = ALIGN(4);
.altinstructions : {
__alt_instructions = .;
*(.altinstructions)
__alt_instructions_end = .;
}. = ALIGN(SEGMENT_ALIGN);
__inittext_end = .;
__initdata_begin = .;.init.data : {
INIT_DATA
INIT_SETUP(16)
INIT_CALLS
CON_INITCALL
INIT_RAM_FS
*(.init.rodata.* .init.bss) /* from the EFI stub */
}
.exit.data : {
EXIT_DATA
}PERCPU_SECTION(L1_CACHE_BYTES)
HYPERVISOR_PERCPU_SECTION.rela.dyn : ALIGN(8) {
*(.rela .rela*)
}__rela_offset = ABSOLUTE(ADDR(.rela.dyn) - KIMAGE_VADDR);
__rela_size = SIZEOF(.rela.dyn);#ifdef CONFIG_RELR
.relr.dyn : ALIGN(8) {
*(.relr.dyn)
}__relr_offset = ABSOLUTE(ADDR(.relr.dyn) - KIMAGE_VADDR);
__relr_size = SIZEOF(.relr.dyn);
#endif. = ALIGN(SEGMENT_ALIGN);
__initdata_end = .;
__init_end = .; //结束
初始化段范围是 [ __init_begin, __init_end ),包括了这里的 initext 段,以及后面 initdata 段,在 kernel_init() 中内核初始化完成,会将这部分内存释放掉,详细看 free_initmem() 函数。
inittext 段
__inittext_begin = .;
INIT_TEXT_SECTION(8)
__exittext_begin = .;
.exit.text : {
EXIT_TEXT
}
__exittext_end = .;. = ALIGN(4);
.altinstructions : {
__alt_instructions = .;
*(.altinstructions)
__alt_instructions_end = .;
}. = ALIGN(SEGMENT_ALIGN);
__inittext_end = .;
本节剖析的是 inittext 段,而不仅仅是 .init.text,inittext 区间为:
[ __inittext_begin, __inittext_end )
其中包括:
.init.text 段;
.exit.text 段;
.altinstuctions 段;
.altinstr_replacement 段;
INIT_TEXT_SECTION(8)
.init.text 段主要通过宏 INIT_TEXT_SECTION() 进行定义,下面来看下这个宏:
include/asm-generic/vmlinux.lds.h
#define INIT_TEXT_SECTION(inittext_align) \
. = ALIGN(inittext_align); \
.init.text : AT(ADDR(.init.text) - LOAD_OFFSET) { \
_sinittext = .; \
INIT_TEXT \
_einittext = .; \
}
#define INIT_TEXT \
*(.init.text .init.text.*) \
*(.text.startup) \
MEM_DISCARD(init.text*)
将所有目标文件中的 .init.text 段 或 .init.text.* 段放入 .init.text 段中;
将所有目标文件中的 .text.startup 段放入 .init.text 段中;
确认是否将所有目标文件中的 .mem##init.text.* 的段放入 .init.text 段中;
exit.text
__exittext_begin = .;
.exit.text : {
EXIT_TEXT
}
__exittext_end = .;
主要是通过宏 EXIT_TEXT 来定义 .exit.text 的输入section描述:
include/asm-generic/vmlinux.lds.h
#define EXIT_TEXT \
*(.exit.text) \
*(.text.exit) \
MEM_DISCARD(exit.text)
initdata段
__initdata_begin = .;
.init.data : {
INIT_DATA
INIT_SETUP(16)
INIT_CALLS
CON_INITCALL
INIT_RAM_FS
*(.init.rodata.* .init.bss) /* from the EFI stub */
}
.exit.data : {
EXIT_DATA
}PERCPU_SECTION(L1_CACHE_BYTES)
HYPERVISOR_PERCPU_SECTION.rela.dyn : ALIGN(8) {
*(.rela .rela*)
}__rela_offset = ABSOLUTE(ADDR(.rela.dyn) - KIMAGE_VADDR);
__rela_size = SIZEOF(.rela.dyn);#ifdef CONFIG_RELR
.relr.dyn : ALIGN(8) {
*(.relr.dyn)
}__relr_offset = ABSOLUTE(ADDR(.relr.dyn) - KIMAGE_VADDR);
__relr_size = SIZEOF(.relr.dyn);
#endif. = ALIGN(SEGMENT_ALIGN);
__initdata_end = .;
本节剖析的是 initdata 段,而不仅仅是 .init.data,initdata 区间为:
[ __initdata_begin,__initdata_end)
这其中包括:
.init.data 段;
.exit.data 段;
.data..percpu 段;
.rela.dyn 段;
.init.data 又包括:
INIT_DATA
INIT_SETUP
INIT_CALLS
CON_INITCALL
INIT_RAM_FS
.init.rodata.*
.init.bss
下面笔者将会逐个剖析这些 .init.data 段的宏定义。当然,initdata 中还有个重要的 .data.percpu 段,通过 PERCPU_SECTION 进行定义,本节也会剖析该字段。
INIT_DATA
include/asm-generic/vmlinux.lds.h
#define INIT_DATA \
KEEP(*(SORT(___kentry+*))) \
*(.init.data init.data.*) \
MEM_DISCARD(init.data*) \
KERNEL_CTORS() \
MCOUNT_REC() \
*(.init.rodata .init.rodata.*) \
FTRACE_EVENTS() \
TRACE_SYSCALLS() \
KPROBE_BLACKLIST() \
ERROR_INJECT_WHITELIST() \
MEM_DISCARD(init.rodata) \
CLK_OF_TABLES() \
RESERVEDMEM_OF_TABLES() \
TIMER_OF_TABLES() \
CPU_METHOD_OF_TABLES() \
CPUIDLE_METHOD_OF_TABLES() \
KERNEL_DTB() \
IRQCHIP_OF_MATCH_TABLE() \
ACPI_PROBE_TABLE(irqchip) \
ACPI_PROBE_TABLE(timer) \
THERMAL_TABLE(governor) \
EARLYCON_TABLE() \
LSM_TABLE() \
EARLY_LSM_TABLE()
INIT_SETUP(16)
include/asm-generic/vmlinux.lds.h
#define INIT_SETUP(initsetup_align) \
. = ALIGN(initsetup_align); \
__setup_start = .; \
KEEP(*(.init.setup)) \
__setup_end = .;
INIT_CALLS
include/asm-generic/vmlinux.lds.h
#define INIT_CALLS \
__initcall_start = .; \
KEEP(*(.initcallearly.init)) \
INIT_CALLS_LEVEL(0) \
INIT_CALLS_LEVEL(1) \
INIT_CALLS_LEVEL(2) \
INIT_CALLS_LEVEL(3) \
INIT_CALLS_LEVEL(4) \
INIT_CALLS_LEVEL(5) \
INIT_CALLS_LEVEL(rootfs) \
INIT_CALLS_LEVEL(6) \
INIT_CALLS_LEVEL(7) \
__initcall_end = .;
INIT_CALLS 总的来说,做了四件事情:
#设定 __initcall_start,这里是initcall 函数指针的起始位置;
#将所有目标文件的 .initcallearly.init 段加入 .init.data 段;
#调用 INIT_CALLS_LEVEL(),将各个initcall 级别的段加入到 .init.data 段中;
#设定 __initcall_end,这里是initcall 函数指针的结束位置;
下面来看下 INIT_CALLS_LEVEL() 做了什么:
#define INIT_CALLS_LEVEL(level) \
__initcall##level##_start = .; \
KEEP(*(.initcall##level##.init)) \
KEEP(*(.initcall##level##s.init)) \
首先,定义每个 level 的起始位置 __initcall##level##_start,连续调用每个 level 下所有 initcall 函数,而这个 level 下第一个 initcall 函数指针,也就是这个 level 函数指针的起始位置定义在 initcall_levels 这个函数指针数组中:
init/main.c
static initcall_entry_t *initcall_levels[] __initdata = {
__initcall0_start,
__initcall1_start,
__initcall2_start,
__initcall3_start,
__initcall4_start,
__initcall5_start,
__initcall6_start,
__initcall7_start,
__initcall_end,
};
回到 INIT_CALLS_LEVEL(),接下来会将所有目标文件中 .initcall##level##.init 和 .initcall##level##s.init 段加入到 .init.data 段中;
这样,在 System.map 中我们清晰看到 initcall 的各个段
...
ffffffc012032ee0 d __initcall_223_42_trace_init_flags_sys_enterearly
ffffffc012032ee0 D __initcall_start
ffffffc012032ee0 D __setup_end
ffffffc012032ee8 d __initcall_224_66_trace_init_flags_sys_exitearly
ffffffc012032ef0 d __initcall_163_146_cpu_suspend_initearly
ffffffc012032ef8 d __initcall_151_267_asids_initearly
ffffffc012032f00 d __initcall_167_688_spawn_ksoftirqdearly
ffffffc012032f08 d __initcall_343_6656_migration_initearly
...
ffffffc012032f90 d __initcall_312_768_initialize_ptr_randomearly
ffffffc012032f98 D __initcall0_start
ffffffc012032f98 d __initcall_241_771_bpf_jit_charge_init0
ffffffc012032fa0 d __initcall_141_53_init_mmap_min_addr0
ffffffc012032fa8 d __initcall_209_6528_pci_realloc_setup_params0
ffffffc012032fb0 d __initcall_339_1143_net_ns_init0
ffffffc012032fb8 D __initcall1_start
ffffffc012032fb8 d __initcall_160_1437_fpsimd_init1
ffffffc012032fc0 d __initcall_181_669_tagged_addr_init1
...
ffffffc012033178 d __initcall_347_1788_init_default_flow_dissectors1
ffffffc012033180 d __initcall_360_2821_netlink_proto_init1
ffffffc012033188 D __initcall2_start
ffffffc012033188 d __initcall_165_139_debug_monitors_init2
ffffffc012033190 d __initcall_141_333_irq_sysfs_init2
...
ffffffc0120332b8 d __initcall_304_814_kobject_uevent_init2
ffffffc0120332c0 d __initcall_184_1686_msm_rpm_driver_init2s
ffffffc0120332c8 D __initcall3_start
ffffffc0120332c8 d __initcall_173_390_debug_traps_init3
ffffffc0120332d0 d __initcall_161_275_reserve_memblock_reserved_regions3
...
ffffffc012033370 d __initcall_132_5273_gsi_init3
ffffffc012033378 d __initcall_149_547_of_platform_default_populate_init3s
ffffffc012033380 D __initcall4_start
...
ffffffc012033878 D __initcall5_start
...
ffffffc0120339d8 d __initcall_317_1188_xsk_init5
ffffffc0120339e0 d __initcall_211_194_pci_apply_final_quirks5s
ffffffc0120339e8 d __initcall_168_680_populate_rootfsrootfs
ffffffc0120339e8 D __initcallrootfs_start
ffffffc0120339f0 D __initcall6_start
...
ffffffc012034b30 D __initcall7_start
...
ffffffc012034c88 d __initcall_150_554_of_platform_sync_state_init7s
ffffffc012034c90 d __initcall_123_29_alsa_sound_last_init7s
ffffffc012034c98 D __con_initcall_start
ffffffc012034c98 d __initcall_151_246_hvc_console_initcon
ffffffc012034c98 D __initcall_end
ffffffc012034ca0 D __con_initcall_end
CON_INITCALL
include/asm-generic/vmlinux.lds.h
#define CON_INITCALL \
__con_initcall_start = .; \
KEEP(*(.con_initcall.init)) \
__con_initcall_end = .;
console initcall 不同其他的incall 函数,这里会单独确定其起始地址 __con_initcall_start 和结尾地址 __con_initcall_end.
INIT_RAM_FS
include/asm-generic/vmlinux.lds.h
#ifdef CONFIG_BLK_DEV_INITRD
#define INIT_RAM_FS \
. = ALIGN(4); \
__initramfs_start = .; \
KEEP(*(.init.ramfs)) \
. = ALIGN(8); \
KEEP(*(.init.ramfs.info))
#else
#define INIT_RAM_FS
#endif
PERCPU_SECTION
include/asm-generic/vmlinux.lds.h
#define PERCPU_SECTION(cacheline) \
. = ALIGN(PAGE_SIZE); \
.data..percpu : AT(ADDR(.data..percpu) - LOAD_OFFSET) { \
__per_cpu_load = .; \
PERCPU_INPUT(cacheline) \
}
首先是进行当前位置的地址对齐,需要 PAGE_SIZE 对齐;
接着,记录 percpu 段的起始位置 __per_cpu_load;
接下来是 percpu 的输入描述:
include/asm-generic/vmlinux.lds.h
#define PERCPU_INPUT(cacheline) \
__per_cpu_start = .; \
*(.data..percpu..first) \
. = ALIGN(PAGE_SIZE); \
*(.data..percpu..page_aligned) \
. = ALIGN(cacheline); \
*(.data..percpu..read_mostly) \
. = ALIGN(cacheline); \
*(.data..percpu) \
*(.data..percpu..shared_aligned) \
PERCPU_DECRYPTED_SECTION \
__per_cpu_end = .;
.data.percpu 段包括:
所有目标文件中的 .data..percpu..first 段;
所有目标文件中的 .data..percpu..page_aligned 段;
所有目标文件中的 .data..percpu..read_mostly 段;
所有目标文件中的 .data..percpu 段;
所有目标文件中的 .data..percpu..shared_aligned 段;
.data 段
_data = .;
_sdata = .;
RW_DATA(L1_CACHE_BYTES, PAGE_SIZE, THREAD_ALIGN)/*
* Data written with the MMU off but read with the MMU on requires
* cache lines to be invalidated, discarding up to a Cache Writeback
* Granule (CWG) of data from the cache. Keep the section that
* requires this type of maintenance to be in its own Cache Writeback
* Granule (CWG) area so the cache maintenance operations don't
* interfere with adjacent 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 = .;
}PECOFF_EDATA_PADDING
__pecoff_data_rawsize = ABSOLUTE(. - __initdata_begin);
_edata = .;
[ _data, _edata ] 代表数据相关段。
下面来看下 RW_DATA_SECTION:
include/asm-generic/vmlinux.lds.h
/*
* Writeable data.
* All sections are combined in a single .data section.
* The sections following CONSTRUCTORS are arranged so their
* typical alignment matches.
* A cacheline is typical/always less than a PAGE_SIZE so
* the sections that has this restriction (or similar)
* is located before the ones requiring PAGE_SIZE alignment.
* NOSAVE_DATA starts and ends with a PAGE_SIZE alignment which
* matches the requirement of PAGE_ALIGNED_DATA.
*
* use 0 as page_align if page_aligned data is not used */
#define RW_DATA_SECTION(cacheline, pagealigned, inittask) \
. = ALIGN(PAGE_SIZE); \
.data : AT(ADDR(.data) - LOAD_OFFSET) { \
INIT_TASK_DATA(inittask) \
NOSAVE_DATA \
PAGE_ALIGNED_DATA(pagealigned) \
CACHELINE_ALIGNED_DATA(cacheline) \
READ_MOSTLY_DATA(cacheline) \
DATA_DATA \
CONSTRUCTORS \
} \
BUG_TABLE \
.bss 段
BSS_SECTION(0, 0, 0)
#define BSS_SECTION(sbss_align, bss_align, stop_align) \
. = ALIGN(sbss_align); \
__bss_start = .; \
SBSS(sbss_align) \
BSS(bss_align) \
. = ALIGN(stop_align); \
__bss_stop = .;
镜像结尾段
. = ALIGN(PAGE_SIZE);
init_pg_dir = .;
. += INIT_DIR_SIZE;
init_pg_end = .;. = ALIGN(SEGMENT_ALIGN);
__pecoff_data_size = ABSOLUTE(. - __initdata_begin);
_end = .;
定义了 init_pg_dir,这是临时页表初始化页表,对于三级页表,会占用 3 个pages 空间。
在页表映射完成之后,这部分的内存会被释放掉,变成普通内存供 buddy 使用。
stab 段
STABS_DEBUG
#define 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) } \
DWARF_DEBUG
全是调试信息
#define DWARF_DEBUG \
/* DWARF 1 */ \
.debug 0 : { *(.debug) } \
.line 0 : { *(.line) } \
/* GNU DWARF 1 extensions */ \
.debug_srcinfo 0 : { *(.debug_srcinfo) } \
.debug_sfnames 0 : { *(.debug_sfnames) } \
/* DWARF 1.1 and DWARF 2 */ \
.debug_aranges 0 : { *(.debug_aranges) } \
.debug_pubnames 0 : { *(.debug_pubnames) } \
/* DWARF 2 */ \
.debug_info 0 : { *(.debug_info \
.gnu.linkonce.wi.*) } \
.debug_abbrev 0 : { *(.debug_abbrev) } \
.debug_line 0 : { *(.debug_line) } \
.debug_frame 0 : { *(.debug_frame) } \
.debug_str 0 : { *(.debug_str) } \
.debug_loc 0 : { *(.debug_loc) } \
.debug_macinfo 0 : { *(.debug_macinfo) } \
.debug_pubtypes 0 : { *(.debug_pubtypes) } \
/* DWARF 3 */ \
.debug_ranges 0 : { *(.debug_ranges) } \
/* SGI/MIPS DWARF 2 extensions */ \
.debug_weaknames 0 : { *(.debug_weaknames) } \
.debug_funcnames 0 : { *(.debug_funcnames) } \
.debug_typenames 0 : { *(.debug_typenames) } \
.debug_varnames 0 : { *(.debug_varnames) } \
/* GNU DWARF 2 extensions */ \
.debug_gnu_pubnames 0 : { *(.debug_gnu_pubnames) } \
.debug_gnu_pubtypes 0 : { *(.debug_gnu_pubtypes) } \
/* DWARF 4 */ \
.debug_types 0 : { *(.debug_types) } \
/* DWARF 5 */ \
.debug_addr 0 : { *(.debug_addr) } \
.debug_line_str 0 : { *(.debug_line_str) } \
.debug_loclists 0 : { *(.debug_loclists) } \
.debug_macro 0 : { *(.debug_macro) } \
.debug_names 0 : { *(.debug_names) } \
.debug_rnglists 0 : { *(.debug_rnglists) } \
.debug_str_offsets 0 : { *(.debug_str_offsets) }
ELF_DETAILS
#define ELF_DETAILS \
.comment 0 : { *(.comment) } \
.symtab 0 : { *(.symtab) } \
.strtab 0 : { *(.strtab) } \
.shstrtab 0 : { *(.shstrtab) }
HEAD_SYMBOLS
arch/arm64/kernel/image.h
/*
* These will output as part of the Image header, which should be little-endian
* regardless of the endianness of the kernel. While constant values could be
* endian swapped in head.S, all are done here for consistency.
*/
#define HEAD_SYMBOLS \
DEFINE_IMAGE_LE64(_kernel_size_le, _end - _text); \
DEFINE_IMAGE_LE64(_kernel_offset_le, TEXT_OFFSET); \
DEFINE_IMAGE_LE64(_kernel_flags_le, __HEAD_FLAGS);
#define DEFINE_IMAGE_LE64(sym, data) \
sym##_lo32 = DATA_LE32((data) & 0xffffffff); \
sym##_hi32 = DATA_LE32((data) >> 32)
定义头符号,分别是:
_kernel_size_le_lo32
_kernel_size_le_hi32 //用以存放 kernel img 实际大小的高32bits和低32bits
_kernel_offset_le_lo32
_kernel_offset_le_hi32 //用以存放 kernel img 相对 RAM 偏移的高32bits和低32bits
_kernel_flags_le_lo32
_kernel_flags_le_hi32 //用以存放 kernel img 的几个信息flag
在内核镜像生成过程中,这几个符号标志代表的值会作为镜像头的一部分输出。