可执行文件的装载

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/kidthephantomthiefI/article/details/79942410

《程序员的自我修养——链接、装载与库》读书笔记

        可执行文件只有装载到内存中以后才能被CPU执行。早期的程序装载的基本过程就是把程序从外部存储器读到内存中的某个位置。随着硬件MMU的诞生,多进程、多用户、虚拟存储的操作系统的出现,装载过程变得复杂起来。程序,也就是可执行文件,是一个静态的概念,装载到内存中以后就成为了进程,进程是一个动态的概念,正所谓”Process is a program in execution”。

1. 虚拟地址空间

        现在的电脑和操作系统都支持虚拟内存技术,由MMU(Memory Management Unit)进行虚拟地址和物理地址的相互转换。每个进程拥有独立的虚拟地址空间(Virtual Address Space),它的大小由计算机的硬件平台决定。32位的CPU下,程序中能访问的虚拟地址空间最大为4G,但是实际上可用的计算机的物理内存空间可以通过PAE(Physical Address Extension),AWE(Address Windowing Extensions)等方式进行扩大。

2. 装载方式

        程序执行时所需要的指令和数据必须在内存中才能正常运行,最简单的方法就是把程序运行需要的指令和数据全部装入内存中,这就是静态装载。但是这样会浪费宝贵的内存,并且很多情况下程序所需要的内存大小大于物理内存。根据局部性原理,我们可以将程序最常用的部分放在内存中,不常用的放在磁盘里,用的时候再装入,这就是动态装载基本原理。

        覆盖装入(Overlay)和页映射(Paging)是两种典型的动态装载的方法。覆盖装入在虚拟内存技术发明之前使用比较广泛。程序员在编写程序时将程序分割成若干块,然后编写一个小小的辅助代码,也就是覆盖管理器(Overlay Manager)来管理这些模块何时应该驻留在内存,何时应该被替换掉。程序员需要将这些模块依据调用关系组织成树状结构,以便于确定何时覆盖和装入某个模块。跨模块的调用都要经过覆盖管理器,以确保被调用的模块都在内存中,如果不在还要从磁盘读取装入,速度比较慢。页映射是虚拟内存技术的一部分,它将程序和内存以 “页”(Page)为单位进行划分,装载的单位就是页。将需要用到的页从磁盘载入内存中,如果物理内存已被用完,就要由页替换算法决定被替换的页。几乎目前所有的主流操作系统都采用的这种方式装载可执行文件。

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

        一个ELF可执行文件往往由很多个节构成,在操作系统装载可执行文件时,如果把每个节映射到一个页,由于它们大小不一样,肯定会产生内存浪费。对于操作系统来说,实际上在装载时它主要关心页的权限问题,即可读、可写或可执行。而ELF文件中的节的权限往往只有为数不多的几种组合:

  • 可读,可执行,如代码节
  • 可读,可写,如数据节和.bss节
  • 只读,如只读数据节

那么对于权限相同的节,完全可以把它们合并到一起进行映射。ELF文件引入了一个概念叫做(Segment),一个段包括一个或多个属性类似的(Section)。装载的时候就把一个段当作一个整体进行映射。这样可以明显的减少页面内部的碎片,节省内存空间。从链接的角度看,ELF文件是按照节存储的,从装载的角度看,ELF文件又可以按照段进行划分。从节的角度来看ELF文件就是链接视图(Linking View),从段的角度来看就是执行视图(Execution View)。段的概念实际上是从装载的角度重新划分了ELF的各个节。在将目标文件链接成可执行文件时,链接器会尽量把权限属性相同的节分配在同一空间。

        使用readelf -l指令可以查看ELF文件的段的信息。正如描述节的属性的结构叫做节表,描述段的属性的结构叫做程序头表(Program Header Table),它描述了ELF文件如何被操作系统映射到进程的虚拟内存空间。由于ELF目标文件不需要被装载,它没有程序头表,而ELF可执行文件和共享文件都有。

4. 段地址对齐

        装载的过程一般是通过虚拟内存的页映射机制完成的。在映射的过程中,页是最小单位。对于Intel 80x86系列处理器来说,默认的页大小为4096字节。也就是说如果我们要将一段物理内存和进程的虚拟地址空间之间建立映射关系,这段内存空间的长度必须是4096的整数倍,并且这段空间在物理内存和进程虚拟地址空间中的起始地址必须是4096的整数倍。那么可执行文件就要尽量优化自己的空间和地址安排,以节省空间。

        最简单的做法就是每个段分开映射,长度不足一个页的部分也占据一个页,也就是说段的首地址对齐到了4096的整数倍。但是这样会造成很多内部碎片。为了解决这种问题,有些UNIX系统采用了一种取巧的方法,就是让那些各个段接壤的部分共享一个物理页面,然后将该物理页面分别映射两次。从某种角度看,好像是整个ELF文件从文件开头到某个点结束,被逻辑上分成了以4096字节为单位的若干个块,每个块都被装载到物理内存中去。那些包含了多个段的物理内存中的块,将会被映射到虚拟地址空间中多次。当然不同的段还有自己的不同对齐属性,这一点在为ELF文件分配虚拟内存空间时也要考虑。

5. 总结

        这里简单的描述了ELF文件的装载过程,但是实际上这里我们假设了程序都是静态链接的,因此只有一个可执行模块。有了这里的基本概念,就可以更进一步的去了解动态链接和动态链接的程序的装载过程。

猜你喜欢

转载自blog.csdn.net/kidthephantomthiefI/article/details/79942410