学懂C++ (二十):高级教程——通过C++信号处理实例解析学懂信号处理机制

先来看一个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信号被接收到时,操作系统将2SIGINT的编号)作为参数传入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): 终止信号

三、代码执行流程总结

  1. 程序开始执行 :主函数进入循环,初始化i为0。
  2. 信号注册 :调用 signal(SIGINT, signalHandler) 注册信号处理函数。
  3. 循环输出 :每次循环增加i并输出"Going to sleep...."。
  4. 主动发送信号 :当i等于3时,调用raise(SIGINT),主动发送SIGINT信号。
  5. 信号处理
    • 操作系统接收到SIGINT信号,调用注册的signalHandler函数,并将signum参数设置为2。
    • signalHandler中,输出"Interrupt signal (2) received."并结束程序。

通过这种方式,程序优雅地响应了外部信号,并在适当时机清理和退出

相关推荐
编程零零七1 小时前
Python数据分析工具(三):pymssql的用法
开发语言·前端·数据库·python·oracle·数据分析·pymssql
2401_858286112 小时前
52.【C语言】 字符函数和字符串函数(strcat函数)
c语言·开发语言
铁松溜达py2 小时前
编译器/工具链环境:GCC vs LLVM/Clang,MSVCRT vs UCRT
开发语言·网络
everyStudy2 小时前
JavaScript如何判断输入的是空格
开发语言·javascript·ecmascript
C-SDN花园GGbond4 小时前
【探索数据结构与算法】插入排序:原理、实现与分析(图文详解)
c语言·开发语言·数据结构·排序算法
迷迭所归处5 小时前
C++ —— 关于vector
开发语言·c++·算法
架构文摘JGWZ5 小时前
Java 23 的12 个新特性!!
java·开发语言·学习
leon6255 小时前
优化算法(一)—遗传算法(Genetic Algorithm)附MATLAB程序
开发语言·算法·matlab
CV工程师小林5 小时前
【算法】BFS 系列之边权为 1 的最短路问题
数据结构·c++·算法·leetcode·宽度优先
white__ice6 小时前
2024.9.19
c++