51 读书笔记:第10章 信号 (4)

10.11 信号集

        POSIX.1定义了数据类型sigset_t以包含一个信号集,并且定义了下列五个处理信号集的函数。

       #include <signal.h>

       int sigemptyset(sigset_t *set);
       int sigfillset(sigset_t *set);
       int sigaddset(sigset_t *set, int signum);
       int sigdelset(sigset_t *set, int signum);
        // 四个函数的返回值:若成功则返回0,若出错则返回-1
       int sigismember(const sigset_t *set, int signum);
        // 返回值:若真则返回1,若假则返回0,若出错则返回-1

        函数sigemptyset初始化由set指向的信号集,清除其中所有信号。函数sigfillset初始化由set指向的信号集,使其包括所有信号。所有应用程序在使用信号集前,要对该信号集调用sigemptyset或sigfillset一次。这是因为C编译器将未赋值的外部和静态变量都初始化为0,而这是否与给定系统上信号集的实现相对应却并不清楚。

        一旦已经初始化了一个信号集,就可以在该信号集中增、删特定的信号。函数sigaddset将一个信号添加到信号集中,sigdelset则从信号集中删除一个信号。

        函数sigismember测试一指定信号。

10.12 sigprocmask函数

        一个进程的信号屏蔽字规定了当前阻塞而不能递送给该进程的信号集。调用函数sigprocmask可以检测或更改其信号屏蔽字,或者在一个步骤中同时执行这两个操作。

       #include <signal.h>

       int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
        // 返回值:若成功则返回0,若出错则返回-1

        若oldset是非空指针,那么进程的当前信号屏蔽字通过oldset返回。

        若set是非空指针,则参数how指示如何修改当前信号屏蔽字。how的可选用值如下:

                SIG_BLOCK    该进程新的信号屏蔽字是其当前信号屏蔽字和set指向信号集的并集。set包含了我们希望阻塞的附加信号。

                SIG_UNBLOCK    该进程新的信号屏蔽字是其当前信号屏蔽字和set所指向信号集补集的交集。set包含了我们希望解除阻塞的信号。

                SIG_SETMASK    该进程新的信号屏蔽字将被set指向的信号集的值代替。

        如果set是空指针,则不改变进程的信号屏蔽字,how的值也无意义。

        在调用sigprocmask后,如果有任何未决的、不再阻塞的信号,则在sigprocmask返回前,至少会将其中一个信号递送给进程。

        sigprocmask是仅为单线程的进程定义的。

10.13 sigpending函数

        sigpending函数返回信号集,其中的各个信号对于调用进程时阻塞的而不能递送,因而也一定是当前未决的。该信号集通过set参数返回。

       #include <signal.h>

       int sigpending(sigset_t *set);
        // 返回值:若成功则返回0,若出错则返回-1

        《UNIX环境高级编程》P260:程序清单10-11 信号设置和sigprocmask实例

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>

static void sig_quit(int signo);

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

    if (signal(SIGQUIT, sig_quit) == SIG_ERR)
        fprintf(stderr, "can't catch SIGQUIT\n");

    sigemptyset(&newmask);          // 初始化newmask,清除其中所有信号
    sigaddset(&newmask, SIGQUIT);   // 添加SIGQUIT信号

    // 阻塞SIGQUIT信号,保存当前信号屏蔽字
    if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0)
        fprintf(stderr, "SIG_BLOCK error\n");

    sleep(5);       // 在此期间所产生的退出信号(Ctrl+\)都会被阻塞

    if (sigpending(&pendmask) < 0)          // 返回被阻塞的信号集
        fprintf(stderr, "sigpending error\n");
    if (sigismember(&pendmask, SIGQUIT))    // 检测SIGQUIT是否被阻塞
        printf("\nSIGQUIT pending\n");

    //  用旧的信号屏蔽字重新设置进程信号屏蔽字
    if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)
        fprintf(stderr, "SIG_SETMASK error\n");
    printf("SIGQUIT unblocked\n");
    
    sleep(5);
    exit(0);
}

