我一直认为战略上蔑视技术,战术上重视技术是很有必要的学习态度。这是一篇 Bringup SoC 芯片的指导手册,更是一篇了解整个系统流程的地图。不会深入了解每个模块的细节,但提供了整个系统的宏观描述,让你站在上帝视角俯视每个知识点,为了对读者更负责,我打算以付费的方式和大家见面,对技术细节有需求的小伙伴欢迎加我微信(rrjike)交流,保证超有所值。
系统在启动的时候,无论是 ROM 加载 Uboot(SPL Bootloader),还是 Uboot 加载 Kernel,都是把相关的镜像放到对应的内存不同地址,然后运行启动。这个过程看起来很简单,但中间涉及到多个知识点,了解各个子过程对理解启动的本质有很大的帮助作用,对 bring up 芯片更是有必要。我们先从镜像的结构开始,后面逐一介绍 ROM->SPL->UBoot->Kernel 的全过程。
ELF 文件
在介绍加载镜像之前,需要先了解下 ELF 文件的格式定义。
从图中可以看出,一个 ELF 文件是由 4 部分组成,分别是:
ELF 头部,程序头表,节,节头表。
有 3 种类型的 ELF 文件:
目标文件:被链接器读取,用来产生一个可执行文件或者共享库文件; 可执行文件:被操作系统中的加载器从硬盘上读取,载入到内存中去执行; 共享库文件:在动态链接的时候,由 ld-linux.so 来读取;
ELF 头部
描述 ELF header 的结构体:
代码语言:javascript复制typedef struct elfhdr{
unsigned char e_ident[EI_NIDENT]; /* ELF Identification */
Elf32_Half e_type; /* object file type */
Elf32_Half e_machine; /* machine */
Elf32_Word e_version; /* object file version */
Elf32_Addr e_entry; /* virtual entry point */
Elf32_Off e_phoff; /* program header table offset */
Elf32_Off e_shoff; /* section header table offset */
Elf32_Word e_flags; /* processor-specific flags */
Elf32_Half e_ehsize; /* ELF header size */
Elf32_Half e_phentsize; /* program header entry size */
Elf32_Half e_phnum; /* number of program header entries */
Elf32_Half e_shentsize; /* section header entry size */
Elf32_Half e_shnum; /* number of section header entries */
Elf32_Half e_shstrndx; /* section header table's "section
header string table" entry offset */
} Elf32_Ehdr;
看下 ELF 头部的信息:
代码语言:javascript复制$ readelf -h vmlinux
ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: DYN (Shared object file)
Machine: AArch64
Version: 0x1
Entry point address: 0xffff200008080000
Start of program headers: 64 (bytes into file)
Start of section headers: 29609944 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 56 (bytes)
Number of program headers: 4
Size of section headers: 64 (bytes)
Number of section headers: 35
Section header string table index: 32
这个内容与结构体 Elf32_Ehdr 中的成员变量是一一对应的。
程序头表
描述 Program header table 的结构体:
代码语言:javascript复制typedef struct {
Elf32_Word p_type; /* segment type */
Elf32_Off p_offset; /* segment offset */
Elf32_Addr p_vaddr; /* virtual address of segment */
Elf32_Addr p_paddr; /* physical address - ignored? */
Elf32_Word p_filesz; /* number of bytes in file for seg. */
Elf32_Word p_memsz; /* number of bytes in mem. for seg. */
Elf32_Word p_flags; /* flags */
Elf32_Word p_align; /* memory alignment */
} Elf32_Phdr;
看下程序头表的信息:
代码语言:javascript复制$ readelf -l vmlinux
Elf file type is DYN (Shared object file)
Entry point 0xffff200008080000
There are 4 program headers, starting at offset 64
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flags Align
LOAD 0x0000000000010000 0xffff200008080000 0xffff200008080000 0x0000000000f534c8 0x0000000000f534c8 R E 10000
LOAD 0x0000000000f70000 0xffff200008fe0000 0xffff200008fe0000 0x00000000007dda00 0x000000000106af20 RWE 10000
NOTE 0x000000000133a028 0xffff2000093aa028 0xffff2000093aa028 0x0000000000000024 0x0000000000000024 R 4
GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 RW 10
Section to Segment mapping:
Segment Sections...
00 .head.text .text
01 .rodata .init___kcrctab sort .init__ksymtab_strings .pci_fixup __ksymtab __ksymtab_gpl __kcrctab __kcrctab_gpl __ksymtab_strings __param __modver __ex_table .notes .init.text .exit.text .altinstructions .altinstr_replacement .init.data .data..percpu .rela.dyn .data .got.plt .init___ksymtab sort __bug_table .mmuoff.data.write .mmuoff.data.read .pecoff_edata_padding .bss
02 .notes
03
可以看出该 vmlinux 的入口地址是 0xffff200008080000。一共有 4个段(segment),前两个需要 load 到内存的段,它们分别是代码段和数据段,属性是读、执行(RE)和读、写、执行(RWE)。
代码段包含了两个 section。数据段包含了很多 section。
节
- text 段
代码段,通常是指用来存放程序执行代码的一块内存区域。这部分区域的大小在程序运行前就已经确定。
- data 段
数据段,通常是指用来存放程序中已初始化的全局变量的一块内存区域。数据段属于静态内存分配。
- bss 段
通常是指用来存放程序中未初始化的全局变量和静态变量的一块内存区域。BSS段属于静态内存分配。
- init 段
linux定义的一种初始化过程中才会用到的段,一旦初始化完成,那么这些段所占用的内存会被释放掉,后续会继续说明。
节头表
描述 Section header table 的结构体:
代码语言:javascript复制typedef struct {
Elf32_Word sh_name; /* name - index into section header
string table section */
Elf32_Word sh_type; /* type */
Elf32_Word sh_flags; /* flags */
Elf32_Addr sh_addr; /* address */
Elf32_Off sh_offset; /* file offset */
Elf32_Word sh_size; /* section size */
Elf32_Word sh_link; /* section header table index link */
Elf32_Word sh_info; /* extra information */
Elf32_Word sh_addralign; /* address alignment */
Elf32_Word sh_entsize; /* section entry size */
} Elf32_Shdr;
看下节头表的信息:
代码语言:javascript复制$ readelf -S vmlinux
There are 35 section headers, starting at offset 0x1c3cfd8:
Section Headers:
[Nr] Name Type Address Offset Size EntSize Flags Link Info Align
[ 0] NULL 0000000000000000 00000000 0000000000000000 0000000000000000 0 0 0
[ 1] .head.text PROGBITS ffff200008080000 00010000 0000000000001000 0000000000000000 AX 0 0 4096
[ 2] .text PROGBITS ffff200008081000 00011000 0000000000f524c8 0000000000000008 AX 0 0 2048
[ 3] .rodata PROGBITS ffff200008fe0000 00f70000 0000000000358380 0000000000000000 WA 0 0 4096
[ 4] .init___kcrctab s PROGBITS ffff200009338380 012c8380 0000000000000004 0000000000000000 A 0 0 1
[ 5] .init__ksymtab_st PROGBITS ffff200009338384 012c8384 0000000000000005 0000000000000000 A 0 0 1
[ 6] .pci_fixup PROGBITS ffff200009338390 012c8390 0000000000000018 0000000000000000 A 0 0 8
[ 7] __ksymtab PROGBITS ffff2000093383a8 012c83a8 00000000000155c0 0000000000000000 A 0 0 8
[ 8] __ksymtab_gpl PROGBITS ffff20000934d968 012dd968 0000000000014fe0 0000000000000000 A 0 0 8
[ 9] __kcrctab PROGBITS ffff200009362948 012f2948 0000000000005570 0000000000000000 A 0 0 1
[10] __kcrctab_gpl PROGBITS ffff200009367eb8 012f7eb8 00000000000053f8 0000000000000000 A 0 0 1
[11] __ksymtab_strings PROGBITS ffff20000936d2b0 012fd2b0 00000000000358d8 0000000000000000 A 0 0 1
[12] __param PROGBITS ffff2000093a2b88 01332b88 0000000000003548 0000000000000000 A 0 0 8
[13] __modver PROGBITS ffff2000093a60d0 013360d0 0000000000000f30 0000000000000000 A 0 0 8
[14] __ex_table PROGBITS ffff2000093a7000 01337000 0000000000003028 0000000000000000 A 0 0 8
[15] .notes NOTE ffff2000093aa028 0133a028 0000000000000024 0000000000000000 A 0 0 4
[16] .init.text PROGBITS ffff2000093b0000 01340000 0000000000080af8 0000000000000000 AX 0 0 8
[17] .exit.text PROGBITS ffff200009430af8 013c0af8 000000000000421c 0000000000000000 AX 0 0 4
[18] .altinstructions PROGBITS ffff200009434d14 013c4d14 000000000001e3b4 0000000000000000 A 0 0 1
[19] .altinstr_replace PROGBITS ffff2000094530c8 013e30c8 000000000000a36c 0000000000000000 AX 0 0 4
[20] .init.data PROGBITS ffff20000945e000 013ee000 000000000001f580 0000000000000000 WA 0 0 4096
[21] .data..percpu PROGBITS ffff20000947e000 0140e000 0000000000014018 0000000000000000 WA 0 0 128
[22] .rela.dyn RELA ffff200009492018 01422018 00000000001d9c10 0000000000000018 A 0 0 8
[23] .data PROGBITS ffff200009670000 01600000 0000000000137868 0000000000000000 WA 0 0 4096
[24] .got.plt PROGBITS ffff2000097a7868 01737868 0000000000000018 0000000000000008 WA 0 0 8
[25] .init___ksymtab s PROGBITS ffff2000097a7880 01737880 0000000000000010 0000000000000000 WA 0 0 8
[26] __bug_table PROGBITS ffff2000097a7890 01737890 00000000000150b4 0000000000000000 WA 0 0 4
[27] .mmuoff.data.writ PROGBITS ffff2000097bd000 0174d000 000000000000000c 0000000000000000 WA 0 0 2048
[28] .mmuoff.data.read PROGBITS ffff2000097bd800 0174d800 0000000000000008 0000000000000000 WA 0 0 8
[29] .pecoff_edata_pad PROGBITS ffff2000097bd808 0174d808 00000000000001f8 0000000000000000 WA 0 0 1
[30] .bss NOBITS ffff2000097be000 0174da00 000000000088cf20 0000000000000000 WA 0 0 4096
[31] .comment PROGBITS 0000000000000000 0174da00 0000000000000027 0000000000000001 MS 0 0 1
[32] .shstrtab STRTAB 0000000000000000 01c3ce4a 000000000000018b 0000000000000000 0 0 1
[33] .symtab SYMTAB 0000000000000000 0174da28 00000000002ea610 0000000000000018 34 92369 8
[34] .strtab STRTAB 0000000000000000 01a38038 0000000000204e12 0000000000000000 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings)
I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
O (extra OS processing required) o (OS specific), p (processor specific)
最后用一幅图总结 ELF 文件 vmlinux 的分布:
镜像的生成
ELF 文件格式是 Linux 环境下的可执行文件格式,在 Linux 环境下,加载器根据 ELF 文件里的地址信息,就可以把它加载到内存指定的地址运行,但是系统启动过程中并没有 ELF 文件的执行环境,需要将 ELF 文件转换为二进制纯指令文件,即 Image。