freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

V8内存申请
2021-10-01 20:23:51

正文

本节主要讲解V8的内存申请,在V8创建第一个Isolate之前向操作系统申请4G内存的过程。讲解分成两部分,第一部分是以hello-world.cc中相关代码为主线,讲解实现过程和重要的数据结构。第二部分讲解代码中用到的计算机相关知识:C++语言、段页式内存管理等。

1.V8代码

前文提到,hello-world.cc是本文主线,先看来部分代码

int main(int argc, char* argv[]) {
  // Initialize V8.
  v8::V8::InitializeICUDefaultLocation(argv[0]);
  v8::V8::InitializeExternalStartupData(argv[0]);
  std::unique_ptr<v8::Platform> platform = v8::platform::NewDefaultPlatform();
  v8::V8::InitializePlatform(platform.get());
  v8::V8::Initialize();//本节的主要内容!!!!

  // Create a new Isolate and make it the current one.
  v8::Isolate::CreateParams create_params;
  create_params.array_buffer_allocator =
      v8::ArrayBuffer::Allocator::NewDefaultAllocator();
  v8::Isolate* isolate = v8::Isolate::New(create_params);
//.... 省略了很多代码,请自查阅....
  // Dispose the isolate and tear down V8.
  isolate->Dispose();
  v8::V8::Dispose();
  v8::V8::ShutdownPlatform();
  delete create_params.array_buffer_allocator;
  return 0;
}

v8::V8::Initialize()是本节的讲解内容。这个函数可以用VS的debug跟踪进来,可以看到如下图
image这是在VS2019中截取的图片,图中99行的变量是一些配置开关(宏定义实现),包括:指针压缩、Smi、堆栈等。我们只关心Initialize(),他是V8向操作系统申请内存的主要入口,debug跟踪进入,能看到下图 image这张图中,给出了两个关键信息,一个是api.cc这文件的位置,方便自行查看,另一个是Initialize方法的具体实现。Initialize方法的代码只有40多行,不算多,但我的屏幕无法全显示,我只截取了一部分,这些代码中只有一句是我们本次的重点,也就是return语句之前,是i::V8::Initialize(),这是我们要学习的,debug跟踪进入后,能看到下图
image这个代码没其它的功能,直接调用了InitializeOncePerProcess(),再次跟进,就到了
image这里我们看到了CallOnce,从名字得知他是一个只执行一次的函数,他的作用与内存申请无关,先按下不表。说他的第二个参数&InitializeOncePerProcessImpl,这参数是一个函数指针,在VS2019中Ctrl+鼠标左键可以跟进去,如下图
image这个方法的代码行数多,我屏幕显示不全。可以在这个方法的开头下个断点了,这里开始将是最重要的初始化,当然包括今天要说的内存申请。这个方法的最后面包括了如下几行代码

#if defined(V8_USE_PERFETTO)
  if (perfetto::Tracing::IsInitialized()) TrackEvent::Register();
#endif
  IsolateAllocator::InitializeOncePerProcess();//我们的重点内容
  Isolate::InitializeOncePerProcess();

#if defined(USE_SIMULATOR)
  Simulator::InitializeOncePerProcess();
#endif
  CpuFeatures::Probe(false);
  ElementsAccessor::InitializeOncePerProcess();
  Bootstrapper::InitializeOncePerProcess();
  CallDescriptors::InitializeOncePerProcess();
#if V8_ENABLE_WEBASSEMBLY
  wasm::WasmEngine::InitializeOncePerProcess();
#endif  // V8_ENABLE_WEBASSEMBLY

  ExternalReferenceTable::InitializeOncePerProcess();

IsolateAllocator::InitializeOncePerProcess();是我们的重点内容,跟踪进来,看到如下代码:

// static
void IsolateAllocator::InitializeOncePerProcess() {
...省略很多行代码 ...
  if (!GetProcessWidePtrComprCage()->InitReservation(params,
                                                     existing_reservation)) {
    V8::FatalProcessOutOfMemory(
        nullptr,
        "Failed to reserve virtual memory for process-wide V8 "
        "pointer compression cage");
  }
#endif
}

这个InitReservation是V8申请内存的全过程,代码位置src\utils\allocation.cc。下面先从代码执行过程中看看V8具体是怎么做的。

// Reserve a region of twice the size so that there is an aligned address
// within it that's usable as the cage base.
VirtualMemory padded_reservation(params.page_allocator,
                               params.reservation_size * 2,
                               reinterpret_cast<void*>(hint));
