06可执行文件装载

第六章 可执行文件的装载与进程
 
1     进程虚拟地址空间
32 位的进程虚拟空间为 4G ,在 linux 系统中,操作系统占高位的 1G ,从 0xC0000000 0xFFFFFFFF 。用户进程虚拟空间从 0x080480000 开始映射。
 
2     装载方式
静态装载 and 动态装载
覆盖装入 and 页映射
覆盖装入 需要程序员写一个常驻内存的覆盖管理器,需要保证两点:
           1 )模块被调用时,整个调用路径上的模块必须都在内存中。
           2 )任何一个模块不允许跨过树状结构进行调用。
页映射 是虚拟存储的一部分,而是将内存和磁盘中的数据按页进行划分,当用到文件中的哪个页,则将其调入内存,这部分工作由操作系统的存储管理器负责。
 
3     从操作系统看可执行文件装载
进程的建立: 一个进程的建立最关键的特征是拥有独立的虚拟地址空间。进程的建立分三步:
1 创建独立的虚拟地址空间:创建虚拟地址空间实际上是建立虚拟地址空间到物理空间的映射,在 i386 linux 实际上就是创建一个页目录,或者称为页表。
2 读取可执行文件头,建立虚拟空间与可执行文件的映射关系。当程序发生页错误时,操作系统将从内存中分配一个物理页,并将该缺页从磁盘读入内存。然后设置页的映射关系。
显然,当缺页时,操作系统需要知道程序当前需要的页在磁盘中的哪个位置,此时指令的虚拟地址是知道的,这就需要建立一个虚拟地址到可执行文件之间的映射。
这样的一种数据结构称为 VMA ,它记录了各个段对应的虚拟空间,并记录该段在文件中的偏移。
3 CPU 指令寄存器设置成可执行程序入口,启动。
页错误: CPU 执行某条指令,如果该指令所在页是空页,则发生段错误。操作系统捕获,找到该指令所在的 VMA ,并计算出该页在文件中的偏移,分配一个物理页,将文件内容读入内存,设置虚拟地址也物理地址的映射关系,即页表。将控制权还给程序。
 
4     进程虚拟空间分布
ELF 文件的链接视图和执行视图:
根据各个段的权限,相同权限段基本都在一起,可以将段分成不同的 Segment VMA 实际上记录的是 Segment 信息。
Section 是链接视图,而 Segment 是执行视图。可执行文件的程序头表就是记录各个 Segment 的一个数据结构。
Typedef struct{
          Elf32_Word               p_type;              //segment 类型
          Elf32_Word               p_offset;           //segment  在文件中的偏移
          Elf32_Addr                p_vaddr;           //segment 第一个字节在虚拟地址空间中的位置
          ------------                   p_paddr;           // 物理装载位置
          Elf32_Word               p_filesz;            // ELF 文件中占空间长度
          -------------                  p_memse;        // 在虚拟地址空间中所占的长度
          ------------                   p_flags;             // 读写执行属性
          ------------                   p_align;             // 对齐属性
}Elf32_Phdr;
 
堆和栈
堆和栈也是通过 VMA 进行管理,不映射到文件,称为匿名虚拟内存区域。有时候数据 Segment 并没有 VMA 大小对应,因为 BSS 段的数据可能被操作系统放在了堆里面。
 
段地址对齐
最简单的情况是,各个 Segment 在物理内存中都分别映射, Segment 起始地址都是 4096 整数倍。这样容易造成物理空间的浪费。
一种巧妙的方法是将各个段接壤的部分共享一个物理页,然后将该物理页映射两次。
为什么要映射两次?映射两次后的 VMA 地址怎么计算?
 
进程栈初始化
操作系统在进程启动前会会将一些信息保存在虚拟空间栈中( Stack VMA ),最基本的是系统环境变量和进程运行参数。
栈顶指针指向初始化后栈顶位置。
进程启动后,程序的库部分会把堆栈里的初始化信息传递给 main() 函数,也就是 argc argv ,分别对应命令行参数数量和命令行参数字符串指针数组。
 
5     Linux 内核装载
1         Fork() 一个新进程
2         新进程调用 execve 系统调用执行指定 ELF 文件
a)         读取可执行文件前 128 个字节,进行分析,看是什么文件格式
b)         根据文件格式进行装载, ELF 文件会进入 load_elf_binary() 进行处理。
                         i.              分析文件头
                       ii.              寻找动态链接的 .interp 段(动态链接)
                      iii.              根据程序头表进行映射
                      iv.              初始化 ELF 进程环境
                       v.              将返回地址修改为程序入口
 
: 链接完成后,所有的虚拟地址空间就已经确定了

你可能感兴趣的:(职场,休闲,程序员的自我修养,可执行文件装载,链接装载和库)