天天看點

chrome/chromium 上的記憶體管理子產品-allocator介紹

本文介紹chromium在不同平台上 malloc/new 是如何封裝調用的。

從代碼中很容易發現,chromium的基礎代碼并不是僅僅使用“malloc”來配置設定記憶體

例如:

        renderer(Blink)大部分都是用chromium單獨設計的PartitionAlloc和BlinkGC(Oilpan)

        像javascript引擎V8這樣比較獨立的子系統使用自己的記憶體管理機制

        還有部分子產品會使用抽象化的記憶體管理子產品如 ShareMemory或者DiscardMemory,和上面的記憶體管理器類似,他們也有自己的頁面級記憶體管理器

背景:

allocator配置設定器的目标是在程式編譯階段實作定義malloc/new 在特定的平台上實作哪些任務。與此相關的編譯選項包括 “use_allocator”和“win_use_allocator_shim”.

預設的實作方式是:

**windows平台**

use_allocator        :winheap, 即windows系統預設的heap;

另外,static_libary(即 外部庫)通過設定win_use_allocator_shim會對malloc/new編譯出一個封裝層,該層對外提供安全的API調用,例如防止第三方子產品配置設定過大的記憶體而導緻出現各種bug。

**linux平台/CrOS**

use_allocator        :tcmalloc, 這個是基于third_party/tcmalloc/chromium下的tcmalloc拉出來的。設定use_allocator為none,預設就會編譯系統(glibc)的符号表。

**Android平台**

use_allocator        :none, 使用的allocator符号就是Android的libc(Bionic)。Bionic作為系統的一部分,對于small devices或者記憶體緊張的裝置來說就是最優的選擇。目前Bionic的malloc 符号取決于board的配置并且是可以

修改的(一般情況下Nexus使用dlmalloc或jemalloc)

**Mac/iOS**

use_allocator        :none,通常使用系統的配置設定器實作。

另外,當編譯為asan/msan/syzyasan/valgrind 等記憶體調試模式的時候,allocator shim等是禁用的。

架構模型

allocator的設計目标是既能夠為tcmalloc(使用該管理器的平台)提供源檔案,又可以為windows shim layer提供linker flags。base是唯一一個依賴于allocator的子產品。除了少數可執行程式或動态連結庫,沒有其他子產品再依賴

于allocator子產品,這些可執行程式或動态連結庫直接或間接使用base子產品。

重要的一點,base子產品之外,不應該再有使用特殊的allocator配置設定的地方(如直接使用 third_party/tcmalloc),如果需要使用配置設定器功能的話,應使用base子產品裡面的抽象類來完成。(參見/base/allocator/allocator_exrension.hhe /base/memory/)

**為什麼base要依賴于allocator**

這是因為他要依賴于具體的allocator來提供服務。以前 base 被當作allocator是不可知的而被其他層用來引入配置設定屬性。這導緻了使用上的混淆。

詳細參見[allocator cleanuo doc][url-allocator-cleanup]

連結單元(可執行程式或共享庫)通過某種方式依賴于base(大部分在代碼裡面)來自動擷取linker flag,以此來引入tcmalloc或者windows shim layer

**source code**

這個目錄下僅僅包含了allocator layer(即 shim),該層實作了在不同具體記憶體配置設定器之間轉換的功能。

tcmalloc的庫來自于chromium之外且放置在../../third_party/tcmalloc(目前,實際的路徑定義在allocator.gyp 檔案)。third party 的源碼通過提供vendor-branch SCM的方式來跟蹤

chromium的特殊修改,以此來與上遊部分的修改獨立出來。這樣做的目的是促使上遊子產品來做适配修改這樣我們就不需要随證時間的推移而重新拉出新的分支了。

**統一的allocator shim層**

在很多平台上,chrome都會重寫 malloc /new 等方法,(包括相應的 free/delete 和其他一些方法)這是為了強制的安全檢查以及最近啟用的新功能[memory-infra heap profiler][rul-memory-heap-profiler]

以前,不同的平台上對于allocator  在代碼處理的地方方法都有自己不同的實作邏輯。統一的allocator shim子產品的設計目标就是在中間層定義一個标準的allocator 實作邏輯。

-- 文檔:[Allocator shim design doc][url-allocator-shim]

-- 進展: Linux CrOS和android上目前可用

