Linux内核如何装载和启动一个可执行程序
李洋 原创作品转载请注明出处
《Linux内核分析》MOOC课程
首先简单介绍一下相关背景:ELF(Executable and Linking Format)是一种对象文件的格式,用于定义不同类型的对象文件(Object files)中都放了什么东西、以及都以什么样的格式去放这些东西。它自最早在 System V 系统上出现后,被 xNIX 世界所广泛接受,作为缺省的二进制文件格式来使用。
所谓对象文件(Object files)有三个种类:
1) 可重定位的对象文件(Relocatable file)
这是由汇编器汇编生成的 .o 文件。后面的链接器(link editor)拿一个或一些 Relocatable object files 作为输入,经链接处理后,生成一个可执行的对象文件 (Executable file) 或者一个可被共享的对象文件(Shared object file)。我们可以使用 ar 工具将众多的 .o Relocatable object files 归档(archive)成 .a 静态库文件。
2) 可执行的对象文件(Executable file)
这我们见的多了。文本编辑器vi、调式用的工具gdb、播放mp3歌曲的软件mplayer等等都是Executable object file。
3) 可被共享的对象文件(Shared object file)
这些就是所谓的动态库文件,也即 .so 文件。如果拿前面的静态库来生成可执行程序,那每个生成的可执行程序中都会有一份库代码的拷贝。如果在磁盘中存储这些可执行程序,那就会占用额外的磁盘空间;另外如果拿它们放到Linux系统上一起运行,也会浪费掉宝贵的物理内存。如果将静态库换成动态库,那么这些问题都不会出现。动态库在发挥作用的过程中,必须经过两个步骤:
a) 链接编辑器(link editor)拿它和其他Relocatable object file以及其他shared object file作为输入,经链接处理后,生存另外的 shared object file 或者 executable file。
b) 在运行时,动态链接器(dynamic linker)拿它和一个Executable file以及另外一些 Shared object file 来一起处理,在Linux系统里面创建一个进程映像。
ELF文件由4部分组成,分别是ELF头(ELF header)、程序头表(Program header table)、节(Section)和节头表(Section header table)。实际上,一个文件中不一定包含全部内容,而且他们的位置也未必如同所示这样安排,只有ELF头的位置是固定的,其余各部分的位置、大小等信息有ELF头中的各项值来决定。
Linking 视角 Execution 视角
============ ==============
ELF header ELF header
Program header table (optional) Program header table
Section 1 Segment 1
... Segment 2
Section n ...
Section header table Section header table (optional)
具体结构定义如下:
1 #define EI_NIDENT 16 2 3 typedef struct { 4 unsigned char e_ident[EI_NIDENT]; 5 Elf32_Half e_type; 6 Elf32_Half e_machine; 7 Elf32_Word e_version; 8 Elf32_Addr e_entry; 9 Elf32_Off e_phoff; 10 Elf32_Off e_shoff; 11 Elf32_Word e_flags; 12 Elf32_Half e_ehsize; 13 Elf32_Half e_phentsize; 14 Elf32_Half e_phnum; 15 Elf32_Half e_shentsize; 16 Elf32_Half e_shnum; 17 Elf32_Half e_shstrndx; 18 } Elf32_Ehdr;
e_type 它标识的是该文件的类型。
e_machine 表明运行该程序需要的体系结构。
e_version 表示文件的版本。
e_entry 程序的入口地址。
e_phoff 表示Program header table 在文件中的偏移量(以字节计数)。
e_shoff 表示Section header table 在文件中的偏移量(以字节计数)。
e_flags 对IA32而言,此项为0。
e_ehsize 表示ELF header大小(以字节计数)。
e_phentsize 表示Program header table中每一个条目的大小。
e_phnum 表示Program header table中有多少个条目。
e_shentsize 表示Section header table中的每一个条目的大小。
e_shnum 表示Section header table中有多少个条目。
e_shstrndx 包含节名称的字符串是第几个节(从零开始计数)。
最后总结一下Linux内核ELF文件的装载过程:
检查ELF可执行文件的有效性,比如程序头表中段的数量。
寻找动态链接的“.interp”段,设置动态链接器路径(与动态链接有关)。
根据ELF可执行文件的程序头表的描述,对ELF文件进行映射,比如代码,数据,只读数据。
初始化ELF进程环境。
将系统调用的返回地址修改为ELF可执行文件的入口,这个入口点取决于程序的链接方式,对于静态链接的可执行文件,这个程序入口就是ELF文件的文件头中e_entry所指的地址;对于动态链接的ELF可执行文件,程序入口点是动态链接器。新的程序开始执行,ELF可执行文件装载完成。
当新程序开始运行后,每当遇到缺页错误(可以理解为没有为虚拟页面关联实际的物理页面,因此产生缺页错误),就会新分配一个物理页,并把该缺页内容(ELF文件的相应内容)从磁盘中读取到内存中,同时设置虚拟页面也物理页面的映射关系,并从缺页的虚拟地址处开始执行。