当进程收到一个信号的时候,可以对信号采取如下三种措施:
`忽略
`捕获
`默认处理
首先明白几个常用的术语:当导致信号发生的事件出现时,就产生针对某个进程的信号,当进程对信号采取措施时,就称该信号被递送,在产生信号和递送信号之间的时间间隔,称为信号未决,递送信号可以被阻塞或延迟。信号的部署是指进程如何响应信号,一个被阻塞的信号也被称为未决的信号。一个信号集合是一个C数据类型sigset_t,定义在<signal.h>中,能够表示多种信号。一个进程的信号掩码是进程目前正阻塞不能递送的信号的集合。
发送信号
使用kill命令,或者kill函数,前面已经说过kill了,这里不再细说。
在kill失败后调用waitpid是很安全的,能够保证子进程不会变成僵进程。
int raise(int sig);用来向自身发送一个信号。
捕获信号
每个进程都能决定如何响应出SIGSTOP和SIGKILL信号之外的其他所有信号,而这两个信号不能被捕获或者忽略。
设置超时
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
一个进程只能设置一次超时,把seconds设置为0就能取消前面的设置。
pause函数
#include <unistd.h>
int pause(void);
只有进程捕获一个信号时,pause才会返回调用进程,如果递送到的信号引发了对信号的处理,那么处理工作将在pause返回之前执行,pause总是返回-1,并把errno设置为EINTR。
定义一个信号处理器
首先使用ANSI C定义的函数,这个比较简单,只要指出信号与对该信号的处理方式就足以。
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
一般的步骤是先创建一个信号集合,设置想要捕获的信号,向内核登记一个信号处理器,然后等待捕获信号。
创建一个信号集合:
#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);
int sigismember(const sigset_t *set, int signum);
前两个用来初始化信号集合,不同是,第一个清空,第二个填满。
然后两个用来添加和删除信号集合中的信号,这四个成功返回0,出错返回-1,
最后一个是用来测试信号是否在集合中。是返回1,否则返回0;
登记一个信号处理器:
首先,必须使用sigprocmask设置或修改当前的信号掩码--如果还没有设置一个信号掩码,那么所有的信号都具有他们默认的部署。每个进程都会有一个信号屏蔽字,它规定了当前进程要阻塞的信号集。对于每种可能的信号,信号屏蔽字中都会有一位与之对应,如果该位被设置,该信号当前就是阻塞的。进程可以通过sigprocmask()来获得和修改当前进程的信号屏蔽字。
#include <signel.h>
int sigprocmask(int how, const sigset_t * set, sigset_t * oldset);
根据how的取值不同,sigprocmask设置或检查当前的信号掩码,how可取:
`SIG_BLOCK set包含其他要阻塞的信号
`SIG_UNBLOCK set包含要解除阻塞的信号
`SIG_SEYMASK set包含新的信号掩码
如果how为NULL,他就会被忽略,如果set为NULL,当前的掩码就保存在oldset中。如果oldset为NULL,它也被忽略。成功时返回0,失败返回-1。
如果我们想阻塞SIGUSR1,有两种方式。
// using SIG_BLOCK
sigset_t sigset;
sigemptyset(&sigset);
sigaddset(&sigset, SIGUSR1);
sigprocmask(SIG_BLOCK, &sigset, NULL);
// or using SIG_SETMASK
sigset_t set, oldset;
// get current signal mask
sigprocmask(SIG_SETMASK, NULL, &set);
// add SIGUSR1 into the signal mask
sigaddset(&set, SIGUSR1);
sigprocmask(SIG_SETMASK, &set, &oldset);
同样,如果要解除阻塞SIGUSR1,也有两种方式。
// using SIG_UNBLOCK
sigset_t sigset;
sigemptyset(&sigset);
sigaddset(&sigset, SIGUSR1);
sigprocmask(SIG_UNBLOCK, &sigset, NULL);
// or using SIG_SETMASK
sigset_t set, oldset;
// get current signal mask
sigprocmask(SIG_SETMASK, NULL, &set);
// delete SIGUSR1 from the signal mask
sigdelset(&set, SIGUSR1);
sigprocmask(SIG_SETMASK, &set, &oldset);
登记信号处理器
#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
为signum指定信号处理器,
struct sigaction {
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void)
}
sigaction()的功能是为信号指定相关的处理程序,但是它在执行信号处理程序时,会把当前信号加入到进程的信号屏蔽字中,从而防止在进行信号处理期间信号丢失。从前面的例子我们可以看到,简单的signal()函数也具有同样的功能,这是由于signal()已经被重新实现的缘故,所以如果不在乎对信号的更多的控制,我们尽可放心大胆的使用简单的signal()函数。signum指定将要改变处理行为的信号;act指定该信号的处理动作,oldact用于返回该信号先前的处理动作。
在sigaction结构中,sa_handler和sa_sigaction用于指定信号处理函数,但要注意,二者只能用其一,因为它们在内部可能会实现为union结构。除了在为sa_flags指定SA_SIGINFO标志时,会使用sa_sigaction字段外,其他情况下都应该只用sa_handler字段。
sa_mask用于指定在当前信号处理程序执行期间,需要阻塞的信号集。
sa_flags用于指定信号处理动的选项标志,详见手册。这里我想说的是SA_RESTART和SA_SIGINFO。SA_RESTART用于控制信号的自动重启动机制,对signal(),Linux默认会自动重启动被中断的系统调用;而对于sigaction(),Linux默认并不会自动重启动,所以如果希望执行信号处理后自动重启动先前中断的系统调用,就需要为sa_flags指定SA_RESTART标志。对于SA_SIGINFO,手册上说此标志可能会导致对信号的可靠排队,但是从下面的例子我们将会看到,Linux并没有对信号进行排队。
sa_handler是当信号发生时调用的信号处理函数,他的取值还可以是SIG_DFL默认处理 SIG_IGN忽略这个信号
当执行信号处理时,出发他的信号会被阻塞,sa_mask定义了在执行处理期间,应该阻塞的其他信号集合的信号掩码,sa_flags是修正sa_handler行为的掩码,他可以是:
SA_NOCLDSTOP 进程忽略子进程产生的任何SIGSTOP,SIGTSTP,SIGTTIN,SIGTTOU信号
SA_ONESHOT 或SA_RESETHAND 登记的自定义信号处理器只执行一次。在执行完毕后,恢复信号的默认动作。
SA_RESTART 让可重启的系统调用起作用
SA_NOMASK或SA_NODEFER不避免在信号自己的处理器中接受信号本身。
获得未决的信号sigpending
int sigpending(sigset_t * set);
该函数在set中返回进程中当前尚未递送的信号集。
信号跳转函数sigsetjump和siglongjump
int sigsetjmp(sigjmp_buf env, int savesigs);
void siglongjmp(sigjmp_buf env, int val);
sigsetjmp()有多次返回,对于直接调用者(一般是主程序),它返回0;若从siglongjmp()调用(一般是信号处理程序),则返回返回siglongjmp()中的val值。所以为了避免混淆,最好不要在调用siglongjmp()时,让val=0。
另外需要说明的是sigsetjmp()的第二个参数,它用于告诉内核,要不要保存进程的信号屏蔽字。当savesigs为非0时,调用sigsetjmp()会在env中保存当前的信号屏蔽字,然后在调用siglongjmp()时恢复之前保存的信号屏字。由于信号处理函数使用siglongjmp()跳转时不属于正常返回,所以在进入信号处理函数时被阻塞的当前信号就没有机会在返回时恢复。sigsetjmp()的savesigs参数就用于是告诉系统,在调用siglongjmp时,是否需要恢复先前的信号屏蔽字。
extern char * sys_siglist[];
这是一个以信号为索引的字符串数组,通过它可以很容易的找到信号的字符串名称。
void psignal(int signum, const char * msg);
此函数类似于perror(),输出对指定信号的字符串描述。
char * strsignal(int signum);
返回指定信号的字符串描述。