-- bug跟蹤:[https://crbug.com/550886][crbug.com/550886].

-- 編譯标簽: ues_experimental_allocator_shim

**allocator shim子產品概覽**

allocator shim 子產品包含了三部分:

malloc & friends  symbols definition  shimlayer implementation Routing to allocator

-- libc symbols (malloc,calloc, free, ...) 

-- C++ symbols (operator new, delete, ...) 

-- glibc weak symbols  (__libc_malloc, ...)

-- Security checks    

-- Chain of dispatchers

    that can intercept

    and override 

    allocations

-- tcmalloc 

-- glibc   

-- Android  bionic   

-- WinHeap 

**1. malloc 的定義**

這部分主要重寫 malloc、 free 、operator new 、operator delete及friends并在allocator shim 内部實作這些方法的調用方式(next point)

這些在頭檔案allocator_shim_override_* 中描述

*Linux和CrOS上*:這些記憶體操作方法在allocator_shim_override_libc_symbos.h(包括malloc free friends)和allocator_shim_override_cpp_symbos.h(包括operator new、operator delete、friends)中被聲明為外部全局方法

這樣可以使主可執行程式所使用的malloc方法與任何第三方記憶體管理庫都可以适當的比對。Linux上的符号解析是從跟連結單元開始的廣度優先搜尋,這個是可執行程式(參見 EXECUTABLE AND LINKABLE FORMAT(ELF)- 便攜式格式規範))

另外,當tcmalloc作為預設記憶體配置設定器的時候,一些其他的glibc 方法在allocator_shim_override_glibc_weak_symbos.h也會有定義。詳細的原因參見:The Linux/CrOS shim was introduced by

[crrev.com/1675143004](https://crrev.com/1675143004).

**android上**:(不同于linux和CrOS上的實作)在加載階段插入實作方法是不可能的,因為android的程序都是android aygote fork出來的,zygote程序會預加載libc.so,而後面本地代碼的加載

使用過dlopen完成的(配置設定的方法是來自于dlopen所加載的庫實作的,加載在另外的空間)

這種情況下,該方案通過--Wl,-wrap,malloc 等連結flag代替連結時(即編譯時)包裝符号表的解決方案。

使用wrap flag會導緻:

chrome中所有引用allocator symbos的代碼都要重寫為引用__wrap_malloc和friends 。__wrap_malloc定義在檔案allocator_shim_override_linker_wraped_symbos.h中,實作在allocator shim層中。

最原始的malloc(系統庫libc.so中定義的)引用可以通過特定的__real_malloc和friends來實作(它将在加載的時候被重置來對應 malloc)

總之,這種方法對動态加載是透明的,動态加載看到的仍然是未定義的malloc引用。這些方法将會對應libc.so的方法。

詳見: [crrev.com/1719433002](https://crrev.com/1719433002).

**2.shim layer層實作**

這部分主要是shim layer層的實作部分。包含了:

--一個獨立的連結排程表(指向malloc的函數指針結構體)。排程表可以在運作的時候動态插入(通過API:InsertAllocatorDispatch)。

它們可以攔截和重寫allocator方法。

--安全檢查(同過std::new_handler在malloc ~ failure過程中 釋放等等)

這些都在allocator_shim.cc内實作。

**3.最終的allocator 實作**

上面提到的排程結構鍊的最終實作單元是在編譯的時候定義死的,并最終将allocator指引到最終的實際配置設定器allocator(如上面背景部分描述的)。這個在allocator_shim_default_dispatch_to_*檔案中有引述。

附錄:

略......

附:

linux下C的程式可以用wrap的方式替換系統malloc

     編譯加上選項:gcc -Wl,-wrap,malloc

     可以做到對malloc這個函數,linker會調用__wrap_malloc代替之, 若要調用原來的malloc函數__real_malloc;

例如,想把所有的malloc函數都作修改,以便讓malloc出的記憶體都是32位元組對齊的。

我們可以給ld傳選項“­­wrap=malloc”, 告訴ld,我們将替換名稱為malloc的函數。

接着再如下定義一個新的malloc函數:

void * __wrap_malloc( size_t size) {

  void* result;

  result=memalign(32, size);

  return result;

}

可以看到,程式中有個類似庫函數名稱的__wrap_malloc。

ld在遇到__wrap選項後,會使用__wrap_malloc函數名替換所有對malloc的調用。

并以此實作替換的作用。

那麽,如果還向調用原函數怎麼辦呢?

可以使用__real_malloc,這個函數名稱就對應了原來的malloc。

每次調用malloc時都列印生成的指針位址。

void * __wrap_malloc( size_t size) {

  void* result;

  result= __real_malloc( size);

  printf("Malloc: allocate %d byte mem, start at %p", size, result);

  return result;

}

在chromium的allocator上:

malloc->__wrap_malloc_ShimMalloc->alloc_function->RealMalloc->__real_malloc(android bionic)

libc上:

__real_malloc->je_malloc

/bionic/libc/bionic/malloc_common.cpp

#define Malloc(function)  je_ ## function

extern "C" void* malloc(size_t bytes) {

  auto _malloc = __libc_globals->malloc_dispatch.malloc;

  if (__predict_false(_malloc != nullptr)) {

    return _malloc(bytes);

  }

  return Malloc(malloc)(bytes); //=je_malloc()

}

Jemalloc的路徑:external/jemalloc/

#0  je_bitmap_set (bitmap=<optimized out>, binfo=<optimized out>, bit=<optimized out>) at external/jemalloc/include/jemalloc/internal/bitmap.h:176

176    external/jemalloc/include/jemalloc/internal/bitmap.h: No such file or directory.

(gdb) bt

#0  je_bitmap_set (bitmap=<optimized out>, binfo=<optimized out>, bit=<optimized out>) at external/jemalloc/include/jemalloc/internal/bitmap.h:176

#1  je_bitmap_sfu (bitmap=<error reading variable: DWARF-2 expression error: DW_OP_reg operations must be used either alone or in conjunction with DW_OP_piece or DW_OP_bit_piece.>, binfo=<optimized out>)

    at external/jemalloc/include/jemalloc/internal/bitmap.h:228

#2  arena_run_reg_alloc (run=0x90f82654, bin_info=<optimized out>) at external/jemalloc/src/arena.c:290

#3  arena_malloc_small (tsdn=<optimized out>, binind=11, arena=<optimized out>, zero=<optimized out>) at external/jemalloc/src/arena.c:2578

#4  je_arena_malloc_hard (tsdn=0x90f6d688, arena=<optimized out>, size=<optimized out>, ind=11, zero=<error reading variable: access outside bounds of object referenced via synthetic pointer>)

    at external/jemalloc/src/arena.c:2694

#5  0xadbcf302 in je_arena_malloc (arena=<optimized out>, size=<optimized out>, zero=<error reading variable: access outside bounds of object referenced via synthetic pointer>, 

    slow_path=<error reading variable: access outside bounds of object referenced via synthetic pointer>, tsdn=<optimized out>, ind=<optimized out>, tcache=<optimized out>)

    at external/jemalloc/include/jemalloc/internal/arena.h:1365

#6  je_iallocztm (size=<optimized out>, zero=<error reading variable: access outside bounds of object referenced via synthetic pointer>, 

    is_metadata=<error reading variable: access outside bounds of object referenced via synthetic pointer>, 

    slow_path=<error reading variable: access outside bounds of object referenced via synthetic pointer>, tsdn=<optimized out>, ind=<optimized out>, tcache=<optimized out>, arena=<optimized out>)

    at external/jemalloc/include/jemalloc/internal/jemalloc_internal.h:1061

