1.信号概述

信号 signal 可以理解为由操作系统传给程序(进程)的事件,用来通知程序发生了什么事件。signal.h 是处理信号的C++ 库,其定义了如下类型的信号:

编号信号备注
1SIGHUP本信号在用户终端连接(正常或非正常)结束时发出, 通常是在终端的控制进程结束时, 通知同一session内的各个作业, 这时它们与控制终端不再关联.
2SIGINT程序终止(interrupt)信号, 在用户键入INTR字符(通常是Ctrl-C)时发出
3SIGQUIT和SIGINT类似, 但由QUIT字符(通常是Ctrl-)来控制. 进程在因收到SIGQUIT退出时会产生core文件, 在这个意义上类似于一个程序错误信号.
4SIGILL执行了非法指令. 通常是因为可执行文件本身出现错误, 或者试图执行数据段. 堆栈溢出时也有可能产生这个信号.
5SIGTRAP由断点指令或其它trap指令产生. 由debugger使用.
6SIGABRT程序自己发现错误并调用abort时产生.
6SIGIOTSIGABRT别名,在PDP-11上由iot指令产生, 在其它机器上和SIGABRT一样.
7SIGBUS非法地址, 包括内存地址对齐(alignment)出错. eg: 访问一个四个字长的整数, 但其地址不是4的倍数.
8SIGFPE在发生致命的算术运算错误时发出. 不仅包括浮点运算错误, 还包括溢出及除数为0等其它所有的算术的错误.
9SIGKILL用来立即结束程序的运行. 本信号不能被阻塞, 处理和忽略.
10SIGUSR1留给用户使用
11SIGSEGV试图访问未分配给自己的内存, 或试图往没有写权限的内存地址写数据.
12SIGUSR2留给用户使用
13SIGPIPEBroken pipe
14SIGALRM时钟定时信号, 计算的是实际的时间或时钟时间. alarm函数使用该信号.
15SIGTERM程序结束(terminate)信号, 与SIGKILL不同的是该信号可以被阻塞和处理. 通常用来要求程序自己正常退出. shell命令kill缺省产生这个信号.
17SIGCHLD子进程结束时, 父进程会收到这个信号.
18SIGCONT让一个停止(stopped)的进程继续执行. 本信号不能被阻塞. 可以用一个handler来让程序在由stopped状态变为继续执行时完成特定的工作. 例如, 重新显示提示符
19SIGSTOP停止(stopped)进程的执行. 注意它和terminate以及interrupt的区别:该进程还未结束, 只是暂停执行. 本信号不能被阻塞, 处理或忽略.
20SIGTSTP停止进程的运行, 但该信号可以被处理和忽略. 用户键入SUSP字符时(通常是Ctrl-Z)发出这个信号
21SIGTTIN当后台作业要从用户终端读数据时, 该作业中的所有进程会收到SIGTTIN信号. 缺省时这些进程会停止执行.
22SIGTTOU类似于SIGTTIN, 但在写终端(或修改终端模式)时收到.
23SIGURG有”紧急”数据或out-of-band数据到达socket时产生.
24SIGXCPU超过CPU时间资源限制. 这个限制可以由getrlimit/setrlimit来读取/改变
25SIGXFSZ超过文件大小资源限制.
26SIGVTALRM虚拟时钟信号. 类似于SIGALRM, 但是计算的是该进程占用的CPU时间.
27SIGPROF类似于SIGALRM/SIGVTALRM, 但包括该进程用的CPU时间以及系统调用的时间.
28SIGWINCH窗口大小改变时发出.
29SIGIO文件描述符准备就绪, 可以开始进行输入/输出操作.
30SIGPWRPower failure

在ubuntu在可以运行kill -l 查看系统支持的信号类型。

2. 使用signal函数处理信号

当程序捕捉到操作系统提供的信号时,可以调用signal对相应信号进行处理,其函数原型为:

signal(registered signal, signal handler)

这个函数接收两个参数:第一个参数是要处理的信号类型;第二个参数描述了与信号关联的动作;

动作可以分为三类:
    I. 默认处理:对信号进行该信号的系统默认处理,第二参数为 SIG_DFL。
    II. 忽略信号:忽略该信号,第二参数为 SIG_IGN。
    III. Function handler:指定处理函数,由该函数来处理,第二参数为 函数指针 。

例子1.:使用 signal() 函数捕获 SIGINT 信号,并指定处理函数输出相关信息

#include <iostream>
#include <csignal>
#include <unistd.h>
 
using namespace std;
 
void signalHandler( int signum )
{
    cout << "Interrupt signal (" << signum << ") received.\n";
 
    // 清理并关闭
    // 终止程序  
 
   exit(signum);  
 
}
 
int main ()
{
    // 注册信号 SIGINT 和信号处理程序
    signal(SIGINT, signalHandler);  
 
    while(1){
       cout << "Going to sleep...." << endl;
       sleep(1);
    }
 
    return 0;
}

