可执行文件的装载与进程 2

1.ELF文件的链接视图和执行视图

在前面一篇中可执行文件的装载与进程 1,假设了只有一个.text段,那么就对应这一个VMA。而事实上,一个可执行文件往往有很多段,比如代码段、数据段、BSS等。

那么页映射是按照一页为单位的,当不够一个页时,就按照一个页来映射,那么势必会有很多的浪费。

而实际上,从操作系统的角度来看,是按照权限来划分的。权限基本分3种:

①可读可执行,比如代码段

②可读可写,比如数据段,BSS段

③只读,比如只读数据段

那么在映射的时候,将相同权限的段映射到一起,就节约了空间。

比如两个相同权限的段,.data段的大小是5000,BSS段大小是50.如果按照原来的单个段映射,那么就要用3个页。

现在以相同的权限映射,只需要2个即可。

可执行文件的装载与进程 2_第1张图片

这是一个Segment的概念,将多个权限相同或者相似的合并在一起,映射在虚存空间里是一个VMA。节省了存储空间,减少磁盘碎片。

以一个小程序为例:

#include<stdio.h>
int main{
    while(1){
        sleep(1000);
    }
    
    return 0;
}

将其编译成可执行的ELF文件。这里是静态编译的,即加了 -static 参数。

那么他到底有多少段呢?

readelf -S vm.elf

可执行文件的装载与进程 2_第2张图片

可执行文件的装载与进程 2_第3张图片

可以看到实际上一个可执行文件多达31个段。这里显示的是段,那么按刚才说的,怎么映射的呢?

可执行文件的装载与进程 2_第4张图片

如此可见,实际加载的Segment有两个,每个映射为一个VMA,并且每个里面分别包含很多不同的段,这些段的权限相同或者相似。

这些信息是在哪里记录着呢?

ELF的可执行文件有个专门的结构,叫做程序头表,用来保存Segment的信息。

同样,在/usr/include/elf.h中定义:

可执行文件的装载与进程 2_第5张图片

这里的定义跟上面readelf -l vm.elf的结果是对应的。

每个成员的定义,查找资料可知:

可执行文件的装载与进程 2_第6张图片

以上介绍了可执行文件被链接完成和被加载完成的存放及加载情况。


2.虚存里的堆和栈

这里引出堆和栈的概念,在【内存篇】再深入探讨。

可执行文件的装载与进程 2_第7张图片

可以看到除了上面说的,映射了2个VMA以外,还有3个VMA。一个是堆(c语言的malloc的内存就在这里分配),

一个是栈。另一个的为什么说与内核相关呢?

观察他的地址,已经到了内核空间。实际它与内核通信有关。

但是,这3个VMA没有主次设备号,说明没有映射到文件中,所有是匿名的虚拟内存区域。

那么一个进程的VMA大致有这么几个:

①代码VMA 可读可执行

②数据VMA 可读可写

③堆VMA 可读写,匿名的并且向上生长

④栈VMA 可读写,匿名的并且向下生长

这样一个进程的大致轮廓:

可执行文件的装载与进程 2_第8张图片


3.小测试

可以申请的最大堆内存空间

可执行文件的装载与进程 2_第9张图片

在我的Ubuntu 32位的虚拟机上,可以申请到大约2.8GB的空间。这个值应该是因系统而异的。

4.进程栈的初始化

每个进程开始启动的时候,总有一些初始化的环境,如系统变量,运行参数等。这些往往保存到栈VMA中。

假设环境变量:Home=/usr/bin

运行命令:prog up

进程栈初始化完成后:

可执行文件的装载与进程 2_第10张图片



到此。


你可能感兴趣的:(可执行文件装载)