openGauss内存保护机制

openGauss内存统计、内存保护机制。

内存保护

申请内存palloc时,GenericMemoryAllocator::AllocSetAlloc根据shard和session id 来选择合适的内存申请函数malloc。 最终是调用func->malloc来申请内存。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// 选择函数
if (is_shared) {
MemoryContextLock(context);
func = &SharedFunctions;
} else {
if (context->session_id > 0)
func = &SessionFunctions;
else
func = &GenericFunctions;
}

// 不同类型的函数
MemoryProtectFuncDef GenericFunctions = {MemoryProtectFunctions::gs_memprot_malloc<MEM_THRD>,
MemoryProtectFunctions::gs_memprot_free<MEM_THRD>,
MemoryProtectFunctions::gs_memprot_realloc<MEM_THRD>,
MemoryProtectFunctions::gs_posix_memalign<MEM_THRD>};

MemoryProtectFuncDef SessionFunctions = {MemoryProtectFunctions::gs_memprot_malloc<MEM_SESS>,
MemoryProtectFunctions::gs_memprot_free<MEM_SESS>,
MemoryProtectFunctions::gs_memprot_realloc<MEM_SESS>,
MemoryProtectFunctions::gs_posix_memalign<MEM_SESS>};

MemoryProtectFuncDef SharedFunctions = {MemoryProtectFunctions::gs_memprot_malloc<MEM_SHRD>,
MemoryProtectFunctions::gs_memprot_free<MEM_SHRD>,
MemoryProtectFunctions::gs_memprot_realloc<MEM_SHRD>,
MemoryProtectFunctions::gs_posix_memalign<MEM_SHRD>};

// 内存申请
if (GS_MP_INITED)
block = (AllocBlock)(*func->malloc)(blksize, (value & IS_PROTECT) == 1 ? true : false);
else
gs_malloc(blksize, block, AllocBlock);

可以看到调用的malloc其实是gs_memprot_malloc( src/common/backend/utils/mmgr/memprot.cpp),其中有一个内存类型的模版参数。内存类型用于调整统计指标和预留内存指标。只有通过内存充足检查后,才能调用malloc进行内存的分配。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
template <MemType mem_type>
void* MemoryProtectFunctions::gs_memprot_malloc(Size sz, bool needProtect)

template <MemType type>
bool memTracker_ReserveMem(int64 requestedBytes, bool needProtect)

template <MemType type>
static bool memTracker_ReserveMemChunks(int32 numChunksToReserve, bool needProtect)

// 内存统计
if (type == MEM_SHRD) {
tc = (uint64)shareTrackedBytes >> chunkSizeInBits;
tb = gs_atomic_add_64(&shareTrackedBytes, requestedBytes);
gs_lock_test_and_set(&shareTrackedMemChunks, tc); /* reset the value */
} else if (type == MEM_THRD) {
tc = t_thrd.utils_cxt.trackedMemChunks;
t_thrd.utils_cxt.trackedBytes += requestedBytes;
tb = t_thrd.utils_cxt.trackedBytes;
} else {
tc = u_sess->stat_cxt.trackedMemChunks;
u_sess->stat_cxt.trackedBytes += requestedBytes;
tb = u_sess->stat_cxt.trackedBytes;
}

// 内存预留&&保护(限额)
if (t_thrd.utils_cxt.backend_reserved) {
currSize = &backendUsedMemInChunk;
maxSize = &backendReservedMemInChunk;
} else {
currSize = &processMemInChunks;
maxSize = &maxChunksPerProcess;
}

其中根据 t_thrd.utils_cxt.backend_reserved 来选择 backendReservedMemInChunk 为最大允许的内存。这些变量定义在memprot.cpp文件中,为全局变量。因此进程限额是全局级别的。

