APUE——alarm与pause、sigsetjmp、siglongjmp、sigsuspend

1、alarm与pause

  1. 每个进程只能有一个alarm,如果之前有个alarm,在本次alarm执行完毕的时候如果还没有结束,则本次alarm返回上次alarm剩余的值。如果本次alarm执行的结果为0,则取消原来的闹钟。
  2. SIGALARM的默认执行动作是终止进程,但是大多数闹钟会捕获该信号,如果要捕获该信号,应该在alarm执行前,注册信号处理函数!
    在这里插入图片描述
    在这里插入图片描述

1.1 例子分析

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2、sigsetjmp、siglongjmp

  1. sigsetjmp与siglongjmp是为了解决,当捕捉信号进入信号处理函数时后,该信号被进入信号屏蔽集,但是如果跳转后,该信号不会自动从信号屏蔽集中清除的问题,而正常的处理函数返回时,会自动从屏蔽集里,清除该信号。
#include <setjmp.h>

int sigsetjmp(sigjmp_buf env, int savemask);
返回值:若直接调用则返回0,若从siglongjmp调用返回则返回非0void siglongjmp(sigjmp_buf env, int val);
函数说明:sigsetjmp()会保存目前堆栈环境,然后将目前的地址作一个记号,
而在程序其他地方调用siglongjmp()时便会直接跳到这个记号位置,然后还原堆栈,继续程序的执行。 
参数env为用来保存目前堆栈环境,一般声明为全局变量 
参数savemask若为非0则代表搁置的信号集合也会一块保存 
当sigsetjmp()返回0时代表已经做好记号上,若返回非0则代表由siglongjmp()跳转回来。 
返回:若直接调用则为0,若从siglongjmp调用返回则为非0
在信号处理程序中经常调用longjmp函数以返回到程序的主循环中,而不是从该处理程序返回。

但是,调用longjmp有一个问题。当捕捉到一个信号时,进入信号捕捉函数,此时当前信号被自动地加到进程的信号屏蔽字中。这阻止了后来产生的这种信号中断该信号处理程序。(仅当从信号捕捉函数返回时再将进程的信号屏蔽字复位为原先值:链接)如果用longjmp跳出信号处理程序,那么,对此进程的信号屏蔽字会发生什么呢()?(setjmp和longjmp保存和恢复信号屏蔽字,还是不保存和恢复,不同的实现各有不同。)

为了允许两种形式的行为并存,POSIX.1并没有说明setjmp和longjmp对信号屏蔽字的作用,而是定义了两个新函数sigsetjmp和siglongjmp。在信号处理程序中进行非局部转移时使用这两个函数。

  1. 下面例子中在执行完sigsetjmp后,将canjump 赋1,为了避免信号因为随时可能发生,导致先siglongjmp的情况
#include "apue.h"
#include <setjmp.h>
#include <time.h>

static void            sig_usr1(int), sig_alrm(int);
static sigjmp_buf        jmpbuf;
static volatile sig_atomic_t     canjump;

int
main(void)
{
    if (signal(SIGUSR1, sig_usr1) == SIG_ERR)
        err_sys("signal(SIGUSR1) error");

    if (signal(SIGALRM, sig_alrm) == SIG_ERR)
        err_sys("signal(SIGALRM) error");
    
    pr_mask("starting main: ");

    if (sigsetjmp(jmpbuf, 1))
    {
        pr_mask("ending main: ");
        exit(0);
    }
    canjump = 1;    /* now sigsetjmp() is OK */

    for(; ;)
        pause();
}

static void
sig_usr1(int signo)
{    
    time_t starttime;

    if (canjump == 0)
        return;        /* unexpected signal, ignore */

    pr_mask("starting sig_usr1: ");
    alarm(3);        /* SIGALRM in 3 seconds */
    starttime = time(NULL);
    for(; ;)        /* busy wait for 5 seconds */
        if (time(NULL) > starttime + 5)
            break;
    pr_mask("finishing sig_usr1: ");
    
    canjump = 0;
    siglongjmp(jmpbuf, 1);    /* jump back to main, don't return */
}


static void 
sig_alrm(int signo)
{
    pr_mask("in sig_alrm: ");
}

3、sigsuspend

相关链接
问题引入:
如果在信号阻塞时将其发送给进程,那么该信号的传递就被推迟直到对它解除了阻塞。对应用程序而言,该信号好像发生在解除对SIGINT的阻塞和pause之间。如果发生了这种情况,或者如果在解除阻塞时刻和pause之间确实发生了信号,那么就产生了问题。因为我们可能不会再见到该信号,所以从这种意义上而言,在此时间窗口(解除阻塞和pause之间)中发生的信号丢失了,这样就使pause永远阻塞。

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

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 */
if(sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)
    err_sys("SIG_SETMASK error");

