关于信号,信号是一种进程间通信的机制,用于在程序执行过程中通知进程发生了一些事件。在Unix和类Unix系统中,信号是一种异步通知机制,通过发送信号,一个进程可以通知另一个进程发生了某个事件,如按下 Ctrl+C、除零错误等。
在C++中,可以使用 头文件提供的信号处理机制来捕获和处理信号。
信号的基本概念:
常见的信号:
C++使用 signal 函数可以为特定的信号注册信号处理函数。
语法使用:
void (*signal(int signum, void (*handler)(int)))(int);
也可以写成:
signal(SIGINT, signalHandler);
在上述代码中,
信号处理函数的声明:
void handlerFunction(int signum)。
关于信号处理函数的定义应该尽量简单,因为它在异步环境中执行,同时有一些函数(例如‘printf’,‘malloc’)不是异步安全的,所以尽量不要在信号处理函数中使用它们。
异步环境是指程序执行时存在多个同时运行的线程或进程,这些线程或进程在执行过程中可能会相互干扰,因为它们共享某些资源(如内存、文件描述符等)。在异步环境中,执行顺序是不确定的,因此程序的行为可能受到非常复杂的影响。
printf 和 malloc 不是异步安全的主要是因为它们在执行时可能涉及到对共享资源的访问,而这样的访问在异步环境中是不安全的。
因此,在异步环境中,为了确保代码的正确性,应该尽量避免在信号处理函数或多线程环境中使用不可重入(non-reentrant)的函数。不可重入函数是指在执行过程中依赖于全局状态或静态变量的函数,而这在异步环境中可能导致不确定的结果。
为了在异步环境中安全使用输出函数和内存分配函数,通常建议使用异步安全的替代版本。例如,在信号处理函数中,可以使用 write 函数代替 printf,而在多线程环境中,可以使用 pthread 库提供的线程安全的输出函数和内存分配函数。
#include
#include
// 信号处理函数
void signalHandler(int signum) {
std::cout << "Received signal: " << signum << std::endl;
// 自定义处理逻辑可以在这里添加
// ...
// 恢复对 SIGINT 的默认处理
signal(SIGINT, SIG_DFL);
}
int main() {
// 注册信号处理函数
signal(SIGINT, signalHandler);
std::cout << "Press Ctrl+C to trigger the signal." << std::endl;
// 一个简单的循环,使程序保持运行
while (true) {
// 等待信号的到来
}
return 0;
}
在上述代码中,声明了一个自定义的信号处理函数signalHandler;之后的main函数中,用signal注册信号处理函数,来处理SIGINT信号。signal函数的第一个参数就是要识别的信号的编号,第二个参数就是指向信号处理函数的指针。
而在信号处理函数signalHandler中,可以添加自定义的处理逻辑。
上述代码运行后,因为while(ture),程序会一直保持运行,直到我们按下Ctrl+C发生中断后,signal函数在捕获信号后,信号处理函数发挥作用,打印了Received signal: 2;
因为SIGINT 的信号编号是 2,所以signum的值是2;
如果想要在信号处理完成后恢复对该信号的默认处理,可以使用 signal(SIGINT, SIG_DFL)。
忽略和恢复信号:
关于恢复信号,当你按下 Ctrl+C 触发 SIGINT 信号时,如果没有 signal(SIGINT, SIG_DFL); 这一行,那么程序将继续执行 signalHandler 函数,但不会将 SIGINT 的处理方式恢复为默认。这意味着如果再次按下 Ctrl+C,signalHandler 函数将再次被调用,而不会终止程序。
实际上,如果不将 SIGINT 恢复为默认处理方式,程序可能会对多次 Ctrl+C 信号作出相应,而不是默认的行为(终止程序)。
raise 函数是用于在程序中手动触发一个信号的函数。
声明:
int raise(int sig);
raise 函数返回一个整数值,表示函数调用的结果。如果成功发送信号,返回 0;如果失败,返回非零值。
示例:
#include
#include
// 信号处理函数
void signalHandler(int signum) {
std::cout << "Received signal: " << signum << std::endl;
}
int main() {
// 注册信号处理函数
signal(SIGINT, signalHandler);
std::cout << "Press Ctrl+C to trigger the signal." << std::endl;
// 模拟其他程序逻辑
int count = 0;
while (true) {
// 模拟其他程序逻辑
std::cout << "Working... (" << count << ")" << std::endl;
// 在某个条件下手动触发 SIGINT 信号
if (count > 500) {
std::cout << "Manually triggering SIGINT..." << std::endl;
raise(SIGINT);
}
// 模拟其他程序逻辑
// ...
// 增加计数
count++;
}
return 0;
}
上述代码的运行结果:
可以看到在进行俩次的模拟生成信号后,程序就停止了,这是因为没有重新注册 SIGINT 的处理函数,程序将使用默认的信号处理方式,即终止程序。
如果想要第一次信号处理后继续运行,可以重新注册 SIGINT 的处理函数。
在signalHandler函数中添加:
// 重新注册信号处理函数
signal(SIGINT, signalHandler);