Linux | 信号【02】

一、竞态条件

进程运行期间由于在竞争,在程序执行阶段先后顺序发生变化导致结果不一样;

1、pause函数
int pause(void);
/*
* @func:导致调用进程(或线程)进入休眠状态,直到一个信号被传递,该信号要么终止了进程,要么导致调用信号捕获函数;
* return:若信号动作是捕捉,则调用完信号处理函数后,pause返回-1;
*/
2、时序竞态

在多个进程中,若出现了优先级较高的进程,有可能会导致该程序无法按照顺序进行执行,从而导致程序卡死,报错等现象;

/*----------------------------------------------------------------------
	> File Name: my_sleep.cpp
	> Author: Jxiepc
	> Mail: Jxiepc
	> Created Time: Mon 27 Dec 2021 04:19:28 PM CST
----------------------------------------------------------------------*/

#include <iostream>
#include <unistd.h>
#include <signal.h>
using namespace std;

void catch_sig(int signo){
    cout << "catch..." << endl;
}

/**
 * pause
 * -----
 * 自定义一个sleep函数使用pause结合alarm
 * */
unsigned int my_sleep(unsigned int seconds){
    /* 注册捕捉信号 */
    struct sigaction act, oldact;
   
    act.sa_handler = catch_sig;
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;
    // 注册信号
    int ret = sigaction(SIGALRM, &act, &oldact);
    if(ret < 0){
        cout << "si`gaction error " << endl;
        exit(1);
    }
    // 计时发送ALRM信号
    alarm(seconds);
    // 将程序挂起,先处理好信号再处理程序挂起
    ret = pause();

    // 恢复注册信号
    ret = alarm(0);
    sigaction(SIGALRM, &oldact, NULL);

    return ret;
}


int main(int argc, char* argv[])
{
    while(1){
        my_sleep(3);
        cout << "----------------" << endl;
    }
    return 0;
}

2.1 如何解决时许竞态

可设置信号屏蔽来解决;但有可能在俩个操作期间失去cpu资源。故可将步骤合并成原子步骤。而sigsyspend即可满足,且用改函数替换pause

int sigsuspend(const sigset_t *mask)

/*
* @func:挂起等待信号,终止进程,期间进程信号屏蔽字由mask指定;
* return:返回时进程的信号屏蔽字恢复为原来对改信号的屏蔽态,并且总是返回-1;
* 注:无法阻止SIGKILL或SIGSTOP;
*/

竞态条件跟系统负载有着紧密联系,系统负载越严重,出现时序混乱,信号越不可靠

/*----------------------------------------------------------------------
	> File Name: impro_sleep.cpp
	> Author: Jxiepc
	> Mail: Jxiepc
	> Created Time: Mon 27 Dec 2021 04:50:14 PM CST
----------------------------------------------------------------------*/
#include <iostream>
#include <unistd.h>
#include <signal.h>
using namespace std;

void catch_sig(int signo){
    cout << "catch..." << endl;
}

/**
 * sigsuspend
 * -----
 * 自定义一个sleep函数使用sigsuspend结合alarm
 * 为了解决时序竞态问题,使用pause时将程序挂起
 * */
unsigned int my_sleep(unsigned int seconds){

    struct sigaction act, oldact;
    sigset_t newmask, oldmask, tmpmask;
   
    // 注册信号
    act.sa_handler = catch_sig;
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;

    int ret = sigaction(SIGALRM, &act, &oldact);
    if(ret < 0){
        cout << "si`gaction error " << endl;
        exit(1);
    }
    
    // 设置阻塞信号集
    sigemptyset(&newmask);
    sigaddset(&newmask, SIGALRM);
    sigprocmask(SIG_BLOCK, &newmask, &oldmask);

    // 计时发送ALRM信号
    alarm(seconds);

    // 剔除alarm信号
    tmpmask = oldmask;
    sigdelset(&tmpmask, SIGALRM);

    /**
     * 挂起等待
     * 当该函数被唤醒时,恢复原由的阻塞信号集
     * */ 
    sigsuspend(&tmpmask);

    // 恢复注册信号
    ret = alarm(0);
    sigaction(SIGALRM, &oldact, NULL);
    sigprocmask(SIG_SETMASK, &oldmask, NULL);

    return ret;
}


int main(int argc, char* argv[])
{
    while(1){
        my_sleep(3);
        cout << "----------------" << endl;
    }
    return 0;
}

