美文网首页LinuxLinux学习之路
APUE读书笔记-10信号(11)

APUE读书笔记-10信号(11)

作者: QuietHeart | 来源:发表于2020-06-11 08:33 被阅读0次

16、sigsuspend函数

我们已经知道如何改变一个进程的signal mask来阻塞或者取消阻塞被选择的信号。我们可以使用这个技术来保护代码的关键区域,防止它被信号打断。如果我们想要取消阻塞一个信号然后pause,并等待之前被阻塞的信号发生,这会怎样?假设信号是SIGINT,正确的做法是:

sigset_t     newmask, oldmask;
sigemptyset(&newmask);
sigaddset(&newmask, SIGINT);
/*阻塞SIGINT信号,然后保存当前的signal mask*/
if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0)
        err_sys("SIG_BLOCK error");
/*代码的关键区域*/
/*重新设置signal mask,这样会取消对SIGINT的阻塞*/
if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)
        err_sys("SIG_SETMASK error");
/* 容易发生问题的时间窗口在这里出现了!!!(也就是重新设置signal mask和suspend这两步之间易发生问题) */
pause();  /* wait for signal to occur */
/*后续的处理*/

当信号在被阻塞的时候被发送给进程,这个信号的发送将会被延迟,直到信号被取消阻塞。对于应用程序来说,这个看起来就好象信号是在取消阻塞和调用pause之间发生的(取决于内核如何执行信号相关的处理).如果信号真的是在取消对它的阻塞和pause调用之间发生了,那么我们就会有一个问题。然后出现在上述容易发生问题的时间窗口中的这个信号,都会丢失,我们无法再看到这个信号了,这样pause就会被永远地阻塞在了那里。这个问题,也是早期非可靠信号机制中的另一种问题。

为了修正这个问题,我们需要一个方法,可以把重新设置信号,以及将进程设置到sleep状态这两步操作变成一个单一的原子性质的操作。通过sigsuspend函数就可以实现这个目的。

#include <signal.h>
int sigsuspend(const sigset_t *sigmask);

返回:返回1(其值实际为-1),并且将errno设置成EINTR.

进程的signal mask被设置成参数sigmask指向的值,然后进程被suspend,直到信号被捕获,或者直到一个信号发生导致进程被终止。如果一个信号被捕获,并且如果信号处理函数返回了,那么sigsuspend会返回,然后进程的signal mask被设置成调用sigsuspend之前的值。

注意,这个sigsuspend没有成功的返回值,如果它从调用中返回了,那么它一定会返回一个1,并且同时将errno设置成EINTR(表征一个被打断的系统调用)。

举例:

static void sig_int(int signo)
{
    pr_mask("\nin sig_int: ");
}
int main(void)
{
    sigset_t    newmask, oldmask, waitmask;
    pr_mask("program start: ");/*打印字符串以及signal mask.*/

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

    /*
    * 阻塞SIGINT 信号并且保存当前的signal mask.
    */
    if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0)
        err_sys("SIG_BLOCK error");

    /*
    * 关键代码区域.
    */
    pr_mask("in critical region: ");/*打印字符串以及signal mask.*/

    /*
    * 暂停并允许除了SIGUSR1之外所有的信号.
    * 这里就将重新设置signal mask和suspend合并为一个原子操作了,没有时间窗口问题了。
    */
    if (sigsuspend(&waitmask) != -1)
        err_sys("sigsuspend error");

    pr_mask("after return from sigsuspend: ");

    /*
    * 恢复SIGINT信号的阻塞.
    */
    if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)
        err_sys("SIG_SETMASK error");

    /*
    * 继续处理...
    */
    pr_mask("program exit: ");

    exit(0);
}

上面的例子,给出了保护关键代码区域不被指定的信号所打扰的正确的方法。

需要注意的是,当sigsuspend返回的时候,它会把signal mask设置成这个调用之前的signal mask.在这个例子中,SIGINT信号将会被阻塞。所以我们后来将signal mask重新设置为之前我们保存的值(oldmask).

运行上述代码程序,输出大致如下:

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

^?                               键入中断信号字符.
in sig_int: SIGINT SIGUSR1
after return from sigsuspend: SIGINT
program exit:

在调用sigsuspend的时候,我们把SIGUSR1添加到现有的signal mask中了,所以当信号函数运行的时候,我们可以看到signal mask实际上被改变了。在sigsuspend返回的时候,我们可以看到signal mask 被恢复到调用之前的值了。

第二个例子省略了,主要思想就是,虽然前面posix列出了许多可重入的函数,但是为了让非posix的系统尽可能的好用,我们在信号处理函数中只设置标记,而不是调用什么系统调用。

第三个例子涉及到父子进程同步,有待仔细考虑其中的问题…

译者注

原文参考

参考: APUE2/ch10lev1sec16.html

17、abort函数

在前面我们提到过abort函数会导致程序非正常地终止,

#include <stdlib.h>
void abort(void);

这个函数不会返回。

这个函数会发送SIGABRT信号给调用者.(进程不应该忽略这个信号.)ISOC要求调用abort将会通过raise(SIGABRT)给主机环境发送一个非成功的标记。

ISO C要求,如果信号被捕获,并且信号处理函数返回了,那么abort也不会返回到它的调用者处。如果信号被捕获了,只能通过调用exit,_exit,_Exit,longjmp,或者siglongjmp让信号处理函数不返回。POSIX.1也指定abort会覆盖进程对信号的阻塞和忽略(???)。

让进程捕获SIGABRT的目的是让它在进程终止之前可以进行它想要进行的清理工作。如果进程没有从信号处理函数中终止,POSIX.1已经说了,当信号处理函数返回的时候,abort会终止进程。

