原创,因为LINUX系统编程水平有限某些用词不当请指出
一、信号处理以及多线程先信号处理基础知识
在LINUX中信号是一种由内核处理的一种软中断机制,他满足简单、不能携带大量信息、并且要满足一定条件才会发送等特征。
信号会经历产生-->阻塞信号集-->未决信号集-->信号递达-->信号处理方式
首先信号的产生可以有多种方式比如我们经常用的kill命名,下面将一些kill 中常用信号列举一下并且给出默认处理方式
(摘取自邢文鹏LINUX讲义)
其次我们经常的按键也可以产生
Ctrl+c 2)SIGINT
Ctrl+\ 3)SIGQUIT
Ctrl+z 4)SIGTSTP
当然还有很多其他触发方式比如硬件异常,raise函数,abort函数,alarm函数等等。
其次是阻塞信号集,阻塞信号集能够对想除(9/19号信号以外)的信号进行屏蔽,如果屏蔽后信号自然不会到达递达状态,也就谈不上
处理了。我们通过sigprocmask函数进行阻塞信号集的设置,但之前必须要设置sigset_t 集合,通过sigaddset sigdelset sigemptyset
sigfillset等函数设置。
未决信号集是不能被操作的,只能被获取通过sigpending函数获取,但是他和阻塞信号集一起可以控制信号的递达
信号递达后就需要处理信号,默认的行为上面都列举了,但是信号(9/19号信号以外)是可以被捕获改变其处理方式的,我们可以自定义函数
作为某个信号的处理方式,这可以通过signal函数和sigaction函数进行捕获和处理,sigaction函数相对复杂需要有一个struct sigaction的
结构体变量,其中包含了sa_handler\sa_mask\sa_flags\sa_siaction 成员,这里不做解释可以自行查看LINUX man page
上面是单进程下的信号处理方式,在多线程下,线程之间公用处理方式,但是可以有不同的信号屏蔽集,在多线程下一般采用设置统一的信号
屏蔽字和信号处理方式使用pthread_mask函数继承到各个线程,同时使用sigwait/sigwaitinfo等函数设置一个单独的信号处理线程来进行统一
处理,MYSQL就是这样处理的。
二、MYSQL中的信号处理
首先我们可以发现MYSQL中有一个单独的signal处理线程
| 36 | 1927 | sql/signal_handler | NULL | BACKGROUND | NULL | NULL |
这个线程对整个MYSQLD进程的信号进行统一的处理,特别是涉及到SIGTERM,SIGQUIT,SIGHUP等信号的处理。
下面从源码触发我们来分析一下
1、信号初始化
void my_init_signals()函数
下面我们将一些关于信号处理的源码放出来进行解释
到这里我们发现MYSQL线程实际上屏蔽了SIGQUIT、SIGHUP、SIGTERM、SIGTSTP
信号,通过一个专门的线程来处理这几个信号。同时很多信号也重新捕获改变了其
处理方式,详见上面解释。
2、信号处理线程
start_signal_handler-->signal_hand 来建立信号处理线程。
我们重点关注signal_hand这个回调函数,下面是一些源码和解释
这里我们看到很多我们关心的东西:
kill/kill -15/kill -SIGTERM做什么:
他们都是一样的都是SIGTERM信号MYSQL关闭所有活跃的连接同时干净的关闭innodb,我通过gdb确实看到了innodb关闭函数的调用,
这很容易我只要断点打到innobase_shutdown_for_mysql(),同时kill mysqldpid即可
kill -3/kill -SIGQUIT做什么:
和上面一样,这在源码中清楚的看到了
kill -1/kill -SIGHUP做什么:
源码解释中说了做
reload_acl_and_cache(NULL,(REFRESH_LOG | REFRESH_TABLES | REFRESH_FAST |
REFRESH_GRANT | REFRESH_THREADS | REFRESH_HOSTS),
NULL, ?_used); // Flush logs
函数调用关于参数2
@param options What should be reset/reloaded (tables, privileges, slave...)
我们可以看到这样的解释,那么
REFRESH_LOG | REFRESH_TABLES | REFRESH_FAST |REFRESH_GRANT | REFRESH_THREADS | REFRESH_HOSTS
代表什么,明显他们是位图方式,他们代表的东西在源码中有解释如下:
他们基本都是自解释的,不用过多描述,那么我们也清楚的明白了SIGHUP在mysqld中被从新定义了,做各种刷新操作而已。
当然大家千万不要直接kill -9这个信号不能被屏蔽也不能被捕获,而是强制终止进程这个时候innodb不可能调用
innobase_shutdown_for_mysql来干净的关闭MYSQLD。
作者微信:
一、信号处理以及多线程先信号处理基础知识
在LINUX中信号是一种由内核处理的一种软中断机制,他满足简单、不能携带大量信息、并且要满足一定条件才会发送等特征。
信号会经历产生-->阻塞信号集-->未决信号集-->信号递达-->信号处理方式
首先信号的产生可以有多种方式比如我们经常用的kill命名,下面将一些kill 中常用信号列举一下并且给出默认处理方式
(摘取自邢文鹏LINUX讲义)
点击(此处)折叠或打开
- 1) SIGHUP:当用户退出shell时,由该shell启动的所有进程将收到这个信号,默认动作为终止进程
- 2)SIGINT:当用户按下了<Ctrl+C>组合键时,用户终端向正在运行中的由该终端启动的程序发出此信号。默认动
- 作为终止里程。
- 3)SIGQUIT:当用户按下<ctrl+\>组合键时产生该信号,用户终端向正在运行中的由该终端启动的程序发出些信
- 号。默认动作为终止进程。
- 4)SIGILL:CPU检测到某进程执行了非法指令。默认动作为终止进程并产生core文件
- 5)SIGTRAP:该信号由断点指令或其他 trap指令产生。默认动作为终止里程 并产生core文件。
- 6 ) SIGABRT:调用abort函数时产生该信号。默认动作为终止进程并产生core文件。
- 7)SIGBUS:非法访问内存地址,包括内存对齐出错,默认动作为终止进程并产生core文件。
- 8)SIGFPE:在发生致命的运算错误时发出。不仅包括浮点运算错误,还包括溢出及除数为0等所有的算法错误。默
- 认动作为终止进程并产生core文件。
- 9)SIGKILL:无条件终止进程。本信号不能被忽略,处理和阻塞。默认动作为终止进程。它向系统管理员提供了可
- 以杀死任何进程的方法。
- 10)SIGUSE1:用户定义 的信号。即程序员可以在程序中定义并使用该信号。默认动作为终止进程。
- 11)SIGSEGV:指示进程进行了无效内存访问。默认动作为终止进程并产生core文件。
- 12)SIGUSR2:这是另外一个用户自定义信号 ,程序员可以在程序中定义 并使用该信号。默认动作为终止进程。1
- 13)SIGPIPE:Broken pipe向一个没有读端的管道写数据。默认动作为终止进程。
- 14) SIGALRM:定时器超时,超时的时间 由系统调用alarm设置。默认动作为终止进程。
- 15)SIGTERM:程序结束信号,与SIGKILL不同的是,该信号可以被阻塞和终止。通常用来要示程序正常退出。执行
- shell命令Kill时,缺省产生这个信号。默认动作为终止进程。
- 16)SIGCHLD:子进程结束时,父进程会收到这个信号。默认动作为忽略这个信号。
- 17)SIGCONT:停止进程的执行。信号不能被忽略,处理和阻塞。默认动作为终止进程。
- 18)SIGTTIN:后台进程读终端控制台。默认动作为暂停进程。
- 19)SIGTSTP:停止进程的运行。按下<ctrl+z>组合键时发出这个信号。默认动作为暂停进程。
- 21)SIGTTOU:该信号类似于SIGTTIN,在后台进程要向终端输出数据时发生。默认动作为暂停进程。
- 22)SIGURG:套接字上有紧急数据时,向当前正在运行的进程发出些信号,报告有紧急数据到达。如网络带外数据
- 到达,默认动作为忽略该信号。
- 23)SIGXFSZ:进程执行时间超过了分配给该进程的CPU时间 ,系统产生该信号并发送给该进程。默认动作为终止
- 进程。
- 24)SIGXFSZ:超过文件的最大长度设置。默认动作为终止进程。
- 25)SIGVTALRM:虚拟时钟超时时产生该信号。类似于SIGALRM,但是该信号只计算该进程占用CPU的使用时间。默
- 认动作为终止进程。
- 26)SGIPROF:类似于SIGVTALRM,它不公包括该进程占用CPU时间还包括执行系统调用时间。默认动作为终止进
- 程。
- 27)SIGWINCH:窗口变化大小时发出。默认动作为忽略该信号。
- 28)SIGIO:此信号向进程指示发出了一个异步IO事件。默认动作为忽略。
- 29)SIGPWR:关机。默认动作为终止进程。
- 30)SIGSYS:无效的系统调用。默认动作为终止进程并产生core文件。
- 31)SIGRTMIN~(64)SIGRTMAX:LINUX的实时信号,它们没有固定的含义(可以由用户自定义)。所有的实时信
- 号的默认动作都为终止进程
Ctrl+c 2)SIGINT
Ctrl+\ 3)SIGQUIT
Ctrl+z 4)SIGTSTP
当然还有很多其他触发方式比如硬件异常,raise函数,abort函数,alarm函数等等。
其次是阻塞信号集,阻塞信号集能够对想除(9/19号信号以外)的信号进行屏蔽,如果屏蔽后信号自然不会到达递达状态,也就谈不上
处理了。我们通过sigprocmask函数进行阻塞信号集的设置,但之前必须要设置sigset_t 集合,通过sigaddset sigdelset sigemptyset
sigfillset等函数设置。
未决信号集是不能被操作的,只能被获取通过sigpending函数获取,但是他和阻塞信号集一起可以控制信号的递达
信号递达后就需要处理信号,默认的行为上面都列举了,但是信号(9/19号信号以外)是可以被捕获改变其处理方式的,我们可以自定义函数
作为某个信号的处理方式,这可以通过signal函数和sigaction函数进行捕获和处理,sigaction函数相对复杂需要有一个struct sigaction的
结构体变量,其中包含了sa_handler\sa_mask\sa_flags\sa_siaction 成员,这里不做解释可以自行查看LINUX man page
上面是单进程下的信号处理方式,在多线程下,线程之间公用处理方式,但是可以有不同的信号屏蔽集,在多线程下一般采用设置统一的信号
屏蔽字和信号处理方式使用pthread_mask函数继承到各个线程,同时使用sigwait/sigwaitinfo等函数设置一个单独的信号处理线程来进行统一
处理,MYSQL就是这样处理的。
二、MYSQL中的信号处理
首先我们可以发现MYSQL中有一个单独的signal处理线程
| 36 | 1927 | sql/signal_handler | NULL | BACKGROUND | NULL | NULL |
这个线程对整个MYSQLD进程的信号进行统一的处理,特别是涉及到SIGTERM,SIGQUIT,SIGHUP等信号的处理。
下面从源码触发我们来分析一下
1、信号初始化
void my_init_signals()函数
下面我们将一些关于信号处理的源码放出来进行解释
点击(此处)折叠或打开
- /*
- 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).
- */
- //这里将一些列如段错误、浮点数例外、总线错误、CPU非法指令等信号的默认处理
- //方式进行修改,修改为handle_fatal_signal函数调用,这个函数应该是打印一些
- //出错时候的状态信息等,我没有仔细看这个回调函数
- 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);
- }
-
- // Ignore SIGPIPE and SIGALRM
- //这里忽略掉管道错误和定时器信号
- sa.sa_flags= 0;
- sa.sa_handler= SIG_IGN;
- (void) sigaction(SIGPIPE, &sa, NULL);
- (void) sigaction(SIGALRM, &sa, NULL);
-
- //自定义信号,从注释来看是终止socket通信的
- //回调函数为empty_signal_handler
- // SIGUSR1 is used to interrupt the socket listener.
- sa.sa_handler= empty_signal_handler;
- (void) sigaction(SIGUSR1, &sa, NULL);
-
-
- //这里估计是什么特殊处理因为SIGTERM、SIGHUP已经在后面设置了
- //阻塞,应该和平台有关
- // 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);
-
- //下面开始设置我们的阻塞信号集通过pthread_sigmask生效
- //要阻塞SIGQUIT、SIGHUP、SIGTERM、SIGTSTP等信号
- //都是一些常用的可以终止进程的信号
- 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);
-
信号,通过一个专门的线程来处理这几个信号。同时很多信号也重新捕获改变了其
处理方式,详见上面解释。
2、信号处理线程
start_signal_handler-->signal_hand 来建立信号处理线程。
我们重点关注signal_hand这个回调函数,下面是一些源码和解释
点击(此处)折叠或打开
- extern "C" void *signal_hand(void *arg MY_ATTRIBUTE((unused)))
- {
- my_thread_init();
- //这里设置sigset_t信号集合,并将SIGTERM/SIGQUIT/SIGHUP设置
- sigset_t set;
- (void) sigemptyset(&set);
- (void) sigaddset(&set, SIGTERM);
- (void) sigaddset(&set, SIGQUIT);
- (void) sigaddset(&set, SIGHUP);
-
- .....MUTEX相关不考虑
-
- for (;;)
- {
- int sig;
- while (sigwait(&set, &sig) == EINTR) //调用sigwait堵塞捕获信号
- {}
- if (cleanup_done)
- {
- my_thread_end();
- my_thread_exit(0); // Safety
- return NULL; // Avoid compiler warnings
- }
- switch (sig) { //下面的判断非常重要如果是SIGTERM和SIGQUIT会
- //pthread_kill终止所有活动的会话线程每个线程将收到
- //SIGUSR1信号然后调用empty_signal_handler回调函数进行
- //处理然后close_connections然后my_thread_end关闭,
- //这里是否调用innodb关闭操作,可以通过GDB打断点
- //到innobase_shutdown_for_mysql函数上,我做了
- //测试确实调用了后面会给出,也就是说kill SIGTERM和
- //SIGQUIT是安全的,因为他们调用innodb关闭函数正常
- //的关闭了innodb
- case SIGTERM:
- case SIGQUIT:
- // Switch to the file log message processing.
- query_logger.set_handlers((log_output_options != LOG_NONE) ?
- LOG_FILE : LOG_NONE);
- DBUG_PRINT("info", ("Got signal: %d abort_loop: %d", sig, abort_loop));
- 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.
- */
- mysql_mutex_lock(&LOCK_socket_listener_active);
- while (socket_listener_active)
- {
- DBUG_PRINT("info",("Killing socket listener"));
- if (pthread_kill(main_thread_id, SIGUSR1))
- {
- DBUG_ASSERT(false);
- break;
- }
- mysql_cond_wait(&COND_socket_listener_active,
- &LOCK_socket_listener_active);
- }
- mysql_mutex_unlock(&LOCK_socket_listener_active);
-
- close_connections();
- }
- my_thread_end();
- my_thread_exit(0);
- return NULL; // Avoid compiler warnings
- break;
- case SIGHUP: //这里也是大家很关心的关于SIGHUP到底了做了什么
- //我们可以看到他调用reload_acl_and_cache来刷新
- //一个东西,具体后面给出,并没有其他什么操作
- //这个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
- // Reenable query logs after the options were reloaded.
- query_logger.set_handlers(log_output_options);
- }
- break;
- default:
- break; /* purecov: tested */
- }
- }
- return NULL; /* purecov: deadcode */
- }
这里我们看到很多我们关心的东西:
kill/kill -15/kill -SIGTERM做什么:
他们都是一样的都是SIGTERM信号MYSQL关闭所有活跃的连接同时干净的关闭innodb,我通过gdb确实看到了innodb关闭函数的调用,
这很容易我只要断点打到innobase_shutdown_for_mysql(),同时kill mysqldpid即可
点击(此处)折叠或打开
- (gdb) bt
- #0 innobase_shutdown_for_mysql () at /root/mysql5.7.14/percona-server-5.7.14-7/storage/innobase/srv/srv0start.cc:2785
- #1 0x00000000019a0f9c in innobase_end (hton=0x2e9a450, type=HA_PANIC_CLOSE) at /root/mysql5.7.14/percona-server-5.7.14-7/storage/innobase/handler/ha_innodb.cc:4360
- #2 0x0000000000f62215 in ha_finalize_handlerton (plugin=0x2fe31a0) at /root/mysql5.7.14/percona-server-5.7.14-7/sql/handler.cc:813
- #3 0x00000000015d3179 in plugin_deinitialize (plugin=0x2fe31a0, ref_check=true) at /root/mysql5.7.14/percona-server-5.7.14-7/sql/sql_plugin.cc:995
- #4 0x00000000015d3562 in reap_plugins () at /root/mysql5.7.14/percona-server-5.7.14-7/sql/sql_plugin.cc:1077
- #5 0x00000000015d54c7 in plugin_shutdown () at /root/mysql5.7.14/percona-server-5.7.14-7/sql/sql_plugin.cc:1845
- #6 0x0000000000ebf5eb in clean_up (print_message=true) at /root/mysql5.7.14/percona-server-5.7.14-7/sql/mysqld.cc:1336
- #7 0x0000000000ec6c7e in mysqld_main (argc=56, argv=0x2e98768) at /root/mysql5.7.14/percona-server-5.7.14-7/sql/mysqld.cc:5358
- #8 0x0000000000ebd404 in main (argc=9, argv=0x7fffffffe418) at /root/mysql5.7.14/percona-server-5.7.14-7/sql/main.cc:25
和上面一样,这在源码中清楚的看到了
kill -1/kill -SIGHUP做什么:
源码解释中说了做
reload_acl_and_cache(NULL,(REFRESH_LOG | REFRESH_TABLES | REFRESH_FAST |
REFRESH_GRANT | REFRESH_THREADS | REFRESH_HOSTS),
NULL, ?_used); // Flush logs
函数调用关于参数2
@param options What should be reset/reloaded (tables, privileges, slave...)
我们可以看到这样的解释,那么
REFRESH_LOG | REFRESH_TABLES | REFRESH_FAST |REFRESH_GRANT | REFRESH_THREADS | REFRESH_HOSTS
代表什么,明显他们是位图方式,他们代表的东西在源码中有解释如下:
点击(此处)折叠或打开
- #define REFRESH_GRANT 1 /* Refresh grant tables */
- #define REFRESH_LOG 2 /* Start on new log file */
- #define REFRESH_TABLES 4 /* close all tables */
- #define REFRESH_HOSTS 8 /* Flush host cache */
- #define REFRESH_STATUS 16 /* Flush status variables */
- #define REFRESH_THREADS 32 /* Flush thread cache */
- #define REFRESH_SLAVE 64 /* Reset master info and restart slave
- thread */
- #define REFRESH_MASTER 128 /* Remove all bin logs in the index
- and truncate the index */
- #define REFRESH_ERROR_LOG 256 /* Rotate only the erorr log */
- #define REFRESH_ENGINE_LOG 512 /* Flush all storage engine logs */
- #define REFRESH_BINARY_LOG 1024 /* Flush the binary log */
- #define REFRESH_RELAY_LOG 2048 /* Flush the relay log */
- #define REFRESH_GENERAL_LOG 4096 /* Flush the general log */
- #define REFRESH_SLOW_LOG 8192 /* Flush the slow query log */
当然大家千万不要直接kill -9这个信号不能被屏蔽也不能被捕获,而是强制终止进程这个时候innodb不可能调用
innobase_shutdown_for_mysql来干净的关闭MYSQLD。
作者微信: