上面已经说明,更改进程的信号屏蔽字可以阻塞所选择的信号,或解除对它们的阻塞。使用这种技术可以保护不希望由信号中断的代码临界区。如果希望对一个信号解除阻塞,然后pause以等待以前被阻塞的信号发生,则又将如何呢?假定信号是SIGINT,实现这一点的一种不正确的方法是:

sigset_t  newmask, oldmask;
sigemptyset(&newmask);
sigaddset(&newmask, SIGINT);
/* block SIGINT and save current signal mask */
if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) <0)
    err_sys("SIG_BLOCK error");
/* critical region of code */
/* restore signal mask, which unblocks SIGINT */
if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)
    err_sys("SIG_SETMASK error");
/* window is open */
pause(); /* wait for signal to occur continue processing */

如果在信号阻塞时,产生了信号,那么该信号的传递就被推迟直到对它解除了阻塞。

对应用程序而言,该信号好像发生在解除对SIGINT的阻塞和pause之间(取决于内核如何实现信号)。如果发生了这种情况,或者如果在解除阻塞时刻和 pause 之间确实发生了信号,那么就会产生问题。因为可能不会再见到该信号,所以从这种意义上讲,在此时间窗口中发生的信号丢失了,这样就使得pause永远阻塞。这是早期的不可靠信号机制的另一个问题。

为了纠正此问题,需要在一个原子操作中先恢复信号屏蔽字,然后使进程休眠。这种功能是由sigsuspend函数所提供的。

#include <signal.h>
int sigsuspend(const sigset_t *sigmask);
//返回值:−1,并将errno设置为EINTR

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

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

实例

图10-22显示了保护代码临界区,使其不被特定信号中断的正确方法。

#include "apue.h"

static void    sig_int(int);

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

    pr_mask("program start: ");

    if (signal(SIGINT, sig_int) == SIG_ERR)
        err_sys("signal(SIGINT) error");
    sigemptyset(&waitmask);
    sigaddset(&waitmask, SIGUSR1);
    sigemptyset(&newmask);
    sigaddset(&newmask, SIGINT);

    /*
     * Block SIGINT and save current signal mask.
     */
    if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0)
        err_sys("SIG_BLOCK error");

    /*
     * Critical region of code.
     */
    pr_mask("in critical region: ");

    /*
     * Pause, allowing all signals except SIGUSR1.
     */
    if (sigsuspend(&waitmask) != -1)
        err_sys("sigsuspend error");

    pr_mask("after return from sigsuspend: ");

    /*
     * Reset signal mask which unblocks SIGINT.
     */
    if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)
        err_sys("SIG_SETMASK error");

    /*
     * And continue processing ...
     */
    pr_mask("program exit: ");

    exit(0);
}

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

图10-22 保护临界区不被信号中断

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

运行图10-22中的程序得到下面的输出:

$ ./a.out
program start:
in critical region: SIGINT
^C                键入中断字符
in sig_int: SIGINT SIGUSR1
after return from sigsuspend: SIGINT
program exit:

在调用sigsuspend时,将SIGUSRI信号加到了进程信号屏蔽字中,所以当运行该信号处理程序时,我们得知信号屏蔽字已经改变了。从中可见,在 sigsuspend 返回时,它将信号屏蔽字恢复为调用它之前的值。

实例

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

#include "apue.h"

volatile sig_atomic_t    quitflag;    /* set nonzero by signal handler */

static void
sig_int(int signo)    /* one signal handler for SIGINT and SIGQUIT */
{
    if (signo == SIGINT)
        printf("\ninterrupt\n");
    else if (signo == SIGQUIT)
        quitflag = 1;    /* set flag for main loop */
}

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

    if (signal(SIGINT, sig_int) == SIG_ERR)
        err_sys("signal(SIGINT) error");
    if (signal(SIGQUIT, sig_int) == SIG_ERR)
        err_sys("signal(SIGQUIT) error");

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

    /*
     * Block SIGQUIT and save current signal mask.
     */
    if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0)
        err_sys("SIG_BLOCK error");

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

    /*
     * SIGQUIT has been caught and is now blocked; do whatever.
     */
    quitflag = 0;

    /*
     * Reset signal mask which unblocks SIGQUIT.
     */
    if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)
        err_sys("SIG_SETMASK error");

    exit(0);
}

