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



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

所以在这种情况下,我的问题是:

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

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

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

谢谢大家。

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

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

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

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

在这种情况下,代码提取会导致页面错误,就像您的代码是从尚未从磁盘加载的大型静态数组的一部分加载一样。 在可能从磁盘加载页面(如果页面缓存中尚不存在)并更新页面表后,将在出错的地址恢复执行,以重试指令。

CPU的虚拟内存硬件("MMU",尽管这在现代CPU中实际上并不是一个单独的东西)处理从未映射的地址的加载/存储/代码提取的检测。 (根据硬件可以看到的实际页表未映射。 当一个进程"逻辑上"映射了一些内存,但操作系统对此很懒惰时,我们说内存没有"连接"到页表中,所以页面错误会把它带入内存(如果还没有),并且会把它连接到页表中,以便硬件可以访问它(在 TLB 未命中触发硬件分页漫游之后)。


如果有任何运行时符号重定位(也称为修正),以考虑程序在内存中需要任何绝对地址时在它链接的基址以外的基址上加载,则它们可能需要编写代码页或其他只读数据,弄脏虚拟内存页,使其由页面文件而不是磁盘上的可执行文件支持。 例如,如果您的 C 源包含全局范围的int *foo = &bar;,或者int &foo = bar;

  1. 在跳转到程序入口点之前,操作系统将多少页代码复制到 RAM 中?

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

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

页面具有一组属性,分页的全部意义在于将一组属性与每个地址相关联。
由于对每个地址都这样做太过令人望而却步,因此使用页面代替。
网页中的所有地址共享相同的属性。

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

在各种属性中,有:

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

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

考虑到这一点:

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

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

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

最新更新