static void sig_quit(int signo)
{
    printf("caught SIGQUIT\n");
    if (signal(SIGQUIT, SIG_DFL) == SIG_ERR)    // 恢复为默认处理方式
        fprintf(stderr, "can't reset SIGQUIT\n");
}

        如果编写一个可能由其他人使用的函数,而且需要在函数中阻塞一个信号,则不能用SIG_UNBLOCK简单地解除对此信号的阻塞,这是因为此函数的调用者在调用本函数之前可能也阻塞了此信号。在这种情况下必须使用SIG_SETMASK将信号屏蔽字复位为原先值。

        运行该程序:

$ ./11
^\^\^\^\^\^\^\^\^\              (Ctrl+\)产生信号多次
SIGQUIT pending                 从sleep返回后
caught SIGQUIT                  在信号处理程序中
SIGQUIT unblocked               sigprocmaks返回后
^\退出 (核心已转储)             再次产生信号

        在休眠期间如果产生了退出信号,那么此时(调用sigprocmask恢复旧的屏蔽字时)该信号是未决的,但是不再受阻塞,所以在sigprocmask返回之前,它被送到调用进程。从程序的输出中可以看出:SIGQUIT处理程序(sig_quit)中的printf语句先执行,然后再执行sigprocmask之后的printf语句。

        在进程休眠期间多次产生SIGQUIT信号,但是解除了该信号后,只会向进程传送一次SIGQUIT。从中可以看出,在此系统上没有对信号进行排队。

10.14 sigaction函数

        sigaction函数的功能是检查或修改与指定信号相关联的处理动作。此函数取代了UNIX早期版本使用的signal函数。

       #include <signal.h>

       int sigaction(int sig, const struct sigaction *restrict act, struct sigaction *restrict oact);
        // 返回值:若成功则返回0,若出错则返回-1

        其中sig是要检测或修改其具体动作的信号编号。若act指针非空,则要修改其动作。若oact指针非空,则系统经由oact指针返回该信号的上一个动作。此函数使用下列结构。

           struct sigaction {
               void     (*sa_handler)(int);
               void     (*sa_sigaction)(int, siginfo_t *, void *);
               sigset_t   sa_mask;
               int        sa_flags;
               void     (*sa_restorer)(void);
           };

        当更改信号动作时,如果sa_handler字段包含一个信号捕捉函数的地址(与常量SIG_IGN或SIG_DFL相对),则sa_mask字段说明了一个信号集,在调用该信号捕捉函数之前,这一信号集要加到进程的信号屏蔽字中。仅当从信号捕捉函数返回时再将进程的信号屏蔽字复位为原先值。这样,在调用信号处理程序时就能阻塞某些信号。在信号处理程序被调用时,操作系统建立的新信号屏蔽字包括正被递送的信号。因此保证了在处理一个给定的信号时,如果这种信号再次发生,那么它会被阻塞到对前一个信号的处理结束为止。若同一种信号多次发生,通常并不将它们排队,所以如果在某种信号被阻塞时它发生了多次,那么对这种信号解除阻塞后,其信号处理函数通常只会发生一次。

        一旦对给定的信号设置了一个动作,那么在调用sigaction显示地改变它之前,该设置就一直有效。

        act结构的sa_flags字段指定对信号进程处理的各个选项。(具体说明省略P262)

        sa_sigaction字段是一个替代的信号处理程序。当在sigaction结构中使用了SA_SIGINFO标志时,使用该信号处理程序。对于sa_sigaction字段和sa_handler字段这两者,其实现可能使用同一存储区,所以应用程序只能一次使用这两个字段中的一个。

        通常,按下列方式调用信号处理程序:void handler(int sig);

        但是,如果设置了SA_SIGINOF标志,那么按下列方式调用信号处理程序:void handler(int sig, siginfo_t *info, void *context);

        siginfo_t结构包含了信号产生原因的有关信息。该结构大致使用如下所示:

           siginfo_t {
               int      si_signo;    /* Signal number */
               int      si_errno;    /* An errno value */
               int      si_code;     /* Signal code */
               int      si_trapno;   /* Trap number that caused
                                        hardware-generated signal
                                        (unused on most architectures) */
               pid_t    si_pid;      /* Sending process ID */
               uid_t    si_uid;      /* Real user ID of sending process */
               int      si_status;   /* Exit value or signal */
               clock_t  si_utime;    /* User time consumed */
               clock_t  si_stime;    /* System time consumed */
               sigval_t si_value;    /* Signal value */
               int      si_int;      /* POSIX.1b signal */
               void    *si_ptr;      /* POSIX.1b signal */
               int      si_overrun;  /* Timer overrun count; POSIX.1b timers */
               int      si_timerid;  /* Timer ID; POSIX.1b timers */
               void    *si_addr;     /* Memory location which caused fault */
               long     si_band;     /* Band event (was int in
                                        glibc 2.3.2 and earlier) */
               int      si_fd;       /* File descriptor */
               short    si_addr_lsb; /* Least significant bit of address
                                        (since Linux 2.6.32) */
           }

        各种信号的si_code值使用Single UNIX Specification定义的。(有点多,此处省略 P264)

        若信号是SIGCHLD,则将设置si_pid、si_status和si_uid字段。若信号是SIGILL或SIGSEGV,则si_addr包含造成故障的根源地址,尽管该地址并不准确。若信号是SIGPOLL,那么si_band字段将包含STREAMS消息的优先级段,该消息产生POLL_IN、POLL_OUT或POLL_MSG事件。si_errno字段包含错误编号,它对应引发信号产生的条件,并由实现定义。

        信号处理程序的context参数是无类型指针,它可被强制转换为ucntext_t结构类型,用于标识信号传递时进程的上下文。
        当实现支持实时信号扩展时,用SA_SIGINFO标志建立的信号处理程序将导致信号可靠地排队。一些保留信号可由实时应用程序使用。如果信号由sigqueue产生,那么siginfo结构能包含应用特有的数据。

        现在用sigaction实现signal函数。很多平台都是这样做的。

        《UNIX环境高级编程》P265:程序清单10-12 用sigaction实现的signal函数