#7  je_ialloc (tsd=<optimized out>, size=<optimized out>, zero=<error reading variable: access outside bounds of object referenced via synthetic pointer>, 

    slow_path=<error reading variable: access outside bounds of object referenced via synthetic pointer>, ind=<optimized out>) at external/jemalloc/include/jemalloc/internal/jemalloc_internal.h:1073

#8  ialloc_body (slow_path=<error reading variable: access outside bounds of object referenced via synthetic pointer>, size=<optimized out>, zero=<optimized out>, tsdn=<optimized out>, 

    usize=<optimized out>) at external/jemalloc/src/jemalloc.c:1611

#9  je_malloc (size=<optimized out>) at external/jemalloc/src/jemalloc.c:1650

#10 0x97836480 in ShimCppNew () at ../../base/allocator/allocator_shim.cc:171

#11 operator new () at ../../base/allocator/allocator_shim_override_cpp_symbols.h:19

#12 0x98a28576 in re2::Compiler::AllocInst () at ../../third_party/re2/src/re2/compile.cc:288

#13 0x98a287ec in re2::Compiler::ByteRange () at ../../third_party/re2/src/re2/compile.cc:403

#14 0x98a28a92 in re2::Compiler::Literal () at ../../third_party/re2/src/re2/compile.cc:846

#15 0x98a29970 in re2::Compiler::PostVisit () at ../../third_party/re2/src/re2/compile.cc:919

#16 0x98a29b24 in re2::Compiler::PostVisit () at ../../third_party/re2/src/re2/compile.cc:864

#17 0x98a29d34 in re2::Regexp::Walker<re2::Frag>::WalkInternal () at ../../third_party/re2/src/re2/walker-inl.h:210

#18 0x98a29e3c in WalkExponential () at ../../third_party/re2/src/re2/walker-inl.h:243

#19 re2::Compiler::Compile () at ../../third_party/re2/src/re2/compile.cc:1157

#20 0x98a32a58 in re2::RE2::Init () at ../../third_party/re2/src/re2/re2.cc:214

