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++类中多线程的编码方式

相关推荐
程序猿阿伟10 分钟前
《平衡之策:C++应对人工智能不平衡训练数据的数据增强方法》
前端·javascript·c++
CodeGrindstone35 分钟前
Muduo网络库剖析 --- 架构设计
网络·c++·网络协议·tcp/ip
9毫米的幻想35 分钟前
【C++】—— set 与 multiset
开发语言·c++·rpc
想成为高手49938 分钟前
深入理解AVL树:结构、旋转及C++实现
开发语言·数据结构·c++
£suPerpanda40 分钟前
P3916 图的遍历(Tarjan缩点和反向建边)
数据结构·c++·算法·深度优先·图论
黑果果的思考1 小时前
C++看懂并使用-----回调函数
开发语言·c++
Neophyte06085 小时前
C++算法练习-day52——216.组合总和3
开发语言·c++·1024程序员节
Lbs_gemini06037 小时前
C++研发笔记14——C语言程序设计初阶学习笔记12
c语言·开发语言·c++·笔记·学习
我的老子姓彭10 小时前
C++学习笔记
c++·笔记·学习
hefaxiang10 小时前
【C++】数组
开发语言·c++