1
2
3
4
5
6
7
int32 maxChunksPerProcess = 0;   // be set by GUC variable --max_dynamic_memory
volatile int32 processMemInChunks = 200; // track the memory used by process --dynamic_used_memory
int32 peakChunksPerProcess = 0; // the peak memory of process --dynamic_peak_memory
int32 comm_original_memory = 0; // original comm memory
int32 maxSharedMemory = 0; // original shared memory
int32 backendReservedMemInChunk = 0; // reserved memory for backend threads
volatile int32 backendUsedMemInChunk = 0; // the memory usage for backend threads

再看这个t_thrd.utils_cxt.backend_reserved 是何时设置的,何时更改的。

t_thrd.utils_cxt.backend_reserved

总结:在线程启动时,进行了是否启用预留内存的设置。并开启内存保护的审查机制,然后在上下文初始化函数中,初始化thrd的内存统计。

1
2
3
4
5
6
7
8
9
template <knl_thread_role thread_role>
int GaussDbThreadMain(knl_thread_arg* arg)
{
...
/* Check this thread will use reserved memory or not */
is_memory_backend_reserved(arg);
/* Initialize the Memory Protection at the thread level */
gs_memprot_thread_init();
MemoryContextInit();

其中的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
static void is_memory_backend_reserved(const knl_thread_arg* arg)
{
if (arg->role == WORKER) {
Port port = ((BackendParameters*)(arg->save_para))->port;
if (processMemInChunks >= maxChunksPerProcess * 0.8 &&
IsHAPort(&port)) {
t_thrd.utils_cxt.backend_reserved = true;
} else {
t_thrd.utils_cxt.backend_reserved = false;
}
return;
}

switch (arg->role) {
case WALWRITER:
case WALRECEIVER:
case WALRECWRITE:
case DATARECIVER:
case DATARECWRITER:
t_thrd.utils_cxt.backend_reserved = true;
break;
default:
t_thrd.utils_cxt.backend_reserved = false;
break;
}
}

/* thread level initialization */
void gs_memprot_thread_init(void)
{
/* The Process level protection has been triggered */
if (maxChunksPerProcess) {
t_thrd.utils_cxt.gs_mp_inited = true;
}
}

void MemoryContextInit(void)
{
CStoreMemAlloc::Init();
if (TopMemoryContext != NULL) {
return;
}

/* init the thread memory track object */
t_thrd.utils_cxt.trackedMemChunks = 0;
t_thrd.utils_cxt.trackedBytes = 0;
t_thrd.utils_cxt.peakedBytesInQueryLifeCycle = 0;
t_thrd.utils_cxt.basedBytesInQueryLifeCycle = 0;
}

backendReservedMemInChunk

总结:在gs_memprot_init中计算可用的总内存avail_mem。 然后以chunk为单位,将总内存分为backendReservedMemInChunk和maxChunksPerProcess两部分。

1
2
3
4
5
6
/* process level initialization only called in postmaster main thread */
void gs_memprot_init(Size size)

void gs_memprot_reserved_backend(int avail_mem)
backendReservedMemInChunk = reserved_mem;
maxChunksPerProcess = ((unsigned int)avail_mem >> BITS_IN_KB) - reserved_mem;

内存追踪

有哈希表

1
2
3
4
5
6
7
8
9
10
void RemoveTrackMemoryInfo(const void* pointer)
{
uint64 key = (uint64)pointer;

if (g_instance.stat_cxt.track_memory_inited == false) {
return;
}

(void)hash_search(g_instance.stat_cxt.track_memory_info_hash, &key, HASH_REMOVE, NULL);
}

暂不清楚这个哈希表的作用。

内存视图

1
2
3
4
5
6
7
Datum pv_session_memory_detail(PG_FUNCTION_ARGS)
g_threadPoolControler->GetSessionCtrl()->getSessionMemoryDetail(rsinfo->setResult, rsinfo->setDesc, &sess);

Datum pv_thread_memory_detail(PG_FUNCTION_ARGS)
getThreadMemoryDetail(rsinfo->setResult, rsinfo->setDesc, &procIdx);

Datum pv_total_memory_detail(PG_FUNCTION_ARGS)
作者

Desirer

发布于

2024-11-04

更新于

2024-11-15

许可协议