Linux可执行文件与进程的虚拟地址空间

栏目: IT技术 · 发布时间: 4年前

内容简介:作者简介:本文由西邮陈莉君教授研一学生贺东升编辑,梁金荣、张孝家校对Linux可执行文件与进程的虚拟地址空间

作者简介:

本文由西邮陈莉君教授研一学生贺东升编辑,梁金荣、张孝家校对 Linux可执行文件与进程的虚拟地址空间

Linux可执行文件与进程的虚拟地址空间

一个可执行文件被执行的同时也伴随着一个新的进程的创建。Linux会为这个进程创建一个新的虚拟地址空间,然后会读取可执行文件的文件头,建立虚拟地址空间与可执行文件的映射关系,然后将CPU的指令指针寄存器设置成可执行文件的入口地址,然后CPU就会从这里取指令执行。

一个可执行文件包含可被CPU执行的指令和待处理的数据,上CPU之前,指令和数据全部被翻译成成二进制的形式。在可执行的文件的内部,划分出了一些专门的段,如代码段,数据段,BSS段等。代码段中存放的是可执行的二进制指令,数据段存放初始化过的变量,BSS段存放未初始化的变量,从装载的角度,把这些段称为segment。

32位的虚拟地址空间

Linux可执行文件与进程的虚拟地址空间

64位的虚拟地址空间

Linux可执行文件与进程的虚拟地址空间

Proc目录下的进程虚拟地址空间布局

Linux在装载可执行文件的时候,会将这些segment映射到进程的地址空间中。映射的时候,这里面的segment会对应一个VMA。Linux将进程虚拟地址空间中的一个段叫做虚拟内存区域(VMA)。在/proc目录下,可以查看一个进程的虚拟地址空间,通过命令

cat /proc/pid/maps

Linux可执行文件与进程的虚拟地址空间

这里面的每一行都对应一个VMA,每一个VMA都通过 vm_area_struct 结构体来描述。结构体中的 vm_start vm_end 是VMA的起始地址和结束地址,还有其他的一些域来描述VMA的权限等。我们需要关注的是前三个VMA,这是ELF可执行文件的segment映射过来的。可以看到,这里面并没有标明哪个是TEXT段,哪个是DATA段和BSS段。但是可以发现,前三个VMA的权限都不一样。

虚拟地址空间存储区的分布

Linux可执行文件与进程的虚拟地址空间

所以,操作系统实际上并不关心可执行文件各个段所包含的的实际内容,OS只关心一些跟装载相关的问题,最主要的是段的权限(可读,可写,可执行)。

ELF文件中,段的权限往往只有为数不多的几种组合,基本上就3种:

  1. 以代码段为代表的权限为可读可执行的段

  2. 以数据段和BSS段为代表的权限为可读可写的段

  3. 以只读数据段为代表的权限为只读的段

ELF可执行文件中有两个概念,分别是段(segment)和节(section)。通过readelf -S name.elf可以查看ELF可执行文件的节头表,这里面有所有节的信息

Linux可执行文件与进程的虚拟地址空间

在将目标文件链接成可执行文件的时候,链接器会尽量把相同权限属性的段分配在同一空间。比如可读可执行的段都放在一起,这种段的典型是代码段;可读可写的段都放在一起,这种段的典型是数据段。在ELF中,把这些属性相似的,又连在一起的段叫做一个“segment”,而系统正是按照“segment”而不是“section”来映射可执行文件的。

可以使用命令  readelf -l name.elf 来查看ELF的段。在ELF的程序头表,保存着segment的信息

Linux可执行文件与进程的虚拟地址空间

最下面是是段与节的归属关系:

Linux可执行文件与进程的虚拟地址空间

可以看到这个可执行文件中共有9个segment。从装载的角度看,我们只关心两个“LOAD”型的segment,因为只有它是需要被映射的,其他诸如“NOTE”,"GNU_STACK"都是在装载时起辅助作用的。下面的0到8分别对应着上面的一个segment,两个LOAD类型的segment分别对应着02和03,可以看到每个LOAD类型的segment里面都包含了许多的section。

