第六章
可执行文件的装载与进程
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.
将返回地址修改为程序入口
注:
链接完成后,所有的虚拟地址空间就已经确定了