警告:这很长,但是我希望以后对像我这样的人有用。
我想我知道什么是程序计数器,惰性内存分配的工作方式,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;
}
这是我尝试将所有内容放在一起的方法:
在调用execve()
之后,部分可执行文件被加载到内存中,例如文本和数据段,但大多数代码不是-按需加载(按需分页)。
第一条指令的地址在进程表的程序计数器(PC)字段中,以及物理上在PC寄存器中,可供使用。
随着CPU执行指令,PC被更新(通常为+1,但是跳转可以转到其他地址)。
输入主要功能:ptr
,n
和i
在堆栈中。
接下来,当我们调用malloc
时,C库将要求操作系统(我认为是通过sbrk()
sys调用,还是mmap()
?)分配一些内存在堆上。
malloc
成功,返回了虚拟内存地址(VMA),但是物理内存可能尚未分配。页面表不包含VMA,因此,当CPU尝试访问此类VMA时,将生成页面错误。
在我们的情况下,当我们执行ptr[0] = 99
时,CPU会引发页面错误。我不确定是分配了整个数组还是只分配了第一页(4k大小)。
但是现在我不知道如何将高速缓存访问放入图片中。 i
如何放入L1缓存中?它与VMA有什么关系?
抱歉,这令人困惑。我只是希望有人能帮助我完成整个过程...
答案 0 :(得分:3)
在程序运行之前,操作系统和C运行时会在CPU寄存器中设置必要的值。
您已经注意到,预期的PC值由操作系统(例如,加载程序)设置,然后设置CPU的PC(也称为IP)寄存器,可能使用“从中断返回”指令,切换到用户模式(为该进程激活虚拟内存映射),并为CPU加载适当的PC值(虚拟地址)。
此外,以某种方式设置SP寄存器:在某些系统中,这与“从中断返回”过程中的PC相似,但是在其他(较旧的)系统中,用户代码将SP设置为预定位置。无论哪种情况,SP都保留一个虚拟内存地址。
通常,在用户进程中运行的第一条指令位于传统上称为_start
的例程中,该例程位于称为crt0
的库中(C运行时0(又名启动))。 _start
通常是用汇编语言编写的,用于处理从操作系统到用户模式的转换。根据需要,_start
将建立调用C代码所必需的其他任何内容,然后调用main
。如果main
返回到_start
,它将进行exit
的系统调用。
当_start
的第一条指令得到控制时,CPU缓存(可能是TLB)将变冷。用户模式下的所有地址都是虚拟内存地址,它们指定进程的(虚拟)地址空间内的内存。处理器正在用户模式下运行。操作系统可能已经预加载了保存_start
的页面(或者至少是_start
的开始)。因此,当处理器从_start
执行一条指令提取时,可能会发生TLB丢失,但不是页面错误,然后是缓存丢失。
TLB是一组寄存器,在CPU中形成缓存,支持虚拟到物理地址的转换/映射。当TLB丢失时,将从进程的虚拟内存映射中的结构(例如页表)中加载。由于该第一页已预加载,因此映射尝试将成功,然后TLB将填充从虚拟PC页到物理页的正确映射。但是,L1 / L2等高速缓存也很冷,因此下一次访问会导致高速缓存未命中。存储系统将通过在每个级别上填充高速缓存行来满足高速缓存未命中的问题。最后,将指令字或一组字提供给处理器,并开始执行指令。
如果TLB中不存在代码的虚拟地址(通过PC)或数据(通过某种取消引用),则处理器将查询页表,并且其中的丢失可能导致可恢复或不可恢复。可恢复页面错误。可恢复的页面错误是虚拟的,而不是页面表中不存在的物理映射,因为数据在磁盘上并且需要操作系统的干预。而不可恢复的错误是对错误的虚拟内存的访问,即由于它们指的是未经操作系统分配/授权的虚拟内存,因此是不允许的。
变量i
被main
称为相对堆栈位置。因此,当main要写入i
时,它将写入内存和与SP的偏移量,例如SP + 8(i
也可以是一个寄存器变量,但是我离题了)。由于SP是保存虚拟内存地址的指针,因此i
具有虚拟地址。该虚拟地址经过上述步骤:从虚拟页到物理页的TLB映射,可能的页面故障以及可能的高速缓存未命中。随后的访问将产生TLB命中和缓存命中,以便全速运行。 (在运行该进程之前,操作系统可能还会预加载一些但不是全部的堆栈页面。)
malloc
操作将使用一些系统调用,这些调用最终导致将额外的虚拟内存添加到进程中。 (尽管您也注意到,malloc
对于当前请求而言已绰绰有余,所以不会每隔malloc
进行一次系统调用。)malloc
将返回虚拟内存地址,即指针在用户模式虚拟地址空间中。对于仅通过系统调用获得的内存,TLB和缓存也可能是代码,并且页面甚至可能还没有加载。在后一种情况下,将发生可恢复的页面错误,并且操作系统将分配要使用的物理页面。如果操作系统很智能,它将知道这是一个新的数据页,因此可以用零填充而不是从页面文件中加载它。然后,它将设置页表条目以进行正确的映射,并继续用户过程,这可能会导致TLB丢失,从页表填充TLB条目,然后缓存未命中,并填充物理页的缓存行。