3、全局变量异步IO

尽量少用全局变量,在通过全局变量传递信号时,容易出现时许混乱,导致程序卡死;

/*----------------------------------------------------------------------
	> File Name: asyn_add_num.cpp
	> Author: Jxiepc
	> Mail: Jxiepc
	> Created Time: Mon 27 Dec 2021 07:12:53 PM CST
----------------------------------------------------------------------*/

#include <iostream>
#include <unistd.h>
#include <signal.h>
using namespace std;

/** 定义全局变量 */
int num = 0, flag = 0;

/** 子进程捕捉函数 */
void catch_sig_child(int n){
    cout << "I am child num 【" << num << "】" << endl; 
    num += 2;
    flag = 1;
    sleep(1);
}

/** 父进程捕捉函数 */
void catch_sig_parent(int n){
    cout << "I am parent num 【" << num << "】" << endl; 
    num += 2;
    flag = 1;
    sleep(1);
}

int main(int argc, char* argv[])
{
    /* 创建子进程 */
    pid_t pid ;

    struct sigaction act_c, act_p;

    if((pid = fork()) == -1){
        cout << "fork error" << endl;
        exit(1);
    }else if(pid == 0){  /* 子进程 */
        num = 2;
        /* 注册信号 */
        act_c.sa_handler = catch_sig_child;
        sigemptyset(&act_c.sa_mask);
        act_c.sa_flags = 0;

        sigaction(SIGUSR2, &act_c, NULL);
        
        while(1){
            if(flag == 1){
                kill(getppid(), SIGUSR1); // 给父进程发送信号
                flag = 0;   
            }
        }
    }else {     /* 父进程 */
        num = 1;
        /* 注册信号 */
        act_p.sa_handler = catch_sig_parent;
        sigemptyset(&act_p.sa_mask);
        act_p.sa_flags = 0;

        sigaction(SIGUSR1, &act_p, NULL);
        
        catch_sig_parent(0);
        while(1){
            if(flag == 1){
                kill(pid, SIGUSR2); // 给父进程发送信号
                flag = 0;   
            }
        }
    }
    return 0;
}

4、可/不可重入函数

某个函数在调用执行期间,由于时序混乱导致被重复调用;

可重入函数

  • 函数内不能含有全局变量以及static变量,不能使用mallocfree
  • 且信号捕捉函数应该设计为可重入函数;

不可重入函数

  • 使用静态数据结构;
  • 调用了malloc或free;
  • 是标准I/O函数;

可通过man 7 signal进行查看

在这里插入图片描述

二、SIGCHLD信号
1、产生条件
  • 子进程终止时;
  • 子进程接收到SIGSTOP信号停止时;
  • 子进程处于停止态,接受到SIGCONT后唤醒时。
/*----------------------------------------------------------------------
	> File Name: killChild.cpp
	> Author: Jxiepc
	> Mail: Jxiepc
	> Created Time: Sat 25 Dec 2021 10:53:29 PM CST
----------------------------------------------------------------------*/

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

using namespace std;


/** 
 * kill
 * ----
 * func:测试杀死指定子进程
 * 参数1:pid
 * 参数2:信号
 * */
void testKill(){
    int i;
    pid_t pid, tmp_pid;

    for(i=0; i<5; ++i){
        pid = fork();
        if(pid == 0)
            break;
        if(i == 2)
            tmp_pid = pid;
    }

    /* 子进程 */
    if(i < 5){
        while(1){
            cout << "I am child " << i << " pid = " << pid << endl; 
            sleep(1);
        }
    }else { /* 父进程 */ 
        sleep(1);
        kill(tmp_pid, SIGKILL);
        while(1);
    }
}



int main(int argc, char* argv[])
{
    testKill();

    return 0;
}

三、信号传参
1、发送信号传参
int sigqueue(pid_t pid, int sig, const union sigval value);

/*
* @func:)将sig中指定的信号发送给PID中给定的进程。 发送信号所需的权限与kill(2)相同。 和kill(2)一样,null信号(0)可以用于检查具有给定PID的进程是否存在
* @param value:
* 		   union sigval {
               int   sival_int;
               void *sival_ptr;
           };
* return:成功返回0,错误返回-1;
*/
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Jxiepc

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

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

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

打赏作者

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

抵扣说明:

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

余额充值