#include <signal.h>

typedef void Sigfunc(int);

Sigfunc *signal(int signo, Sigfunc *func)
{
    struct sigaction        act, oact;

    act.sa_handler = func;
    sigemptyset(&act.sa_mask);
    act.sa_falgs = 0;

    /* 对SIGALRM以外的所有信号,我们都有意尝试设置SA_RESTART标志
     * 于是被这些信号中断的系统调用都能重启动。不希望重启动SIGALRM
     * 的原因是:我们希望对I/O可以设置时间限制
    */
    if (signo == SIGALRM) {
#ifdef SA_INTERRUPT
        act.sa_flags |= SA_INTERRUPT;
#endif
    } else {
#ifdef SA_RESTART
        act.sa_flags |= SA_RESTART;
#endif
    }   

    if (sigaction(signo, &act, &oact) < 0)
        return(SIG_ERR);
    return (oact.sa_handler);
}

        注意,必须用sigemptyset函数初始化act结构的sa_mask成员。不能保证 act.sa_mask = 0; 会做同样的事情。

        除非说明了SA_RESTART,否则sigaction函数不再重启动被中断的系统调用。

        程序清单10-13是signal的另一个版本,它力图阻止任何被中断的系统调用重启动

        《UNIX环境高级编程》P265:程序清单10-13 signal_intr函数

#include <signal.h>

typedef void Sigfunc(int);

Sigfunc *signal_intr(int signo, Sigfunc *func)
{
    struct sigaction        act, oact;

    act.sa_handler = func;
    sigemptyset(&act.sa_mask);
    act.sa_falgs = 0;

#ifdef SA_INTERRUPT
    act.sa_flags |= SA_INTERRUPT;
#endif

    if (sigaction(signo, &act, &oact) < 0)
        return(SIG_ERR);
    return (oact.sa_handler);
}

        如果系统定义了SA_INTERRUPT标志,那么为了提供可移植性,我们sa_flags中增加该标志,这样也就阻止了被中断的系统调用重启动。

你可能感兴趣的:(读书笔记,《UNIX环境高级编程》)