#21 0x98a32bba in re2::RE2::RE2 () at ../../third_party/re2/src/re2/re2.cc:112

#22 0x97bed83c in StringMismatch () at ../../gpu/config/gpu_control_list.cc:104

#23 0x97bef30e in gpu::GpuControlList::GpuControlListEntry::Contains () at ../../gpu/config/gpu_control_list.cc:1308

#24 0x97bef6a8 in gpu::GpuControlList::GpuControlListEntry::Contains () at ../../gpu/config/gpu_control_list.cc:1354

#25 0x97bf1a10 in gpu::GpuControlList::MakeDecision () at ../../gpu/config/gpu_control_list.cc:1523

#26 0x97fcfee2 in content::GpuDataManagerImplPrivate::UpdateGpuInfoHelper () at ../../content/browser/gpu/gpu_data_manager_impl_private.cc:639

#27 0x97fcd972 in content::GpuDataManagerImpl::UpdateGpuInfo () at ../../content/browser/gpu/gpu_data_manager_impl.cc:154

#28 0x97fd3ab8 in content::GpuProcessHost::OnInitialized () at ../../content/browser/gpu/gpu_process_host.cc:815

#29 0x97fd3002 in DispatchToMethodImpl<content::GpuProcessHost*, void (content::GpuProcessHost::*)(bool, gpu::GPUInfo const&, gpu::GpuFeatureInfo const&), std::__ndk1::tuple<bool, gpu::GPUInfo, gpu::GpuFeatureInfo> const&, 0u, 1u, 2u> () at ../../base/tuple.h:91

#30 DispatchToMethod<content::GpuProcessHost*, void (content::GpuProcessHost::*)(bool, gpu::GPUInfo const&, gpu::GpuFeatureInfo const&), std::__ndk1::tuple<bool, gpu::GPUInfo, gpu::GpuFeatureInfo> const&> () at ../../base/tuple.h:98

#31 DispatchToMethod<content::GpuProcessHost, void (content::GpuProcessHost::*)(bool, gpu::GPUInfo const&, gpu::GpuFeatureInfo const&), void, std::__ndk1::tuple<bool, gpu::GPUInfo, gpu::GpuFeatureInfo> >

    () at ../../ipc/ipc_message_templates.h:26

#32 IPC::MessageT<GpuHostMsg_Initialized_Meta, std::__ndk1::tuple<bool, gpu::GPUInfo, gpu::GpuFeatureInfo>, void>::Dispatch<content::GpuProcessHost, content::GpuProcessHost, void, void (content::GpuProcessHost::*)(bool, gpu::GPUInfo const&, gpu::GpuFeatureInfo const&)> () at ../../ipc/ipc_message_templates.h:121

#33 0x97fd30b2 in content::GpuProcessHost::OnMessageReceived () at ../../content/browser/gpu/gpu_process_host.cc:702

#34 0x9813795c in content::ChildProcessHostImpl::OnMessageReceived () at ../../content/common/child_process_host_impl.cc:245

#35 0x97c9215a in IPC::ChannelMojo::OnMessageReceived () at ../../ipc/ipc_channel_mojo.cc:414

#36 0x97c9540e in IPC::internal::MessagePipeReader::Receive () at ../../ipc/ipc_message_pipe_reader.cc:110

#37 0x97c9af82 in IPC::mojom::ChannelStubDispatch::Accept () at gen/ipc/ipc.mojom.cc:268

#38 0x97c9e218 in mojo::InterfaceEndpointClient::HandleValidatedMessage () at ../../mojo/public/cpp/bindings/lib/interface_endpoint_client.cc:411

#39 0x97c97d08 in Accept () at ../../ipc/ipc_mojo_bootstrap.cc:755

#40 0x97c9cf64 in mojo::Connector::ReadSingleMessage () at ../../mojo/public/cpp/bindings/lib/connector.cc:284

#41 0x97c9d0a6 in mojo::Connector::ReadAllAvailableMessages () at ../../mojo/public/cpp/bindings/lib/connector.cc:311

#42 0x97b949e0 in Run () at ../../base/callback.h:80

#43 mojo::SimpleWatcher::OnHandleReady () at ../../mojo/public/cpp/system/simple_watcher.cc:262

#44 0x977dcbde in Run () at ../../base/callback.h:91

#45 base::debug::TaskAnnotator::RunTask () at ../../base/debug/task_annotator.cc:59

#46 0x977ed280 in base::MessageLoop::RunTask () at ../../base/message_loop/message_loop.cc:423

#47 0x977ed790 in base::MessageLoop::DeferOrRunPendingTask () at ../../base/message_loop/message_loop.cc:434

繼續閱讀