/* * Copyright 2020 ByteDance Games, Inc. All Rights Reserved. * * Author: Liuziming */ #pragma once #include #include #include #include #include #include #include #include "vptr_manager.h" #include "shm_helper.h" #include // Containers redefined for shm from `std` namespace. namespace bg { using ShmString = std::basic_string, detail::ShmAllocator>; template using ShmVector = std::vector>; template using ShmList = std::list>; template> using ShmSet = std::set>; template> using ShmMap = std::map>>; template, typename Pred = std::equal_to> using ShmUnorderedSet = std::unordered_set>; template, typename Pred = std::equal_to> using ShmUnorderedMap = std::unordered_map>>; } // namespace bg // C-style memory related functions for shm. namespace bg { /** * 分配一块共享内存块 * @param bytes 要分配的共享内存块的大小 * @return 分配的共享内存块的首地址,如果分配失败,返回NULL */ void* ShmMalloc(size_t bytes); /** * 调整已分配的共享内存块的大小 * @param old_ptr 要调整的共享内存块的首地址 * @param new_bytes 要调整的新的大小 * @return 调整后的共享内存块的首地址 * @note 调用本函数分以下几种情况: * 1. 如果old_ptr为NULL,则该调用等同于ShmMalloc(new_bytes) * 2. 如果new_bytes为0,则该调用等同于ShmFree(old_ptr),并返回NULL * 3. 其它情况,视new_bytes大小决定: * - 原共享内存块大小足够并且不触发内存缩减操作时,直接返回原共享内存块 * - 原共享内存块大小不足或者new_bytes远小于原共享内存块大小时,调用ShmMalloc分配新共享内 * 存块,将原内存的内容使用memcpy拷贝到新内存,然后调用ShmFree释放原共享内存块并返回新共 * 享内存块。如果ShmMalloc分配失败,则不释放原内存并返回NULL。注意,如果new_bytes小于原内 * 存大小,则在调用memcpy时会产生内容截断。 */ void* ShmRealloc(void* old_ptr, size_t new_bytes); /** * 分配n块连续的共享内存块并填充0 * @param n 要分配的共享内存块的数量 * @param bytes 要分配的共享内存块的大小 * @return 分配的已填充0的连续的共享内存块的首地址,如果分配失败,返回NULL */ void* ShmCalloc(size_t n, size_t bytes); /** * 释放传入的共享内存块 * @param ptr 要释放的共享内存块的首地址 */ void ShmFree(void* ptr); } // namespace bg // Helper functions. namespace bg { namespace detail { template T* ShmNew(std::true_type /* objects with vptr(s) */, Args&& ... args) { return VptrManager::NewVptrObject(std::forward(args)...); } template T* ShmNewExtraSpace(std::true_type /* objects with vptr(s) */, size_t extra_space, Args&& ... args) { return VptrManager::NewVptrObjectExtraSpace(extra_space, std::forward(args)...); } template T* ShmNew(std::false_type /* objects without vptr(s) */, Args&& ... args) { void* ptr = ShmMalloc(sizeof(T)); if(SHM_LIKELY(ptr)) { new(ptr) T(std::forward(args)...); } return static_cast(ptr); } template T* ShmNewExtraSpace(std::false_type /* objects without vptr(s) */, size_t extra_space, Args&& ... args) { void* ptr = ShmMalloc(sizeof(T) + extra_space); if(SHM_LIKELY(ptr)) { new(ptr) T(std::forward(args)...); } return static_cast(ptr); } template void ShmDelete(std::true_type /* objects with vptr(s) */, T* ptr) { VptrManager::DelVptrObject(ptr); } template void ShmDelete(std::false_type /* objects without vptr(s) */, T* ptr) { if(SHM_LIKELY(ptr)) { ptr->~T(); ShmFree(ptr); } } } // namespace detail } // namespace bg // C++-style memory related functions for shm. namespace bg { /** * 构造一个保存在共享内存中的类的实例 * @tparam T 要保存在共享内存中的类的类型 * @tparam Args T类型的构造函数的参数类型 * @param args T类型的构造函数的参数 * @return 指向T类型实例的指针,如果分配失败,返回NULL */ template T* ShmNew(Args&&... args) { return detail::ShmNew(detail::HasVptr(), std::forward(args)...); } /** * 构造一个保存在共享内存中的类的实例,并且分配额外的内存(额外的内存紧临该实例) * @tparam T 要保存在共享内存中的类的类型 * @tparam Args T类型的构造函数的参数类型 * @param extra_space 要额外分配的内存大小 * @param args T类型的构造函数的参数 * @return 指向T类型实例的指针,如果分配失败,返回NULL */ template T* ShmNewExtraSpace(size_t extra_space, Args&&... args) { return detail::ShmNewExtraSpace(detail::HasVptr(), extra_space, std::forward(args)...); } /** * 析构一个保存在共享内存中的类的实例 * @tparam T 保存在共享内存中的类的类型 * @param ptr T类型实例的指针 */ template void ShmDelete(T* ptr) { detail::ShmDelete(detail::HasVptr(), ptr); } } // namespace bg // Singleton related functions. namespace bg { /** * 获得一个保存在共享内存中的单例 * @tparam T 要获取的单例类型 * @tparam Args T类型的构造函数的参数类型 * @param args T类型的构造函数的参数 * @return 指向T类型单例的指针,如果分配失败,返回NULL * @note T类型不能是多态类型,否则编译失败 */ template T* ShmGetSingleton(Args&& ... args) { static_assert(!detail::HasVptr::value, "singleton does not support polymorphic types."); detail::TypeName type(typeid(T).name(), sizeof(T)); bool first_call = false; void* ptr = detail::ShmGetSingleton(type, sizeof(T), &first_call); if(ptr && first_call) { new(ptr) T(std::forward(args)...); } return static_cast(ptr); } /** * 销毁一个保存在共享内存中的单例 * @tparam T 单例类的类型 * @note T类型不能是多态类型,否则编译失败 */ template void ShmDeleteSingleton() { static_assert(!detail::HasVptr::value, "singleton does not support polymorphic types."); detail::TypeName type(typeid(T).name(), sizeof(T)); if(!detail::ShmHasSingleton(type)) { return; } void* ptr = detail::ShmGetSingleton(type, sizeof(T), nullptr); SHM_ASSERT_RETURN_VOID(ptr); static_cast(ptr)->~T(); detail::ShmFreeSingleton(type); } } // namespace bg // Initialize/Finalize functions. namespace bg { /** * 初始化整个Shm模块时的一些可配置选项 */ struct ShmOptions { // 是否以resume模式初始化Shm模块 bool resume; // 是否要替换系统内存分配器,目前没有实现,但是依然强烈不建议打开此选项 bool replace_sys_alloc; // 是否在ShmInit里面去修复虚表指针,默认值为true,如果有dlopen打开so,并且so // 里面有虚表指针需要修复的话,建议此选项为false,同时,此时需要调用者自己在 // dlopen之后去调用修复虚表指针的函数 bool fix_vptr_on_init; // Shm模块的日志输出函数,如果为NULL,则所有日志默认输出到stdout ShmLogger* logger; // Shm模块管理器挂载的内存地址,默认值为0x600000000000,通常情况下不需要自定 // 义。如果要自定义该值,请避开进程中已映射的区域(可查看/proc//maps),并 // 尽量选用较空的位置,不然整个Shm模块将无法工作 uintptr_t main_address; // 每块原始共享内存的映射大小,也是每块原始共享内存的最大大小,默认值为4GB,通 // 常情况下不需要自定义 size_t shm_block_mmap_size; // 在原始共享内存不足时增长的大小,默认值是32MB,通常情况下不需要自定义 size_t shm_block_grow_size; // 原始共享内存可挂载的内存地址的数量,默认值为16(即fixed_addresses的大小) size_t fixed_address_count; // Shm模块的标识符,由于共享内存全系统可见,为防止冲突,请务必保证每个进程有 // 独一无二的标识符,并且在进程重启后保证标识符不变 char identifier[64]; // 原始共享内存可挂载的内存地址列表,默认有16个地址,通常情况下这些地址不需要 // 自定义。如果要自定义,请参考main_address的自定义方式,并且保证从每个地址开 // 始,到加上shm_block_grow_size结束的区域不会和别的内存映射区域重叠 uintptr_t fixed_addresses[16]; /** * 配置项构造函数,设置传入的选项,并将其它选项设置为默认值 * @param resume 设置是否是resume模式 * @param identifier 设置模块标识符,不能超过64字节 * @param logger 设置日志输出函数,可以为NULL */ ShmOptions(bool resume, const char* identifier, ShmLogger* logger); bool AddFixedAddress(uintptr_t addr); bool PopFixedAddress(uintptr_t* addr); static size_t FillDefaultFixedAddresses(uintptr_t* result, size_t max_count); }; /** * 初始化整个Shm模块 * @param options 用于初始化Shm模块的一些配置项 * @return 成功返回true,失败返回false,并输出相应的错误日志 * @note 在使用任何本模块的内存操作函数前,必须调用本函数进程初始化 */ bool ShmInit(const ShmOptions& options); /** * 销毁整个Shm模块 * @note 本函数会销毁整个Shm模块,以及共享内存中的数据,因此,只有在 * 正常停服时才需要调用本函数,进行热更新或者进程崩溃等需要重启进 * 程并使用原来共享内存的情况下,请务必*不要*调用本函数 */ void ShmFini(); /** * 修复在共享内存中实例的虚表指针 * @note 通常情况下,虚表指针会在ShmInit中进行修复,但有些程序会用 * dlopen打开so,而如果dlopen发生在ShmInit之后,则ShmInit没办法修 * 复so中的虚表指针,此时,需要调用者在dlopen之后显式调用该函数进 * 行虚表指针修复 */ void ShmFixVptr(); } // namespace bg // A dirty hack on `std::hash` for `bg::ShmString`. namespace std { template<> struct hash { size_t operator()(const bg::ShmString& str) const noexcept { return std::_Fnv1a_append_value(str.length(), str.data()); } }; } // namespace std