ELF将相同或者相似属性的section合并为一个segment并映射到一个VMA中,是为了减少页面内部碎片,以节省内存空间的使用。因为在有了虚拟存储机制以后,装载的时候采用页映射的方式。Intel系列的处理器,页尺寸最小是4096个字节,也就是4KB。当写的程序很小的时候,每个section可能只有几十或者几百个字节,如果每个section都占用一个页的话,对内存的浪费是海量的。所以在将目标文件链接成可执行文件的时候,链接器会尽量把相同或相似权限属性的section分配在同一空间,在程序头表中,将一个或多个属性类似的section合并为一个segment,然后在装载的时候,将这个segment映射到进程虚拟地址空间中的一个VMA中。

ELF可执行文件与进程虚拟地址空间的映射关系

Linux可执行文件与进程的虚拟地址空间

很明显,属性相同或相似的section会被归类到一个segment,并且被映射到同一个VMA。

Linux可执行文件与进程的虚拟地址空间

总的来说,“segment”和“section”是从不同的角度来划分同一个ELF文件。这个在ELF文件中被称为不同的视图(view),从section的角度来看ELF文件就是链接视图(Linking View),从segment的角度来看就是执行视图(Execution View)。当我们在谈到ELF装载时,段专门指segment,而在其他的情况下,段指的是section。

在实际的映射过程中,只发现有代码段映射的VMA,有数据段映射的VMA,却没有BSS段映射的VMA。

Linux可执行文件与进程的虚拟地址空间

如果仔细观察程序头表,查看两个LOAD型的segment,会发现一些映射的细节。

Linux可执行文件与进程的虚拟地址空间

FileSiz表示segment在ELF文件中所占的大小,MemSiz表示segment在进程虚拟地址空间中所占的大小。可以发现,MemSiz比FileSiz多出了0x20个字节,十六进制的20对应的十进制是32。再来看一下这个ELF可执行文件中BSS段的大小。

Linux可执行文件与进程的虚拟地址空间

可以看到,BSS段的大小正好是十进制的32,。这说明在实际映射的时候,数据段在内存中所分配的空间大小超过实际的大小,超出去的这部分空间就是BSS段,并没有为BSS段进行专门的映射,这就是为什么在查看程序头表时,只看到了两个LOAD类型的段,而不是三个,BSS段已经被合并到了数据类型的段里面。

这样做的好处就是在构造ELF可执行文件时,不需要再额外设立BSS的segment了,只需把数据segment的内存扩大,那些额外的部分就是BSS。而这部分多出的BSS空间,会被全部填充为0 。在 C语言 中,没有初始化的全局变量和一些静态变量会被默认初始化为0 ,这就是原因,因为它们会被分配到BSS段上,被一次性初始化为0。

最后我们通过一个打印变量地址的小程序进行验证,仔细观察没有初始化的全局变量和一些静态变量的线性地址。

Linux可执行文件与进程的虚拟地址空间

视频讲解

这部分内容录了一个视频,因为这是我第一次录讲解视频,没有什么经验,如果视频内容有任何问题还希望各路大神指出,不胜感激。

  • 腾讯视频:


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

美铁之战

美铁之战

[英]帕特里克·蒂利 / 黑曜、超侠 / 百花文艺出版社 / 2018-9 / 44.80元

本书的故事发生在未来,一场核战毁灭了北美大陆上的人类文明,残存下来的人类分化成两拨:生活在地面上退化到刀耕火种时代的平原人;躲藏在地下苟延残喘的沙穴人。几百年后,当保留着战前文明的沙穴人尝试着登上地面,和平原人的同室操戈将不可避免地上演……一起来看看 《美铁之战》 这本书的介绍吧!

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具