📜  C++ |信号处理(1)

📅  最后修改于: 2023-12-03 14:59:47.939000             🧑  作者: Mango

C++ | 信号处理

简介

信号处理(Signal Handling)是指在程序运行中,应用程序接收到来自操作系统的信号,并作出相应的处理。在Unix类系统中,信号是一种可以被操作系统传递给进程的软件中断,用于通知进程发生了某些事件。使用信号处理,可以实现对进程的控制,达到优雅退出、异常处理等目的。

信号处理的基本概念
  • 信号编号:用于标识信号的数字编号。Unix系统中有一些标准的信号编号,例如SIGINT表示在终端上按下Ctrl+C键,SIGKILL表示直接杀死程序等。
  • 信号处理器:负责接收来自操作系统的信号,并对其进行处理的程序。每个信号都有一个默认信号处理器,可以由用户自定义信号处理器,覆盖默认处理器即可。
  • 信号集:由一些信号构成的集合,用于表示应用程序希望接收或屏蔽的信号。
信号处理的基本用法
捕捉信号

使用系统调用signal()可以指定一个信号处理器来处理特定信号的接收。

#include <iostream>
#include <csignal>

void signal_handler(int signum){
    std::cout<<"Received signal: "<<signum<<std::endl;
}

int main(){
    // 注册SIGINT信号处理器
    signal(SIGINT, signal_handler);

    // 死循环
    while(1);
    
    return 0;
}

除了可读性更好外,可以看到我们要处理的信号SIGINT被和处理函数signal_handler绑定到了一起。当在运行程序的终端中按下Ctrl+C键时,会输出Received signal: 2,表示程序已经收到了SIGINT信号。

阻塞信号

我们也可以使用sigprocmask()系统调用来阻塞一个或多个信号,使得这些信号在阻塞期间不会被处理程序接收。

#include <iostream>
#include <csignal>
#include <unistd.h>

void signal_handler(int signum){
    std::cout<<"Received signal: "<<signum<<std::endl;
}

int main(){
    sigset_t blockset, tempset;
    sigemptyset(&blockset); // 清空信号阻塞集
    sigaddset(&blockset, SIGINT); // 将SIGINT信号添加到阻塞集

    // 阻塞SIGINT信号
    sigprocmask(SIG_BLOCK, &blockset, &tempset);
    std::cout<<"Blocked SIGINT signal!"<<std::endl;

    // 休眠20s
    sleep(20);

    // 取消阻塞SIGINT信号
    sigprocmask(SIG_SETMASK, &tempset, NULL);
    std::cout<<"Unblocked SIGINT signal!"<<std::endl;

    // 死循环
    while(1);

    return 0;
}

在程序开始时,我们创建了一个信号阻塞集blockset,将SIGINT信号添加到其中。接着,我们调用sigprocmask()函数来将阻塞集中的信号阻塞,使得在接下来的20s内不会接收到SIGINT信号,并输出Blocked SIGINT signal!。之后,恢复原信号屏蔽位,重新接收SIGINT信号,并输出Unblocked SIGINT signal!

发送信号

使用kill()系统调用可以向指定进程内发送指定信号。

#include <iostream>
#include <csignal>
#include <unistd.h>
#include <sys/types.h>

void signal_handler(int signum){
    std::cout<<"Received signal: "<<signum<<std::endl;
}

int main(){
    pid_t pid = getpid();
    std::cout<<"Main process PID: "<<pid<<std::endl;

    // 注册SIGUSR1信号处理器
    signal(SIGUSR1, signal_handler);

    // 休眠3s
    std::cout<<"Sleeping for 3s..."<<std::endl;
    sleep(3);

    // 向本进程发送SIGUSR1信号
    kill(pid, SIGUSR1);
    std::cout<<"Sent SIGUSR1 signal to process "<<pid<<"!"<<std::endl;

    // 死循环
    while(1);

    return 0;
}

程序开始运行时,先获取本程序的PID,并输出,之后注册SIGUSR1信号处理器。在休眠3s后,使用kill()函数向本进程发送SIGUSR1信号,并输出发送成功的信息。在程序运行过程中,可以在终端中使用kill -USR1 [PID]命令来模拟发送SIGUSR1信号。

注意事项
  • 信号处理尽可能的简洁,不要在信号处理中进行复杂的运算,避免影响程序的正常运行。
  • 信号处理函数需要是可重入的,即多个信号同时到达时,处理函数需要能够正确处理。
  • 信号处理在Windows系统中并不是很实用,因为Windows并没有提供一个很好的信号处理机制,Windows下处理异常通常使用SEH(Structured Exception Handling)。
参考文献