• 2005-09-22

    解疑sigsuspend - [技术前沿]

    版权声明:转载时请以超链接形式标明文章原始出处和作者信息及本声明
    http://bigwhite.blogbus.com/logs/1453143.html

    Unix提供了等待信号的系统调用,sigsuspend就是其中一个,在CU(www.chinaunix.net)上曾经讨论过一个关于该系统调用的问题,这里也做一下解疑。

    CU网友讨论的问题的核心就是到底sigsuspend先返回还是signal handler先返回。这个问题Stevens在《Unix环境高级编程》一书中是如是回答的“If a signal is caught and if the signal handler returns, then sigsuspend returns and the signal mask of the process is set to its value before the call to sigsuspend.”,由于sigsuspend是原子操作,所以这句给人的感觉就是先调用signal handler先返回,然后sigsuspend再返回。但其第一个例子这么讲又说不通,看下面的代码:
    CU上讨论该问题起于中的该例子:
    int main(void) {
       sigset_t   newmask, oldmask, zeromask;

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

       sigemptyset(&zeromask);

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

       /* allow all signals and pause */
       if (sigsuspend(&zeromask) != -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 ... */
       exit(0);
    }

    static void sig_int(int signo) {
       pr_mask("\nin sig_int: ");
       return;
    }
     
    结果:
    $a.out
    in critical region: SIGINT
    ^C
    in sig_int: SIGINT
    after return from sigsuspend: SIGINT

    如果按照sig_handler先返回,那么SIGINT是不该被打印出来的,因为那时屏蔽字还没有恢复,所有信号都是不阻塞的。那么是Stevens说错了么?当然没有,只是Stevens没有说请在sigsuspend的原子操作中到底做了什么?
    sigsuspend的整个原子操作过程为:
    (1) 设置新的mask阻塞当前进程;
    (2) 收到信号,恢复原先mask;
    (3) 调用该进程设置的信号处理函数;
    (4) 待信号处理函数返回后,sigsuspend返回。
    大致就是上面这个过程,噢,原来signal handler是原子操作的一部分,而且是在恢复屏蔽字后执行的,所以上面的例子是没有问题的,Stevens说的也没错。由于Linux和Unix的千丝万缕的联系,所以在两个平台上绝大部分的系统调用的语义是一致的。上面的sigsuspend的原子操作也是从《深入理解Linux内核》一书中揣度出来的。书中的描述如下:
    The sigsuspend( ) system call puts the process in the TASK_INTERRUPTIBLE state, after having blocked the standard signals specified by a bit mask array to which the mask parameter points. The process will wake up only when a nonignored, nonblocked signal is sent to it. The corresponding sys_sigsuspend( ) service routine executes these statements:

    mask &= ~(sigmask(SIGKILL) | sigmask(SIGSTOP));
    spin_lock_irq(¤t->sigmask_lock);
    saveset = current->blocked;
    siginitset(¤t->blocked, mask);
    recalc_sigpending(current);
    spin_unlock_irq(¤t->sigmask_lock);
    regs->eax = -EINTR;
    while (1) {
        current->state = TASK_INTERRUPTIBLE;
        schedule(  );
        if (do_signal(regs, &saveset))
            return -EINTR;
    }
    而最后的do_signal函数调用则是负责调用User Signal Handler的家伙。我想到这CU上的那个问题该被解疑清楚了吧。


    收藏到:Del.icio.us




    引用地址: