手把手写C++服务器(32):三大事件之信号详解

Posted 沉迷单车的追风少年

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了手把手写C++服务器(32):三大事件之信号详解相关的知识,希望对你有一定的参考价值。

本系列文章导航: 手把手写C++服务器(0):专栏文章-汇总导航【更新中】 

前言:信号实际上是一种软中断,信号机制实际上是进程间通信的一种方式。状态改变、系统异常、系统状态的变化等等,这些是信号的来源。信号作为一个异步事件,对维护服务器稳定,避免异常终止有重要的作用。本文从常见的64中Linux信号出发,步步深入,详解信号事件。

目录

64种Linux信号

信号来源

硬件方式

软件方式

信号的发送

信号的接受与处理

中断系统调用

信号安装

signal系统调用

sigaction系统调用

信号发送

信号集操作

信号挂起

参考


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 ())
取值名称解释默认动作
1SIGHUP挂起
2SIGINT中断
3SIGQUIT退出
4SIGILL非法指令
5SIGTRAP断点或陷阱指令
6SIGABRTabort发出的信号
7SIGBUS非法内存访问
8SIGFPE浮点异常
9SIGKILLkill信号不能被忽略、处理和阻塞
10SIGUSR1用户信号1
11SIGSEGV无效内存访问
12SIGUSR2用户信号2
13SIGPIPE管道破损,没有读端的管道写数据
14SIGALRMalarm发出的信号
15SIGTERM终止信号
16SIGSTKFLT栈溢出
17SIGCHLD子进程退出默认忽略
18SIGCONT进程继续
19SIGSTOP进程停止不能被忽略、处理和阻塞
20SIGTSTP进程停止
21SIGTTIN进程停止,后台进程从终端读数据时
22SIGTTOU进程停止,后台进程想终端写数据时
23SIGURGI/O有紧急数据到达当前进程默认忽略
24SIGXCPU进程的CPU时间片到期
25SIGXFSZ文件大小的超出上限
26SIGVTALRM虚拟时钟超时
27SIGPROFprofile时钟超时
28SIGWINCH窗口大小改变默认忽略
29SIGIOI/O相关
30SIGPWR关机默认忽略
31SIGSYS系统调用异常

当然,这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用于保存被挂起的信号集。

参考

以上是关于手把手写C++服务器(32):三大事件之信号详解的主要内容,如果未能解决你的问题,请参考以下文章

手把手写C++服务器(36):手撕代码——高并发高QPS技术基石之非阻塞recv万字长文

手把手写C++服务器(38):面试必背!Linux网络socket编程必会十问!

手把手写C++服务器:常用boost之program_options命令行参数解析

手把手写C++服务器(37):手撕代码——高并发多线程技术基石之异步connect万字长文

手把手写C++服务器(35):手撕代码——高并发高QPS技术基石之非阻塞send万字长文

手把手写C++服务器(26):常用I/O操作创建文件描述符