/* window is open */
pause();    /* wait for signal to occur */

/* continue processing */
#include <signal.h>

int sigsuspend( const sigset_t *sigmask );
返回值:-1,并将errno设置为EINTR
  1. 在sigprocmask和pause之间发生了信号,进程还未pause,那么如果后续没有信号后,进程一直在pause,永久挂起
  2. sigsuspend是为了解决前面阻塞了一个信号后,解除阻塞,并且挂起的操作如果进程切换会永久挂起的问题。将其变成原子操作
  3. sigsuspend是临时替换进程信号屏蔽集,并将进程挂起,如果接收到信号,且执行完信号处理函数后,会继续执行进程,且将临时信号屏蔽集替换为原信号屏蔽集!
    在这里插入图片描述
  4. 将进程的信号屏蔽字设置为由sigmask指向的值。在捕捉到一个信号或发生了一个会终止该进程的信号之前,该进程被挂起。如果捕捉到一个信号而且从该信号处理程序返回,则sigsuspend返回,并且将该进程的信号屏蔽字设置为调用sigsuspend之前的值。
  5. 此函数没有成功返回值。如果它返回到调用者,则总是返回-1,并将errno设置为EINTR(表示一个被中断的系统调用)。
    sigsuspend细述

3.1 例子1

#include "apue.h"

static void sig_int(int);

void fun();
void pr_mask(const char *str)
{
    sigset_t    sigset;
    int        errno_save;
    
    //errno_save = errno;    /* we can be called by signal handlers */
    if (sigprocmask(0, NULL, &sigset) < 0)
        printf("sigprocmask error");

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

    /* remaining signals can go here */

    printf("\n");
    //errno = errno_save;
}
int main(void)
{
    sigset_t     newmask, oldmask, waitmask;
    
    pr_mask("program start: ");

    if(signal(SIGINT, sig_int) == SIG_ERR)
        printf("signal(SIGINT) error");
    sigemptyset(&waitmask);
    sigaddset(&waitmask, SIGUSR1);
    sigemptyset(&newmask);
    sigaddset(&newmask, SIGINT);
    // if(signal(SIGUSR2, fun) == SIG_ERR)
    //     printf("signal(SIGUSR2) error");
    signal(SIGUSR2, fun);
    /*
    * Block SIGINT and save current signal mask.
    */
    if(sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0)
        printf("SIG_BLOCK error: ");

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

    /*
    * Pause, allowing all signals except SIGUSR1.
    */
    if(sigsuspend(&waitmask) != -1)
        printf("sigsuspend error");
    
    pr_mask("after return from sigsuspend: ");

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

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

    exit(0);
}

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

执行结果如下1

lxz@lxz-VirtualBox:~/liuxz/testapue$ gcc sigsuspend.c -o sigsuspend
lxz@lxz-VirtualBox:~/liuxz/testapue$ ./sigsuspend 
program start: ,2597
in critical region: ,2597SIGINT //表示SIGINT在sigprocmask后被阻塞
USR2
,2597SIGUSR1 
after return from sigsuspend: ,2597SIGINT //表示在sigsuspend执行完毕后,SIGINT恢复阻塞
program exit: ,2597  //解除阻塞

执行结果如下2

lxz@lxz-VirtualBox:~/liuxz/testapue$ ./sigsuspend 
program start: ,2642
in critical region: ,2642SIGINT 
^C                                          // 执行INT,发现sigsuspend解除了INT的阻塞
in sig_int: ,2642SIGUSR1 
after return from sigsuspend: ,2642SIGINT 
program exit: ,2642

3.2 例子2

#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 */
{
    signal(SIGINT, sig_int);   //这里要注意对于老版本的linux,执行完信号处理函数后,信号处理方式变成SIG_DFL
    							//但是新版本不会,而需要在sigaction,会SA_RESETHAND
    signal(SIGQUIT, sig_int);
    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)
        printf("signal(SIGINT) error");
    if(signal(SIGQUIT, sig_int) == SIG_ERR)
        printf("signal(SIGQUIT) error");

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

    /*
    * Block SIGQUIT and save current signal mask.
    */
    if(sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0)
        printf("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)
        printf("SIG_SETMASK error");
    
    exit(0);
}

结果1,按照上述写法,老版linux

^C
interrupt
^C
interrupt
^C
interrupt
^C
interrupt
^C
interrupt
^C
interrupt

结果2,没有在信号处理函数中重新写signal,老版linux
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_44537992/article/details/105810999