1 装载方式:页映射。操作系统将物理内存划分成页(比如每个页大小16K),可执行文件也按照页划分。每次需要哪个页时就将其装载到物理内存中某个空闲页。若没有空闲页,可以采取一定策略将物理内存中某个页换出。
2 可执行文件装载的过程。
(1)创建虚拟地址空间。分配页目录。这一步是建立虚拟空间到物理空间的映射关系。
(2)读取可执行文件头,建立虚拟空间与可执行文件的映射关系。这个映射关系的作用是以后发生页错误的时候,操作系统能够从这个映射关系中知道缺页在可执行文件中的位置。
(3)将cpu的指令寄存器设置成可执行文件的入口,开始运行。
3 VMA的概念。比如只读的数据放在一个VMA,可读可执行的放在一个VMA,所以VMA是进程虚拟空间的连续一段的称呼。
4 链接视图和执行视图。在可执行文件中,从链接视图来看,是按照section来存储的。因为每个section在加载的时候都是占据若干个页,即使某个section只有几十字节也要占据一个页,这样会造成物理内存的浪费。所以实际上,操作系统将权限相同的section都集中到一起,叫做segment。所以从执行视图来看待可执行文件,它是按照segment来划分的。
5 可执行文件中有很多segment,每条segment的内容包含:
(1)p_type: 类型。其中最主要的一个类型是LOAD。只有这个类型的segment是装载到物理内存的
(2)p_offset:在文件中的偏移
(3)p_vaddr:在进程虚拟空间的偏移
(4)p_filesz:在文件中大小
(5)p_memse:在虚拟进程空间大小(这个跟p_filesz一般是相同的)
(6)p_flags:权限,读,写,执行
(7)p_align:对齐属性
6 堆和栈。除了可执行文件中的segment加载之后是一个个的VMA,还有堆和栈也是两个VMA。堆向上增长,栈向下增长
7 segment的对齐。假设每个页的大小是4096字节。其实,可执行文件中的segment在加载后,并不是每个segment的其实地址都是4096的倍数。因为在有很多segment很小的时候,这样也会造成很大的浪费。实际上是这样做的:将各个segment依次连续存储到物理内存,这样的话,某个物理内存中某个页可能包含两个(或者更多)segment,所以这时候会将这个页映射两次,分别到每个segment在进程虚拟空间对应的VMA。(可执行文件有几个segment,在进程虚拟空间就有就有几个VMA)。
8 linux装载elf可执行文件的过程。首先bash通过fork创建一个进程,新进程调用execve执行的elf。总的流程是,读取文件头,根据魔数判断是elf文件,还是java文件,还是什么脚本文件。如果是elf文件,会调用装载elf的程序,这个程序会得到segment的数量,然后装载,将系统调用的返回地址设置为可执行文件的入口地址。然后返回,可执行文件就开始执行了。