一. mysqld的信号处理
mysqld的信号处理部分分为两大部分逻辑:
- 信号处理初始化:my_init_signals();
- 信号处理工作线程的创建:start_signal_handler();
说白就是把将对信号的异步处理转换成同步处理,也就是用一个专门的线程来“同步地等待”信号的到来并进行处理,而其它的线程可以完全不被该信号中断/打断(interrupt)。这样就在相当程度上简化了在多线程环境中对信号的处理。可以保证主要的线程不受信号的影响。这样整个进程对信号就可以完全预测,因为它不再是异步的,而是同步的-----完全知道信号会在哪个线程中的哪个执行点到来及处理多线程中使用信号机制 pthread_sigmask()_JoJo的博客-CSDN博客。
二. 信号处理初始化
以mysql 5.7.26为例,在mysqld_main()主函数中,my_init_signals()函数负责信号处理的初始化。此时此刻,mysqld刚刚启动进行初始化,还只有一个主线程在工作,其他线程都还没启动。下面就是对该函数进行精简后进行分析。
致命信号
SIGSEGV,SIGABRT,SIGBUS,SIGILL和SIGFPE这五个信号被mysqld视为致命(fatal)信号,这些信号是可以被捕捉的。为这五个信号设置处理方法handle_fatal_signal,该方法会把相关信息输出到stderr并产生core文件,因此这五个信号无需考虑如何处理,mysqld直接core掉。
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++mysqld.cc
void my_init_signals()
{
struct sigaction sa;
(void) sigemptyset(&sa.sa_mask);
if (!(test_flags & TEST_NO_STACKTRACE) || (test_flags & TEST_CORE_ON_SIGNAL))
{
/*
SA_RESETHAND resets handler action to default when entering handler.
SA_NODEFER allows receiving the same signal during handler.
E.g. SIGABRT during our signal handler will dump core (default action).
*/
sa.sa_flags= SA_RESETHAND | SA_NODEFER;
sa.sa_handler= handle_fatal_signal;
// Treat all these as fatal and handle them.
(void) sigaction(SIGSEGV, &sa, NULL);
(void) sigaction(SIGABRT, &sa, NULL);
(void) sigaction(SIGBUS, &sa, NULL);
(void) sigaction(SIGILL, &sa, NULL);
(void) sigaction(SIGFPE, &sa, NULL);
}
SA_RESETHAND:当调用信号处理函数时,将信号的处理函数重置为缺省值SIG_DFL。
SA_NODEFER:默认情况下当信号处理函数运行时,内核将阻塞该给定信号, 但是如果设置了SA_NODEFER,那么在该信号处理函数运行时,内核将不阻塞该信号。
handle_fatal_signal实际是my_write_core的wrapper,my_write_core将以上五个信号通过signal(2)恢复成SIG_DFL后,通过pthread_kill杀死my_thread_self()进程。
考虑到这五个信号的linux默认处理方式就是core掉,因此msyql捕捉这些信号只是为了单纯的在core之前多输出一些日志而已。
忽视SIGPIPE和SIGALRM信号
当client端提前关闭了连接,当mysqld向client发送信息时就会收到SIGPIPE信号,这种情况mysqld端可以直接忽略是OK的;
初始时忽略SIGALRM信号。因为my_lock使用了alarm系统调用,这里先忽略SIGALRM,而每次my_lock调用时通过signal为SIGALRM注册一次性的handler;
// Ignore SIGPIPE and SIGALRM
sa.sa_flags= 0;
sa.sa_handler= SIG_IGN;
(void) sigaction(SIGPIPE, &sa, NULL);
(void) sigaction(SIGALRM, &sa, NULL);
// SIGUSR1 is used to interrupt the socket listener.
sa.sa_handler= empty_signal_handler;
(void) sigaction(SIGUSR1, &sa, NULL);
// Fix signals if ignored by parents (can happen on Mac OS X).
sa.sa_handler= SIG_DFL;
(void) sigaction(SIGTERM, &sa, NULL);
(void) sigaction(SIGHUP, &sa, NULL);
sigset_t set;
(void) sigemptyset(&set);
/*
Block SIGQUIT, SIGHUP and SIGTERM.
The signal handler thread does sigwait() on these.
*/
(void) sigaddset(&set, SIGQUIT);
(void) sigaddset(&set, SIGHUP);
(void) sigaddset(&set, SIGTERM);
(void) sigaddset(&set, SIGTSTP);
/*
Block SIGINT unless debugging to prevent Ctrl+C from causing
unclean shutdown of the server.
*/
if (!(test_flags & TEST_SIGINT))
(void) sigaddset(&set, SIGINT);
pthread_sigmask(SIG_SETMASK, &set, NULL); // 屏蔽掉set里的信号
DBUG_VOID_RETURN;
}
SIGUSR1打断socket listener的阻塞
在mysqld退出逻辑中,会向主线程发送SIGUSR1以中断socket监听。除了上述致命信号,其他大部分信号都被专门的信号处理函数来处理或者直接被忽略,因此当进程退出时需要一个信号来打断主线程正在进行的套接字listen。实际这个empty_signal_handler方法什么也没做直接退出,达到了打断目标线程的目的。
正常的退出信号
SIGQUIT,SIGHUP,SIGTERM和SIGSTP被认为是正常的退出信号,这些信号当前被屏蔽,这一策略会被后续所有子线程继承,但是后续会看到主线程会启动的专门的子线程来sigwait并处理这些信号,通过这种方法把信号进行同步处理。当然最终实际上子线程只处理了前三个信号,SIGSTP依然被屏蔽。
三. 信号处理线程
如前所示,有一大部分信号需要一个专们的信号处理线程来处理。mysqld主线程的mysqld_main会在执行完信号系统初始化后不久再通过start_signal_handler()方法启动这个信号处理线程。
实际thread_create中调用的子线程执行handler是signal_hand()函数。从如下简化代码看线程处理了SIGQUIT,SIGHUP和SIGTERM信号。
- SIGQUIT,SIGTERM:标记abort_loop标记,向主线程发送SIGUSR1信号,如前所述这会打断主线程对套接字的监听;关闭所有连接;
- SIGHUP:输出debug info并flush日志文件;
/** This thread handles SIGTERM, SIGQUIT and SIGHUP signals. */
/* ARGSUSED */
extern "C" void *signal_hand(void *arg MY_ATTRIBUTE((unused)))
{
sigset_t set;
(void) sigemptyset(&set);
(void) sigaddset(&set, SIGTERM);
(void) sigaddset(&set, SIGQUIT);
(void) sigaddset(&set, SIGHUP);
for (;;)
{
int sig;
while (sigwait(&set, &sig) == EINTR)
{}
if (cleanup_done)
{
my_thread_end();
my_thread_exit(0); // Safety
return NULL; // Avoid compiler warnings
}
switch (sig) {
case SIGTERM:
case SIGQUIT:
if (!abort_loop)
{
abort_loop= true; // Mark abort for threads.
/*
Kill the socket listener.
The main thread will then set socket_listener_active= false,
and wait for us to finish all the cleanup below.
*/
while (socket_listener_active)
{
DBUG_PRINT("info",("Killing socket listener"));
if (pthread_kill(main_thread_id, SIGUSR1))
{
break;
}
}
close_connections();
}
my_thread_end();
my_thread_exit(0);
return NULL; // Avoid compiler warnings
break;
case SIGHUP:
if (!abort_loop)
{
int not_used;
mysql_print_status(); // Print some debug info
reload_acl_and_cache(NULL,
(REFRESH_LOG | REFRESH_TABLES | REFRESH_FAST |
REFRESH_GRANT | REFRESH_THREADS | REFRESH_HOSTS),
NULL, ¬_used); // Flush logs
}
break;
default:
break; /* purecov: tested */
}
}
return NULL; /* purecov: deadcode */
}
因此,A. 主/工作线程;B.信号处理线程;两类线程对所有信号的处理就分为如下图五种情况了。