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