APUE第十章用信号实现了父子进程间的同步,关键点是sigsuspend函数,这里仔细分析一下实现机制.
<tellwait.c>:
#include <signal.h>
#include "ourhdr.h"
/*
数据类型sig_atomic_t由ANSI C定义,在写时不会被中断。它意味着这种变量在具有虚存的系统上不会跨越页边界,可以用一条机器指令对其存取。这种类型的变量总是与ANSI类型修饰符volatile一并出现,防止编译器优化带来的不确定状态。
*/
static volatile sig_atomic_t sigflag;
/* set nonzero by signal handler */
static sigset_t newmask, oldmask, zeromask;
static void
sig_usr(int signo) /* one signal handler for SIGUSR1 and SIGUSR2 */
{
sigflag = 1;
return;
}
/*
父子进程共享正文段,所以,子进程继承信号处理函数;
另外,子进程复制父进程数据空间以及堆栈,所以前面的静态变量在子进程中仍然可以使用.
*/
void
TELL_WAIT()
{
if (signal(SIGUSR1, sig_usr) == SIG_ERR)
err_sys("signal(SIGINT) error");
if (signal(SIGUSR2, sig_usr) == SIG_ERR)
err_sys("signal(SIGQUIT) error");
sigemptyset(&zeromask);
sigemptyset(&newmask);
sigaddset(&newmask, SIGUSR1);
sigaddset(&newmask, SIGUSR2);
/* block SIGUSR1 and SIGUSR2, and save current signal mask */
if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0)
err_sys("SIG_BLOCK error");
}
void
TELL_PARENT(pid_t pid)
{
kill(pid, SIGUSR2); /* tell parent we're done */
}
/*
sigsuspend相当于sigprocmask + pause,但是它保证这两个操作是原子的,也就是说,在两者之间不会有信号过来.这就保证了信号肯定会被pause截住.这一点是非常重要的,因为如果在pause之前收到信号,信号处理函数被调用,此后如果不再有信号过来,pause将永远阻塞.
*/
void
WAIT_PARENT(void)
{
/*
解除对两个信号的阻塞,并睡眠,直到有信号过来
*/
while (sigflag == 0)
sigsuspend(&zeromask); /* and wait for parent */
sigflag = 0;
/* reset signal mask to original value */
if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)
err_sys("SIG_SETMASK error");
}
void
TELL_CHILD(pid_t pid)
{
kill(pid, SIGUSR1); /* tell child we're done */
}
/*
WAIT_CHILD与WAIT_PARENT完全一样
*/
void
WAIT_CHILD(void)
{
while (sigflag == 0)
sigsuspend(&zeromask); /* and wait for child */
sigflag = 0;
/* reset signal mask to original value */
if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)
err_sys("SIG_SETMASK error");
}
测试代码:
#include <sys/types.h>
#include "ourhdr.h"
int
main(void)
{
pid_t pid;
TELL_WAIT();
if ( (pid = fork()) < 0)
err_sys("fork error");
else if (pid == 0) {
WAIT_PARENT(); /* parent goes first */
printf("output from child\n");
} else {
printf("output from parent\n");
TELL_CHILD(pid);
}
exit(0);
}
这样,无论进程如何调度,子进程都将在父进程之后打印信息,达到同步效果.