当上面的代码被编译和执行时,它会产生下列结果:

Going to sleep....
Going to sleep....
Going to sleep....

按 Ctrl+C 来中断程序,程序捕获信号,程序打印如下内容并退出:

Going to sleep....
Going to sleep....
Going to sleep....
Interrupt signal (2) received.

3. 使用raise函数生成信号

函数原型:

int raise (signal sig); //sig 是要发送的信号的编号

其中:sig 是要发送的信号的编号

例子:

#include <iostream>
#include <csignal>
#include <unistd.h>
 
using namespace std;
 
void signalHandler( int signum )
{
    cout << "Interrupt signal (" << signum << ") received.\n";
 
    // 清理并关闭
    // 终止程序 
 
   exit(signum);  
 
}
 
int main ()
{
    int i = 0;
    // 注册信号 SIGINT 和信号处理程序
    signal(SIGINT, signalHandler);  
 
    while(++i){
       cout << "Going to sleep...." << endl;
       if( i == 3 ){
          raise( SIGINT);
       }
       sleep(1);
    }
 
    return 0;
}

运行结果:

Going to sleep....
Going to sleep....
Going to sleep....
Interrupt signal (2) received.

4.sigaction函数

signal 函数的使用方法简单,但并不属于 POSIX 标准,在各类 UNIX 平台上的实现不尽相同,因此其用途受到了一定的限制。而 POSIX 标准定义的信号处理接口是 sigaction 函数,其接口头文件及原型如下:

int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

◆ signum:要操作的信号。
◆ act:要设置的对信号的新处理方式。
◆ oldact:原来对信号的处理方式。
◆ 返回值:0 表示成功,-1 表示有错误发生。

结构体sigaction用来描述对信号的处理,定义如下:

 struct sigaction
 {
  void     (*sa_handler)(int);
  void     (*sa_sigaction)(int, siginfo_t *, void *);
  sigset_t  sa_mask;
  int       sa_flags;
  void     (*sa_restorer)(void);
 };
  • sa_handler:是一个函数指针,其含义与 signal 函数中的信号处理函数类似
  • sa_sigaction: 是另一个信号处理函数,它有三个参数,可以获得关于信号的更详细的信息
  • sa_mask: 用来指定在信号处理函数执行期间需要被屏蔽的信号,特别是当某个信号被处理时,它自身会被自动放入进程的信号掩码,因此在信号处理函数执行期间这个信号不会再度发生。
  • sa_flags: 用来设置信号处理的其他相关操作,它可以是以下值的“按位或”组合:
    ◆ SA_RESTART:使被信号打断的系统调用自动重新发起。
    ◆ SA_NOCLDSTOP:使父进程在它的子进程暂停或继续运行时不会收到 SIGCHLD 信号。
    ◆ SA_NOCLDWAIT:使父进程在它的子进程退出时不会收到 SIGCHLD 信号,这时子进程如果退出也不会成为僵尸进程。
    ◆ SA_NODEFER:一般情况下, 当信号处理函数运行时,内核将阻塞该给定信号。 SA_NODEFER标记使对信号的屏蔽无效,即在信号处理函数执行期间仍能发出这个信号。
    ◆ SA_RESETHAND:信号处理之后重新设置为默认的处理方式。
    ◆ SA_SIGINFO:使用 sa_sigaction 成员而不是 sa_handler 作为信号处理函数。
void show_handler(int sig)
{
    printf("I got signal %d\n", sig);
    int i;
    for(i = 0; i < 5; i++) 
   {
        printf("i = %d\n", i);
        sleep(1);
    }
}
 
int main(void)
{
    int i = 0;
    struct sigaction act, oldact;
    act.sa_handler = show_handler;
    sigaddset(&act.sa_mask, SIGQUIT);         //见注(1)
    act.sa_flags = SA_RESETHAND | SA_NODEFER; //见注(2)
    //act.sa_flags = 0;                      //见注(3)
 
    sigaction(SIGINT, &act, &oldact);
    while(1) 
   {
        sleep(1);
        printf("sleeping %d\n", i);
        i++;
    }
}

(1)如果在信号SIGINT(Ctrl + c)的信号处理函数show_handler执行过程中,本进程收到信号SIGQUIT(Crt+),将阻塞该信号,直到show_handler执行结束才会处理信号SIGQUIT。
(2) SA_RESETHAND:信号处理之后重新设置为默认的处理方式;SA_NODEFER使对信号的屏蔽无效,即在信号处理函数执行期间仍能发出这个信号。
(2)如果不需要重置该给定信号的处理函数为缺省值;并且不需要阻塞该给定信号(无须设置sa_flags标志),那么必须将sa_flags清零,否则运行将会产生段错误。但是sa_flags清零后可能会造成信号丢失!

参考:

Logo

鸿蒙生态一站式服务平台。

更多推荐