Address address =
          VirtualMemoryCageStart(padded_reservation.address(), params);
...省略了很多行代码...
// Now free the padded reservation and immediately try to reserve an
// exact region at aligned address. We have to do this dancing because
// the reservation address requirement is more complex than just a
// certain alignment and not all operating systems support freeing parts
// of reserved address space regions.
padded_reservation.Free();
VirtualMemory reservation(params.page_allocator,
                          params.reservation_size,
                          reinterpret_cast<void*>(address));

先用padded_reservation申请一个两倍大小的内存(8G),再利用padded_reservation.Free()释放,再用reservation申请的4G内存是V8真正想要的内存。先申请一个8G内存的目的是找一个"合适"的地址address,合适是指做内存对齐合适。内存对齐是什么,稍后讲解。先申请、再释放、再申请时就一定能得到合适的地址吗?这是C++中内存申请和回收机制,也稍后讲解。
下面讲解V8管理内存的主要数据结构:VirtualMemoryCage。V8向操作系统申请4G内存,用于后续的所有工作,例如创新Isolate,等等。V8的内存方式采用的段页式,和操作系统(OS)的方法类似,但不像OS有多个段,V8只有一个段,但有很多页。VirtualMemeoryCage的定义在allocation.h中,我们对它的结构进行说明。

// +------------+-----------+-----------  ~~~  -+
// |     ...    |    ...    |   ...             |
// +------------+-----------+------------ ~~~  -+
// ^            ^           ^
// start        cage base   allocatable base
//
// <------------>           <------------------->
// base bias size              allocatable size
// <-------------------------------------------->
//             reservation size

"a VirtualMemory reservation"是V8源码中的叫法,reservation size是4G,也就是v8申请内存的总大小,start是这个内存的基址,cage base是v8用于管理的基址,可以先理解为cage base是页表位置,allocatable开始是v8可以分配的,用于创新isolate的地方。VirtualMemoryCage中的成员reservation_负责指向这个4G内存。
另一个重要的结构是ReservationParams,申请内存大小(4G),对齐方式,指针压缩等参数都在这个结构中定义,它不是本次的重点,待后面讲到isolate建立时,需要分配内存时再深入讲解。最后来看看内存申请完成后VirtualMemeoryCage的内部信息。
image注意看this指针就是VirtualMemoryCage,params就是RservationParams。

2.知识点

讲解与V8内存申请相关的知识点,包括:1.内存对齐;2.段页式内存;3.malloc free 内存回收机制。

2.1内存对齐
内存是由若干个内存颗粒(chip)构成,每个chip内部,是由多个bank组成的,如下图。而每一个bank是一个二维平面上的矩阵。那么对于我们在应用程序中内存中地址连续的8个字节,例如0x0000-0x0007,我们以为它应该在一个bank上,但不是这样,这只是我们程序员想象的。实际上是这8个字节保存在8个不同的bank,是分散不连续的,原因是电路的工作效率,内存是可以并行读取8个bank,然后拼接起来就是8个字节的数据。所以,内存对齐的原因是为了以8字节为单位的并行工作,如果数据不对齐呢,就可能出现需要读两次内存才得到最终结果。所以V8做内存对齐,为了效率。
image

2.2段页式内存
段页式内存就是将程序分为多个逻辑段,在每个段里面又进行分页,即将分段和分页组合起来使用。页式存储可以提高内存的利用率,而段式管理能表达程序的逻辑结构,还方便段共享。段页式就可以很好地结合他们的优势。在段页式中的地址管理需要三个要素:段号、页号、页内偏移,V8中只有一段,所以只用页号和偏移即可,待我们讲到V8内存分配时会提到BoundedPageAllocator这个方法,它是用于内存分配的。
image

2.3 内存回收
V8先申请8G,从中找到合适的地址,再释放后再申请就一定能拿到想要的地址吗?释放的内存操作系统不会马上回收,所以再次申请时可是拿到想要的地址值。V8申请内存用的时VirtualAlloc方法,如下图
image最后来看一下调用堆栈
image

总结

本文主要讲解V8的内存申请,将主要精力放在V8是如何向OS申请4G内存上。内存申请过程还有很多细节没有提及,因为这些细节更多会在内存分配时才会用所,用到时再讲。下次见。

# javascript # chrome # 浏览器 # V8 # Chromium
本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者
文章目录