C++中的操作系统级信号处理——signal与sigaction

在多进程的编程中,信号是一种非常重要的多进程通讯手段。而进程间的信号很大情况是和操作系统是相关的,或者说很多信号是从操作系统中过来的。

我们这一篇就来说一下操作系统的信号。

操作系统中的信号其实在操作系统中可以称作是中断,可以理解为一个循环执行的程序中突然收到一个通知,或者信号,操作系统分配一个中断处理程序来处理这个中断信号。基本上所有的操作系统都是基于这个逻辑。

而中断又可以简单的分成两个大类,硬件中断和软中断。硬件中断可以理解为在电路上就会有一个电信号来给操作系统一个中断,软中断就可以理解为一个逻辑上的通知。

一般来说,硬件中断的优先级要比软中断的优先级要高,关于中断的内容可以讲好多,具体可以参考操作系统原理,有机会再说这一块,后续看看是不是可以整理操作系统级的C++编程再说。今天先简单说一下软中断的处理。

std库的信号处理

上面提到了,信号实际上是和操作系统强绑定的,可以说是操作系统级别的内容了。而C++的std标准库在操作系统之上定义了一层,讲操作系统相关的内容封装了起来,统一对外提供了一个接口,将操作系统的一些标准信号给出了统一的定义。

这个定义在<singal.h>头文件中。

总共定义了32个软中断。

复制代码
#define SIGHUP  1       /* hangup */
#define SIGINT  2       /* interrupt */
#define SIGQUIT 3       /* quit */
#define SIGILL  4       /* illegal instruction (not reset when caught) */
#define SIGTRAP 5       /* trace trap (not reset when caught) */
#define SIGABRT 6       /* abort() */
#if  (defined(_POSIX_C_SOURCE) && !defined(_DARWIN_C_SOURCE))
#define SIGPOLL 7       /* pollable event ([XSR] generated, not supported) */
#else   /* (!_POSIX_C_SOURCE || _DARWIN_C_SOURCE) */
#define SIGIOT  SIGABRT /* compatibility */
#define SIGEMT  7       /* EMT instruction */
#endif  /* (!_POSIX_C_SOURCE || _DARWIN_C_SOURCE) */
#define SIGFPE  8       /* floating point exception */
#define SIGKILL 9       /* kill (cannot be caught or ignored) */
#define SIGBUS  10      /* bus error */
#define SIGSEGV 11      /* segmentation violation */
#define SIGSYS  12      /* bad argument to system call */
#define SIGPIPE 13      /* write on a pipe with no one to read it */
#define SIGALRM 14      /* alarm clock */
#define SIGTERM 15      /* software termination signal from kill */
#define SIGURG  16      /* urgent condition on IO channel */
#define SIGSTOP 17      /* sendable stop signal not from tty */
#define SIGTSTP 18      /* stop signal from tty */
#define SIGCONT 19      /* continue a stopped process */
#define SIGCHLD 20      /* to parent on child stop or exit */
#define SIGTTIN 21      /* to readers pgrp upon background tty read */
#define SIGTTOU 22      /* like TTIN for output if (tp->t_local&LTOSTOP) */
#if  (!defined(_POSIX_C_SOURCE) || defined(_DARWIN_C_SOURCE))
#define SIGIO   23      /* input/output possible signal */
#endif
#define SIGXCPU 24      /* exceeded CPU time limit */
#define SIGXFSZ 25      /* exceeded file size limit */
#define SIGVTALRM 26    /* virtual time alarm */
#define SIGPROF 27      /* profiling time alarm */
#if  (!defined(_POSIX_C_SOURCE) || defined(_DARWIN_C_SOURCE))
#define SIGWINCH 28     /* window size changes */
#define SIGINFO 29      /* information request */
#endif
#define SIGUSR1 30      /* user defined signal 1 */
#define SIGUSR2 31      /* user defined signal 2 */

这些相当于是操作系统给出的一些信号,可以由进程自定义的来处理。

在代码里就可以通过signal函数来注册某种信号的处理函数了。

复制代码
#include <signal.h>
#include <unistd.h>

void signalHandler(int signum)
{
    std::cout<<"Interrupt signal (" << signum<< ") received"<<std::endl;

    exit(signum);
}

int main() {
    signal(SIGINT, signalHandler);

    while(true)
    {
        std::cout<<"going to sleep ... " << std::endl;
        sleep(1);
    }

    return 0;
}

上面的代码就是实现了,当用户输入一个Ctrl+C的中断指令时,可以完成程序员一些自定义的处理。

使用raise函数

signal库中提供了一个raise函数用于信号处理函数的模拟测试,也就是说可以自己给自己发一个信号,看看程序是否可以运行正常。

因为是只能自己给自己发信号,所以除了测试以外,就只能是处理自定义的信号了。

上面的信号中,有两个用户自定义信号。也就是说可以通过raise来发送这两个信号,来实现进程中的消息发送(操作系统已经定义好的信号不建议改变其固有逻辑)。

个人理解是起到了一个GOTO语句的作用。

复制代码
#include <stdio.h>
#include <signal.h>
#include <stdlib.h>

// 定义一个信号处理函数
void signal_handler(int signal) {
    printf("Received signal %d\n", signal);
    exit(0); // 收到信号后退出程序
}

