sigsuspend函数

sigsuspend函数

我们已经见过如何对进程的信号掩码进行阻塞和解锁。我们可以通过这种技术对不想被信号中断的代码段进行保护。如果我们想解锁一个信号然后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 */

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

如果信号是被阻塞状态时,进程会拒收该信号直到信号被解锁。对于我们的应用,可以看做信号发生在未解锁和pause之间(依赖于内核如何实现信号)。如果发生在这个时间段内,那么会有一个问题。任何信号发生在这个时间段内都会丢失,我们可能将不会再次看到该信号发生,后面的pause()会永久的阻塞程序。对于早期不可靠的信号还有其它的问题。

正确解决这个问题,我们须要即重置信号掩码又能让进程休眠的原子操作。这个功能由sigsuspend函数提供。

 

#include <signal.h>

int sigsuspend(const sigset_t *sigmask);

Returns: 1 with errno set to EINTR

进程的信号掩码由sigmask参数指向。然后进程被挂起直到一个信号被捕获或者直到一个信号终止了进程。如果一个信号被捕获并且该信号的信号处理函数返回,之后sigsuspend返回,并且进程的信号掩码被设置成调用sigsuspend之前的值。

注意:这个函数执行成功后没有返回值。如果返回到调用者,它一直都是返回1,并errno设置成EINTR(指明中断了一个系统调用)

例子:

Figure10.22显示了一个正确处理防止重要代码段被特定信号中断的例子。

注意,当sigsuspend返回时,它设置进程的信号掩码是调用sigsuspend函数之前的值。在本例中阻塞SIGINT信号。因此我们重置信号掩码到之前保存的值(oldmask)。

运行Figure10.22的输出如下:

   $ ./a.out
   program start:
   in critical region: SIGINT

   ^?                               type the interrupt character
   in sig_int: SIGINT SIGUSR1
   after return from sigsuspend: SIGINT
   program exit:

当我们调用sigsuspend时,添加了SIGUSR1到信号掩码。所以当信号处理函数运行时,我们能告之信号掩码实际上已经更改了。当sigsuspend返回时我们能看到它恢复了信号掩码到调用sigsuspend之前的值。

Figure 10.22. Protecting a critical region from a signal

#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: ");

例子

另一种使用sigsuspend的方法是等待信号处理函数设置全局变量。在Figure10.23的程序中我们捕获了中断信号和退出信号,但是我们想在捕获退出信号时唤醒主函数。

该程序的简单输出是:

   $ ./a.out

   ^?                type the interrupt character
   interrupt

   ^?                type the interrupt character again
   interrupt

   ^?                and again
   interrupt

   ^?                and again
   interrupt

   ^?                and again
   interrupt

   ^?                and again
   interrupt

   ^?                and again
   interrupt

   ^\ $              now terminate with quit character

 

Figure 10.23. Using sigsuspend to wait for a global variable to be set

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

为了在支持ISO C的非POSIX系统和POSIX.1系统间的兼容性,我们只要在信号处理函数中做一件事情,那就是给变量添加一个sig_atomic_t的限定符,其它的什么也不用做。在Figure10.4这个函数列表中的函数可以安全的运行在信号函数函数中,但是如果我们使用了sig_atomic_t的话,我们的代码就可能在非POSIX的系统上正确运行。

Figure 10.4. Reentrant functions that may be called from a signal handler

accept

fchmod

lseek

sendto

stat

access

fchown

lstat

setgid

symlink

aio_error

fcntl

mkdir

setpgid

sysconf

aio_return

fdatasync

mkfifo

setsid

tcdrain

aio_suspend

fork

open

setsockopt

tcflow

alarm

fpathconf

pathconf

setuid

tcflush

bind

fstat

pause

shutdown

tcgetattr

cfgetispeed

fsync

pipe

sigaction

tcgetpgrp

cfgetospeed

ftruncate

poll

sigaddset

tcsendbreak

cfsetispeed

getegid

posix_trace_event

sigdelset

tcsetattr

cfsetospeed

geteuid

pselect

sigemptyset

tcsetpgrp

chdir

getgid

raise

sigfillset

time

chmod

getgroups

read

sigismember

timer_getoverrun

chown

getpeername

readlink

signal

timer_gettime

clock_gettime

getpgrp

recv

sigpause

timer_settime

close

getpid

recvfrom

sigpending

times

connect

getppid

recvmsg

sigprocmask

umask

creat

getsockname

rename

sigqueue

uname

dup

getsockopt

rmdir

sigset

unlink

dup2

getuid

select

sigsuspend

utime

execle

kill

sem_post

sleep

wait

execve

link

send

socket

waitpid

_Exit & _exit

listen

sendmsg

socketpair

write

例子

另一个信号的例子,我们显示了信号曾经能用于同步父子进程。Figure10.24显示了8.9节中五个例程的实现:TELL_WAITTELL_PARENTTELL_CHILDWAIT_PARENTWAIT_CHILD

我们使用两个用户定义信号:SIGUSR1信号是用来从父进程发向子进程的,SIGUSR2是由子进程发往父进程的。在之前Figure15.7中显示了这五个函数人使用管道的实现。

Figure 10.24. Routines to allow a parent and child to synchronize

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

如果我们想让进程休眠并等待信号发生sigsuspend函数很适合,但是如果我们想在等待的同时调用其它系统函数会发生什么?很不幸,我们没有解决方法,除非使用多线程时让一个单独的线程处理信号,我们将会在APUE12.8节看到。

在没有使用线程时,我们最好是在当信号发生时在信号处理函数中设置一个全局变量。例如:如何我们要捕获SIGINTSIGALRM,并且使用signal_intr函数安装信号处理函数,信号会中断任何慢系统调用,使其被阻塞。信号多数会发生在当我们被阻塞在select函数中等待一个慢设备输入时。(特别是对于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 (select( ... ) < 0) {
           if (errno == EINTR) {
               if (alrm_flag)
                   handle_alrm();
               else if (intr_flag)
                   handle_intr();
          } else {
              /* some other error */
          }
      }

如果select返回一个系统调用错误,那么我们在调用select之前再次测试全局标记。如果信号在前两个if和随后的select之间就会有问题发生。已经在程序的注释处写明。信号处理函数被调用,并且它们设置适当的全局函数,但是select绝对不会返回(除非有一些数据准备读取)。

下面的步骤是我们应该能去做的,顺序是:

  1. 阻塞SIGINTSIGALRM
  2. 测试两个全局变量,并查看它们是否某个信号已经发生了,如果发生了,处理这个条件。
  3. 调用select(或任何其它系统函数,类似read)并解锁两个信号,类似一个原子操作。

只有当第三步是一个pause操作时,sigsuspend函数才对我们有用。(The sigsuspend function helps us only if step 3 is a pause operation.)

发表回复