【问题标题】:How does a JIT compiler like v8 structure its memory (i.e. the stack, heap, code, and data)?像 v8 这样的 JIT 编译器如何构造其内存(即堆栈、堆、代码和数据)?
【发布时间】:2020-03-27 18:02:11
【问题描述】:

我试图找出how Linux stores the stack,甚至C,因为每个进程都可以有自己的堆栈。每个图表都显示了用于管理 非常原始 堆栈(也只有 1 个堆栈)的汇编指令和寄存器,所以我怀疑这就是更高级别的系统是如何做到的(即具有多线程、进程、纤维、工作者、协程、生成器等)。这就是为什么这个问题是关于 v8....

v8(在高层次上,如果有用的话可能会链接到一些相关代码)如何构造堆栈?它是如何构建堆的?还有代码和数据。你总是看到像this这样的图表:

但是 (a) 像 v8 这样的生产 JIT 系统是如何做到的? (b) 关键结构的实际实现是什么?使用了哪些数据结构?堆栈实际上是某种链表吗?或者它是一个数组?堆是否分解为一定大小的块的双向链表?它们是在物理上连续的内存块中分配的(在 v8 中),还是在 Linux 上使用特定于操作系统的系统调用(如 mmap)来分配内存,然后使用某种块架构以自己的方式构建内存(这就是我想知道)?还是仅仅依赖malloc之类的?

基本上,我对模拟生产 JIT 编译器的工作方式很感兴趣,并且停留在内存结构化(使用结构等)的开始部分。我不确定堆栈是否实际上实际上是一个链表,或者其他什么。我很想知道。

我认为这些图表是谎言的原因是因为我们通常在这里处理虚拟内存,物理内存被抽象掉了。即使这样,虚拟内存也被分成小页面,可能分散在各处,我不知道。所以我觉得这些视觉效果是完全不正确和误导的,我想知道它实际上是什么样子的。

【问题讨论】:

标签: c memory compiler-construction stack v8


【解决方案1】:

v8 如何构建堆栈?

就像任何其他程序一样:rbp 指向当前函数堆栈帧的底部,rsp 指向“顶部”(尽管向下增长); pushpop 指令用于写入/读取rsp 指向的位置,同时相应地递减/递增rsp。没有数据结构,只有原始内存。

堆 [...] 在 Linux 上是否使用特定于操作系统的系统调用(如 mmap)来分配内存

是的。 V8 的托管堆使用mmap 或其他操作系统上的等价物一次分配一页。 请注意,此类图表所称的“堆”是 C++ 意义上的堆分配内存,而不是 VM 意义上的托管堆。有人可能会争辩说,后者只是前者的特殊部分/情况,而您是否使用 mmapmalloc 是一个并不重要的实现细节(除了可能的某些性能差异,取决于使用模式)。

我认为这些图是骗人的,因为我们通常在这里处理虚拟内存

我会反过来说:虚拟内存是这些图表(略微简化但)大部分准确的原因。每个进程都认为它在世界上是孤独的,它在其内存地址空间中看不到任何其他进程(只是内核的一部分,用于与之交互)。进程看到的内存看起来确实像原始内存。只是地址不是物理地址,中间有一个翻译层(由内核+CPU管理)。

【讨论】:

  • 但是如果 v8 使用原始内存和汇编作为堆栈,它如何支持多个异步进程或线程,每个进程或线程都有自己的堆栈?这就是我要说的。
  • 再次,就像任何其他程序一样。每个进程/线程都有自己的堆栈。操作系统负责管理,这是内核的核心工作之一。
  • 如果我理解你的问题,线程堆栈的内存没有特殊属性。例如,一个进程可以有 4 个 8K 大小的堆栈,进程中的虚拟地址为 8K-16K、16K-24K、24K-32K 和 32K-40K。现在(过于简化),每次任务切换器(例如内核)将任务交给给定的处理器时,它都会更新它的堆栈指针(基址和顶部),然后程序继续。如果没有适当的保护,如果一个堆栈的大小超出其大小,它将覆盖其他堆栈(例如,这种情况在嵌入式很多中发生)。是不是更清楚了?
猜你喜欢
  • 2011-09-29
  • 2018-09-27
  • 1970-01-01
  • 2016-08-13
  • 2019-10-01
  • 1970-01-01
  • 2012-01-19
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多