图10-23 用sigsuspend等待一个全局变量被设置

此程序的样本输出是:

$ ./a.out
^C        键入中断字符interrupt
^C        再次键入中断字符interrupt
^C        再一次interrupt
^\ $       用退出符终止

考虑到支持ISO C的非POSIX系统与POSIX系统两者之间的可移植性,在一个信号处理程序中唯一应当做的是为sig_atomic_t类型的变量赋一个值。POSIX.1规定得更多一些,它详细说明了在一个信号处理程序中可以安全地调用的函数列表(见图10-4),但是如果这样来编写代码,则它们可能不会正确地在非POSIX系统上运行。

实例

可以用信号实现父、子进程之间的同步,这是信号应用的另一个实例。图 10-24 给出了 8.9节中提到的5个例程的实现,它们是TELL_WAITTELL_PARENTTELL_CHILDWAIT_PARENTWAIT_CHILD

AMY : 这里原著的TELL_WAIT没有加下划线,错字。

#include "apue.h"

static volatile sig_atomic_t sigflag; /* set nonzero by sig handler */
static sigset_t newmask, oldmask, zeromask;

static void
sig_usr(int signo)    /* one signal handler for SIGUSR1 and SIGUSR2 */
{
    sigflag = 1;
}

void
TELL_WAIT(void)
{
    if (signal(SIGUSR1, sig_usr) == SIG_ERR)
        err_sys("signal(SIGUSR1) error");
    if (signal(SIGUSR2, sig_usr) == SIG_ERR)
        err_sys("signal(SIGUSR2) 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 */
}

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 */
}

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");
}

图10-24 父子进程可用来实现同步的例程

其中使用了两个用户定义的信号:SIGUSR1由父进程发送给子进程,SIGUSR2由子进程发送给父进程。图15-7显示了使用管道的这5个函数的另一种实现。

如果在等待信号发生时希望去休眠,则使用sigsuspend 函数是非常适当的(正如在前面两个例子中所示),但是如果在等待信号期间希望调用其他系统函数,那么将会怎样呢?遗憾的是,在单线程环境下对此问题没有妥善的解决方法。如果可以使用多线程,则可专门安排一个线程处理信号(见12.8节中的讨论)。

如果不使用线程,那么我们能尽力做到最好的是,当信号发生时,在信号捕捉程序中对一个全局变量置1。例如,若我们捕捉SIGINTSIGALRM这两种信号,并用signal_intr函数设置这两个信号的处理程序,使得它们中断任一被阻塞的慢速系统调用。当进程阻塞在调用read函数等待慢速设备输入时,很可能发生这两种信号(如果设置闹钟以阻止永远等待输入,那么对于SIGALRM信号,这种情况尤其会发生)。处理这种问题的代码类似于下面所示:

if (intr_flag)    /* flag set by our SIGINT handler*/
    handle_intr();
if (alrm_flag)    /* flag set by our SIGALRM handler */
    handle_alrm();
/* signals occurring in here are lost */
while (read( ... ) < 0) {
    if (errno == EINTR) {
        if (alrm_flag)
            handle_alrm();
        else if (intr_flag)
            handle_intr();
    } else {
        /* some other error */
    }
} else if (n == 0) {
    /* end of file */
} else {
    /* process input */
}

AMY : 原书的花括号就是上面这样,看的我一脸懵碧,虽然是伪代码但是意思表达的还算清楚,凑合看吧,应该把while小括号里的read挪出来不是在里面,类似while true : n = read if n <0: else if n==0: else:这种形式

在调用read之前测试各全局标志,如果read返回一个中断的系统调用错误,则再次进行测试。如果在前两个if语句和后随的read 调用之间捕捉到两个信号中的任意一个,则问题就发生了。正如代码中的注释所指出的,在此处发生的信号丢失了。调用信号处理程序,它们设置了相应的全局变量,但是read决不会返回(除非某些数据已准备好可读)。

我们希望实现下列操作步骤。

  1. 阻塞SIGINTSIGALRM
  2. 测试两个全局变量以判别是否发生了一个信号,如果已发生则对此进行处理。
  3. 调用 read(或任何其他系统函数)并解除对这两个信号的阻塞,这两个操作应当是一个原子操作。仅当第(3)步是pause操作时,sigsuspend函数才能帮助我们。

results matching ""

    No results matching ""