先来看一个C++信号处理的实例,并针对实例进行逐步解析,通过实例更容易理解C++信号处理机制。
示例:
cpp
#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;
}
一、代码解析
1、C++信号处理库应用
信号是由操作系统传给进程的中断,会提早终止一个程序。在 UNIX、LINUX、Mac OS X 或 Windows 系统上,可以通过按 Ctrl+C 产生中断。
有些信号不能被程序捕获,但是下表所列信号可以在程序中捕获,并可以基于信号采取适当的动作。这些信号是定义在 C++ 头文件 <csignal> 中。
信号 | 描述 |
---|---|
SIGABRT | 程序的异常终止,如调用 abort。 |
SIGFPE | 错误的算术运算,比如除以零或导致溢出的操作。 |
SIGILL | 检测非法指令。 |
SIGINT | 程序终止(interrupt)信号。 |
SIGSEGV | 非法访问内存。 |
SIGTERM | 发送到程序的终止请求。 |
上面示例主要用了C++信号处理库的signal 函数和raise函数,下面对这两个函数进行介绍。
signal() 函数
C++ 信号处理库提供了 signal 函数,用来捕获突发事件。以下是 signal() 函数的语法:
void (*signal (int sig, void (*func)(int)))(int);
这个看起来有点费劲,以下语法格式更容易理解:
signal(registered signal, signal handler)
这个函数接收两个参数:第一个参数是要设置的信号的标识符,第二个参数是指向信号处理函数的指针。函数返回值是一个指向先前信号处理函数的指针。如果先前没有设置信号处理函数,则返回值为 SIG_DFL。如果先前设置的信号处理函数为 SIG_IGN,则返回值为 SIG_IGN。
raise() 函数
您可以使用函数 raise() 生成信号,该函数带有一个整数信号编号作为参数,语法如下:
int raise (signal sig);
在这里,sig 是要发送的信号的编号,这些信号包括:SIGINT、SIGABRT、SIGFPE、SIGILL、SIGSEGV、SIGTERM、SIGHUP。
2、信号处理函数:
cpp
void signalHandler(int signum) {
cout << "Interrupt signal (" << signum << ") received.\n";
exit(signum);
}
- 该函数用于处理接收到的信号。参数
signum
表示接收到的信号编号。
3、主函数:
cpp
int main() {
int i = 0;
signal(SIGINT, signalHandler);
while(++i) {
cout << "Going to sleep...." << endl;
if(i == 3) {
raise(SIGINT);
}
sleep(1);
}
return 0;
}
- 信号注册 :使用
signal(SIGINT, signalHandler)
将SIGINT
信号与signalHandler
函数关联。 - 循环 :
while(++i)
是一个无限循环,每次循环输出"Going to sleep...."。 - 发送信号 :当
i
等于3时,使用raise(SIGINT)
主动发送SIGINT
信号给自身,模拟用户按Ctrl+C
的效果。 - 休眠 :
sleep(1)
使程序暂停1秒,以便观察输出。
二、深度分析
1. signalHandler
函数的signum
参数是如何传入的?
当进程接收到信号时,操作系统执行以下步骤:
- 生成信号 :信号可以由用户(例如,按
Ctrl+C
)或程序自身(通过raise()
函数)生成。在这个例子中,信号是通过raise(SIGINT)
主动发送的。 - 调用信号处理函数 :操作系统检测到信号被发送后,会检查该信号是否有注册的处理函数。在本例中,我们通过
signal(SIGINT, signalHandler)
注册了信号处理函数。 - 传递参数 :当信号处理函数被调用时,操作系统将信号的编号作为参数传递给该函数。在本例中,当
SIGINT
信号被接收到时,操作系统将2
(SIGINT
的编号)作为参数传入signalHandler
函数,因此在函数内部,signum
的值等于2。
2. signum
的值为什么输出是2?
在Unix/Linux系统中,每个信号都有一个固定的编号,SIGINT
信号的编号是2。这一编号是由操作系统定义的,符合POSIX标准。
在上面的示例中,当程序接收到SIGINT
信号时,操作系统会将信号编号(即2)作为参数传递给信号处理函数signalHandler
。因此,在signalHandler
函数内,signum
的值为2。
-
SIGINT
的编号 :在大多数Unix/Linux系统中,信号SIGINT
被定义为编号2。这是一个约定,确保程序员在处理信号时能够理解和使用这些编号。 -
查看信号编号 :可以使用
kill -l
命令查看所有信号及其编号。在终端中执行该命令,输出将显示SIGINT
的编号为2。 -
以下是一些常见信号及其对应的信号编号(在Unix/Linux系统上):
-
SIGHUP
(1): 终端挂起 -
SIGINT
(2): 中断信号,通常由用户通过Ctrl+C
触发 -
SIGQUIT
(3): 退出信号,通常由用户通过Ctrl+\
触发 -
SIGILL
(4): 非法指令 -
SIGABRT
(6): 中止信号 -
SIGFPE
(8): 浮点异常 -
SIGKILL
(9): 强制终止信号 -
SIGSEGV
(11): 段错误 -
SIGTERM
(15): 终止信号
三、代码执行流程总结
- 程序开始执行 :主函数进入循环,初始化
i
为0。 - 信号注册 :调用
signal(SIGINT, signalHandler)
注册信号处理函数。 - 循环输出 :每次循环增加
i
并输出"Going to sleep...."。 - 主动发送信号 :当
i
等于3时,调用raise(SIGINT)
,主动发送SIGINT
信号。 - 信号处理 :
- 操作系统接收到
SIGINT
信号,调用注册的signalHandler
函数,并将signum
参数设置为2。 - 在
signalHandler
中,输出"Interrupt signal (2) received."并结束程序。
- 操作系统接收到
通过这种方式,程序优雅地响应了外部信号,并在适当时机清理和退出