在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();函数调用后不恢复原始的信号屏蔽集
再写一个方法用来恢复信号屏蔽集,在不需要进行同步后调用该方法