C语言 将 CPU 和内存管理模型放在一起



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

我想我知道什么是程序计数器,惰性内存分配如何工作,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("%dn", ptr[0]);
free(ptr);
return 0;
}

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

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

  2. 第一条指令的地址位于工艺表的程序计数器(PC)字段中,并实际位于PC寄存器中,随时可用。

  3. 当CPU执行指令时,PC会更新(通常为+1,但跳转到不同的地址)。

  4. 输入主函数:堆栈中ptrni

  5. 接下来,当我们调用malloc时,C 库将要求操作系统(我认为通过 sys 调用sbrk()还是mmap()

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

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

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

抱歉,如果这令人困惑。我只是希望有人能帮助我完成整个过程......

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

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

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

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

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

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

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

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

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

相关内容

  • 没有找到相关文章

最新更新