Linux-信号3_sigaction、volatile与SIGCHLD


前言

本章内容主要对之前的内容做一些补充。


一、sigaction

#include <signal.h>
int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);

之前我们学过signal来对信号进行捕捉,sigaction也是一个对信号进行捕捉的系统接口函数,不过sigaction要相对复杂一些。

参数 int signum 是要捕捉的信号编号。

参数struct* sigaction 在这里作为输入型参数,是提供给我们的一个结构体指针类型,这里的结构体名和函数名相同。

参数struct sigaction *oldact 在这里作为输出型参数。

那么struct sigaction 里面有什么呢?

struct sigaction
  {
    /* Signal handler.  */
#ifdef __USE_POSIX199309
    union
      {
	/* Used if SA_SIGINFO is not set.  */
	__sighandler_t sa_handler;
	/* Used if SA_SIGINFO is set.  */
	void (*sa_sigaction) (int, siginfo_t *, void *);
      }
    __sigaction_handler;
# define sa_handler	__sigaction_handler.sa_handler
# define sa_sigaction	__sigaction_handler.sa_sigaction
#else
    __sighandler_t sa_handler;
#endif

    /* Additional set of signals to be blocked.  */
    __sigset_t sa_mask;

    /* Special flags.  */
    int sa_flags;

    /* Restore handler.  */
    void (*sa_restorer) (void);
  };

我们今天主要对函数体内部的sa_handler和sa_mask进行讨论

__sighandler_t sa_handler;

typedef void __signalfn_t(int);
typedef __signalfn_t *__sighandler_t;

根据__sighandler_t的定义,我们可以知道其本质是一个函数指针,所以这里的我们就可以知道其实本质也是像signal一样使用回调函数来进行信号的捕捉。

__sigset_t sa_mask;

typedef __sigset_t sigset_t;

之前我们在学习sigprocmask和sigaddset等信号集接口函数的时候有过接触sigset_t,那么这里的sa_mask是什么呢?

先提出一个观点,在一个信号被处理(递达)过程中,如果同一个信号再次被发送且进入pending表,那么OS是怎样处理的? OS的处理方式是block(阻塞)相同信号,不再重复递达,等到处理完正在被处理的信号再根据情况决定。 而sa_mask在这里的作用就是可以根据其信号集的有效信号,在signum信号正在被处理时,同时阻塞sa_mask的有效信号和其本身信号。

示例代码如下

#include<iostream>
#include<cstdio>
#include<signal.h>
#include<unistd.h>

void ShowPending()
{
    sigset_t pending;
    sigemptyset(&pending);
    for (int i = 1; i <= 31; i++)
    {
        sigpending(&pending);
        // 通过sigismember来打印我们的pending信号集
        std::cout << sigismember(&pending, i);
    }
    std::cout << std::endl;
}

void catchSig(int signum)
{
    std::cout << "捕捉到" << signum << "信号!" << std::endl; 

    int count = 0;
    while(1)
    {
        ShowPending();
        count++;
        if(count == 50) break;
        sleep(5);
    }
}
int main()
{
    std::cout << "pid: " << getpid() << std::endl;

    //1.定义struct sigaction类型
    struct sigaction act , oldact;

    //2.mask信号集初始化
    sigset_t mask;
    sigemptyset(&mask);

    //3.mask信号集添加1号,2号,3号, 4号,5号,6号作为有效信号
    sigaddset(&mask,1);
    sigaddset(&mask,2);
    sigaddset(&mask,3);
    sigaddset(&mask,4);
    sigaddset(&mask,5);
    sigaddset(&mask,6);

    //4.修改act中的数据
    act.sa_handler = catchSig;
    act.sa_mask = mask;

    //5.调用sigaction
    sigaction(2, &act , &oldact);

    while(1) sleep(1);

    return 0;
}

运行结果
在这里插入图片描述

二、volatile关键字

我们之前的学习过程中,也提到过编译器会进行优化,例如我们之前讲的拷贝构造和右值引用都有提到过,而volatile主要解决关于编译器优化所导致的问题。

是的,编译器优化在少数情况下是会造成一些问题的。

而Linux中的gcc编译器是有几种不同程度的优化方案的

-O -O0 -O1 -O2 -O3 -Os -Ofast -Og

在使用gcc或g++命令时,上面的选项从左到右,编译时优化程度依次变大。

示例代码如下

#include<iostream>
#include<cstdio>
#include<signal.h>
#include<unistd.h>

int flag = 0;

void catchSig(int signum)
{
    std:: cout << flag ;
    flag = 1;
    std::cout << "->" << flag <<std::endl; 
}

int main()
{
    signal(2, catchSig);
    while(1)
    {
        if(flag == 1) break;
        ;
    }
    std::cout << "程序正常退出" << std::endl;
    return 0;
}

这段代码如果使用

g++ -o mysignal mysignal.cc -std=c++11

进行编译

结果则是
在这里插入图片描述

这段代码如果使用

g++ -o mysignal mysignal.cc -std=c++11 -O3

进行编译

结果则是
在这里插入图片描述
程序不会自动退出。

这是因为在-O3的优化程度下,编译器检测默认执行流不会修改flag的数据,所以这里的cpu寄存器一直储存着原有的flag值0,导致在判断flag时,一直使用寄存器中的0在判断,导致循环无法退出。

现在我们使用volatile来试试

#include<iostream>
#include<cstdio>
#include<signal.h>
#include<unistd.h>

volatile int flag = 0;

void catchSig(int signum)
{
    std:: cout << flag ;
    flag = 1;
    std::cout << "->" << flag <<std::endl; 
}

int main()
{
    signal(2, catchSig);
    while(1)
    {
        if(flag == 1) break;
        ;
    }
    std::cout << "程序正常退出" << std::endl;
    return 0;
}

在这里插入图片描述
这个时候程序就正常推出了,所以这里volatile的意思就是让告诉编译器不要对flag进行优化,要让寄存器看到内存中的数据!


三、SIGCHLD

SIGCHLD 在子进程停止或者退出时可能收到。

所以我们再学习几种进程等待的方法。

方法一

#include <iostream>
#include <cstdio>
#include <signal.h>
#include <unistd.h>

int main()
{
    signal(SIGCHLD, SIG_IGN);
    pid_t id = fork();
    if(id == 0)
    {
        //子进程
        sleep(10);
        exit(0);
    }

    //父进程
    while(1);
    return 0;
}

将SIGCHLD信号的处理方式变为忽略。
在这里插入图片描述
子进程没有僵尸,而是成功回收。

方法二

#include <iostream>
#include <cstdio>
#include <signal.h>
#include <unistd.h>
#include <sys/wait.h>
void catchCHLD(int signum)
{
    std::cout << "捕捉到SIGCHLD信号!" << std::endl;
    int pid = 0;
    while((pid = waitpid(0,nullptr,WNOHANG)) > 0)
    {
        std::cout << "成功等待" << pid << "号进程" << std::endl;
    }
}

int main()
{
    signal(SIGCHLD, catchCHLD);
    pid_t id = fork();
    if(id == 0)
    {
        //子进程
        sleep(10);
        exit(0);
    }

    //父进程
    while(1);
    return 0;
}

在这里插入图片描述
也同样可以成功回收!

  • 23
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

风君子吖

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值