详解apue中TELL_WAIT,WAIT_PARENT,TELL_CHILD

在apue中使用TELL_WAIT,WAIT_PARENT,TELL_CHILD函数用来对父子进程进行同步

TELL_WAIT,WAIT_PARENT,TELL_CHILD代码实现
#include <signal.h>

static sig_atomic_t sigflag;   // 信号标志位
static sigset_t newmask, oldmask, zeromask;
static void sig_handler(int signo) {
    sigflag = 1;
}
void TELL_WAIT(void) {
    if (signal(SIGUSR1, sig_handler) == SIG_ERR)  // 安装SIGUSR1信号处理程序
        perror("signal(SIGUSR1) error"); 
    if (signal(SIGUSR2, sig_handler) == SIG_ERR)  // 安装SIGUSR2信号处理程序
        perror("signal(SIGUSR2) error");

    sigemptyset(&zeromask);
    sigemptyset(&newmask);
    sigaddset(&newmask, SIGUSR1);
    sigaddset(&newmask, SIGUSR2);

    // 阻塞SIGUSR1和SIGUSR2信号,保存旧的信号屏蔽字,并设置新的信号屏蔽字
    if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0)
        perror("SIG_BLOCK error");
}

void TELL_PARENT(pid_t pid) {
    kill(pid, SIGUSR2); // 向父进程发送SIGUSR2信号,表示子进程已经执行完毕
}

void WAIT_PARENT(void) {
    while (sigflag == 0)
        sigsuspend(&zeromask);  // 解除对SIGUSR1和SIGUSR2信号的阻塞,等待信号到来
    sigflag = 0;

    // 恢复原有的信号屏蔽字
    if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)
        perror("SIG_SETMASK error");
}

void TELL_CHILD(pid_t pid) {
    kill(pid, SIGUSR1);  // 向子进程发送SIGUSR1信号,表示父进程已经执行完毕
}

void WAIT_CHILD(void) {
    while (sigflag == 0)
        sigsuspend(&zeromask);
    sigflag = 0;

    if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)
        perror("SIG_SETMASK error");
}

以下对上述代码进行分析

[1]
在TELL_WAIT中首先对SIGUSR1,SIGUSR2设置信号捕捉函数,然后在newmask中添加这两个信号
然后调用sigprocmask阻塞这两个信号,并将原先的进程的信号集保存在oldmask中。
这是为了在之后的wait_parent/child函数返回后恢复原先的进程信号屏蔽集。

  • 可以看到调用了sigemptyset(&zeromask)函数置零zeromask,信号集zeromask信号集会在之后解释。
void TELL_WAIT(void) {
    if (signal(SIGUSR1, sig_handler) == SIG_ERR)  // 安装SIGUSR1信号处理程序
        perror("signal(SIGUSR1) error"); 
    if (signal(SIGUSR2, sig_handler) == SIG_ERR)  // 安装SIGUSR2信号处理程序
        perror("signal(SIGUSR2) error");

    sigemptyset(&zeromask);
    sigemptyset(&newmask);
    sigaddset(&newmask, SIGUSR1);
    sigaddset(&newmask, SIGUSR2);
    
    // 阻塞SIGUSR1和SIGUSR2信号,保存旧的信号屏蔽字,并设置新的信号屏蔽字
    if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0)
        perror("SIG_BLOCK error");
}
[2]再来看WAIT_PARENT函数
void WAIT_PARENT(void) {
    while (sigflag == 0)
        sigsuspend(&zeromask);  // 解除对SIGUSR1和SIGUSR2信号的阻塞,等待信号到来
    sigflag = 0;

    // 恢复原有的信号屏蔽字
    if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)
        perror("SIG_SETMASK error");
}

在该函数中调用sigsuspend(&zeromask);用来等待SIGUSR1和SIGUSR2信号的触发.

这时你可能会有一个问题,我在调用TELL_WAIT函数后已经设置了对SIGUSR1和SIGUSR2的阻塞,
SIGUSR1和SIGUSR2无法递达,sigsuspend怎么可以等待这两个信号的到来呢?

  因为zeromask中并没有添加任何信号,当调用sigsuspend()函数时,
它会暂时使用zeromask替换当前的阻塞信号集
而zeromask是全0信号集所以解除了对所有信号的阻塞,等待任何信号的到来。
所以当SIGUSR1或SIGUSR2信号到来时会调用设定的回调函数sigflag变量会被置1退出循环while (sigflag == 0)。

这时你可能又会有令一个问题,既然sigsuspend()会解除对所有信号的屏蔽等待SIGUSR1或SIGUSR2信号
那我在TELL_WAIT中设置对这两个信号的阻塞的意义是什么?

  考虑一种情况在你的子进程中执行到WAIT_PARENT函数之前父进程已经调用了TELL_CHILD函数向子进程发送了SIGUSR1信号而你并没有设置对该信号的屏蔽,信号处理函数提前被调用,这时wait函数还未被调用
你的程序中又做了其他操作等等比如sigflag又被置为0
再调用wait_parent而父进程已经发送过TELL_CHILD,使子进程被彻底阻塞。
所以是为了wait_parent时再处理该信号,避免各种不可预测的行为。

[3]在TELL_WAIT中的这段代码的作用是什么?
    if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)
        perror("SIG_SETMASK error");

解除对SIGUSR1或SIGUSR2的阻塞恢复调用TELL_WAIT之前进程所设置的信号屏蔽集
所以如果你已经进行了父子进程同步,(调用过了WAIT_CHILD/PARENT函数)
想要再次进行同步操作需要重新调用TELL_WAIT函数设置对SIGUSR1或SIGUSR2信号的阻塞

WAIT_PARENT();
TELL_WAIT();//之后还会有进行同步的情况,立刻调用TELL_WAIT();

但是以上操作不是原子性的
所以可以考虑更改WAIT_PARENT();函数调用后不恢复原始的信号屏蔽集
再写一个方法用来恢复信号屏蔽集,在不需要进行同步后调用该方法

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值