4

警告:这很长,但我希望它将来对像我这样的人有用。

我想我知道程序计数器是什么,惰性内存分配是如何工作的,MMU 是做什么的,虚拟内存地址如何映射到物理地址以及 L1、L2 缓存的用途。我真正遇到的问题是,当我们运行 C 代码时,它们如何在高层次上结合在一起。

假设我有这个 C 代码:

#include <stdio.h>
#include <stdlib.h>
int main()
{
    int* ptr;
    int n = 1000000, i = 0;

    // Dynamically allocate memory using malloc()
    ptr = (int*)malloc(n * sizeof(int));

    ptr[0] = 99;
    i += 100;
    printf("%d\n", ptr[0]);
    free(ptr);
    return 0;
}

因此,这是我尝试将所有内容放在一起的尝试:

  1. 调用后execve(),可执行文件的一部分被加载到内存中,例如文本和数据段,但大部分代码不是——它们是按需加载的(按需分页)。

  2. 第一条指令的地址在进程表的程序计数器 (PC) 字段中以及物理上的 PC 寄存器中,随时可以使用。

  3. 随着 CPU 执行指令,PC 被更新(通常 +1,但跳转可以转到不同的地址)。

  4. 进入主函数:ptr, n, 和i在堆栈中。

  5. 接下来,当我们调用 时malloc,C 库将要求操作系统(我认为是通过sbrk()sys 调用,或者是mmap()吗?)在堆上分配一些内存。

  6. malloc在这种情况下成功,返回一个虚拟内存地址 (VMA),但物理内存可能尚未分配。页表中不包含 VMA,因此当 CPU 尝试访问此类 VMA 时,会产生页面错误。

  7. 在我们的例子中,当我们这样做时ptr[0] = 99,CPU 会引发页面错误。我不确定是分配了整个数组还是仅分配了第一页(4k 大小)。

但是现在我不知道如何将缓存访问放入图片中。如何i放入L1缓存?它与 VMA 有什么关系?

对不起,如果这令人困惑。我只是希望有人可以帮助我完成整个过程......

4

1 回答 1

3

在程序运行之前,操作系统和 C 运行时会在 CPU 寄存器中设置必要的值。

正如您已经注意到的,预期的 PC 值由操作系统(例如,加载程序)设置,然后设置 CPU 的 PC(又名 IP)寄存器,可能带有“从中断返回”指令,两者都切换到用户模式(激活该进程的虚拟内存映射)以及使用正确的 PC 值(虚拟地址)加载 CPU。

此外,SP 寄存器以某种方式设置:在某些系统中,这将类似于 PC 在“从中断返回”期间完成,但在其他(较旧的)系统中,用户代码将 SP 设置为预先安排的位置。在任何一种情况下,SP 还拥有一个虚拟内存地址。

通常,在用户进程中运行的第一条指令是在一个传统上在一个名为(C RunTime 0(又名启动))  _start的库中调用的例程中。通常用汇编语言编写并处理从操作系统到用户模式的转换。根据需要,将建立调用 C 代码所需的任何其他内容,然后调用. 如果返回到,它将执行系统调用。crt0_start_startmainmain_startexit

_start当' 的第一条指令获得控制权时,CPU 缓存(可能还有 TLB)会变冷。用户模式下的所有地址都是虚拟内存地址,它们在进程的(虚拟)地址空间内指定内存。处理器正在用户模式下运行。可能操作系统已经预加载了页面保存_start(或至少开始_start)。因此,当处理器从 执行取指令时_start,它可能会 TLB 未命中,但不会出现页面错误,然后是缓存未命中。

TLB 是一组寄存器,在 CPU 中形成高速缓存,支持虚拟到物理地址的转换/映射。TLB 未命中时,将从进程的虚拟内存映射中的结构(例如页表)中加载。由于第一个页面是预加载的,映射的尝试将成功,然后 TLB 将填充从虚拟 PC 页面到物理页面的正确映射。但是,L1/L2 等缓存也是冷的,因此接下来的访问会导致缓存未命中。内存系统将通过在每个级别填充高速缓存行来满足高速缓存未命中。最后,一个指令字或一组字被提供给处理器,它开始执行指令。

如果代码(通过 PC)或数据(通过某些取消引用)的虚拟地址不存在于 TLB 中,则处理器将查询页表,其中的未命中可能导致可恢复或不可恢复的页面错误. 可恢复的页错误是页表中不存在的虚拟到物理映射,因为数据在磁盘上并且需要操作系统干预;而不可恢复的故障是对错误的虚拟内存的访问,即不允许,因为它们指的是尚未由操作系统分配/授权的虚拟内存。

变量i被称为main堆栈相对位置。所以,当 main 想要写入它时,i它会写入内存和从 SP 的偏移量,例如 SP+8 (i也可能是一个寄存器变量,但我离题了)。既然SP是一个持有虚拟内存地址的指针,i那么就有了虚拟地址。该虚拟地址经过上述步骤:从虚拟页面到物理页面的 TLB 映射、可能的页面错误以及可能的缓存未命中。后续访问会产生TLB命中、缓存命中,从而全速运行。(操作系统可能还会在运行进程之前预加载一些但不是所有的堆栈页面。)

操作将malloc使用一些系统调用,这些系统调用最终会导致将额外的虚拟内存添加到进程中。(尽管您也注意到,malloc对于当前请求来说已经足够了,所以系统调用并不是每次都完成malloc。)  malloc将返回一个虚拟内存地址,即用户态虚拟地址空间中的一个指针。对于刚刚通过系统调用获得的内存,TLB 和缓存也很可能是代码,也可能页面还没有加载。在后一种情况下,将发生可恢复的页面错误,并且操作系统将分配一个物理页面以供使用。如果操作系统很智能,它将知道这是一个新的数据页,因此可以用零填充它,而不是从分页文件中加载它。然后它将为正确的映射设置页表条目,并恢复用户进程,这可能会导致 TLB 未命中,从页表中填充 TLB 条目,然后缓存未命中,并从物理页面中填充缓存行。

于 2019-08-23T05:33:59.460 回答