Linux信号与信号处理
信号(signal)是一种进程间通信机制,它给应用程序提供一种异步的软件中断,使应用程序有机会接受其他程序活终端发送的命令(即信号)。应用程序收到信号后,有三种处理方式:忽略,默认,或捕捉。进程收到一个信号后,会检查对该信号的处理机制。如果是SIG_IGN,就忽略该信号;如果是SIG_DFT,则会采用系统默认的处理动作,通常是终止进程或忽略该信号;如果给该信号指定了一个处理函数(捕捉),则会中断当前进程正在执行的任务,转而去执行该信号的处理函数,返回后再继续执行被中断的任务。
下面就来说说与信号有关的函数吧。
最简单signal函数
typedef void (*sighandler_t) (int)
sighandler_t signal(int signum, sighandler_t handler);
返回原信号处理函数,或SIG_ERR
signal()是最简单的给进程安装信号处理器的函数,第一个参数指定信号,第二个参数为该信号指定一个处理函数。
如下是一个最简单的处理信号的程序,它捕捉SIGUSR1,忽略SIGUSR2,按系统默认处理SIGINT,SIGUSR1和SIGUSR2是Linux提供的用户定义信号,可用于任何应用程序。主程序什么都不干,只用pause()循环等待信号。
例程1 最简单的信号处理
static void pr_mask(const char * string) {
sigset_t procmask;
sigprocmask(SIG_SETMASK, NULL, &procmask);
printf("%s: ", string);
if(sigismember(&procmask, SIGINT))
printf("SIGINT ");
if(sigismember(&procmask, SIGUSR1))
printf("SIGUSR1 ");
if(sigismember(&procmask, SIGUSR2))
printf("SIGUSR2 ");
if(sigismember(&procmask, SIGTERM))
printf("SIGTERM ");
if(sigismember(&procmask, SIGQUIT))
printf("SIGQUIT ");
printf("\n");
}
static void sigusr(int signum)
{
pr_mask(“int sigusr”);
if(signum == SIGUSR1)
printf(“SIGUSR1 received\n”);
else if(signum == SIGUSR2)
printf(“SIGUSR2 received\n”);
else
printf(“signal %d received\n”, signum);
}
int main(void)
{
if(signal(SIGUSR1, sig_usr) == SIG_ERR) {
printf(“error catching SIGUSR1\n”);
exit(1);
}
if(signal(SIGUSR2, SIG_IGN) == SIG_ERR) {
printf(“error ignoring SIGUSR2\n”);
exit(1);
}
if(signal(SIGINT, SIG_DFT) == SIG_ERR) {
printf(“error setting SIGINT to default\n”);
exit(1);
}
while(1)
pause();
exit(0);
}
后台运行该程序,并用kill发送信号给它。
$./a.out &
[1] 3725
$kill -USR1 3725
in sigusr: SIGUSR1
SIGUSR1 received
$kill -USR2 3725
[1]+ User defined signal 2 ./a.out
我们可以看到,Linux系统对SIGUSR2的默认动作是终止进程。
中断与自动重启动
前面说过,信号是一种软件中断机制,这就产生了一个问题:如果信号到来时进城正在执行某个低速系统调用,系统应该怎么处理?是暂时阻塞系统调用返回,在信号处理程序完成后继续没完成的系统调用呢,还是让系统调用出错返回,同时把errno设置为EINTR,让调用者去做进一步的出错检查呢?用事实说话,让我们做一个试验先吧。
下面的程序读取标准输入并把它输出到标准输出,在此期间,我们给进程发送SIGUSR1信号,以此来确定Linux在收到信号后是如何对处理系统调用的。
例程2 信号与自动重启动的signal版本
int main(void)
{
char buf[BUFSIZ];
int n;
signal(SIGUSR1, sig_usr);
while(1) {
if((n = read(STDIN_FILENO, buf, BUFSIZ)) == -1) {
if(errno == EINTR)
printf(“read is interrupted\n”);
}
else {
write(STDOUT_FILENO, buf, n);
}
}
exit(0);
}
运行该程序,并从另一个终端给该进程发送信号SIGUSR1。
$./a.out
first line
first line
in sigusr: SIGUSR1
SIGUSR1 received
second line
second line
in sigusr: SIGUSR1
SIGUSR1 received
^C
可见对由signal()函数安装的信号处理程序,系统默认会自动重启动被中断的系统调用,而不是让它出错返回,所以应用程序不必针对慢速系统调用的errno,做EINTR检查,这就是自动重启动机制。
我们再来看另外一个例子,它使用另一个函数sigaction()来安装信号处理程序。sigaction()允许进程对信号进行更多的控制:
例程3 信号与自动重启动的sigaction版本
int main(void)
{
char buf[BUFSIZ];
int n;
struct sigaction sa_usr;
sa_usr.flags = 0; //SA_RESART
sa_usr.sa_handler = sig_usr;
sigaction(SIGUSR1, &sa_usr, NULL);
//signal(SIGUSR1, sig_usr);
while(1) {
if((n = read(STDIN_FILENO, buf, BUFSIZ)) == -1) {
if(errno == EINTR)
printf(“read is interrupted\n”);
}
else {
write(STDOUT_FILENO, buf, n);
}
}
exit(0);
}
此时再运行这个程序,并从另一终端给该进程发送信号SIGUSR1,我们会得到如下结果。
$./a.out
first line
first line
in sigusr: SIGUSR1
SIGUSR1 received
read is interrupted
second line
second line
in sigusr: SIGUSR1
SIGUSR1 received
read is interrupted
^C
由此我们可以得出,Linux对sigaction()的默认动作是不自动重启动被中断的系统调用,因此如果我们在使用sigaction()时需要自动重启动被中断的系统调用,就需要使用sigaction的SA_RESTART选项,见上例注释,关于sigaction(),下文会有更多的描述。这和《UNIX环境高级编程》中对Linux信号处理的描述是一致的。
可重入函数
如前所述,进程在收到信号并对其进行处理时,会暂时中断当前正在执行的指令序列,转而去执行信号处理程序。但是信号的到来,往往是无法预测的,我们无法确定进程会在何时收到信号。如果进程在收到信号时正在执行malloc()调用,而此时捕捉到信号,进城就会转而去执行信号处理程序,而信号处理程序中又再次调用了malloc()函数,那结果将会怎样呢?进程的栈空间很可能就会受到破坏,从而产生无法预料的结果。所以有些函数是不能在信号处理程序中调用的,这些函数被称为不可重入函数,而那些允许在信号处理函数中调用的函数,则称为可重入函数。下表列出了Linux系统中的可重入函数(摘自《UNIX环境高级编程》),对不在该表中的函数,信号处理函数中要慎用。
表1 可重入函数
_exit(),access(),aio_error(),aio_return(),aio_suspend(),alarm(),bind(),cfgetispeed(),cfgetospeed(),cfsetispeed(),cfsetospeed(),chdir(),chmod(),chown(),clock_gettime(),close(),connect(),creat(),dup(),dup2(),execle(),execve(),_Exit(),_exit(),fchmod(),fchown(),fcntl(),fdatasync(),fork(),fpathconf(),fstat(),fsync(),ftruncate(),getegid(),geteuid(),getgid(),getgroups(),getpeername(),getpgrp(),getpid(),getppid(),getsockname(),getsockopt(),getuid(),kill(),link(),listen(),lseek(),lstat(),mkdir(),mkfifo(),open(),pathconf(),pause(),pipe(),poll(),posix_trace_event(),pselect(),raise(),read(),readlink(),recv(),recvfrom(),recvmsg(),rename(),rmdir(),select(),sem_post(),send(),sendmsg(),sendto(),setgid(),setpgid(),setsid(),setsockopt(),setuid(),shutdown(),sigaction(),sigaddset(),sigdelset(),sigemptyset(),sigfillset(),sigismember(),signal(),sigpending(),sigprocmask(),sigsuspend(),sleep(),socket(),socketpair(),stat(),symlink(),sysconf(),tcdrain(),tcflow(),tcflush(),tcgetattr(),tcgetpgrp(),tcsendbreak(),tcsetattr(),tcsetpgrp(),time(),times(),umask(),uname(),unlink(),utime(),wait(),waitpid(),write().
发送信号的kill和raise函数
int kill(pid_t pid, int sig);
int raise(int sig);
kill()发送信号给指定进程,raise()发送信号给进程本身。对kill()的pid,有如下描述:
pid > 0 将信号发送给ID为pid的进程
pid == 0 将信号发送给与发送进程属于同意个进程组的所有进程
pid < 0 将信号发送给进程组ID等于pid绝对值的所有进程
pid == -1 将信号发送给该进程有权限发送的系统里的所有进程
所有信号的发送都要先经过权限检查,如果进程没有相应发送的权限,kill()会出错返回,并把errno设为EPERM。但也有一个例外,对SIGCONT,进程可以将它发送给当前会话的所有进程。
产生时钟信号SIGALRM的alarm函数
unsigned int alarm(unsigned int seconds);
alarm()函数可设置一个计时器,计时器超时就产生SIGALRM信号。由于每个进程只能有一个SIGALRM处理程序,所以只能为一个进程设置一个计时器,所以alarm()和setitimer()会共享同一个SIGALRM信号和该信号的处理函数。也就是说,alarm()和setitimer()彼此会互相影响。调用alarm(),会使先前设置的计时器失效,并把没有超时的时间作为当前alarm的返回值。如先前设置的时钟为100秒,当前调用alarm()时才经过30秒,剩余的70秒就作为alarm()的返回值,并用alarm()中指定的秒数重新设置计时器。如果seconds为0,则会取消先前设置的计时器,并将其余留值作为alarm()的返回值。
等待信号的pause函数
int pause(void);
pause()会使当前进程挂起,直到捕捉到一个信号,对指定为忽略的信号,pause()不会返回。只有执行了一个信号处理函数,并从其返回,puase()才返回-1,并将errno设为EINTR。详见前面的第一个例子。
信号屏蔽字(process signal mask)
每个进程都会有一个信号屏蔽字,它规定了当前进程要阻塞的信号集。对于每种可能的信号,信号屏蔽字中都会有一位与之对应,如果该位被设置,该信号当前就是阻塞的。进程可以通过sigprocmask()来获得和修改当前进程的信号屏蔽字。
信号集(signal set)
信号集是一种特殊的数据类型,由于无法确定信号的多少,所以不能用简单数据类型来包含所有可能的信号,所以系统就定义了一个sigset_t的数据类型专门用于信号集。同时还定义了一族用于处理信号集的函数。这样用户可以不必关心信号集的实现,只要使用这组函数来处理信号集就可以了。
信号集函数
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(sigset_t * set, int signum);
sigemptyset()和sigfillset()都用于初始化一个信号集,前者用于清空信号集中所有的信号,后者则用于设置信号集中所有的信号;信号集在使用前必须要经过初始化,初始化后,就可以用sigaddset()和sigdelset()往信号集里添加删除信号了。sigismember()用于判断指定信号是否在信号集中。
修改信号屏蔽字的sigprocmask函数
int sigprocmask(int how, const sigset_t * set, sigset_t * oldset);
sigpromask()根据how指定的方式,设置进程的当前信号屏蔽字为set,并将旧的信号屏蔽字保存在oldset中返回。如果set为NULL,则不修改当前信号屏蔽字,而将其通过oldset返回;如果oldset为NULL,则不会返回旧的信号屏蔽字。how支持三种方式,见下表。
表2 设置信号屏蔽字的方式
how
说明
SIG_BLOCK
SIG_UNBLOCK
SIG_SETMASK
设置阻塞set指定的信号集
设置解除阻塞set指定的信号集
设置当前信号屏蔽字为set,在set中的信号都会被阻塞,不在set中的信号会被递送
如果我们想阻塞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);
信号未决(pending)
信号是由某些事件产生的,这些事件可能是硬件异常(如被零除),软件条件(如计时器超时),终端信号或调用kill()/raise()函数。信号产生时,内核通常会在进程表中设置某种标志,表示当前信号的状态。当内核对信号采取某种动作时,我们说向进程递送(deliver)了一个信号,而在信号产生和递送之间的间隔内,该信号的状态是未决的(pending)。
获得未决的信号sigpending
int sigpending(sigset_t * set);
该函数在set中返回进程中当前尚未递送的信号集。
功能更全的sigaction函数
int sigaction(int signum, const struct sigaction * act, struct sigaction * oldact);
struct sigaction {
void (*sa_handler) (int);
void (*sa_sigaction) (int, siginfo_t *, void *);
sigset_t sa_mask;
int sa_flags;
};
siginfo_t {
int si_signo; // Signal number
int si_errno; // An errno value
int si_code; // signal code
pid_t si_pid; // sending process ID
pid_t si_uid; // Real user ID of sending process
int si_status; // Exit value or signal
...
};
sigaction()的功能是为信号指定相关的处理程序,但是它在执行信号处理程序时,会把当前信号加入到进程的信号屏蔽字中,从而防止在进行信号处理期间信号丢失。从前面的例子我们可以看到,简单的signal()函数也具有同样的功能,这是由于signal()已经被重新实现的缘故,所以如果不在乎对信号的更多的控制,我们尽可放心大胆的使用简单的signal()函数。signum指定将要改变处理行为的信号;act指定该信号的处理动作,oldact用于返回该信号先前的处理动作。
在sigaction结构中,sa_handler和sa_sigaction用于指定信号处理函数,但要注意,二者只能用其一,因为它们在内部可能会实现为union结构。除了在为sa_flags指定SA_SIGINFO标志时,会使用sa_sigaction字段外,其他情况下都应该只用sa_handler字段。
sa_mask用于指定在当前信号处理程序执行期间,需要阻塞的信号集。如在处理SIGUSR1期间,我们希望暂时阻塞SIGUSR2,就应该把SIGUSR2加到SIGUSR1的sa_mask中。信号处理程序返回后,会自动解除对SIGUSR2的阻塞,详见例程4。
sa_flags用于指定信号处理动的选项标志,详见手册。这里我想说的是SA_RESTART和SA_SIGINFO。SA_RESTART用于控制信号的自动重启动机制,如前面例子所示,对signal(),Linux默认会自动重启动被中断的系统调用;而对于sigaction(),Linux默认并不会自动重启动,所以如果希望执行信号处理后自动重启动先前中断的系统调用,就需要为sa_flags指定SA_RESTART标志。对于SA_SIGINFO,手册上说此标志可能会导致对信号的可靠排队,但是从下面的例子我们将会看到,Linux并没有对信号进行排队。
例程4 sigaction函数
int main(void)
{
struct sigaction act_usr;
act_usr.sa_flags = 0;
act_usr.sa_handler = sigusr;
sigemptyset(&act_usr.sa_mask);
// add the signal you want to block while SIGUSR1 is processing here
sigaddset(&act_usr.sa_mask, SIGUSR2);
// we dont care about the old action of SIGUSR1
sigaction (SIGUSR1, &act_usr, NULL);
while(1)
pause();
}
运行结果如下:
$./a.out &
[1] 16385
$kill -USR1 16385
in sig_usr1: SIGUSR1 SIGUSR2
SIGUSR1 recieved
可见在SIGUSR1处理期间,SIGUSR2已经被加入到进程的屏蔽字中了,所以在此期间,SIGUSR2是被暂时阻塞的。
信号排队
如果进程阻塞了一个信号,在没有对其解除阻塞之前,该信号产生了多次,将会如何处理呢?Linux并不会对信号排队,当信号解除阻塞后,内核只向进程递送一个信号,而不管在其阻塞期间有多少个信号产生。
下面是上例的改进版。首先我们阻塞SIGUSR1,然后在SIGUSR2的处理函数里解除对SIGUSR1的阻塞,这样我们就有机会在SIGUSR1阻塞期间,多发送几个SIGUSR1来确定Linux内核是怎样处理的。我们期望能看到Linux对信号的排队。
例程5 信号排队
static void sig_usr2(int sig)
{
sigset_t set;
printf("SIGUSR2 recieved\n");
// unblock SIGUSR1
sigprocmask(SIG_SETMASK, NULL, &set);
sigdelset(&set, SIGUSR1);
sigprocmask(SIG_SETMASK, &set, NULL);
}
static void handler(int signum, siginfo_t * info, void * context)
{
// dump signal information
printf("si_signo: %d\n", info->si_signo);
printf("si_errno: %d\n", info->si_errno);
printf("si_code: %d\n", info->si_code);
printf("si_pid: %d\n", info->si_pid);
printf("si_uid: %d\n", info->si_uid);
}
int main(void)
{
struct sigaction act_usr1;
struct sigaction act_usr2;
sigset_t mask;
act_usr1.sa_flags = SA_SIGINFO;
//act_usr1.sa_handler = sigusr;
sigemptyset(&act_usr1.sa_mask);
act_usr1.sa_sigaction = handler;
sigaction(SIGUSR1, &act_usr1, NULL);
act_usr2.sa_flags = 0;
act_usr2.sa_handler = sig_usr2;
sigemptyset(&act_usr2.sa_mask);
sigaction(SIGUSR2, &act_usr2, NULL);
// block SIGUSR1
sigprocmask(SIG_SETMASK, NULL, &mask);
sigaddset(&mask, SIGUSR1);
sigprocmask(SIG_SETMASK, &cmask, NULL);
while(1)
pause();
exit(0);
}
$ ./a.out &
[1] 17165
$ kill -USR1 17165
$ kill -USR1 17165
$ kill -USR1 17165
$ kill -USR1 17165
$ kill -USR1 17165
$ kill -USR2 17165
SIGUSR2 recieved
si_signo: 10
si_errno: 0
si_code: 0
si_pid: 3945
si_uid: 500
$ ps
PID TTY TIME CMD
3945 pts/1 00:00:00 bash
在SIGUSR1阻塞期间,我们向进程发送了5个SIGUSR1,而解除阻塞后,内核只递送了一个SIGUSR1,说明Linux并不支持信号排队。另外我们还可以看到,si_signo是收到的信号的数值;si_pid是发送进程的进程ID,ps输出我的终端进程ID正是3945;si_uid是发送进程的有效用户ID,而我的用户ID也正是500。对于siginfo结构中的其它成员,我没有打印,有兴趣的可以自己研究。
信号跳转函数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时,是否需要恢复先前的信号屏蔽字。
下例向你展示了如何使用sigsetjmp()和siglongjmp(),注意这里引入了一个全局变量canjmp,它是一种同步保护机制,用于告诉信号处理程序,在进程环境没有准备好之前,不要跳转,否则可能会导致混乱。
例程6 信号跳转
static sigjmp_buf jmpbuf;
// for synchronizing
static volatile sig_atomic_t canjmp;
static void sigusr1(int signum)
{
printf(“SIGUSR1 reveived\n”);
// main process initialization is not completed
if(canjmp == 0)
return;
siglongjmp(jmpbuf, 1);
}
satic void sigusr2(int signum)
{
printf(“SIGUSR2 reveived\n”);
if(canjmp == 0)
return;
siglongjmp(jmpbuf, 2);
}
int main(void)
{
int n;
int savemask = 1;
signal(SIGUSR1, sigusr1);
signal(SIGUSR2, sigusr2);
// need to save the procmask, otherwise, u have to reset the procmask
n = sigsetjmp(jmpbuf, savemask);
if(n == 1) {
// jump from SIGUSR1
printf(“Jump to here from SIGUSR1\n”);
if(savemask == 0) {
// prevent from long jumping
canjmp = 0;
// reset the procmask, unblock SIGUSR1
sigset_t set;
sigprocmask(SIG_SETMASK, NULL, &set);
sigdelset(&set, SIGUSR1);
sigprocmask(SIG_SETMASK, &set, NULL);
canjmp = 1;
}
}
else if(n == 2) {
printf(“Jump to here from SIGUSR2\n”);
if(savemask == 0) {
canjmp = 0;
sigset_t set;
sigprocmask(SIG_SETMASK, NULL, &set);
sigdelset(&set, SIGUSR1);
sigprocmask(SIG_SETMASK, &set, NULL);
canjmp = 1;
}
}
canjmp = 1;
while(1)
pause();
exit(0);
}
$ ./a.out &
[1] 5485
$ kill -USR1 5485
SIGUSR1 recieved
Jump to here from SIGUSR1
$ kill -USR2 5485
SIGUSR2 recieved
Jump to here from SIGUSR2
例程6告诉我们,根据sigsetjmp()的返回值,我们也可以通过信号实现程序的多分支控制。另外如果没有在sigsetjmp()时设置了savesigs,那么在siglongjmp()返回后,就要重新设置进程的信号屏蔽字,否则该信号在一次siglongjmp()之后将被永久阻塞。
难以捉摸的sigsuspend函数
int sigsuspend(const sigset_t * sigmask);
对于这个函数,我始终无法清晰的理解,关于它的用法,它的作用,它的语义,都让我一头雾水。《UNIX环境高级编程》,Linux手册,看了几遍,都无法开塞,真是愚钝至极啊!
从《UNIX环境高级编程》对sigsuspend()的引言看,该函数的出现是为了解决早期不可靠信号,即信号丢失的问题的。在早期的信号机制中,对信号解除阻塞和等待信号需要两步进行:
sigprocmask(SIG_SETMASK, &unblockmask, NULL);
pause();
在对信号解除阻塞之后和调用pause()之前有一个时间窗口,所以在这之间产生的信号就可能会丢失,从而是本该返回的pause()没有返回。
sigsuspend()能让解除信号阻塞和等待信号成为一个原子操作,这样就避免了上述的问题。它会把当前进程的信号屏蔽字设定为sigmask指定的值,所以在等待信号期间,sigmask中的信号会被暂时阻塞,而sigmask之外的信号都会被暂时解除阻塞。然后sigsuspend()挂起当前进程,等待,直到捕捉到一个信号或发生了一个会终止该进程的信号。如果是捕捉到一个信号并从出来程序中返回,则sigsuspend()返回-1,把进程信号屏蔽字设回调用sigsuspend()之前的值,并将errno设为EINTR。注意,指定为忽略的信号,并不会导致sissuspend()返回。
注意,sigsuspend()只是暂时解除对不在sigmask中的信号的阻塞,在捕捉到一个信号后,以前阻塞的信号还会被重新阻塞,所以如果你要对一个以前阻塞的信号解除阻塞的话,在sigsuspend()返回之后,还要重新用sigprocmask来解除对该信号的阻塞。这就是我的疑问了,如果我无意让进程等待任何信号的话,那这个sigsuspend()不是对我几乎毫无用处吗?
sigsuspend()的另一个用途就是让进程等待一个信号处理程序来设置一个全局变量。这个似乎还比较有用,在进程等待某一信号时,可让进程先挂起,直到收到该信号,设置的全局变量并导致sigsuspend()返回,进程才处理相应的任务,避免了CPU的无谓等待。如下例程就展示了如何让进程等待SIGUSR1,并在每收到一次SIGUSR1后去执行一组相同的操作。
例程7 sigsuspend的一个应用
static volatile sig_atomic_t ok;
static void sigusr(int signum)
{
// do nothing, set the flag only
ok = 1;
}
int main(void)
{
sigset_t emptymask, waitmask, oldmask;
// block SIGUSR1 first
sigemptyset(&waitmask);
sigaddset(&waitmask, SIGUSR1);
sigprocmask(SIG_BLOCK, &waitmask, &oldmask);
// set SIGUSR1 handler
signal(SIGUSR1, sigusr);
sigemptyset(&emptymask);
while(1) {
// waiting for SIGUSR1 to set ok
while(ok == 0)
sigsuspend(&emptymask);
// sigsuspend return, SIGUSR1 has come, and now SIGUSR1 is blocked
// reset the flag, so the process will keep waiting after the
// following things are done
ok = 0;
//
// other things need to do here
//
}
exit(0);
}
我对sigsuspend()的理解仅限于此,不敢多卖弄,就此打住吧:)
发送SIGABRT的专用函数abort
int abort(void);
abort会向当前进程发送SIGABRT信号,让进程做一些“善后”处理,如刷新流缓冲区,关闭文件等,然后终止进程。
其它几个有用的小函数:
extern char * sys_siglist[];
这是一个以信号为索引的字符串数组,通过它可以很容易的找到信号的字符串名称。
void psignal(int signum, const char * msg);
此函数类似于perror(),输出对指定信号的字符串描述。
char * strsignal(int signum);
返回指定信号的字符串描述。
例程8 其它函数
int main(void) {
printf("sys_siglist:\n");
printf("SIGUSR1: %s\n", sys_siglist[SIGUSR1]);
printf("SIGSEGV: %s\n", sys_siglist[SIGSEGV]);
printf("SIGHUP: %s\n\n", sys_siglist[SIGHUP]);
printf("strsignal:\n");
printf("SIGUSR1: %s\n", strsignal(SIGUSR1));
printf("SIGSEGV: %s\n", strsignal(SIGSEGV));
printf("SIGHUP: %s\n\n", strsignal(SIGHUP));
printf("psignal:\n");
psignal(SIGUSR1, "SIGUSR1");
psignal(SIGSEGV, "SIGSEGV");
psignal(SIGHUP, "SIGHUP");
exit(0);
}
输出形式如下:
sys_siglist:
SIGUSR1: User defined signal 1
SIGSEGV: Segmentation fault
SIGHUP: Hangup
strsignal:
SIGUSR1: User defined signal 1
SIGSEGV: Segmentation fault
SIGHUP: Hangup
psignal:
SIGUSR1: User defined signal 1
SIGSEGV: Segmentation fault
SIGHUP: Hangup
最后的忠告:千万不要去写一个复杂的信号处理程序,那是最出力不讨好的事情,信号处理程序每多一行,你的程序莫名崩溃的可能性就增大一分,谁能保证你在信号处理函数里调用的都是可重入函数呢?本文中的很多例程使用的printf()就不是可重入的……信号处理程序应该尽量简单,对稍微复杂的任务,应该想办法(如siglongjmp(),设置全局标志等)交给主程序处理。
吼吼,花了一天时间,终于把它整理完了,乱七八糟,但会方便各位查阅吧!其中大部分内容参考了《UNIX环境高级编程》,有不对的地方大家尽管拍砖,但不要骂俺抄袭,会脸红的^_^。图灵教育说的好,站在巨人的肩膀上,Standing on shoulders of the giants, I think I can fly...