int main() {
    // 设置SIGUSR1信号的处理函数为signal_handler
    if (signal(SIGUSR1, signal_handler) == SIG_ERR) {
        perror("Error setting signal handler");
        return 1;
    }

    printf("Raising SIGINT signal\n");
    // 使用raise函数发送SIGINT信号给当前进程
    raise(SIGUSR1);

    // 如果没有收到信号,程序会继续执行到这里
    printf("This line will not be executed\n");

    return 0;
}

另外,这个signal和raise函数是无法正常处理32及以上的信号的。如果使用32以上的信号,信号处理函数是无法被调用的。

更牛逼的sigaction

上面的代码中,定义的这个信号处理函数只能收到一个信号,无法有更多的信息。

在std库的signal包中,还提供了一个更厉害一点的处理方法:sigaction。

在signal.h中看一下这个sigaction的结构体:

复制代码
struct  sigaction {
	union __sigaction_u __sigaction_u;  /* signal handler */
	sigset_t sa_mask;               /* signal mask to apply */
	int     sa_flags;               /* see signal options below */
};
  • 第一个成员是一个union类型,这个union的类型定义为:

    复制代码
    /* union for signal handlers */
    union __sigaction_u {
        void    (*__sa_handler)(int);
        void    (*__sa_sigaction)(int, struct __siginfo *,
            void *);
    };

    也就是说,如果使用第一个__sa_handler的时候,就是和上面提到的普通的信号处理一样使用。

    这里还配套了两个宏定义:

    复制代码
    /* if SA_SIGINFO is set, sa_sigaction is to be used instead of sa_handler. */
    #define sa_handler      __sigaction_u.__sa_handler
    #define sa_sigaction    __sigaction_u.__sa_sigaction

    下面的代码一起来写这一块。

  • 如果使用另外一个union的成员的话,操作系统就会将siginfo的内容提供到信号响应函数。

  • 先看一下这个siginfo是什么东西,signal中也给出了定义:

    复制代码
    typedef struct __siginfo {
        int     si_signo;               /* signal number */
        int     si_errno;               /* errno association */
        int     si_code;                /* signal code */
        pid_t   si_pid;                 /* sending process */
        uid_t   si_uid;                 /* sender's ruid */
        int     si_status;              /* exit value */
        void    *si_addr;               /* faulting instruction */
        union sigval si_value;          /* signal value */
        long    si_band;                /* band event for SIGPOLL */
        unsigned long   __pad[7];       /* Reserved for Future Use */
    } siginfo_t;
  • 上述内容需要使用的话,需要配合第三个参数sa_flags用,将sa_flags赋值为SA_SIGINFO。

不多说,直接上代码吧:

复制代码
#include <iostream>
#include <csignal>
#include <unistd.h>

// 定义一个信号处理函数
void signalHandler(int signum) {
    std::cout << "Interrupt signal (" << signum << ") received.\n";

}

void signalHandlerMore(int signum, siginfo_t *info, void *context) {
    std::cout << "Received signal " << signum << std::endl;
    if (info) {
        std::cout << "Signal originates from process: " << info->si_pid << std::endl;
    }
    
}

int main() {
    struct sigaction action;
    // 设置信号处理函数
    action.sa_handler = signalHandler;
    // 清空信号集
    sigemptyset(&action.sa_mask);
    // 不使用特殊标志
    action.sa_flags = 0;
    
    struct sigaction action_more;
    // 设置信号处理函数
    action_more.sa_sigaction = signalHandler;
    // 清空信号集
    sigemptyset(&action_more.sa_mask);
    // 不使用特殊标志
    action.sa_flags = 0;

    // 注册信号处理函数
    if (sigaction(SIGUSR1, &action, NULL) < 0) {
        perror("sigaction");
        return 1;
    }
    
    // 注册信号处理函数
    if (sigaction(SIGUSR2, &action_more, NULL) < 0) {
        perror("sigaction");
        return 1;
    }
    
    raise(SIGUSR1);
    raise(SIGUSR2);

    return 0;
}

context可以传一些参数进行,比如this指针之类,就可以在不同的函数中实现传参了,参考:

C++类中多线程的编码方式

相关推荐
mljy.23 分钟前
C++《C++11》(上)
c++
艾莉丝努力练剑27 分钟前
GCC编译器深度解剖:从源码到可执行文件的全面探索
c++·ide·经验分享·gcc
源代码•宸1 小时前
Leetcode—721. 账户合并【中等】
c++·经验分享·算法·leetcode·并查集
melonbo1 小时前
c++工程如何提供http服务接口
c++·http
程序喵大人1 小时前
写C++十年,我现在怎么设计类和模块?(附真实项目结构)
开发语言·c++·类和模板
liulilittle2 小时前
Unix/Linux 平台通过 IP 地址获取接口名的 C++ 实现
linux·开发语言·c++·tcp/ip·unix·编程语言
深耕AI2 小时前
【MFC 小白日记】对话框编辑器里“原型图像”到底要不要勾?3 分钟看懂!
c++·编辑器·mfc
Nerd Nirvana2 小时前
C++编程——异步处理、事件驱动编程和策略模式
开发语言·c++·策略模式·嵌入式开发·事件驱动·异步处理
一拳一个呆瓜2 小时前
【MFC】对话框节点属性:Condition(条件)
c++·mfc
快去睡觉~2 小时前
力扣416:分割等和子集
数据结构·c++·算法·leetcode·职场和发展·动态规划