每个进程加载到内存中的可执行文件中的代码数量和特征的挑战

时间:2018-05-02 15:19:22

标签: assembly x86 operating-system

当Windows等操作系统想要运行可执行文件时,首先应将其加载到RAM中。由于防止浪费内存,将其部分加载到内存中似乎比完全加载它更具智慧。

所以在这样的条件下,我的问题是:

  1. 当控件到达包含加载代码范围超出范围的JMP之类的指令时,会发生什么?换句话说,操作系统如何识别它必须停止执行指令以避免跳转到不相关的地址,以及如何计算相关地址位于哪个页面?

  2. 在跳转到程序的入口点之前,OS会将多少页代码复制到RAM中?我的意思是操作系统是否总是将固定数量的代码或固定数量的页面复制到RAM中或者可能不确定?

  3. 如果操作系统决定应该将多少代码或多少页面加载到内存中,那么在做出决定之前会考虑哪些条件?

  4. 感谢所有人。

2 个答案:

答案 0 :(得分:3)

现代操作系统的程序加载器基本上使用mmap,而不是read https://en.wikipedia.org/wiki/Memory-mapped_file#Common_uses说:

  

对于内存映射文件,最常见的用途可能是大多数现代操作系统(包括Microsoft Windows和类Unix系统)中的进程加载程序。

这将创建一个由文件支持的私有映射。 (https://en.wikipedia.org/wiki/Virtual_memory)。

  
      
  1. ...换句话说,操作系统如何识别它必须停止执行指令以避免跳转到不相关的地址,以及它如何计算相关地址所在的页面?
  2.   

在这种情况下,代码获取会导致页面错误,就像您的代码是从尚未从磁盘加载的大型静态数组的一部分加载一样。在可能从磁盘加载页面(如果它尚未存在于页面缓存中)并更新页面表之后,将在发生故障的地址处继续执行,以重试该指令。

CPU虚拟内存硬件(“MMU”,虽然在现代CPU中实际上不是一个单独的东西)处理从未映射的地址检测加载/存储/代码获取。 (根据HW可以看到的实际页面表未映射。当一个进程“逻辑上”有一些内存映射,但操作系统对它很懒,我们说内存没有“连线”到页面表中,所以a页面错误会将其带入内存(如果尚未存在),并将其连接到页面表中,以便HW可以访问它(在TLB未命中后触发硬件页面遍历。)

如果有任何运行时symbol relocations, aka fixups,要考虑正在加载的程序,而不是链接的基地址,如果它需要内存中的任何绝对地址,则可能需要编写代码页或否则只读数据,弄脏虚拟内存页面,使其由页面文件而不是磁盘上的可执行文件支持。例如如果您的C来源在全球范围内包含int *foo = &bar;,或int &foo = bar;

  
      
  1. 在跳转到程序的入口点之前,OS会将多少页代码复制到RAM中?
  2.   

程序加载器可能有一些启发式方法,以确保在第一次尝试之前映射入口点和其他一些页面。除了IDK之外,如果可执行文件/库与非可执行映射的虚拟内存代码中有任何特殊的启发式方法。

答案 1 :(得分:3)

处理器将地址空间划分为称为 pages 的地址集 在x86上,典型页面的大小为4KiB,但也可以是其他大小(例如1GiB,2 MiB)。
页面是连续的,因此第一页是从地址0x00000000到地址0x00000fff,对于每个地址,都有一个与之关联的唯一页面。

页面有一组属性,整个分页点是将一组属性与每个地址相关联 由于为每个地址执行此操作会过于禁止,因此会使用页面 页面中的所有地址共享相同的属性。

我通过不区分虚拟地址(实际上是分页的,即它们可以具有属性)和物理地址(要使用的真实地址,虚拟地址可以映射到不同的物理地址)来简化故事。 。

在各种属性中有:

  • 告诉CPU是否要将页面视为未加载 基本上,这会使CPU在指令尝试访问页面时生成异常(例如,从中读取,包括执行或写入页面)。
  • 权限
    像只读,不可执行,主管等
  • 实际地址
    分页的主要用途是隔离,可以通过将相同的虚拟地址 X 映射到不同的物理地址 Y1 Y1 来实现。过程分别为 P1 P2

请记住,这些属性是每页的,它们适用于页面中的整个地址范围(例如,它们会影响4 KiB页面的4 KiB地址)。

考虑到这一点:

  1. 创建流程时,其所有页面都标记为不存在。访问它们会使CPU出错 当操作系统加载程序时,会加载一组最小的页面(例如内核,部分内容,公共库,程序代码和数据的一部分)并标记为当前。 当程序访问未加载的页面时,操作系统会检查程序是否分配了地址,如果是(这是一个有效的页面错误),它会加载页面并继续执行。
    如果未分配地址,则会发生invalid page fault,并将异常报告给程序本身。

  2. 我不知道加载的确切页数,可以用不同的方式验证它,包括查看Linux内核(对于Linux案例)。
    我没有这样做,因为使用的实际策略可能很复杂,我觉得它并不特别相关:操作系统可以加载整个程序,如果它足够小并且内存压力很小。
    可能存在调整以选择一种或另一种策略的设置 通常,假设仅乐观地加载固定数量的页面是合理的。

  3. 影响决策的因素可能是:可用内存量,加载进程的优先级,系统管理员对系统的策略(防止膨胀),进程类型(类似的服务) DBMS可以被标记为内存密集型),对程序的限制(例如,在NUMA机器中,过程可以被标记为使用,主要是本地存储器,从而可以访问比可用总量更少的存储器),由OS实现的euristics (例如,它知道上次执行需要 K 页面的代码/数据,从 M 毫秒开始)。
    简而言之,用于加载最佳页数的算法必须稍微预测未来,因此通常考虑案例(即假设,简化,数据收集等)。