1 可执行文件的装载过程
1.1 进程虚拟地址空间
一个可执行文件被装载到内存变成程序后(进程和程序的区别在于一个是静态的一个是动态的,程序就是菜谱,进程就是厨师参考菜谱做菜的过程),拥有自己独立的地址空间。该地址空间是一个虚拟的地址空间,在该进程看来该空间内包含内核和自身,32bit系统该空间的大小是4GB,64bit系统是2的64次方-1bit。也就是说,一个程序实际上能够用到的虚拟内存空间实际上是小于理论值的,因为操作系统需要占用。
1.2 装载的方式
静态装入:将程序执行时所需要的指令和数据全部载入到内存中。
动态装入:利用程序的局部性原理,仅仅将程序中需要运行的部分装入,类似虚拟页的换入换出。
动态转入的两种实现:
- 覆盖装入:覆盖装入的方法把挖掘内存潜力的任务交给了程序员,程序员在编写程序的时候必须手工将程序分割成若干块,然后编写一个小的辅助代码来管理这些模块何时应该驻留内存而何时应该被替换掉;
- 页映射:页映射是将内存和所有磁盘中的数据和指令按照页为单位划分成若干个页,以后所有的装载和操作的单位就是页,利用页面调度算法进行页面调度。
1.3 进程的创建过程
一般进程的创建需要做三件事情:
- 创建独立的虚拟内存空间。进程首次创建时只会分配一个页目录,页映射关系是在有后续发生页错误时进行设置;
- 读取可执行文件,建立虚拟地址空间和可执行文件的映射关系。系统会根据可执行文件的大小建立相关的映射关系。
- 将CPU指令寄存器设置成可执行文件的入口,开始进入进程调度循环。
1.4 进程虚拟地址空间分布
1.4.1 ELF文件链接视图和执行视图
创建进程时,由于可执行文件往往存在多个段,如果针对每个段都单独进行内存映射,这样很容易造成内存的浪费。比如数据段可能就2个字节,但是如果依然按照一个页大小分配,剩余的4k-2的内存都浪费了。如果站在操作系系统的角度看,操作系统并不关心不同段的内容,只关心段的权限(可读?可写?可执行?)。因此可以通过将相同权限的段合并统一进行映射减少内存的浪费。在ELF中多个段的合并的集合就是一个Segment,而Segment中每个段都是相同类型的Section。
- 代码段可读可执行;
- 数据段和BSS段可读可写;
- 只读数据段只读。
下面利用下面简单的程序展示如何合并
//编译命令clang -static main.c -o main.elf
#include <unistd.h>
int main(){
while(1){
sleep(1000);
}
return 0;
}
使用readelf -S main.elf查看详细的Section。
Section Headers:
[Nr] Name Type Address Offset Size EntSize Flags Link Info Align
[ 0] NULL 0000000000000000 00000000 0000000000000000 0000000000000000 0 0 0
[ 1] .note.ABI-tag NOTE 0000000000400190 00000190 0000000000000020 0000000000000000 A 0 0 4
readelf: Warning: [ 2]: Link field (0) should index a symtab section.
[ 2] .rela.plt RELA 00000000004001b0 000001b0 0000000000000228 0000000000000018 AI 0 19 8
[ 3] .init PROGBITS 00000000004003d8 000003d8 0000000000000017 0000000000000000 AX 0 0 4
[ 4] .plt PROGBITS 00000000004003f0 000003f0 00000000000000b8 0000000000000000 AX 0 0 8
[ 5] .text PROGBITS 00000000004004b0 000004b0 000000000008f3d0 0000000000000000 AX 0

本文详细介绍了进程在操作系统中的装载过程,包括虚拟地址空间的概念、装载方式、进程创建过程及虚拟地址空间分布等内容,并对比了Linux ELF和Windows PE两种不同格式的可执行文件的装载流程。
2375

被折叠的 条评论
为什么被折叠?



