手把手写C++服务器(32):三大事件之信号详解
Posted 沉迷单车的追风少年
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了手把手写C++服务器(32):三大事件之信号详解相关的知识,希望对你有一定的参考价值。
本系列文章导航: 手把手写C++服务器(0):专栏文章-汇总导航【更新中】
前言:信号实际上是一种软中断,信号机制实际上是进程间通信的一种方式。状态改变、系统异常、系统状态的变化等等,这些是信号的来源。信号作为一个异步事件,对维护服务器稳定,避免异常终止有重要的作用。本文从常见的64中Linux信号出发,步步深入,详解信号事件。
目录
64种Linux信号
使用 kill -l 命令查看Linux信号:
kill -l
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR
31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3
38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7
58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
63) SIGRTMAX-1 64) SIGRTMAX
这些信号都定义在bits/signum.h头文件中:
/* Signals. */
#define SIGHUP 1 /* Hangup (POSIX). 终端连接断开信号*/
#define SIGINT 2 /* Interrupt (ANSI). 中断信号,终端中输入ctrl+c,可中断前台进程*/
#define SIGQUIT 3 /* Quit (POSIX). 退出信号,终端中输入ctrl+\\,可退出前台进程,同时产生core文件*/
#define SIGILL 4 /* Illegal instruction (ANSI). 非法指令信号,4.3BSD的abort函数产生该信号 */
#define SIGTRAP 5 /* Trace trap (POSIX). 调试信号,当在程序中设置断点后,该信号使得调试程序获得控制权*/
#define SIGABRT 6 /* Abort (ANSI). 程序异常终止信号,abort函数产生该信号 */
#define SIGIOT 6 /* IOT trap (4.2 BSD). 功能同SIGABRT*/
#define SIGBUS 7 /* BUS error (4.2 BSD). 程序访问不存在的内存区域时,产生该信号*/
#define SIGFPE 8 /* Floating-point exception (ANSI). 算术异常,如除以0*/
#define SIGKILL 9 /* Kill, unblockable (POSIX). 不能被忽略,非阻塞,可杀死任意一个运行中的进程*/
#define SIGUSR1 10 /* User-defined signal 1 (POSIX). 用户自定义1*/
#define SIGSEGV 11 /* Segmentation violation (ANSI). 当程序访问没有访问权限的内存区域,或者访问非可读的内存区域时,产生该信号,如数组越界 */
#define SIGUSR2 12 /* User-defined signal 2 (POSIX). 用户自定义2 */
#define SIGPIPE 13 /* Broken pipe (POSIX). 当管道读端已关闭,继续往管道中写,产生该信号 */
#define SIGALRM 14 /* Alarm clock (POSIX). alarm函数超时时产生该信号,默认动作是程序终止 */
#define SIGTERM 15 /* Termination (ANSI). 终止程序信号,命令kill默认使用该参数*/
#define SIGSTKFLT 16 /* Stack fault. */
#define SIGCLD SIGCHLD /* Same as SIGCHLD (System V). */
#define SIGCHLD 17 /* Child status has changed (POSIX). 子进程终止或停止时,产生该信号,默认被忽略*/
#define SIGCONT 18 /* Continue (POSIX). */
#define SIGSTOP 19 /* Stop, unblockable (POSIX). 停止一个作业控制进程*/
#define SIGTSTP 20 /* Keyboard stop (POSIX). ctrl+z产生该信号,改信号使得前台进程挂起*/
#define SIGTTIN 21 /* Background read from tty (POSIX). */
#define SIGTTOU 22 /* Background write to tty (POSIX). */
#define SIGURG 23 /* Urgent condition on socket (4.2 BSD). */
#define SIGXCPU 24 /* CPU limit exceeded (4.2 BSD). 进程超过了CPU软限制产生该信号*/
#define SIGXFSZ 25 /* File size limit exceeded (4.2 BSD). 进程超过了文件大小软限制产生该信号*/
#define SIGVTALRM 26 /* Virtual alarm clock (4.2 BSD). */
#define SIGPROF 27 /* Profiling alarm clock (4.2 BSD). */
#define SIGWINCH 28 /* Window size change (4.3 BSD, Sun). */
#define SIGPOLL SIGIO /* Pollable event occurred (System V). */
#define SIGIO 29 /* I/O now possible (4.2 BSD). */
#define SIGPWR 30 /* Power failure restart (System V). */
#define SIGSYS 31 /* Bad system call. */
#define SIGUNUSED 31
#define _NSIG 65 /* Biggest signal number + 1
(including real-time signals). */
#define SIGRTMIN (__libc_current_sigrtmin ())
#define SIGRTMAX (__libc_current_sigrtmax ())
取值 | 名称 | 解释 | 默认动作 |
---|---|---|---|
1 | SIGHUP | 挂起 | |
2 | SIGINT | 中断 | |
3 | SIGQUIT | 退出 | |
4 | SIGILL | 非法指令 | |
5 | SIGTRAP | 断点或陷阱指令 | |
6 | SIGABRT | abort发出的信号 | |
7 | SIGBUS | 非法内存访问 | |
8 | SIGFPE | 浮点异常 | |
9 | SIGKILL | kill信号 | 不能被忽略、处理和阻塞 |
10 | SIGUSR1 | 用户信号1 | |
11 | SIGSEGV | 无效内存访问 | |
12 | SIGUSR2 | 用户信号2 | |
13 | SIGPIPE | 管道破损,没有读端的管道写数据 | |
14 | SIGALRM | alarm发出的信号 | |
15 | SIGTERM | 终止信号 | |
16 | SIGSTKFLT | 栈溢出 | |
17 | SIGCHLD | 子进程退出 | 默认忽略 |
18 | SIGCONT | 进程继续 | |
19 | SIGSTOP | 进程停止 | 不能被忽略、处理和阻塞 |
20 | SIGTSTP | 进程停止 | |
21 | SIGTTIN | 进程停止,后台进程从终端读数据时 | |
22 | SIGTTOU | 进程停止,后台进程想终端写数据时 | |
23 | SIGURG | I/O有紧急数据到达当前进程 | 默认忽略 |
24 | SIGXCPU | 进程的CPU时间片到期 | |
25 | SIGXFSZ | 文件大小的超出上限 | |
26 | SIGVTALRM | 虚拟时钟超时 | |
27 | SIGPROF | profile时钟超时 | |
28 | SIGWINCH | 窗口大小改变 | 默认忽略 |
29 | SIGIO | I/O相关 | |
30 | SIGPWR | 关机 | 默认忽略 |
31 | SIGSYS | 系统调用异常 |
当然,这64种信号我们不需要全部掌握,重点关注一下网络编程常用的SIGHUP、SIGPIPE、SIGURG、SIGALRM、SIGCHLD等。
信号来源
信号来源分为硬件类和软件类:
硬件方式
- 用户输入:比如在终端上按下组合键ctrl+C,产生SIGINT信号;
- 硬件异常:CPU检测到内存非法访问等异常,通知内核生成相应信号,并发送给发生事件的进程;
软件方式
通过系统调用,发送signal信号:kill(),raise(),sigqueue(),alarm(),setitimer(),abort()
- kernel,使用 kill_proc_info()等
- native,使用 kill() 或者raise()等
- java,使用 Procees.sendSignal()等
信号的发送
使用kill函数进行信号发送,函数原型如下:
#include <sys/types.h>
#include <sygnal.h>
int kill(ipd_t pid, int sig);
函数的含义是将sig信号发送给目标进程,目标进程由pid参数指定。
pid参数如下:
pid参数 | 含义 |
pid > 0 | 信号发送给PID为pid的进程 |
pid = 0 | 信号发送给本进程组内的其他进程 |
pid = -1 | 信号发送给除init进程之外的所有进程; 但发送者需要拥有对目标进程发送信号的权限 |
pid < -1 | 信号发送给组ID为-pid的进程组中的所有成员 |
如果sig信号等于0,则kill不发送任何信号,单数可以以此来监测目标进程是否存在。
信号的接受与处理
信号处理函数原型如下:
#include <bits/signal.h>
typedef void (*__sighandler_t) (int);
信号处理函数只带有一个整型参数,该参数用来指示信号类型。
除了用户自定义信号处理函数之外,还定义了信号两种其他处理方式:
#include <bits/signum.h>
#define SIG_DFL ((__sighandler_t))
#define SIG_IGN ((__sighandler_t))
SIG_DFL表示使用信号默认处理方式:结束进程、忽略信号、结束进程并生成核心转存储文件、暂停进程、继续进程
SIG_IGN表示忽略目标信号
中断系统调用
如果程序在执行处于阻塞状态的系统调用时接收到信号,并且为该信号设置了信号处理函数,则默认情况下系统调用将被中断,并将errno设置为EINTR。
信号安装
进程处理某个信号前,需要先在进程中安装此信号。安装过程主要是建立信号值和进程对相应信息值的动作。
信号安装函数
- signal():不支持信号传递信息,主要用于非实时信号安装;
- sigaction():支持信号传递信息,可用于所有信号安装;
signal系统调用
signal为一个信号设置处理函数:
#include <signal.h>
_sighandler_t signal(int sig, _sighandler_t _handler);
其中sig表示要捕获信号类型,_handler是函数指针,指定信号sig处理函数。
sigaction系统调用
相比于signal函数,sigaction调用更加健壮:
#include <signal.h>
int sigaction(int sig, const struct sigaction* act, struct sigaction* oact);
- sig表示要捕获信号类型
- act表示新的信号处理方式
- oact表示输出信号先前的处理方式
sigaction结构体:
- sa_handler:信号处理函数
- sa_mask:指定信号处理程序执行过程中需要阻塞的信号;
- sa_flags:标示位
- SA_RESTART:使被信号打断的syscall重新发起。
- SA_NOCLDSTOP:使父进程在它的子进程暂停或继续运行时不会收到 SIGCHLD 信号。
- SA_NOCLDWAIT:使父进程在它的子进程退出时不会收到SIGCHLD信号,这时子进程如果退出也不会成为僵 尸进程。
- SA_NODEFER:使对信号的屏蔽无效,即在信号处理函数执行期间仍能发出这个信号。
- SA_RESETHAND:信号处理之后重新设置为默认的处理方式。
- SA_SIGINFO:使用sa_sigaction成员而不是sa_handler作为信号处理函数。
信号发送
- kill():用于向进程或进程组发送信号;
- sigqueue():只能向一个进程发送信号,不能像进程组发送信号;主要针对实时信号提出,与sigaction()组合使用,当然也支持非实时信号的发送;
- alarm():用于调用进程指定时间后发出SIGALARM信号;
- setitimer():设置定时器,计时达到后给进程发送SIGALRM信号,功能比alarm更强大;
- abort():向进程发送SIGABORT信号,默认进程会异常退出。
- raise():用于向进程自身发送信号;
信号集操作
- sigemptyset(sigset_t *set):信号集全部清0;
- sigfillset(sigset_t *set): 信号集全部置1,则信号集包含linux支持的64种信号;
- sigaddset(sigset_t *set, int signum):向信号集中加入signum信号;
- sigdelset(sigset_t *set, int signum):向信号集中删除signum信号;
- sigismember(const sigset_t *set, int signum):判定信号signum是否存在信号集中。
信号挂起
通过sigpending函数获取进程当前被挂起信号集:
#include <signal.h>
int sigpending(sigset_t* set);
参数set用于保存被挂起的信号集。
参考
- https://blog.csdn.net/zb1593496558/article/details/80280346?utm_medium=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.control&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.control
- http://gityuan.com/2015/12/20/signal/
- 《Linux高性能服务器编程》
- https://blog.csdn.net/ruglcc/article/details/86519357
以上是关于手把手写C++服务器(32):三大事件之信号详解的主要内容,如果未能解决你的问题,请参考以下文章
手把手写C++服务器(36):手撕代码——高并发高QPS技术基石之非阻塞recv万字长文
手把手写C++服务器(38):面试必背!Linux网络socket编程必会十问!
手把手写C++服务器:常用boost之program_options命令行参数解析
手把手写C++服务器(37):手撕代码——高并发多线程技术基石之异步connect万字长文