你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

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

[复制链接]
gaosmile 发布时间:2020-5-12 20:03
Linux可执行文件与进程的虚拟地址空间

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

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

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


cat /proc/pid/maps

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

虚拟地址空间存储区的分布
微信图片_20200512195955.png

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

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

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

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

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


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

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

可以使用命令 readelf -l name.elf来查看ELF的段。在ELF的程序头表,保存着segment的信息
微信图片_20200512200001.png
最下面是是段与节的归属关系:
微信图片_20200512200004.png

可以看到这个可执行文件中共有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可执行文件与进程虚拟地址空间的映射关系
微信图片_20200512200008.png
很明显,属性相同或相似的section会被归类到一个segment,并且被映射到同一个VMA。

微信图片_20200512200011.png

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

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

微信图片_20200512200014.png

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

微信图片_20200512200018.png

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

微信图片_20200512200021.png

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

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

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

微信图片_20200512200023.png

视频讲解

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


  • B站:点击阅读原文观看视频


http://www.bilibili.com/video/BV1Ua4y1x7sU?p=1


收藏 1 评论1 发布时间:2020-5-12 20:03

举报

1个回答
likang1202 回答时间:2020-5-13 08:53:35
谢谢分享

所属标签

STM32团队

意法半导体微控制器和微处理器拥有广泛的产品线,包含低成本的8位单片机和基于ARM® Cortex®-M0、M0+、M3、M4、M33、M7及A7内核并具备丰富外设选择的32位微控制器及微处理器


最新内容

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
微信公众号二维码 微信公众号
手机版二维码 手机版