openGauss数据库源码解析(三)| 公共组件源码解析(7)
3.7 模拟信号机制
信号是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内容。相关代码如下。
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;