这个函数的ISO C标准,让具体实现取决定是否刷新输出流,或者删除临时文件。POSIX.1更进一步要求,如果调用abort终止了进程,那么对进程打开的I/O流的影响就像进程在终止之前为每一个流调用了fclose一样。

早期版本的System V会在abort函数中产生SIGIOT信号。此外,进程有可能会忽略或者捕获这个信号,并且从信号处理函数中返回,这时候,abort函数会返回到它的调用之处。

4.3BSD会产生SIGILL信号。在做这个之前,4.3BSD函数会取消信号的阻塞,并且重置信号处理动作为SIG_DFL(终止进程并产生core文件)。这个会阻止一个进程忽略或者捕获信号。

历史上,对abort的实现对标准I/O流的处理有所不同。对于保守点的编程以及更好的可移植的角度来说,如果我们想要标准I/O流被刷新,我们需要在调用abort之前来做它们。

由于大多数UNIX 系统在创建了tmpfile之后立即对tmpfile执行unlink操作,ISO C会警告这样的文件,但是不会考虑我们。

举例

void abort(void)         /* POSIX风格的abort函数 */
{
    sigset_t           mask;
    struct sigaction   action;
    /*
    * 调用者不能忽略SIGABRT,如果忽略则设置成默认的.
    */
    sigaction(SIGABRT, NULL, &action);
    if (action.sa_handler == SIG_IGN) {
        action.sa_handler = SIG_DFL;
        sigaction(SIGABRT, &action, NULL);
    }
    if (action.sa_handler == SIG_DFL)
        fflush(NULL);           /* 刷新所有打开的标准输入输出流 */
    /*
    * 要确保调用者没有对SIGABRT进行阻塞.
    */
    sigfillset(&mask);
    sigdelset(&mask, SIGABRT);  /* signal mask中只有 SIGABRT 被排除在外 */
    sigprocmask(SIG_SETMASK, &mask, NULL);
    kill(getpid(), SIGABRT);    /* 发送信号 */
    /*
    * 如果我们到达了这里,说明进程已经捕获到了 SIGABRT 信号并且返回。
    */
    fflush(NULL);               /* 刷新所有打开的标准输入输出流 */
    action.sa_handler = SIG_DFL;
    sigaction(SIGABRT, &action, NULL);  /* 设置信号为默认的处理 */
    sigprocmask(SIG_SETMASK, &mask, NULL);  /* just in case ... */
    kill(getpid(), SIGABRT);                /* and one more time */
    exit(1);    /* this should never be executed ... */
}

例子给出了一个根据POSIX.1指定的abort函数的实现。

我们首先查看是否有默认的动作发生,如果有,那么我们会刷新所有打开的标准I/O流。这一点和fclose所有打开的流的效果不是一样的,因为前者是只刷新流并没有关闭它们,但是当进程终止的时候,系统会关闭所有打开的文件。如果进程捕获到了信号并且返回了,那么我们再次刷新流,因为期间进程可能又会产生一些输出的。如果进程捕获到了信号,并且调用了_exit或者_Exit,只有这种情况我们不这样做。这时候,内存中任何没有被刷新的标准I/O缓存都会被丢弃。我们假设调用者在不想要刷新缓存的时候这样做。

根据前面所说过的,如果调用kill给caller发送一个信号,并且如果信号没有被阻塞,那么这个信号会在kill返回的时候被发送给进程。我们阻塞了除SIGABRT之外的所有信号,所以我们知道,如果调用的kill返回了,那么进程就捕获到了信号,并且信号处理函数已经返回了。

译者注

原文参考

参考: APUE2/ch10lev1sec17.html

相关文章

  • APUE读书笔记-10信号(11)

    16、sigsuspend函数 我们已经知道如何改变一个进程的signal mask来阻塞或者取消阻塞被选择的信号...

  • APUE读书笔记-10信号(10)

    15、sigsetjmp和siglongjmp函数 前面,我们描述了setjmp和logjmp函数,这个函数可以用...

  • 信号函数编写研究

    APUE读书笔记 ToDoList [ ] sleep初级实现 2017年12月6日 10:41:30 [ ] ...

  • APUE读书笔记-10信号(13)

    19、sleep函数 我们在本文中的许多例子里都使用了sleep函数,并且我们在本章前面给出了两个有缺陷的slee...

  • APUE读书笔记-10信号(2)

    3、signal函数 UNIX系统中最简单的一个信号相关的接口就是signal函数。声明如下: 如果成功,这个函数...

  • APUE读书笔记-10信号(3)

    5、被中断的系统调用 早期Unix系统的一个特性是当一个进程被阻塞在一个很慢的系统调用的时候捕捉到一个信号,这时候...

  • APUE读书笔记-10信号(4)

    6、可重入函数 进程捕捉到信号时,进程正常执行的指令次序会被信号处理打断。进程会继续执行,但是这时候执行的是信号处...

  • APUE读书笔记-10信号(5)

    7、SIGCLD的含义 有两个很容易导致混淆的信号是SIGCLD和SIGCHLD。首先,SIGCLD是System...

  • APUE读书笔记-10信号(6)

    9、kill和raise函数 kill用来给一个进程或者一组进程发送信号,raise函数允许进程给它自己发送信号。...

  • APUE读书笔记-10信号(7)

    11、信号集合 我们需要一种数据类型来表示包含多个信号的信号集合,我们使用诸如sigprocmask这样的函数来告...

网友评论

    本文标题:APUE读书笔记-10信号(11)

    本文链接:https://www.haomeiwen.com/subject/rtwoahtx.html