1.信号的概念
信号时软中断,它提供了一种处理异步时间的方法。
很多条件都会产生信号:
(1)用户按某些键时,引发终端产生信号。
(2)硬件异常产生信号:除数0,无效的内存引用等。
(3)进程调用kill,可以将任意信号发送给任意进程或进程组。
(4)当检测到某种软件条件已经发生时。例如SIGURG,SIGPIPE和SIGALRM。
当某种信号出现时,可以告诉内核使用下列三种方式来处理:
(1)忽略此信号。
(2)捕捉信号。为了做到这一点,用户需之前就定义好相应的处理函数,并设定函数为特定信号的处理函数。
(3)执行系统默认的动作。下图给出了系统默认的动作。注意,对大部分信号系统默认的处理动作都是终止进程。
2.函数signal
UNIX系统信号机制最简单的接口是signal函数
#include <signal.h>
void (*signal(int signo,void (*func)(int)))(int);
signo的值为信号名。func的值是常量SIG_IGN,常量SIG_DEF或当接到此信号后要调用的函数的地址。如果指定SIG_IGN,则表示忽略此信号。如果指定SIG_DFL,则表示按默认的操作(上图)处理此信号。当指定函数地址时,则在信号发生时调用该函数。
3.可重入函数
Single UNIX Specification说明了再信号处理程序中保证调用安全的函数。这些函数时可重入的并被称为是异步信号安全的。
应当了解,即使信号处理函数都是调用的可重入函数。但每个线程都只有一个errno变量,所以信号处理程序可能会修改其原来的值。因此,作为一个通用的规则当在信号处理函数中调用上述函数时,应先保存errno的值,调用完后再将其恢复。
4.可靠信号术语和语义
当造成信号的事件发生时,为进程产生一个信号。当一个信号产生时,内核通常在进程表中以某种形式设置一个标识,我们就说向进程递送了一个信号。在信号产生和递送之间的时间间隔内,称信号是未决的。
进程可以选用“阻塞信号递送”。如果为进程产生了一个阻塞的信号,而且对该信号的动作是系统默认动作或捕捉该信号,则该信号将保持未决状态。直到该进程为此信号解除了阻塞,或将对此信号的动作更改为忽略。内核在递送一个原来被阻塞的信号给进程时,才决定对它的处理方式。于是进程在信号递送给它之前仍可改变对信号的动作。
每个进程都有一个信号屏蔽字,它规定了当前要阻塞递送到该进程的信号集。对于每种可能的信号,屏蔽字中都有一位与之对应。
5.函数kill和raise
kill函数将信号发送给进程或进程组。raise函数则允许进程向自身发送信号。
#include<signal.h>
int kill(pid_t pid,int signo);
int raise(int signo)
对于kill函数:(1)pid>0时,该信号发送给id为pid的信号。
(2)pid=0时,该信号发送给与发送进程属同一进程组的所有进程。
(3)pid<0时,将该信号发送给其进程组ID等于pid绝对值的进程。
(4)pid=-1时,将该信号发送给发送进程有权限向他们发送的所有进程。
6.函数alarm和pause
使用alarm函数可以设置一个定时器,当定时器超时时,产生SIGALRM信号。如果忽略或不捕捉此信号,则其默认动作时终止调用该alarm函数的进程。
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
pause函数使调用进程挂起直至捕捉到一个信号。只有执行了一个信号处理程序并从其返回时,pause才返回。
#include <unistd.h>
int pause(void);
#include <signal.h> #include <unistd.h> static void sig_alrm(int signo) { } unsigned int sleep(unsigned int seconds) { if(signal(SIGALRM,sig_alrm) == SIG_ERR) return seconds; alarm(seconds); pause(); return
7.信号集
我们需要由一个能表示多个信号的数据类型——信号集。POSIX.1定义数据类型sigset_t以包含一个信号集,并且定义了下列5个处理信号的函数。
#include <signal.h>
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset(sigset_t *set);
int sigdelset(sigset_t *set);
int sigismember(const
sigset_t *set,int signo);
8.函数sigprocmask
函数sigprocmask可以检测或更改程序的信号屏蔽字。
#include <signal.h>
int sigprocmask(int how,const sigset_t * restrict set,sigset_t *restrict oset)
如果oset是非空指针,则进程的当前信号屏蔽字通过oset返回。如果set是一个非空指针,则参数how指示如何修改当前信号屏蔽字。how可选SIG_BLOCK,SIG_UNBLOCK,SIG_SETMASK。
特别注意,sigprocmask是仅针对单线程定义的。处理多线程进程中信号的屏蔽使用另一个函数。
例:
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) perror("sigprocmask error"); printf("mask: %s", str); if (sigismember(&sigset, SIGINT)) printf("SIGINT "); if (sigismember(&sigset, SIGQUIT)) printf("SIGQUIT "); if (sigismember(&sigset, SIGUSR1)) printf("SIGUSR1 "); if (sigismember(&sigset, SIGUSR2)) printf("SIGUSR2 "); if (sigismember(&sigset, SIGALRM)) printf("SIGALRM "); /* remaining signals can go here */ printf("\n"); errno = errno_save; }
9.函数sigpending
sigpending函数返回一信号集,对于调用进程而言,其中的各信号是阻塞不能传递的保持未决状态的信号。该信号通过set参数返回。
#include <signal.h>
int sigpending(sigset_t *set);
例:
#include "apue.h" static void sig_quit(int); int main(void) { sigset_t newmask,oldmask,pendmask; if(signal(SIGQUIT,sig_quit) == SIG_ERR) err_sys("can't catch SIGQUIT"); sigemptyset(&newmask); sigaddset(&newmask,SIGQUIT); if(sigprocmask(SIG_BLOCK,&newmask,&oldmask) < 0) err_sys("SIG_BLOCK error"); sleep(5); if(sigpending(&pendmask) < 0) err_sys("sigpending error\n"); if(sigismember(&pendmask,SIGQUIT)) printf("\nSIGQUIT pending\n"); if(sigprocmask(SIG_SETMASK,&oldmask,NULL) < 0) err_sys("SIG_SETMASK error"); 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) err_sys("can't reset SIGQUIT"); }