53 读书笔记:第10章 信号 (6)

10.16 sigsuspend函数

        函数sigsuspend,在一个原子操作中先恢复信号屏蔽字,然后使进程休眠。

       #include <signal.h>

       int sigsuspend(const sigset_t *sigmask);
        // 返回值:-1,并将errno设置为EINTR

        将进程的信号屏蔽字设置为由sigmask指向的值。在捕捉到一个信号或发生了一个会终止该进程的信号之前,该进程挂起。如果捕捉到一个信号而且从该信号处理程序返回,则sigsuspend返回,并且将该进程的信号屏蔽字设置为调用sigsuspend之前的值。

        注意,此函数没有成功返回值。如果它返回到调用者,则总是返回-1,并将errno设置为EINTR(表示一个被中断的系统调用)。


        程序清单10-15显示了保护临界区,使其不被特定信号中断的正确方法。

        《UNIX环境高级编程》P269:程序清单10-15 保护临界区不被信号中断(有改动)

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <errno.h>

static void sig_int(int signo);
void pr_mask(const char *str);

int main(void)
{
    sigset_t    newmask, oldmask, waitmask;

    pr_mask("program start: ");

    if (signal(SIGINT, sig_int) == SIG_ERR)
        fprintf(stderr, "signal(SIGINT) error\n");

    sigemptyset(&waitmask);
    sigaddset(&waitmask, SIGUSR1);

    sigemptyset(&newmask);
    sigaddset(&newmask, SIGINT);

    // 添加SIGINT信号,保存当前信号屏蔽字
    if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0)
        fprintf(stderr, "SIG_BLOCK error\n");

    pr_mask("in critical region: ");

    // 等待除了SIGUSR1之外的信号
    if (sigsuspend(&waitmask) != -1)
        fprintf(stderr, "sigsuspend erro\n");

    pr_mask("after return from sigsuspend: ");

    // 恢复原来的信号屏蔽字
    if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)
        fprintf(stderr, "SIG_SETMASK error\n");

    pr_mask("program exit: ");

    exit(0);
}

static void sig_int(int signo)
{
    pr_mask("\nin sig_int: ");
}

// 输出信号屏蔽字
void pr_mask(const char *str)
{
    sigset_t    sigset;
    int            errno_save;

    errno_save = errno;
    if (sigprocmask(0, NULL, &sigset) < 0)
        fprintf(stderr, "sigprocmask error\n");

    printf("%s", str);
    if (sigismember(&sigset, SIGINT))
        printf("SIGINT ");
    if (sigismember(&sigset, SIGQUIT))
        printf("SIGQUIT ");
    if (sigismember(&sigset, SIGUSR1))
        printf("SIGUSR1 ");
    if (sigismember(&sigset, SIGALRM))
        printf("SIGALRM ");

    printf("\n");
    errno = errno_save;
}

        注意,当sigsuspend返回时,它将信号屏蔽字设置为调用它之前的值。在本例中SIGINT信号被阻塞。因此将信号屏蔽字复位为早先保存的值(oldmask)。

$ ./15
program start: 
in critical region: SIGINT 
^C                                      键入中断字符(Ctrl+C)
in sig_int: SIGINT SIGUSR1 
after return from sigsuspend: SIGINT 
program exit:

        在调用sigsuspend时,将SIGUSR1信号加到了进程信号屏蔽字中(添加,确定?但,SIGINT信号没有被屏蔽。设置为),所以当运行该信号处理程序时,我们得知信号屏蔽字已经改变了。从中可见,在sigsuspend返回时,它将信号屏蔽字恢复为调用它之前的值。


        sigsuspend的另一种应用是等待一个信号处理程序设置一个全局变量。程序清单10-16用于捕捉中断信号和退出信号,但是仅当捕捉到退出信号时,才唤醒主例程。

        《UNIX环境高级编程》P271:程序清单10-16 用sigsuspend等待一个全局变量被设置

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>

volatile sig_atomic_t   quitflag;

static void sig_int(int signo)
{
    if (signo == SIGINT)
        printf("\ninterrupt\n");
    else if (signo == SIGQUIT)
        quitflag = 1;               // 设置全局变量
}

int main(void)
{
    sigset_t        newmask, oldmask, zeromask;

    if (signal(SIGINT, sig_int) == SIG_ERR)
        fprintf(stderr, "signal(SIGINT) error\n");
    if (signal(SIGQUIT, sig_int) == SIG_ERR)
        fprintf(stderr, "signal(SIGQUIT) error\n");

    sigemptyset(&zeromask);
    sigemptyset(&newmask);
    sigaddset(&newmask, SIGQUIT);

    // 添加SIGQUIT,保存当前信号屏蔽字
    if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0)
        fprintf(stderr, "SIG_BLOCK error\n");

    while (quitflag == 0)
        sigsuspend(&zeromask);

    // SIGQUIT被捕获,
    quitflag = 0;

    // 恢复信号屏蔽字
    if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)
        fprintf(stderr, "SIG_SETMASK error\n");

    exit(0);
}

        此程序的示例输出:

$ ./16
^C                  键入中断字符(Ctrl+C)
interrupt
^C
interrupt
^C
interrupt
^C
interrupt
^\$                 用退出键终止(Ctrl+\)

        可以用信号实现父、子进程之间的同步,这是信号应用的另一个实例。程序清单10-17包含了8.9节提到的五个例程的实现,它们是:TELL_WAIT、TELL_PARENT、TELL_CHILD、WAIT_PARENT和WAIT_CHILD。

        《UNIX环境高级编程》P272:程序清单10-17 父子进程可用来实现同步的例程(有改动)

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>

static volatile sig_atomic_t    sigflag;
static sigset_t    newmask, oldmask, zeromask;

static void sig_usr(int signo)
{
    sigflag = 1;
}

void TELL_WAIT(void)
{
    if (signal(SIGUSR1, sig_usr) == SIG_ERR)
        fprintf(stderr, "signal(SIGUSR1) error\n");
    if (signal(SIGUSR2, sig_usr) == SIG_ERR)
        fprintf(stderr, "signal(SIGUSR2) error\n");

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

    // 添加 SIGUSR1 和 SIGUSR2,保存当前信号屏蔽字
    if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0)
        fprintf(stderr, "SIG_BLOCK error\n");
}

void TELL_PARENT(pid_t pid)
{
    kill(pid, SIGUSR2);                // 通知父进程,准备完毕
}

void WAIT_PARENT(void)
{
    while (sigflag == 0)
        sigsuspend(&zeromask);        // 等待父进程
    sigflag = 0;

    // 恢复原来的信号屏蔽字
    if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)
        fprintf(stderr, "SIG_STEMASK error\n");
}

void TELL_CHILD(pid_t pid)
{
    kill(pid, SIGUSR1);                // 通知子进程,准备完毕
}

void WAIT_CHILD(void)
{
    while (sigflag == 0)
        sigsuspend(&zeromask);        // 等待子进程
    sigflag = 0;

    // 恢复原来的信号屏蔽字
    if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)
        fprintf(stderr, "SIG_SETMASK error\n");
}

        其中使用了两个用户定义的信号:SIGUSR1由父进程发送给子进程,SIGUSR2由子进程发送给父进程。

        如果在等待信号发生时希望去休眠,则使用sigsuspend函数是非常适当的。但是如果在等待信号期间希望调用其他系统函数,可以使用多线程,专门安排一个线程处理信号。

        如果不是用线程,那么我们能尽力做到做好的是,当信号发生时,在信号捕捉程序对一个全局变量置1。

你可能感兴趣的:(读书笔记,《UNIX环境高级编程》)