源码深度:Boehm GC在IL2CPP中的集成
元数据
- 系列:Unity和C#的GC剖析与对比
- 章节:05-源码深度-Boehm GC与IL2CPP
- 编号:05-04
- 标签:IL2CPP, Boehm GC, GC排除列表, 内存屏障, 线程挂起, Unity
- 阅读时间:~35分钟
- 依赖知识:05-01至05-03章节、IL2CPP基础
概述
IL2CPP是Unity的C#到C++编译技术,将C#中间语言(IL)转换为C++源码再编译为原生代码。IL2CPP默认使用Boehm GC作为其垃圾回收器。将Boehm GC集成到IL2CPP中需要解决多个挑战:C++对象模型与GC的兼容、精确与保守扫描的混合策略、线程挂起与恢复、以及Unity特有的生命周期管理。本文深入分析IL2CPP中Boehm GC的集成方案。
IL2CPP架构与GC的关系
IL2CPP编译流程
C#源码 -> Roslyn编译 -> IL(中间语言) -> IL2CPP转换 -> C++源码 -> 平台编译器 -> 原生可执行文件 IL2CPP生成的C++代码中: - 每个C#类 -> C++结构体 - 每个C#方法 -> C++方法 - GC分配 -> il2cpp::gc::GarbageCollector::Allocate() - GC引用 -> 通过GC Handle或直接指针IL2CPP GC接口层
// il2cpp_gc.cpp // IL2CPP定义了统一的GC接口,Boehm GC是其实现之一 namespace il2cpp { namespace gc { // GC接口抽象类 class GarbageCollector { public: virtual ~GarbageCollector() {} // 分配 virtual void* Allocate(size_t size, int type) = 0; // GC控制 virtual void Collect() = 0; virtual void Collect(int maxGeneration) = 0; virtual void WaitForPendingFinalizers() = 0; // 根注册 virtual void AddRoot(void* root, size_t size) = 0; virtual void RemoveRoot(void* root) = 0; // Handle管理 virtual uint32_t CreateHandle(void* obj, HandleType type) = 0; virtual void* GetHandleTarget(uint32_t handle) = 0; virtual void FreeHandle(uint32_t handle) = 0; // 线程管理 virtual void RegisterThread(void* threadInfo) = 0; virtual void UnregisterThread() = 0; // 写屏障 virtual void WriteBarrier(void* obj, void** slot, void* value) = 0; // 统计 virtual size_t GetUsedHeapSize() = 0; virtual size_t GetTotalHeapSize() = 0; }; // Boehm GC实现 class BoehmGarbageCollector : public GarbageCollector { // ... }; } }IL2CPP对Boehm GC的适配
对象分配适配
// il2cpp_gc_boehm.cpp class BoehmGarbageCollector : public GarbageCollector { public: void* Allocate(size_t size, int type) override { void* obj; switch (type) { case ALLOC_TYPE_NORMAL: // 普通对象:使用GC_malloc obj = GC_malloc(size); break; case ALLOC_TYPE_PTRFREE: // 无引用对象:使用GC_malloc_atomic // 告诉Boehm GC此对象不含指针,标记时无需扫描 obj = GC_malloc_atomic(size); break; case ALLOC_TYPE_PINNED: // 固定对象:使用GC_malloc_uncollectable // 不参与GC回收 obj = GC_malloc_uncollectable(size); break; case ALLOC_TYPE_LARGE: // 大对象:使用GC_malloc_large obj = GC_malloc_large(size); break; default: obj = GC_malloc(size); break; } if (!obj) { // OOM处理 il2cpp::vm::Exception::RaiseOutOfMemoryException(size); } return obj; } void Collect() override { GC_gcollect(); } void Collect(int maxGeneration) override { // Boehm GC不支持分代,忽略maxGeneration GC_gcollect(); } };对象头与类型信息
// il2cpp_object.h // IL2CPP的对象头结构 // 每个IL2CPP对象前都有一个对象头 struct Il2CppObject { // 方法表指针(指向TypeInfo) // Boehm GC模式下,这个指针不会被GC扫描 // 因为Boehm GC使用保守扫描,会自动扫描整个对象体 Il2CppClass *klass; // 监视器数据(用于lock语句) void *monitor; }; // IL2CPP如何让Boehm GC识别对象 // Boehm GC不依赖类型信息,而是通过地址范围判断 // IL2CPP在分配时注册对象范围到GC void* il2cpp::gc::GarbageCollector::AllocateObject(size_t size, Il2CppClass *klass) { void* obj = Allocate(size, ALLOC_TYPE_NORMAL); // 设置类型信息 ((Il2CppObject*)obj)->klass = klass; // 清零监视器 ((Il2CppObject*)obj)->monitor = NULL; return obj; }GC排除列表
为什么需要排除列表
Boehm GC的保守扫描会将所有看起来像指针的值当作引用。但IL2CPP生成的C++代码中,有许多非托管指针(如C++原生对象、文件句柄、OS资源等),这些不应该被GC扫描。排除列表告诉Boehm GC跳过这些区域。
// il2cpp_gc_exclusion.cpp // GC排除列表:指定不应被GC扫描的内存区域 struct GCExclusionRegion { void* start; void* end; const char* description; // 调试用描述 }; static std::vector<GCExclusionRegion> s_exclusionRegions; // 注册排除区域 void il2cpp::gc::RegisterExclusionRegion(void* start, void* end, const char* description) { GCExclusionRegion region; region.start = start; region.end = end; region.description = description; s_exclusionRegions.push_back(region); // 通知Boehm GC排除此区域 GC_exclude_static_roots(start, end); } // IL2CPP初始化时注册排除区域 void il2cpp::gc::InitializeExclusions() { // 排除原生C++静态数据区 // 这些区域包含非托管指针,不应被GC扫描 extern char __il2cpp_native_static_start[]; extern char __il2cpp_native_static_end[]; RegisterExclusionRegion( __il2cpp_native_static_start, __il2cpp_native_static_end, "Native static data"); // 排除IL2CPP内部数据结构 RegisterExclusionRegion( (void*)&il2cpp::vm::MetadataCache::s_TypeInfoTable, (void*)(&il2cpp::vm::MetadataCache::s_TypeInfoTable + 1), "TypeInfo table (excluded, handled separately)"); // 排除线程本地存储中的非托管数据 // ... }精确扫描区域注册
// il2cpp_gc_precise.cpp // IL2CPP为某些区域注册精确扫描回调 // 替代Boehm GC的保守扫描 // 精确扫描回调函数类型 typedef void (*PreciseScanCallback)(void* obj, void (*markFunc)(void*)); // 为每个类型注册精确扫描信息 struct PreciseScanInfo { Il2CppClass* klass; PreciseScanCallback callback; // 或者使用bitmap描述引用字段位置 uint32_t* refBitmap; int refBitmapWords; }; static std::unordered_map<Il2CppClass*, PreciseScanInfo> s_preciseScanInfo; // 注册类型的精确扫描信息 void il2cpp::gc::RegisterPreciseScan(Il2CppClass* klass, uint32_t* refBitmap, int refBitmapWords) { PreciseScanInfo info; info.klass = klass; info.callback = NULL; info.refBitmap = refBitmap; info.refBitmapWords = refBitmapWords; s_preciseScanInfo[klass] = info; } // 精确扫描对象 void il2cpp::gc::PreciseScanObject(void* obj, void (*markFunc)(void*)) { Il2CppObject* il2cppObj = (Il2CppObject*)obj; Il2CppClass* klass = il2cppObj->klass; auto it = s_preciseScanInfo.find(klass); if (it == s_preciseScanInfo.end()) { // 没有精确扫描信息,回退到保守扫描 GC_scan_object_body(obj); return; } // 使用bitmap精确扫描 PreciseScanInfo& info = it->second; void** fields = (void**)((char*)obj + sizeof(Il2CppObject)); for (int w = 0; w < info.refBitmapWords; w++) { uint32_t bitmap = info.refBitmap[w]; int base = w * 32; while (bitmap) { int bit = __builtin_ctz(bitmap); if (fields[base + bit]) { markFunc(fields[base + bit]); } bitmap &= (bitmap - 1); } } }内存屏障
Boehm GC的内存屏障需求
// il2cpp_gc_barrier.cpp // Boehm GC在非增量模式下不需要写屏障 // 但在增量模式下需要写屏障来跟踪对象图的变更 // IL2CPP的写屏障实现 void il2cpp::gc::BoehmGarbageCollector::WriteBarrier( void* obj, void** slot, void* value) { // 先写入值 *slot = value; // 如果使用增量GC,需要通知GC对象图已变更 if (m_incremental) { // 标记包含slot的页为脏 GC_dirty(obj); } // Boehm GC在非增量模式下不需要写屏障 // 但IL2CPP仍然提供统一的写屏障接口 // 以便未来切换到其他GC(如SGen) } // 读屏障(Boehm GC通常不需要) void* il2cpp::gc::BoehmGarbageCollector::ReadBarrier(void** slot) { void* value = *slot; // Boehm GC不需要读屏障 // 但某些平台(如弱内存模型架构)可能需要 // 确保读取的值是完整的 return value; }精确写屏障优化
// il2cpp_gc_barrier_precise.cpp // IL2CPP在JIT/AOT阶段生成精确的写屏障调用 // 只在写入引用类型字段时调用写屏障 // IL2CPP生成的C++代码中的写屏障调用 // 原始C#代码: class.field = value; // 生成的C++代码: extern "C" void IL2CPPWriteBarrier(void* obj, void** slot, void* value) { *slot = value; // 增量模式下的屏障 if (il2cpp::gc::GarbageCollector::IsIncremental()) { GC_dirty(obj); } } // 对于数组元素写入 extern "C" void IL2CPPArrayWriteBarrier(void* array, int32_t index, void* value) { void** slot = (void**)((char*)array + sizeof(Il2CppArray)) + index; *slot = value; if (il2cpp::gc::GarbageCollector::IsIncremental()) { GC_dirty(array); } }线程挂起与恢复
STW在IL2CPP中的实现
// il2cpp_gc_thread.cpp // IL2CPP需要实现线程挂起以支持Boehm GC的STW // 线程注册 void il2cpp::gc::BoehmGarbageCollector::RegisterThread(void* threadInfo) { // 注册线程到Boehm GC GC_thread_register(threadInfo); // 记录线程栈范围 ThreadInfo* info = (ThreadInfo*)threadInfo; info->stackBase = GetCurrentStackBase(); // 通知Boehm GC线程的栈范围 GC_set_stack_base(info->stackBase, info->stackPtr); } // 线程挂起回调 // Boehm GC通过信号或平台API挂起线程 // IL2CPP需要确保挂起时线程处于安全点 static void OnThreadSuspended(ThreadInfo* info) { // 保存线程的寄存器上下文 // 这些寄存器中可能包含对象引用 GetThreadContext(info->threadId, &info->context); // 更新栈指针 info->stackPtr = (void*)info->context.sp; // 通知Boehm GC可以扫描此线程 GC_thread_suspended(info); } // 安全点检查 // IL2CPP在方法入口和循环回边处插入安全点检查 extern "C" void IL2CPPCheckGC() { if (il2cpp::gc::GarbageCollector::IsGCInProgress()) { // GC正在进行,等待完成 il2cpp::gc::GarbageCollector::WaitForGCCompletion(); } }平台相关的线程挂起
// il2cpp_gc_thread_platform.cpp // 不同平台的线程挂起实现 // Linux: 使用SIGUSR1信号 #if IL2CPP_TARGET_LINUX static void SuspendThreadLinux(pthread_t thread, ThreadInfo* info) { // 发送SIGUSR1信号挂起线程 pthread_kill(thread, SIGUSR1); // 等待线程确认挂起 while (!info->suspended) { sched_yield(); } } // 信号处理函数 static void SuspendSignalHandler(int sig, siginfo_t* info, void* context) { if (sig == SIGUSR1) { // 保存上下文 ucontext_t* uc = (ucontext_t*)context; memcpy(¤tThread->context, &uc->uc_mcontext, sizeof(currentThread->context)); // 标记已挂起 currentThread->suspended = true; // 等待恢复信号 while (currentThread->suspended) { sigsuspend(&resume_mask); } } } #endif // Windows: 使用SuspendThread #if IL2CPP_TARGET_WINDOWS static void SuspendThreadWindows(HANDLE thread, ThreadInfo* info) { DWORD result = SuspendThread(thread); if (result == (DWORD)-1) { // 错误处理 return; } // 获取线程上下文(寄存器) CONTEXT context; context.ContextFlags = CONTEXT_FULL; GetThreadContext(thread, &context); // 保存上下文 memcpy(&info->context, &context, sizeof(CONTEXT)); info->stackPtr = (void*)context.Rsp; // x64 } #endif // macOS: 使用task_suspend #if IL2CPP_TARGET_OSX static void SuspendThreadOSX(mach_port_t thread, ThreadInfo* info) { kern_return_t kr = thread_suspend(thread); if (kr != KERN_SUCCESS) { return; } // 获取线程状态 x86_thread_state_t state; mach_msg_type_number_t count = x86_THREAD_STATE_COUNT; thread_get_state(thread, x86_THREAD_STATE, (thread_state_t)&state, &count); info->stackPtr = (void*)state.uts.ts64.__rsp; } #endifIL2CPP中的Boehm GC配置
配置参数
// il2cpp_gc_config.cpp // IL2CPP中Boehm GC的配置 struct BoehmGCConfig { // 堆初始大小 size_t initialHeapSize; // GC触发阈值 size_t gcThreshold; // 增量模式 bool incremental; // 并行标记 bool parallelMarking; int markThreadCount; // 最大堆大小 size_t maxHeapSize; // 是否使用精确扫描(部分类型) bool usePreciseScan; // 是否启用黑名单 bool enableBlackList; // Lazy Free bool lazyFree; }; static BoehmGCConfig s_config; void il2cpp::gc::InitializeBoehmGC() { // 从Unity配置读取参数 s_config.initialHeapSize = GetUnityConfig( "gc_initial_heap_size", 4 * 1024 * 1024); s_config.gcThreshold = GetUnityConfig( "gc_threshold", 8 * 1024 * 1024); s_config.incremental = GetUnityConfig( "gc_incremental", false); s_config.parallelMarking = GetUnityConfig( "gc_parallel_marking", false); s_config.maxHeapSize = GetUnityConfig( "gc_max_heap_size", 0); // 0=无限制 // 应用配置到Boehm GC GC_set_max_retries(100); GC_set_full_freq(19); GC_set_time_limit(100); // 增量模式时间片(ms) if (s_config.incremental) { GC_enable_incremental(); } // 初始化Boehm GC GC_INIT(); // 注册排除区域 InitializeExclusions(); // 注册线程 RegisterCurrentThread(); }Unity Player Settings中的GC配置
// il2cpp_gc_config_player.cpp // 从Unity Player Settings读取GC配置 void il2cpp::gc::LoadPlayerSettings() { // 读取Player Settings中的GC配置 // 这些设置在Unity编辑器中配置 // GC选择(Boehm或SGen) const char* gcName = GetPlayerSetting("gc"); if (strcmp(gcName, "boehm") == 0) { s_gcType = GC_TYPE_BOEHM; } else if (strcmp(gcName, "sgen") == 0) { s_gcType = GC_TYPE_SGEN; } // 增量GC bool incremental = GetPlayerSettingBool("incrementalGC"); s_config.incremental = incremental; // 堆大小 s_config.initialHeapSize = GetPlayerSettingInt( "initialHeapSize", 4 * 1024 * 1024); s_config.maxHeapSize = GetPlayerSettingInt( "maxHeapSize", 0); }IL2CPP特有的GC问题
值类型盒装
// il2cpp_gc_boxing.cpp // C#中的值类型盒装(boxing)需要GC分配 extern "C" void* il2cpp::gc::BoxObject(void* value, Il2CppClass* klass) { // 分配盒装对象 size_t size = il2cpp_class_instance_size(klass); void* boxed = GarbageCollector::Allocate(size, ALLOC_TYPE_NORMAL); // 设置类型信息 ((Il2CppObject*)boxed)->klass = klass; // 复制值类型数据 memcpy((char*)boxed + sizeof(Il2CppObject), value, size - sizeof(Il2CppObject)); return boxed; }固定对象(Pinned)
// il2cpp_gc_pinned.cpp // C#中的fixed语句和GCHandle.Pinned需要固定对象 extern "C" uint32_t il2cpp::gc::CreatePinnedHandle(void* obj) { // 创建固定句柄 // Boehm GC使用GC_malloc_uncollectable + 特殊标记 uint32_t handle = GC_create_pinned_handle(obj); // 固定对象不会被GC移动(Boehm GC本身不移动对象) // 但需要确保对象不会被回收 return handle; } // C# fixed语句生成的代码 // C#: fixed (int* p = &arr[0]) { ... } // C++: extern "C" void IL2CPPFixed(int32_t* arr, int32_t index, void (*callback)(int32_t*)) { uint32_t handle = il2cpp::gc::CreatePinnedHandle(&arr[index]); callback(&arr[index]); il2cpp::gc::FreeHandle(handle); }总结
IL2CPP Boehm GC集成架构: +--------------------------------------------------+ | C# / IL 代码 | +--------------------------------------------------+ | IL2CPP编译 v +--------------------------------------------------+ | C++ 源码 | | +-- 对象分配 -> il2cpp::gc::Allocate() | | +-- 写屏障 -> IL2CPPWriteBarrier() | | +-- 安全点 -> IL2CPPCheckGC() | | +-- 固定对象 -> CreatePinnedHandle() | +--------------------------------------------------+ | 链接 v +--------------------------------------------------+ | IL2CPP GC接口层 | | +-- GarbageCollector (抽象接口) | | +-- BoehmGarbageCollector (Boehm实现) | | +-- 排除列表管理 | | +-- 精确扫描注册 | | +-- 线程管理 | +--------------------------------------------------+ | 调用 v +--------------------------------------------------+ | Boehm GC 库 | | +-- GC_malloc / GC_malloc_atomic | | +-- GC_gcollect | | +-- GC_push_roots / GC_mark_some | | +-- GC_exclude_static_roots | +--------------------------------------------------+| 集成要点 | 说明 |
|---|---|
| 对象分配 | 通过统一接口,支持NORMAL/PTRFREE/PINNED/LARGE |
| 排除列表 | 排除C++原生数据,避免伪指针 |
| 精确扫描 | 为部分类型注册bitmap,减少保守扫描开销 |
| 内存屏障 | 增量模式下使用GC_dirty |
| 线程挂起 | 平台相关实现(信号/SuspendThread/task_suspend) |
| 安全点 | 方法入口和循环回边检查GC状态 |
IL2CPP通过抽象GC接口层将Boehm GC集成到Unity中,同时通过排除列表和精确扫描注册缓解了保守GC的固有缺陷。但Boehm GC的根本局限——不支持压缩、不支持分代、伪指针风险——仍然存在。下一章将分析这些局限及未来的替代演进方向。