声明:本文的部分内容参考了他人的文章。在编写过程中,我们尊重他人的知识产权和学术成果,力求遵循合理使用原则,并在适用的情况下注明引用来源。
本文主要参考了 OpenGauss1.1.0 的开源代码和《OpenGauss数据库源码解析》一书以及OpenGauss社区学习文档和一些参考资料
学习完了 CU 和 CUStorage 类,我们最后来学习一下 CStoreMemAlloc 类。CStoreMemAlloc 类提供了内存管理的功能,而 CU 和 CUStorage 类负责处理列存储中的具体数据单元和物理存储。在实际的列存储系统中,这些类可能会协同工作以有效地管理内存和存储数据。
CStoreMemAlloc 类在列存储中扮演了一个内存管理的角色,帮助管理和释放与列存储操作相关的动态内存。这对于确保系统在处理大量数据时能够高效使用内存、防止内存泄漏以及正确处理事务回滚等方面非常重要。类的定义如下:(函数路径:src/include/storage/cstore/cstore_mem_alloc.h
)
// 类定义:CStoreMemAlloc
// 用于管理从malloc中分配的内存指针
// 在事务出现错误时,可以在abortTransaction中重置malloc的内存
// 在事务提交时,可以重置malloc的内存
class CStoreMemAlloc {
public:
static void* Palloc(Size size, bool toRegister = true);
static void Pfree(void* pointer, bool registered = true);
static void* Repalloc(void* pointer, Size size, Size old_size, bool registered = true);
static void Register(void* pointer);
static void Unregister(const void* pointer);
static void Reset();
static void Init();
private:
static void* AllocPointerNode();
static void FreePointerNode(PointerNode* pointer);
// 保存所有分配的内存指针数组
static THR_LOCAL PointerList m_tab[MaxPointersArryLen];
// 记录分配的内存指针数量
static THR_LOCAL uint64 m_count;
// 保存已经分配的PointerNode的缓存
static THR_LOCAL uint32 m_ptrNodeCacheCount;
static THR_LOCAL PointerNode* m_ptrNodeCache[MaxPtrNodeCacheLen];
};
下面我们分别来看看CStoreMemAlloc 类成员函数的作用是什么?
CStoreMemAlloc 类中的 Palloc 方法。该方法用于从系统中分配内存,并根据需要将内存指针注册到管理中。具体作用和功能如下:
描述:该方法用于从系统中分配内存,支持动态内存的分配操作。
参数:
size:需要分配的内存大小。
toRegister:表示是否需要将该内存指针在事务管理范围内进行注册。如果为 true,则注册;如果为 false,则不注册。
功能:
确保要分配的内存大小大于0。
调用内部的 InnerMalloc 方法进行实际的内存分配。
如果内存分配失败,抛出内存不足的错误。
如果需要在事务管理范围内注册该内存指针,则进行注册。
返回分配的内存指针。
函数源码如下所示:(路径:src/gausskernel/storage/cstore/cstore_mem_alloc.cpp
)
/*
* @Description: 从系统中分配内存
* @Param[IN] size: 需要的内存大小
* @Param[IN] toRegister: 如果这块内存在线程管理的范围内,则设置为 true;
* 如果这块内存在进程管理的范围内,则设置为 false。
* @See also: 无
*/
void* CStoreMemAlloc::Palloc(Size size, bool toRegister)
{
// 确保分配的内存大小大于0
Assert(size > 0);
// 调用内部的分配方法分配内存
void* ptr = InnerMalloc(size);
// 如果分配失败,抛出内存不足的错误
if (ptr == NULL) {
ereport(ERROR, (errcode(ERRCODE_OUT_OF_MEMORY), errmsg("malloc fails, out of memory: size %lu", size)));
}
// 如果需要注册(在事务管理范围内),则进行注册
if (toRegister) {
/* 步骤2:注册指针 */
Register(ptr);
}
// 返回分配的内存指针
return ptr;
}
这段代码是 CStoreMemAlloc 类中的 AllocPointerNode 方法。该方法用于分配一个指针节点,具体作用和功能如下:
描述:该方法用于分配一个指针节点,该节点可能被用于管理 CStoreMemAlloc 类中的内存指针。
返回:返回分配得到的指针节点。
功能:
如果指针节点缓存中有可用节点,从缓存中取出一个节点。
如果指针节点缓存中没有可用节点,直接使用 malloc 分配一个新的节点。
如果分配失败,抛出内存不足的错误。
返回分配得到的指针节点。
函数源码如下所示:(路径:src/gausskernel/storage/cstore/cstore_mem_alloc.cpp
)
/*
* @Description: 分配一个指针节点
* @Return: 分配得到的指针节点
* @See also: 无
*/
void* CStoreMemAlloc::AllocPointerNode()
{
PointerNode* ptr = NULL;
// 如果缓存中有可用节点,从缓存中取出
if (m_ptrNodeCacheCount > 0) {
ptr = m_ptrNodeCache[--m_ptrNodeCacheCount];
} else {
// 如果缓存中没有可用节点,直接使用malloc分配新节点
ptr = (PointerNode*)malloc(sizeof(PointerNode));
// 分配失败则抛出内存不足的错误
if (ptr == NULL) {
ereport(ERROR, (errcode(ERRCODE_OUT_OF_MEMORY), errmsg("malloc fails, out of memory")));
}
}
// 返回分配得到的指针节点
return ptr;
}
CStoreMemAlloc 类中的 FreePointerNode 方法用于释放一个指针节点,具体作用和功能如下:
描述:该方法用于释放一个指针节点,该节点可能被用于管理 CStoreMemAlloc 类中的内存指针。
参数:ptr 为要释放的指针节点。
功能:
如果指针节点缓存未满,将节点放入缓存中以便后续复用。
如果指针节点缓存已满,直接使用 free 释放节点。
函数源码如下所示:(路径:src/gausskernel/storage/cstore/cstore_mem_alloc.cpp
)
/*
* @Description: 释放一个指针节点
* @Param[IN] ptr: 要释放的指针节点
* @See also: 无
*/
void CStoreMemAlloc::FreePointerNode(PointerNode* ptr)
{
// 如果指针节点缓存未满,将节点放入缓存中
if (m_ptrNodeCacheCount < MaxPtrNodeCacheLen) {
m_ptrNodeCache[m_ptrNodeCacheCount++] = ptr;
} else {
// 如果缓存已满,直接使用free释放节点
free(ptr);
}
}
CStoreMemAlloc 类中的 Repalloc 方法用于重新分配内存(相当于 remalloc 操作),并根据需要注册新分配的内存,取消注册旧内存。具体作用和功能如下:
描述:该方法用于重新分配内存,类似于系统中的 remalloc 操作,同时支持内存的注册和取消注册。
参数:
old_size:旧内存的大小。
size:新内存的大小。
pointer:要释放的旧内存指针。
registered:如果在调用 Palloc() 时传递 true,则为 true;如果传递 false,则为 false。
功能:
断言,确保内存指针和大小的合法性。
调用内部的 InnerMalloc 方法重新分配内存。
如果分配失败,抛出内存不足的错误。
将旧内存中的数据拷贝到新分配的内存中。
如果在 Palloc() 中传递 true,则注册新分配的内存,取消注册旧内存。
断言,确保计数器大于0。
释放旧内存。
返回新分配的内存指针。
函数源码如下所示:(路径:src/gausskernel/storage/cstore/cstore_mem_alloc.cpp
)
/*
* @Description: 重新分配内存(remalloc)从系统中
* @Param[IN] old_size: 旧内存大小
* @Param[IN] size: 新内存大小
* @Param[IN] pointer: 要释放的内存指针
* @Param[IN] registered: 如果在 Palloc() 中传递 true,则为 true;
* 如果在 Palloc() 中传递 false,则为 false。
* @See also: 无
*/
void* CStoreMemAlloc::Repalloc(void* pointer, Size size, Size old_size, bool registered)
{
// 断言,确保内存指针和大小的合法性
Assert(pointer != NULL);
Assert(size > 0);
// 调用内部的 InnerMalloc 方法重新分配内存
void* ptr = InnerMalloc(size);
// 如果分配失败,抛出内存不足的错误
if (ptr == NULL) {
ereport(ERROR, (errcode(ERRCODE_OUT_OF_MEMORY), errmsg("out of memory")));
}
// 将旧内存中的数据拷贝到新分配的内存中
errno_t rc = memcpy_s(ptr, size, pointer, old_size);
securec_check_c(rc, "\0", "\0");
// 如果在 Palloc() 中传递 true,则注册新分配的内存,取消注册旧内存
if (registered) {
Register(ptr);
Unregister(pointer);
// 断言,确保计数器大于0
Assert(m_count > 0);
}
// 释放旧内存
free(pointer);
// 返回新分配的内存指针
return ptr;
}
CStoreMemAlloc 类中的 Pfree 方法用于将内存释放到系统,同时根据需要取消注册内存指针。具体作用和功能如下:
描述:该方法用于释放内存到系统,可以选择是否取消注册内存指针。
参数:
pointer:要释放的内存指针。
registered:如果在调用 Palloc() 时传递 true,则为 true;如果传递 false,则为 false。
功能:
断言,确保内存指针的合法性。
如果在 Palloc() 中传递 true,则取消注册内存指针。
释放内存到系统。
函数源码如下所示:(路径:src/gausskernel/storage/cstore/cstore_mem_alloc.cpp
)
/*
* @Description: 释放内存到系统
* @Param[IN] pointer: 要释放的内存指针
* @Param[IN] registered: 如果在 Palloc() 中传递 true,则为 true;
* 如果在 Palloc() 中传递 false,则为 false。
* @See also: 无
*/
void CStoreMemAlloc::Pfree(void* pointer, bool registered)
{
// 断言,确保内存指针的合法性
Assert(pointer);
// 如果在 Palloc() 中传递 true,则取消注册内存指针
if (registered) {
Unregister(pointer);
}
// 释放内存到系统
free(pointer);
}
CStoreMemAlloc 类中的 Register 方法用于注册内存指针,将其添加到内存指针管理表中。具体作用和功能如下:
描述:该方法用于将内存指针注册,将其添加到内存指针管理表中,以便在事务结束时进行统一的处理。
参数:
pointer:要注册的内存指针。
功能:
断言,确保 MaxPointersArryLen 是2的幂。
计算内存指针在管理表中的索引位置。
如果链表为空,头尾节点都指向新分配的节点。
如果链表不为空,在链表尾部添加新节点。
增加内存指针计数器。
函数源码如下所示:(路径:src/gausskernel/storage/cstore/cstore_mem_alloc.cpp
)
/*
* @Description: 注册内存指针
* @Param[IN] pointer: 要注册的内存指针
* @See also: AllocPointerNode()
*/
void CStoreMemAlloc::Register(void* pointer)
{
// 断言,确保 MaxPointersArryLen 是2的幂
Assert((MaxPointersArryLen & (MaxPointersArryLen - 1)) == 0);
// 计算索引位置
int idx = PointerGetDatum(pointer) & (MaxPointersArryLen - 1);
PointerNode* nodePtr = m_tab[idx].tail;
// 如果尾节点为空,说明链表为空
if (nodePtr == NULL) {
// 头尾节点都指向新分配的节点
Assert(m_tab[idx].header == NULL);
m_tab[idx].header = m_tab[idx].tail = (PointerNode*)AllocPointerNode();
m_tab[idx].header->ptr = pointer;
m_tab[idx].header->next = NULL;
} else {
// 否则在链表尾部添加新节点
nodePtr->next = (PointerNode*)AllocPointerNode();
nodePtr->next->ptr = pointer;
nodePtr->next->next = NULL;
m_tab[idx].tail = nodePtr->next;
}
// 增加计数器
++m_count;
}
CStoreMemAlloc 类中的 Unregister 方法用于取消注册内存指针,将其从内存指针管理表中移除。具体作用和功能如下:
描述:该方法用于取消注册内存指针,将其从内存指针管理表中移除,以便在事务结束时进行统一的处理。
参数:
pointer:要取消注册的内存指针。
功能:
断言,确保内存指针和计数器的合法性。
确定包含该指针的节点列表。
在节点列表中查找指针,如果找到则移除该节点。
如果取消注册的是尾节点,需要修改尾指针。
重置并释放节点。
减少计数器。
断言,确保找到了要取消注册的指针节点。
函数源码如下所示:(路径:src/gausskernel/storage/cstore/cstore_mem_alloc.cpp
)
/*
* @Description: 取消注册内存指针
* @Param[IN] pointer: 要取消注册的内存指针
* @See also: FreePointerNode()
*/
void CStoreMemAlloc::Unregister(const void* pointer)
{
// 断言,确保内存指针和计数器的合法性
Assert(pointer && m_count > 0);
// Step 1: 确定包含该指针的节点列表
Assert((MaxPointersArryLen & (MaxPointersArryLen - 1)) == 0);
int idx = PointerGetDatum(pointer) & (MaxPointersArryLen - 1);
// Step 2: 在节点列表中查找指针
PointerNode* nodePtr = m_tab[idx].header;
Assert(nodePtr);
PointerNode* prePtr = NULL;
while (nodePtr != NULL) {
if (nodePtr->ptr == pointer) {
// 如果前一个节点为空,说明要取消注册的是头节点
if (prePtr == NULL) {
m_tab[idx].header = nodePtr->next;
// 如果这个列表只有一个节点
if (m_tab[idx].tail == nodePtr) {
Assert(m_tab[idx].header == NULL);
m_tab[idx].tail = NULL;
}
} else {
// 否则,修改前一个节点的 next 指针
prePtr->next = nodePtr->next;
// 如果取消注册的是尾节点,需要修改尾指针
if (m_tab[idx].tail == nodePtr) {
m_tab[idx].tail = prePtr;
Assert(m_tab[idx].tail->next == NULL);
}
}
// 重置并释放节点
nodePtr->Reset();
FreePointerNode(nodePtr);
break;
}
prePtr = nodePtr;
nodePtr = nodePtr->next;
}
// 减少计数器
--m_count;
// 断言,确保找到了要取消注册的指针节点
Assert(nodePtr != NULL);
}
CStoreMemAlloc 类中的 Reset 方法用于重置内存指针管理器的状态,释放所有注册的内存。具体作用和功能如下:
描述:该方法用于在事务结束时,重置内存指针管理器的状态,释放所有注册的内存。
功能:
如果存在注册的内存指针:
Step 1: 释放 m_tab 中的每个列表中的内存指针。
遍历 m_tab 数组,释放每个列表中的内存指针。
注意,必须将列表的头尾节点重置为 NULL,因为线程可能被重用。
断言,确保释放的内存数和计数器一致。
Step 2: 释放缓存的节点(如果有的话)。
释放缓存的节点,以便在下一次使用时重新分配。
这个方法的目的是确保在事务结束时,所有注册的内存都被正确释放,以避免内存泄漏。函数源码如下所示:(路径:src/gausskernel/storage/cstore/cstore_mem_alloc.cpp
)
/*
* @Description: 重置内存指针管理器状态,释放所有注册的内存
*/
void CStoreMemAlloc::Reset()
{
uint32 i = 0;
uint32 freeNum = 0;
// 如果存在注册的内存指针
if (m_count) {
// Step 1: 释放 m_tab 中的每个列表
for (i = 0; i < MaxPointersArryLen; ++i) {
PointerNode* nodePtr = m_tab[i].header;
PointerNode* tmpPtr = NULL;
// 释放每个列表中的内存指针
while ((nodePtr != NULL) && (nodePtr->ptr != NULL)) {
free(nodePtr->ptr);
tmpPtr = nodePtr->next;
free(nodePtr);
nodePtr = tmpPtr;
++freeNum;
}
// 注意,必须将 header 和 tail 重置为 NULL
// 因为线程可能被重用
m_tab[i].header = NULL;
m_tab[i].tail = NULL;
}
// 断言,确保释放的内存数和计数器一致
Assert(m_count == freeNum);
m_count = 0;
}
// Step 2: 释放缓存的节点(如果有的话)
for (i = 0; i < m_ptrNodeCacheCount; ++i) {
free(m_ptrNodeCache[i]);
}
m_ptrNodeCacheCount = 0;
}
这段代码是 CStoreMemAlloc 类中的 Init 方法。该方法用于初始化内存指针管理器的状态。具体作用和功能如下:
描述:该方法用于在开始新的事务时,初始化内存指针管理器的状态。
功能:
重置计数器。
断言,确保 MaxPointersArryLen 是2的幂。
初始化 m_tab 数组,将每个列表的头尾节点都设置为 NULL。
初始化缓存节点计数器。
这个方法的目的是确保在每次开始新的事务时,内存指针管理器都处于初始状态。函数源码如下所示:(路径:src/gausskernel/storage/cstore/cstore_mem_alloc.cpp
)
/*
* @Description: 初始化内存指针管理器
*/
void CStoreMemAlloc::Init()
{
// 重置计数器
m_count = 0;
// 断言,确保 MaxPointersArryLen 是2的幂
Assert((MaxPointersArryLen & (MaxPointersArryLen - 1)) == 0);
// 初始化 m_tab 数组
for (uint32 i = 0; i < MaxPointersArryLen; ++i) {
m_tab[i].header = NULL;
m_tab[i].tail = NULL;
}
// 初始化缓存节点计数器
m_ptrNodeCacheCount = 0;
}
CStoreMemAlloc 类
作用: CStoreMemAlloc 类用于管理列存储中的内存分配和释放。
功能:
- Palloc 方法: 分配内存,并根据需要注册到管理器。
- FreePointerNode 方法: 释放缓存的节点。
- Repalloc 方法: 重新分配内存,同时处理注册和取消注册。
- Pfree 方法: 释放内存,同时处理取消注册。
- Register 方法: 注册内存指针,将其添加到管理表。
- Unregister 方法: 取消注册内存指针,将其从管理表中移除。
- Reset 方法: 重置管理器状态,释放所有注册的内存。
- Init 方法: 初始化管理器状态。