OpenGauss线程管理-AIO完成器线程- aiocompleter
aiocompleter 线程使用 Linux Native AIO 完成 AIO 请求。单个 AIO completer线程在与特定 AIO 上下文和 I/O 优先级关联的 AIO 队列上提供服务。
术语解释
- 崩溃:崩溃指计算机或程序异常终止的事件。出现错误后,通常会自动退出。有时出现恶意程序冻结或挂起直到崩溃上报服务记录崩溃的详细信息。对于操作系统内核关键部分的程序,整个计算机可能瘫痪(可能造成致命的系统错误)。
- AIO:(Asynchronous Input/Output异步输入/输出)所有的集成在一个里面(All In One)。异步输入/输出是任何特殊输入/输出流(同步和异步输入/输出)两个基本操作模式之一。在 AIO 的工作模式下,应用程序发起(call)I/O请求(读 / 写)系统调用以后,内核不必等 I/O 完成,即可返回(此时数据区不能释放或修改,需要等访问确认完成),应用程序可以发起新的 I/O 请求,使得应用的单一线程就可以同时发起多个IO请求。通过这种方法,可以提示提升 I/O 吞吐量和性能。
- Linux Native AIO:(Linux 原生 AIO )Linux Native AIO 是 Linux 支持的原生 AIO,因为Linux存在很多第三方的异步 IO 库,如 libeio 和 glibc AIO。所以为了加以区别,Linux 的内核提供的异步 IO 就称为原生异步 IO。
- PG:PG 一般是指的编程调试用的那台计算机。
异步I/O、同步I/O、Linux 原生 AIO
aiocompleter线程
路径: openGauss-server/src/gausskernel/process/postmaster/aiocompleter.cpp
AIO 完成器线程完成预取(Prefetch )和后端写入(BackWrite )I/O操作,因此只有在所有工作线程或后台数据写( bgwrite) 线程都已停止后,它们才会停止。
AIO 完成器线程由postmaster在启动子进程完成后立即启动,或者,如果我们正在执行存档恢复,则在恢复开始时立即启动。 他们一直活着,直到postmaster命令他们终止。 正常终止由 SIGTERM 完成,它指示线程等待任何挂起的 AIO 并准备退出。通过出口 (0)。 紧急终止由 SIGQUIT 进行。
如果完成器线程意外退出,则postmaster会将其视为与后端崩溃相同的情况:共享内存可能已损坏,因此 SIGQUIT 应终止剩余的后端,然后启动恢复周期。
在主线程中的使用
aiocompleter线程先在PostmasterMain中初始化adio使用的资源,然后在SeverLoop中启动
int PostmasterMain(int argc, char* argv[]){
......
#ifndef ENABLE_LITE_MODE
if (g_instance.attr.attr_storage.enable_adio_function)
AioResourceInitialize();//初始化adio使用的资源
#endif
......
}
start int SeverLoop(void){
......
/*
* 如果 AioCompleters 尚未启动,请启动它们。 这些应该无限期地运行。
*/
ADIO_RUN()
{
if (!g_instance.pid_cxt.AioCompleterStarted && !dummyStandbyMode) {
int aioStartErr = 0;
if ((aioStartErr = AioCompltrStart()) == 0) {
g_instance.pid_cxt.AioCompleterStarted = 1;
} else {
ereport(LOG, (errmsg_internal("Cannot start AIO completer threads error=%d", aioStartErr)));
/*
* 如果我们未能 fork aio 进程,只需关闭即可。
* 任何需要的清理都将在下次重新启动时进行。
* 我们设置 g_instance.fatal_error 以便在我们退出时记录“异常关闭”消息。
*/
g_instance.fatal_error = true;
HandleChildCrash(g_instance.pid_cxt.AioCompleterStarted, 1, "AIO process");
}
}
}
ADIO_END();
......
}
代码理解
每个 AIO 完成器线程都有一个唯一的上下文,并可能处理不同类型的请求。 每个完成器线程都有一个aioCompltrThread_t结构,位于compltrArray中。compltrArray 包含 MAX_AIOCOMPLTR_THREADS 插槽。compltrArray 是在postmaster上下文中定义的。前三个参数上下文,eventsp和tid是在线程启动时设置的,它们对于每个线程都是唯一的。cmpltrDesc 指针指向包含完成器参数的 compltrDescArray 中的AioCompltrDesc_t结构。相同类型的完成器使用相同的描述符。compltrDescArray 为每个 AioCompltrType NUM_AIOCOMPLTR_TYPES AioCompltrDesc_t结构一个。compltrDescArray 数组是在postmaster上下文中定义的。
AioCompltrMain——AIO Completer 线程的主入口点
void AioCompltrMain(int ac, char** av)
{
if (ac < 4) {
ereport(WARNING, (errmsg("invalid AIO argument num:%d", ac)));
exit(1);
}
/* compltrIdx 标识此线程。 */
int compltrIdx = atoi(av[3]);
/*
* compltrArray 中完成者描述符的全局线程本地快捷方式,这些是在入口处分配的。
*/
io_context_t context = compltrArray[compltrIdx].context;
io_event* eventsp = compltrArray[compltrIdx].eventsp;
AioCompltrDesc_t* compltrDescp = compltrArray[compltrIdx].compltrDescp;
int min_nr = compltrDescp->min_nr;
int max_nr = compltrDescp->max_nr;
struct timespec timeout;
struct timespec shutdown_timeout;
timeout.tv_sec = compltrDescp->timeout;
timeout.tv_nsec = 0;
shutdown_timeout.tv_sec = AioCompltrShutdownTimeout;
shutdown_timeout.tv_nsec = 0;
AioCallback_t callback = compltrDescp->callback;
/*
* postmaster可能发送给我们的信号
* SIGTERM 使线程准备退出
* SIGQUIT 导致立即退出而不进行清理。
* SIGUSR1 目前未使用 - 保留供将来使用。
*/
(void)gspqsignal(SIGHUP, CompltrConfig); /* 检索配置 */
(void)gspqsignal(SIGINT, SIG_IGN);
(void)gspqsignal(SIGTERM, CompltrShutdown); /* 关闭 */
(void)gspqsignal(SIGQUIT, CompltrQuickDie); /* 硬崩溃时间 */
(void)gspqsignal(SIGALRM, SIG_IGN);
(void)gspqsignal(SIGPIPE, SIG_IGN);
(void)gspqsignal(SIGUSR1, SIG_IGN); /* 预订的 */
(void)gspqsignal(SIGUSR2, SIG_IGN);
/*
* 重置一些postmaster接受但我们不需要的信号。
*/
(void)gspqsignal(SIGCHLD, SIG_DFL);
(void)gspqsignal(SIGTTIN, SIG_DFL);
(void)gspqsignal(SIGTTOU, SIG_DFL);
(void)gspqsignal(SIGCONT, SIG_DFL);
(void)gspqsignal(SIGWINCH, SIG_DFL);
/* 我们始终允许 SIGQUIT (quickdie) */
sigdelset(&t_thrd.libpq_cxt.BlockSig, SIGQUIT);
/* 创建资源所有者来跟踪我们的资源(缓冲引脚等...
*
* 如果我们在这里分配内存,请创建一个内存上下文......
*
* 如果我们不想死,请处理来自 ereport、elog 之类的异常......
//AioCompltrMain() 可能需要更好的异常处理?
*/
/*
* 解除封锁信号(当postmaster fork我们时被封锁)
*/
gs_signal_setmask(&t_thrd.libpq_cxt.UnBlockSig, NULL);
(void)gs_signal_unblock_sigusr2();
/* 宣布 Completer 已经启动 */
ereport(LOG, (errmsg("AIO Completer %d STARTED.", compltrIdx)));
for (;;) {
int eventsReceived;
/*
* 重新加载配置 - 如果请求。
*/
if (t_thrd.aio_cxt.config_requested) {
/* 禁用配置请求,现在不是这个时间。 ProcessConfigFile PGC_SIGHUP; */
t_thrd.aio_cxt.config_requested = false;
}
/*
* 如果请求关闭,则设置 shutdown_pending 并在中断时重新启动 io_getevents()。
* io_getevents() 超时减少到 shutdown_timeout 以允许线程在时间到来时快速退出。
* 一旦请求关闭,就无法返回。
*/
if (t_thrd.aio_cxt.shutdown_requested) {
timeout = shutdown_timeout;
ereport(LOG, (errmsg("AIO Completer %d EXITED.", compltrIdx)));
proc_exit(0);
}
/*
* 等待一些 AIO 请求在给定的上下文中完成。 如果系统调用中断,请重试。
*/
eventsReceived = io_getevents(context, min_nr, max_nr, eventsp, &timeout);
/*
* 如果 io_getevents() 被中断,请借此机会检查待处理的请求。
* 然后重新启动 io_getevents() 调用。
*/
if (eventsReceived == -EINTR) {
continue;
}
/*
* io_getevents() 将错误报告为负值。
*/
if (eventsReceived < 0) {
/* 报告错误 */
ereport(PANIC, (errmsg("AIO Completer io_getevents() failed: error %d .", eventsReceived)));
}
Assert(eventsReceived <= max_nr);
/*
* 为每个返回的事件调用回调我们预计 0 到 max_nr 个请求。
* 这里的 obj 是 I/O 请求和 db 上下文。
*/
for (struct io_event* eventp = eventsp; eventsReceived--; eventp++) {
callback((void*)eventp->obj, eventp->res);
}
}
ereport(LOG, (errmsg("AIO Completer %d EXITED.", compltrIdx)));
exit(0);
}
其他代码解释
1.完整的aiocompleter线程定义
typedef struct AioCompltrDesc {
/* Completer 特性 */
AioCompltrType reqtype; /* Completer 类型 */
AioCallback_t callback; /* Completer 功能 */
int maxevents; /* AIO 正在进行的最大事件 */
/* Completer 参数 */
int min_nr; /* 等待的最小事件数*/
int max_nr; /* 要检索的最大事件数 */
int timeout; /* Max time to wait */
/* 请求属性 */
AioPriority reqprio; /* 请求优先服务 */
} AioCompltrDesc_t;
typedef struct {
io_context_t context; /* AIO 上下文 */
struct io_event* eventsp; /* 要处理的 AIO 事件 */
ThreadId tid; /* AIO 线程 tid */
AioCompltrDesc_t* compltrDescp; /* Completer 描述符 */
} AioCompltrThread_t;
2.compltrDescArray 包含completer线程不同类型的描述。 这些用于为每个completer线程设置上下文。 每种类型都可能具有一组唯一的参数。
目前,只有completer的功能和优先级有所不同。 所有线程的其余值都相同:reqtype 和callback、maxevents、min_nr 和 max_nr 和 timeout、priority。
AioCompltrDesc_t compltrDescArray[NUM_AIOCOMPLTR_TYPES] = {
/* reqtype, callback, maxevents, min_nr, max_nr, tsec, reqprio */
{PageListPrefetchType, CompltrReadReq, 65536, 1, 16384, 60, HighPri},
{PageListBackWriteType, CompltrWriteReq, 65536, 1, 16384, 60, HighPri},
{CUListPrefetchType, CompltrReadCUReq, 65536, 1, 16384, 60, HighPri},
{CUListWriteType, CompltrWriteCUReq, 65536, 1, 16384, 60, HighPri}};
4.AIO Completer Array 定义了 AIO Completer 线程,它由 postmaster 在启动任何 AIO Completer theads 之前使用 CompltrAioInit 初始化。 数组包含每个完成线程的一个元素。
AioCompltrThread_t compltrArray[MAX_AIOCOMPLTR_THREADS];
5.AioCompltrReady 标志从 postmaster 上下文中设置/清除。 它用于记住 Completer 状态。
static bool volatile AioCompltrReady = false;
6.Compltrfork_exec() 和 AioCompltrStart() 用于启动completer线程。
(1)Compltrfork_exec() 函数
PG 旨在通过命令行和共享内存启动进程并传递参数。 我们不需要在我们的线程实现中这样做,而是现在我们将在命令行上传递 compltrIdx 并允许正在运行的线程在全局上下文中的 compltrArray 中找到它的描述符,而不是改变所有这些。
Compltrfork_exec 格式化 arglist,然后 fork 并执行 AIO Completer 线程。 compltrIdx 被转换为 3 个字符的字符串,因此参数不必由中间的 PG 代码专门处理。
ThreadId Compltrfork_exec(int compltrIdx)
{
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("openGauss current do not support AIO")));
return InvalidTid;
}
(2)AioCompltrStart() 函数
设置 Aio Completer 线程描述符并启动线程。 该函数由 postmaster 在全局上下文中调用以启动所有 Aio completer线程。
/* compltrArray 和 AioCompltrThreads 是控制 AIO 完成线程的分配和配置的全局变量。*/
/* AioCompltrThreads 是 AIO completer线程的数量。*/
/*
* 每个 Completer 线程在 compltrArray 中都有一个唯一的 AioCompltrThread_t 上下文。
* 每个 Completer 引用一个 compltrDescp,其中包含 Completer 服务器请求类型的参数。
* 每个 Completer 上下文还包含一个 AIO 上下文和事件数组。
*/
int AioCompltrStart(void)
{
int error = 0;
int try_times = 0;
/*
* 只允许 MAX_AIOCOMPLTR_THREADS
*/
if (AioCompltrThreads > MAX_AIOCOMPLTR_THREADS) {
error = 1;
return error;
}
errno_t rc = memset_s(&compltrArray,
sizeof(AioCompltrThread_t) * MAX_AIOCOMPLTR_THREADS,
0,
sizeof(AioCompltrThread_t) * MAX_AIOCOMPLTR_THREADS);
securec_check(rc, "\0", "\0");
/*
* 初始化 compltrArray
*/
for (int i = 0; i < AioCompltrThreads; i++) {
try_times = 0;
/* 将模板分配给线程描述符 */
compltrArray[i].compltrDescp = AIOCOMPLTR_TEMPLATE(i);
/* 创建 i/o 队列并填写上下文 */
do {
error = io_setup(compltrArray[i].compltrDescp->maxevents, &compltrArray[i].context);
if (error == 0 || error != -EAGAIN) {
break;
}
try_times++;
ereport(LOG, (errmsg("AIO Startup, Completer thread id =%d try times=%d, error=%d", i, try_times, error)));
pg_usleep(100000L);
} while (try_times < 5);
if (error != 0) {
goto AioCompltrStartError;
}
/* 为线程分配事件数组 */
compltrArray[i].eventsp = (io_event*)malloc(compltrArray[i].compltrDescp->max_nr * sizeof(struct io_event));
if (compltrArray[i].eventsp == (struct io_event*)NULL) {
/* malloc 由于某种原因失败 */
error = 2;
ereport(LOG,
(errmsg("AIO Startup malloc io_event failed: max_nr(%d), %d",
compltrArray[i].compltrDescp->max_nr,
error)));
goto AioCompltrStartError;
}
/* 启动 AIO Completer 线程*/
compltrArray[i].tid = Compltrfork_exec(i);
if (compltrArray[i].tid == (ThreadId)-1) {
/* 启动线程失败 */
error = 3;
ereport(LOG, (errmsg("Start AIO Completer thread failed: %d", error)));
goto AioCompltrStartError;
}
};
/* AIO Completers 开始营业 */
AioCompltrReady = true;
/* 成功返回 */
return 0;
AioCompltrStartError:
/*
* 如果出现任何问题,则停止任何已启动的线程并释放资源。
*/
AioCompltrStop(SIGTERM);
ereport(LOG, (errmsg("AIO Startup Failed,error=%d", error)));
return error;
}
7.AioCompltrStop函数——停止 Completer 线程,清理任何部分启动的线程。
发送 SIGQUIT 并忘记线程。 调用者必须确保在使用此函数之前没有 AIO 正在进行。
void AioCompltrStop(int signal)
{
gs_thread_t thread;
AioCompltrReady = false;
/*
* 停止 compltrArray 中的线程。
*/
for (int i = 0; i < AioCompltrThreads; i++) {
/*
* 停止已启动的线程
*/
if (compltrArray[i].tid != 0) {
if (gs_signal_send(compltrArray[i].tid, signal) < 0) {
ereport(LOG, (errmsg("kill(%ld,%d) failed: %m", (long)(compltrArray[i].tid), signal)));
}
}
}
if (signal == SIGQUIT) {
/*
* 如果数据库崩溃,只需杀死completer并救助。
*/
return;
}
/*
* 等待停止的线程退出。
*/
for (int i = 0; i < AioCompltrThreads; i++) {
/*
* 等待被杀死的线程退出
*/
if (compltrArray[i].tid != 0) {
thread.thid = compltrArray[i].tid;
if (gs_thread_join(thread, NULL) != 0) {
/*
* 如果线程不存在,则将其视为正常退出,我们继续进行清理工作。
* 否则,我们将其视为崩溃,因为我们不知道线程的当前状态,
* 最好直接退出哪个更安全。
*/
if (ESRCH == pthread_kill(thread.thid, 0))
ereport(LOG, (errmsg("failed to join thread %lu, no such process", thread.thid)));
else
HandleChildCrash(thread.thid, 1, "AIO process");
}
compltrArray[i].tid = (pid_t)0;
}
}
/*
* 释放它们的上下文和事件数组,如果有的话
*/
for (int i = 0; i < AioCompltrThreads; i++) {
compltrArray[i].compltrDescp = (AioCompltrDesc_t*)NULL;
/* 销毁 AIO 上下文 */
if (compltrArray[i].context) {
io_destroy(compltrArray[i].context);
compltrArray[i].context = (io_context_t)NULL;
}
/* 释放事件数组 */
if (compltrArray[i].eventsp) {
free(compltrArray[i].eventsp);
compltrArray[i].eventsp = (struct io_event*)NULL;
}
}
/* 成功返回 */
return;
}
8.其他函数功能
函数 | 功能 |
---|---|
static void CompltrConfig(SIGNAL_ARGS) | 用于配置的信号处理程序,现在未使用 |
static void CompltrQuickDie(SIGNAL_ARGS) | 在postmaster发出 SIGQUIT 信号时发生 |
static void CompltrShutdown(SIGNAL_ARGS) | 在邮局主管发出 SIGTERM 信号时发生 |
void AioResourceInitialize(void) | 初始化 adio 使用的资源 |