---
title: openGauss数据库源码解析系列文章——公共组件源码解析（下）-官方技术文章-鲲鹏社区
description: 上一篇文章，从系统表、数据库初始化、多线程架构及线程池技术四个方面对公共组件的源代码实现进行介绍。本篇接着从内存管理、多维检测、模拟信号机制展开介绍。
keywords: Gauss,数据库源码解,析系列文章,公共组件源码,解析,官方技术文章,鲲鹏社区,五
url: https://www.hikunpeng.com/developer/techArticles/20251121-6?envFlag=1
section: (其他)
---

# openGauss数据库源码解析系列文章——公共组件源码解析（下）-官方技术文章-鲲鹏社区

URL: https://www.hikunpeng.com/developer/techArticles/20251121-6?envFlag=1
描述: 上一篇文章，从系统表、数据库初始化、多线程架构及线程池技术四个方面对公共组件的源代码实现进行介绍。本篇接着从内存管理、多维检测、模拟信号机制展开介绍。
关键词: Gauss,数据库源码解,析系列文章,公共组件源码,解析,官方技术文章,鲲鹏社区,五

官方技术文章 [了解详情](https://www.hikunpeng.com/zh/developer/techArticles)

openGauss数据库源码解析系列文章——公共组件源码解析（下）

openGauss数据库源码解析系列文章——公共组件源码解析（下）

openGauss

发表于 2025/11/21

0

## 五、内存管理

数据库在运行过程中涉及许多对象，这些对象具有不同的生命周期，有些处理需要频繁分配内存。如一个SQL语句，在解析时需要对词法单元和语法单元分配内存，在执行过程中需要对执行状态分配内存。在事务结束时，如果不是prepare语句，那么SQL语句的执行计划内存和执行过程的状态内存都需要释放。如果是prepare语句，那么执行计划需要保存到缓冲池中，执行过程的状态内存释放即可。为了保证内存分配的高效和避免内存泄漏，openGauss设计开发了自己的内存管理，代码实现在“openGauss-server\src\common\backend\utils\mmgr”目录。

openGauss在内存管理上采用了上下文的概念，即具有同样生命周期或者属于同一个上下文语义的内存放到一个MemoryContext管理，MemoryContext的结构代码如下（结构成员参照注释）：

```text
typedef struct MemoryContextData* MemoryContext;
typedef struct MemoryContextData {
    NodeTag type;                  /* 上下文类别*/
    MemoryContextMethods* methods; /* 虛函数表*/
    MemoryContext parent;       /* 父上下文。顶级上下文为 NULL*/
    MemoryContext firstchild;      /* 子上下文的链表头*/
    MemoryContext prevchild;       /* 前向子上下文 */
    MemoryContext nextchild;       /* 后向子上下文 */
    char* name;                    /* 上下文名称，方便调试 */
    pthread_rwlock_t lock;         /*上下文共享时的并发控制锁 */
    bool is_shared;                /* 上下文是否在多个线程共享 */
    bool isReset;            /* isReset为true时，表示复位后没有内存空间用于分配*/
    int level;                     /* 上下文层次级别*/
    uint64 session_id;             /* 上下文属于的会话ID */
    ThreadId thread_id;            /* 上下文属于的线程ID */
} MemoryContextData;
```

虛函数表就是具体的内存管理操作函数指针，具体定义代码如下（函数功能参照注释）：

```text
typedef struct MemoryContextMethods {
/*在上下文中分配内存*/
    void* (*alloc)(MemoryContext context, Size align, Size size, const char* file, int line);
    /* 释放pointer 内存到上下文中*/
void (*free_p)(MemoryContext context, void* pointer);
/*在上下文中重新分配内存*/
void* (*realloc)(MemoryContext context, void* pointer, Size align, Size size, const char* file, int line);
    void (*init)(MemoryContext context);    /*上下文初始化*/
    void (*reset)(MemoryContext context);   /*上下文复位*/
    void (*delete_context)(MemoryContext context);   /*删除上下文 */
    Size (*get_chunk_space)(MemoryContext context, void* pointer); /*获取上下文块大小 */
    bool (*is_empty)(MemoryContext context);  /*上下文是否为空*/
    void (*stats)(MemoryContext context, int level); /*上下文信息统计*/
#ifdef MEMORY_CONTEXT_CHECKING
    void (*check)(MemoryContext context); /*上下文异常检查*/
#endif
} MemoryContextMethods;
```

这些回调函数指针初始化是在AllocSetContextSetMethods函数中调用AllocSetMethodDefinition函数完成的。AllocSetMethodDefinition函数的实现代码如下：

```text
template <bool enable_memoryprotect, bool is_shared, bool is_traked>
void AlignMemoryAllocator::AllocSetMethodDefinition(MemoryContextMethods* method)
{
    method->alloc = &AlignMemoryAllocator::AllocSetAlloc<enable_memoryprotect, is_shared, is_traked>;
    method->free_p = &AlignMemoryAllocator::AllocSetFree<enable_memoryprotect, is_shared, is_traked>;
    method->realloc = &AlignMemoryAllocator::AllocSetRealloc<enable_memoryprotect, is_shared, is_traked>;
    method->init = &AlignMemoryAllocator::AllocSetInit;
    method->reset = &AlignMemoryAllocator::AllocSetReset<enable_memoryprotect, is_shared, is_traked>;
    method->delete_context = &AlignMemoryAllocator::AllocSetDelete<enable_memoryprotect, is_shared, is_traked>;
    method->get_chunk_space = &AlignMemoryAllocator::AllocSetGetChunkSpace;
    method->is_empty = &AlignMemoryAllocator::AllocSetIsEmpty;
    method->stats = &AlignMemoryAllocator::AllocSetStats;
#ifdef MEMORY_CONTEXT_CHECKING
    method->check = &AlignMemoryAllocator::AllocSetCheck;
#endif
}
```

可以看到，这些实际操作内存管理的函数为AlignMemoryAllocator类中的AllocSetAlloc函数、AllocSetFree函数、AllocSetRealloc函数、AllocSetInit函数、AllocSetReset函数、AllocSetDelete函数、AllocSetGetChunkSpace函数、AllocSetIsEmpty函数、AllocSetStats函数和AllocSetCheck函数。在这些处理函数中，涉及的结构体代码如下：

```text
typedef AllocSetContext* AllocSet;
typedef struct AllocSetContext {
    MemoryContextData header; /*内存上下文，存储空间是在这个内存上下文中分配的 */
     AllocBlock blocks;  /* AllocSetContext所管理内存块的块链表头 */
    AllocChunk freelist[ALLOCSET_NUM_FREELISTS]; /* 空闲块链表*/
    /*这个上下文的分配参数 */
    Size initBlockSize;   /* 初始块大小*/
    Size maxBlockSize;    /* 最大块大小 */
    Size nextBlockSize;   /* 下一个分配的块大小 */
    Size allocChunkLimit; /* 块大小上限*/
    AllocBlock keeper;    /* 在复位时，保存的块 */
    Size totalSpace;      /* 这个上下文分配的总空间 */
    Size freeSpace;       /* 这个上下文总的空闲空间 */
    Size maxSpaceSize;   /* 最大内存空间 */

} AllocSetContext;
AllocBlock定义如下：
typedef struct AllocBlockData* AllocBlock;
typedef struct AllocBlockData {
    AllocSet aset;   /* 哪个AllocSetContext 拥有此块，AllocBlockData 归属AllocSetContext管理*/
    AllocBlock prev; /* 在块链表中的前向指针 */
    AllocBlock next; /* 在块链表中的后向指针 */
    char* freeptr;   /* 这个块空闲空间的起始地址 */
    char* endptr;    /* 这个块空间的结束地址*/
    Size allocSize;  /* 分配的大小*/
#ifdef MEMORY_CONTEXT_CHECKING
    uint64 magicNum; /* 魔鬼数字值，用于内存校验。当前代码固定填写为DADA */
#endif
} AllocBlockData;
typedef struct AllocChunkData* AllocChunk; /* AllocChunk 内存前面部分是一个AllocBlock结构*/
typedef struct AllocChunkData {
       void* aset; /* 拥有这个chunk的AllocSetContext，如果空闲，则为空闲列表链接*/
       Size size; /* chunk中的使用空间 */
#ifdef MEMORY_CONTEXT_CHECKING
       Size requested_size; /* 实际请求大小，在空闲块中时为0 */
       const char* file; /* palloc/palloc0调用时的文件名称 */
       int line;         /* palloc/palloc0 调用时的行号*/
       uint32 prenum;    /* 前向魔鬼数字*/
#endif
} AllocChunkData;
```

从前面的数据结构可以看出，核心数据结构为AllocSetContext，这个数据结构有3个成员“MemoryContextData header;”、“AllocBlock blocks;”和“AllocChunk freelist[ALLOCSET_NUM_FREELISTS];”。这3个成员把内存管理分为3个层次。

（1） MemoryContext管理上下文之间的父子关系，设置MemoryContext的内存管理函数。

（2） AllocBlock blocks把所有内存块通过双链表链接起来。

（3） 具体的内存单元chunk。内存单元chunk是从内存块AllocBlock内部分配的，内存块和内存单元chunk的转换关系为：“AllocChunk chunk = (AllocChunk)(((char*)block) + ALLOC_BLOCKHDRSZ);”和“AllocBlock block = (AllocBlock)(((char*)chunk) - ALLOC_BLOCKHDRSZ);”。

内存单元chunk经过转换得到最终的用户指针，内存单元chunk和用户指针的转换关系为：((AllocPointer)(((char*)(chk)) + ALLOC_CHUNKHDRSZ))和((AllocChunk)(((char*)(ptr)) - ALLOC_CHUNKHDRSZ))。数据结构的基本关系如图1所示。

图1 数据结构的基本关系

下面先看第1层MemoryContext的实现，主要实现在mcxt.cpp文件中，如表1所示。

表1 MemoryContext的实现函数

再看第2层AllocSet的实现，主要实现在aset.cpp文件中，如表2所示。

表2 AllocSet的实现函数

## 六、多维检测

数据库是企业的关键组件，数据库的性能直接决定了很多业务的吞吐量。为了简化数据库维护人员的调优，openGauss对数据库运行进行了多维度的检测，并且开发了一些维护特性，比如WDR（wordload dignostic report，工作负荷诊断报告）性能诊断报告、慢SQL诊断、session性能诊断、系统KPI（key performance indicator，关键性能指标）辅助诊断等，帮助维护人员对数据库的性能进行诊断。这些检测项都以视图的方式对外呈现，集中在DBE_PERF模式下。WDR Snapshot除了自身快照的元数据，其他数据表来源也是DBE_PERF schema下的视图。WDR Snapshot数据表命名原则：snap_{源数据表}，根据这个关系可以找到snap表所对应的原表。对这些视图的解释参照openGauss的官网（https://opengauss.org [了解详情](https://link.zhihu.com/?target=https%3A//opengauss.org)）中《开发者指南》手册的“DBE_PERF schema”章节。

性能视图的源代码在“openGauss-server\src\common\backend\catalog\performance_views.sql”文件中（网址为：https://gitee.com/opengauss/openGauss-server/blob/master/src/common/backend/catalog/performance_views.sql [了解详情](https://link.zhihu.com/?target=https%3A//gitee.com/opengauss/openGauss-server/blob/master/src/common/backend/catalog/performance_views.sql)，安装后会复制到安装路径的“/share/postgresql/performance_views.sql”下）。在数据库初始化阶段由initdb读取这个文件在数据库系统中创建相应的视图。这些视图遵循了openGauss通用视图的实现逻辑，即视图来自函数的封装，这些函数可能是内置函数，也可能是存储函数。OS运行的性能视图“dbe_perf.get_global_os_runtime”的相关代码如下：

```text
CREATE OR REPLACE FUNCTION dbe_perf.get_global_os_runtime
  (OUT node_name name, OUT id integer, OUT name text, OUT value numeric, OUT comments text, OUT cumulative boolean)
RETURNS setof record
AS $$
DECLARE
  row_data dbe_perf.os_runtime%rowtype;
query_str := 'SELECT * FROM dbe_perf.os_runtime';
      FOR row_data IN EXECUTE(query_str) LOOP
   ......
    END LOOP;
    return;
  END; $$
LANGUAGE 'plpgsql' NOT FENCED;
CREATE VIEW dbe_perf.global_os_runtime AS
  SELECT DISTINCT * FROM dbe_perf.get_global_os_runtime();
```

global_os_runtime视图来自存储函数get_global_os_runtime的封装，在存储函数内访问“dbe_perf.os_runtime”视图、os_runtime视图的SQL语句为“CREATE VIEW dbe_perf.os_runtime AS SELECT * FROM pv_os_run_info();”。pv_os_run_info是内置函数，而内置函数负责读取数据库系统的检测指标，pv_os_run_info函数的相关代码如下：

```text
Datum pv_os_run_info(PG_FUNCTION_ARGS)
{
    FuncCallContext* func_ctx = NULL;
    /* 判断是不是第一次调用 */
    if (SRF_IS_FIRSTCALL()) {
        MemoryContext old_context;
        TupleDesc tup_desc;
        /* 创建函数上下文 */
        func_ctx = SRF_FIRSTCALL_INIT();
        /*
         * 切换内存上下文到多次调用上下文
         */
        old_context = MemoryContextSwitchTo(func_ctx->multi_call_memory_ctx);
        /* 创建一个包含5列的元组描述模板*/
        tup_desc = CreateTemplateTupleDesc(5, false);
        TupleDescInitEntry(tup_desc, (AttrNumber)1, "id", INT4OID, -1, 0);
        TupleDescInitEntry(tup_desc, (AttrNumber)2, "name", TEXTOID, -1, 0);
        TupleDescInitEntry(tup_desc, (AttrNumber)3, "value", NUMERICOID, -1, 0);
        TupleDescInitEntry(tup_desc, (AttrNumber)4, "comments", TEXTOID, -1, 0);
        TupleDescInitEntry(tup_desc, (AttrNumber)5, "cumulative", BOOLOID, -1, 0);

        /* 填充元组描述模板 */
        func_ctx->tuple_desc = BlessTupleDesc(tup_desc);
        /* 收集系统信息 */
        getCpuNums();
        getCpuTimes();
        getVmStat();
        getTotalMem();
        getOSRunLoad();
        (void)MemoryContextSwitchTo(old_context);
    }

    /*设置函数的上下文，每次函数调用都需要*/
    func_ctx = SRF_PERCALL_SETUP();
    while (func_ctx->call_cntr < TOTAL_OS_RUN_INFO_TYPES) {
        /* 填充所有元组每个字段的值 */
        Datum values[5];
        bool nulls[5] = {false};
        HeapTuple tuple = NULL;
        errno_t rc = 0;
        rc = memset_s(values, sizeof(values), 0, sizeof(values));
        securec_check(rc, "\0", "\0");
        rc = memset_s(nulls, sizeof(nulls), 0, sizeof(nulls));
        securec_check(rc, "\0", "\0");
        if (!u_sess->stat_cxt.osStatDescArray[func_ctx->call_cntr].got) {
            ereport(DEBUG3,
                (errmsg("the %s stat has not got on this plate.",
                   u_sess->stat_cxt.osStatDescArray[func_ctx->call_cntr].name)));
            func_ctx->call_cntr++;
            continue;
        }
        values[0] = Int32GetDatum(func_ctx->call_cntr);
        values[1] = CStringGetTextDatum(u_sess->stat_cxt.osStatDescArray[func_ctx->call_cntr].name);
      values[2] = u_sess->stat_cxt.osStatDescArray[func_ctx->call_cntr].getDatum(
            u_sess->stat_cxt.osStatDataArray[func_ctx->call_cntr]);
    values[3] = CStringGetTextDatum(u_sess->stat_cxt.osStatDescArray[func_ctx->call_cntr].comments);
        values[4] = BoolGetDatum(u_sess->stat_cxt.osStatDescArray[func_ctx->call_cntr].cumulative);

        tuple = heap_form_tuple(func_ctx->tuple_desc, values, nulls);
        SRF_RETURN_NEXT(func_ctx, HeapTupleGetDatum(tuple));
    }
    /* 填充结束，返回结果 */
    SRF_RETURN_DONE(func_ctx);
}
```

pv_os_run_info函数可以分为3段：

（1） 调用CreateTemplateTupleDesc函数和TupleDescInitEntry函数定义元组描述信息。

（2） 调用getCpuNums函数、getCpuTimes函数、getVmStat函数、getTotalMem函数、getOSRunLoad函数收集系统信息。

（3） 把收集的u_sess信息填充到元组数据中，最后返回给调用者。openGauss提供了实现返回结果的通用SQL函数的实现步骤和方法，它们是SRF_IS_FIRSTCALL、SRF_PERCALL_SETUP、SRF_RETURN_NEXT和SRF_RETURN_DONE。从代码可以看出，pv_os_run_info的实现流程也是遵循openGauss通用的SQL函数实现方法。

系统指标的收集来自读取系统信息，对数据库系统中一些模块进行打点（打点就是按照规格采集指定数据，用以记录系统运行的一些关键点）。很多打点集中在两个方面： 事务执行次数和执行时间。从而推断最大时间、最小时间、平均时间。这些比较分散，代码逻辑相对简单，这里不再进行介绍。只需要根据内置函数读取的变量查看这些变量赋值的地方就可以具体的实现位置。

openGauss数据库主要维护特性的实现代码在“openGauss-server\src\gausskernel\cbb\instruments”目录中，比如WDR、SQL百分位计算，这里不再进行介绍。

性能统计对openGauss的正常运行也会带来一定的性能损耗，所以这些特性都有开关控制。具体说明如下。

（1） 等待事件信息实时收集功能的开关为enable_instr_wait。

（2） Unique SQL信息实时收集功能的开关为enable_instr_unique_sql、enable_instr_rt_percentile。

（3） 数据库检测快照功能的开关为enable_wdr_snapshot。

其他功能也都有相应的GUC参数进行调节，根据平常使用的需要，可以打开具体维护项查看系统的运行情况。

## 七、模拟信号机制

信号是Linux进程/线程之间的一种通信机制，向一个进程信号的系统函数是kill，向一个线程信号的系统函数是pthread_kill。在openGauss中既有gs_ctl向openGauss进程的进程间信号，也有openGauss进程中线程间的信号。

信号是一种有限的资源，OS提供的信号有SIGINT、SIGQUIT、SIGTERM、SIGALRM、SIGPIPE、SIGFPE、SIGUSR1、SIGUSR2、SIGCHLD、SIGTTIN、SIGTTOU、SIGXFSZ等。这些信号一般都是系统专用的，每个信号都有专门的用途，比如SIGALRM是系统定时器的通知信号。留给应用的信号主要是SIGUSR1、SIGUSR2。

在系统信号有限的情况下，为了在openGauss中表达不同的丰富的通信语义，openGauss额外增加了新的变量表示具体的语义。openGauss是多线程架构，在同一个进程内如果不同的线程注册了不同的处理函数，则后者会覆盖前者的信号处理。为了不同线程能够注册不同的处理函数，需要自己管理信号对应的注册函数。为了解决这些问题，openGauss实现了信号的模拟机制。信号模拟的基本原理是每个线程注册管理自己的信号处理函数，信号枚举值仍然使用系统的信号值，线程使用自己的变量记录信号和回调函数对应关系。线程之间信号时，先设置变量为具体的信号值，然后使用系统调用pthread_kill信号，线程收到通知后再根据额外的变量表示的具体信号值，回调对应的信号处理函数。

信号处理涉及的数据结构代码如下。每个线程有一个GsSignalSlot结构，保存了线程ID、线程名称和GsSignal结构，而GsSignal结构保存了每个信号对应的处理函数数组和每个线程相关的信号池。而信号池struct SignalPool包括了使用的信号列表和空闲的信号列表，当一个模拟信号到达时，找一个空闲信号GsNode，然后放到使用的列表中。GsNode中存放了信号值结构GsSndSignal sig_data。在GsSndSignal结构中保存了的信号具体值和的线程ID。当需要设置一些额外检查信息时，设置GsSignalCheck内容。相关代码如下。

```text
typedef struct GsSignalSlot {
    ThreadId thread_id;
    char* thread_name;
    GsSignal* gssignal;
} GsSignalSlot;
typedef struct GsSignal {
    gs_sigfunc handlerList[GS_SIGNAL_COUNT];
    sigset_t masksignal;
    SignalPool sig_pool;
    volatile unsigned int bitmapSigProtectFun;
} GsSignal;
typedef struct SignalPool {
    GsNode* free_head; /* 空闲信号列表头部 */
    GsNode* free_tail; /* 空闲信号列表尾部 */
    GsNode* used_head; /* 使用信号列表头部*/
    GsNode* used_tail; /*使用信号列表尾部*/
    int pool_size;     /* 数组列表大小*/
    pthread_mutex_t sigpool_lock;
} SignalPool;
typedef struct GsNode {
    GsSndSignal sig_data;
    struct GsNode* next;
} GsNode;
typedef struct GsSndSignal {
    unsigned int signo;  /* 需要处理的信号*/
    gs_thread_t thread;  /* 信号的线程ID */
    GsSignalCheck check; /* 信号线程需要检查的信息 */
} GsSndSignal;
typedef struct GsSignalCheck {
    GsSignalCheckType check_type;
    uint64 debug_query_id;
    uint64 session_id;
} GsSignalCheck;
```

信号处理几个的主要流程为初始化模拟信号机制、注册信号处理函数、信号和处理信号。具体的处理逻辑如下。

### (1) 初始化模拟信号机制函数gs_signal_slots_init。在gs_signal_slots_init处理函数中完成如下功能。

① 根据传入的槽位个数，分配内存。遍历每个槽位，进行初始化，初始化时调用gs_signal_init函数对每个槽位的GsSignal（GsSignal是openGauss封装的模拟信号结构体，里面包含了信号掩码和信号处理函数等成员）进行初始化。

② 在gs_signal_init函数中，对GsSignal分配内存和初始化，初始化时调用gs_signal_sigpool_init函数对信号池初始化。

③ 在gs_signal_sigpool_init函数中，对信号池进行分配内存和初始化。

### (2) 注册信号处理函数gspqsignal。在gspqsignal处理函数中完成如下功能。

① 调用“gs_signal_register_handler(t_thrd.signal_slot->gssignal, signo, func);”函数把信号对应的处理函数注册到GsSignal中。在注册之前，需要为线程分配一个signal_slot，这个是在gs_signal_startup_siginfo函数中完成的。

② 在gs_signal_startup_siginfo函数中，会调用gs_signal_alloc_slot_for_new_thread函数为线程分配一个signal_slot。该函数的功能是遍历“g_instance.signal_base->slots”，找到一个未使用的slot（thread_id为0表示未使用），然后设置本线程ID和线程名称。

### (3) 信号函数gs_signal_send。在gs_signal_send处理函数中完成如下功能。

① 调用函数gs_signal_find_slot找到要线程所在的GsSignalSlot。

② 调用函数gs_signal_set_signal_by_threadid设置模拟信号。该函数首先检查信号在使用列表中是否已经存在，如果已经存在，则直接返回。如果不存在，则在空闲列表中找到一个空闲GsNode，设置信号值，线程ID，check_type到sig_data中，最后把空闲GsNode移到使用列表中。

③ 调用函数gs_signal_thread_kill信号通知。该函数遍历GsSignalSlot，找到匹配的线程ID，然后调用“gs_signal_thread_kill(thread_id, RES_SIGNAL);”函数给具体线程信号通知。语句“#define RES_SIGNAL SIGUSR2”表示内部统一都使用SIGUSR2通知。

### (4) 处理信号函数gs_signal_handle。在函数gs_signal_handle中完成如下功能。

① 遍历信号池使用列表，找到一个需要处理的信号。

② 找到这个信号对应的信号处理函数。把GsNode移到空闲列表中。

③ 调用gs_signal_handle_check函数检查当前的条件是否仍然满足。如果仍然有效，回调处理函数。

## 八、小结

本章主要介绍了openGauss的一些公共组件机制，每个内容都比较独立。系统表是openGauss数据库的元数据，主要介绍了系统表的定义和syscache访问机制。数据库初始化是数据库安装后的第一步，它负责创建数据库的模板数据库和数据目录。多线程架构是openGauss数据库启动后的运行机制，介绍了主线程的初始化流程、后台线程的启动、各个线程的功能和线程之间的通信机制。线程池技术是解决大并发链接的有效方法，介绍了线程池机制的原理，各个类之间的关系和设计原因。内存管理是openGauss的内存资源管理组件，介绍了openGauss的3级内存管理机制。多维检测是openGauss性能调优手段的基础，介绍了性能视图的基本实现原理。模拟信号机制是openGauss多线程处理紧急事件的机制，介绍了这个机制的实现原理。

本页内容
