[学习/笔记] 程序员的自我修养-第六章
侧边栏壁纸
  • 累计撰写 65 篇文章
  • 累计收到 3 条评论

[学习/笔记] 程序员的自我修养-第六章

x1n
x1n
2022-03-01 / 0 评论 / 20 阅读 / 正在检测是否收录...

 4.可执行文件的装载与进程

第七章终于看完了,趁着上操作系统课,把第六章整理一下。
我就是大鸽子呜呜呜


可执行文件的装载是一个比较复杂的过程,曾经的装载只是把程序从外部存储器读取到内存中。但是多进程、多用户、虚拟存储使得装载过程变得复杂。本章通过介绍ELF在Linux下的装载过程来介绍装载——从装载是什么、虚地址空间是什么、为什么有。再探讨一下装载方式、虚空间分布情况

6.1 进程虚拟地址空间

对于n位的CPU(现在通常是64位)从硬件上说理论上可以寻址0到2^64-1,由于成书较早,书里主要以32位举例

32位平台下,从0到2^32-1我们总共有4GB的虚拟空间。但是这4GB的虚拟空间并不完全是允许用户使用的,Linux默认把其中0x0到0xC0000000的部分(1GB)留给操作系统,而剩下的3GB才留给进程使用(当然,实际情况下程序不会拥有这3GB,还有一部分是留给其他用途的),而32位Windows默认给系统留下2G的空间

3G的空间,是不太够用的,于是我们有了PAE。对虚拟地址空间来讲,CPU只能访问32位指针,只能寻址0-4G,但是从Pentium Pro开始采用36位地址线,最高可以理论访问64G内存,Intel修改了页映射方式,使得多出来的物理内存可以被访问(这种扩展方式就叫PAE-Physical Address Extension)。这里的被访问也只是操作系统可以访问。

应用程序若想访问这些扩展的空间,一般是通过操作系统提供一个窗口映射,比如在0x10000000-0x20000000申请一段256M的虚拟空间做窗口,然后在高于4G的物理内存空间中申请多段长256M的地址A、B等,根据需要把这段地址映射到A、用到B的时候再映射到B,这种内存访问方式在Win下叫AWE,Linux下通过mmap系统调用实现

由于事实上我们现在已经采用64位,对这部分只做技术上的了解了。

6.2 装载方式

在三级存储系统中,中间的内存往往是比外存更昂贵、空间更少的。我们把最常用的部分留在内存中,把不太常用的地方放外存里,这就是动态装入的基本原理。

两种典型的方法是覆盖装入和页映射。其实从缓存、内存、动态装载,都是利用了程序的局部性原理,这也是三级存储能实现接近外存容量、缓存速度的基础理论。在硬件课上老师的一个理论深得我心(当然这看上去可能是一句废话):当从软件算法的层面无法提升速度,就要深入到硬件里去配合硬件的工作以提高速度。

6.2.1 覆盖装入

    覆盖装入几乎被淘汰了,但是我们可以吸取其中的思想,在嵌入式等受限环境下,可能还存在用武之地。

    覆盖装入要求程序员主动将代码分割成若干块,然后写一个辅助代码管理模块间的驻留和替换关系,称为“覆盖管理器(Overlay manager)”。我们把不会相互调用的代码模块(如A、B)放在相同的内存位置,调用哪部分的时候就把这部分装载进来,管理器往往很小,常驻内存,这时候我们需要的内存就从(A+B)变成了(OM+max(A, B))了。思想大体如此,但是实际上模块依赖可能复杂的多,也远不止两块,就需要程序员手动的把模块调用组织成树形结构。显然,这是一种时间换空间的方法

6.2.2 页映射

    随着虚拟存储的发明,我们有了更好用的页映射方式。它把内存和磁盘中的数据和指令按页划分,并把其作为转载的单位,现在一个页一般都是4K。如果你学过缓存的控制机制,其实会发现页映射的装载方式和缓存的控制方式都几乎是相通的:寻找,若没有则装载,否则使用,对于装载:如果有空内存则直接装载,否则选一个页替换。这其实就是缓存的命中与替换,根据装载管理器的策略来确定装载策略(全相联、组相联)与替换策略(FIFO、LUR)等

——这个装载管理器其实就是操作系统

6.3 从操作系统角度看可执行文件的装载

按照刚刚描述的装载方法,假设程序有 P0-P7 8个页,内存有 M0 - M3 四个页,如果那么每次替换页都要进行重定位,这当然是不可接受的。在虚拟存储中,硬件MMU都提供了地址转换的功能,也正是通过转换和页映射,使得动态加载可执行文件的方式和静态加载有很大区别

6.3.1 进程建立

    进程间的不同的最关键特征是有独立的虚拟地址空间,我们从一个最典型的例子开始:创建进程,装载文件并执行。

    - 创建一个独立的虚拟地址空间
    - 读取可执行文件头、建立文件与虚拟空间映射
    - 把IP放在入口,启动

6.3.1.1 创建虚拟地址空间

    虚拟空间由页映射函数将页映射到相应的物理空间,实际上创建虚拟空间就是创建这个映射的数据结构,对于Linux,只需要分配一个页目录,甚至不需要设置映射关系,等到后面有页错误的时候再设置

6.3.1.2 读取文件头、建立映射

    第一步的映射是虚拟空间到物理空间,而这部分则是虚拟空间和可执行文件的映射。当发生页错误的时候,系统从物理内存中分配一个页,然后从磁盘读到内存中,再设置这个缺页和物理页的映射关系。当系统捕获页错误的时候,它需要程序缺的页在可执行文件的哪一个位置,这就是虚拟空间和可执行文件的映射,也是装载中最重要的一步

    Linux把虚拟空间的一个段叫虚拟内存区域(VMA)而Windows叫虚拟段(Virtual Section)。举个例子,把某ELF从0x10000到0x1000e0(长度对齐到0x1000)的一段text映射到虚拟存储空间的0x08048000 - 0x08049000,这个进程的数据结构中就有了一个text段的VMA,在虚拟空间的地址就是48000-49000,对应ELF中0x10000.

6.3.1.3 设置IP、启动

这一步看似简单,实际上涉及到内核堆栈和用户堆栈的切换、CPU权限的切换,但是对于进程来说,可以简单的认为操作系统执行了一步跳转

6.3.2 页错误

    以上三步执行之后,任何指令数据还都没有装入内存,只是建立了一个映射关系。CPU会在执行当前IP的指令时,发现当前这个页面是一个空页面,于是触发一个页错误,再将控制权还给操作系统,操作系统有例程处理这种情况,操作系统查询建立的数据结构,找到对应VMA,计算其在ELF中的偏移、在内存中分配一个物理页,再把虚拟页和物理页建立映射,再把控制权还给进程,进程继续执行。
    页错误不断产生,操作系统不断分配。如果所需内存超过可用,就需要操作系统分配回收物理内存。不再展开

0

评论 (0)

取消