本章先对信号机制进行综述,并说明每种信号的一般用法。
信号概念
每个信号都有一个名字,这些名字都以3个字符SIG开头。在头文件<signal.h>中,信号名都被定义为正整形常量。
在某个信号出现时,可以按下列3种方式之一进行处理:
1 忽略该信号。大多数信号都可以使用这种方式进行处理,但有两种信号却决不能被忽略:SIGKILL和SIGSTOP(只能执行系统默认动作)。
2 捕获信号。通知内核在某信号发生时,调用一个用户函数对这种时间进行处理。
3 执行系统默认动作。对于大多数信号的系统默认动作是终止该进程。
函数signal
UNIX系统信号机制最简单的接口是signal函数
#include <signal.h> void (*signal(int signo,void (*func)(int)))(int);
可以使用typedef使其变得简单一点
typedef void Sigfunc(int);Sigfunc *signal(int,Sigfunc *);
第一个int参数是要捕获的信号(整形常量),第二个参数是一个函数指针(处理函数),该函数指针指向的函数返回值是void,参数是int。
下面给出一个简单得信号处理程序:
#include "apue.h" static void sig_usr(int); /* one handler for both signals */ int main(void) { if (signal(SIGUSR1, sig_usr) == SIG_ERR) err_sys("can‘t catch SIGUSR1"); if (signal(SIGUSR2, sig_usr) == SIG_ERR) err_sys("can‘t catch SIGUSR2"); for ( ; ; ) pause(); } static void sig_usr(int signo) /* argument is signal number */ { if (signo == SIGUSR1) printf("received SIGUSR1\n"); else if (signo == SIGUSR2) printf("received SIGUSR2\n"); else err_dump("received signal %d\n", signo); }
我们使该程序在后台运行,而且用kill命令将信号发送给它:
中断的系统调用
早期UNIX系统的一个特性是:如果进程在执行一个低速系统调用而阻塞期间捕获到一个信号,则该系统调用就被中断不再继续执行。
该系统调用返回出错,其errno设置为EINTR。后面的章节会更多的涉及到被中断的系统调用。
可重入函数
进程捕获到信号并对其进行处理时,进程正在执行的正常指令序列就被信号处理程序临时中断,它首先执行该信号处理程序中的指令。
如果从信号处理程序返回,则继续执行在捕获到信号时进程正在执行的正常指令序列。
在信号处理函数中调用某些函数可能对导致安全问题(其结果是不可预知的),下面列出了这些异步信号安全的函数,没有列入图中的大多数函数是不可重入的。
函数kill和raise
kill函数将信号发送给进程或进程组,raise函数则允许进程向自身发送信号。
#include <signal.h> int kill(pid_t pid,int signo); int raise(int signo);
函数alarm和pause
函数alarm设置一个定时器,当定时器超时时,产生SIGALRM信号。
#include <unistd.h> unsigned int alarm(unsigned int seconds);
pause函数使调用进程挂起直至捕捉到一个信号
#include <signal.h> int pause(void);
只有执行了一个信号处理程序并从其返回时,pause才返回。此时,pause返回-1,errno设置为EINTR。
信号集
信号集是能表示多个信号的数据结构(sigset_t),下面列出5个处理信号集的函数
#include <signal.h> int sigemptyset(sigset_t *set); int sigfillset(sigset_t *set); int sigaddset(sigset_t *set,int signo); int sigdelset(sigset_t *set,int signo); int siggismember(const sigset_t *set,int signo);
在使用信号集之前,要对该信号集进行初始化(调用sigemptyset或者sigfillset)。
函数sigprocmask
进程的信号屏蔽字规定了当前阻塞而不能递送给该进程的信号集。调用函数sigprocmask可以检测或更改进程的信号屏蔽字。
#include <signal.h> int sigprocmask(int how,const sigset_t *restrict set,sigset_t *restrict oset);
若oset是非空指针,那么进程的当前信号屏蔽字通过oset返回。
若set是一个非空指针,则参数how指示如何修改当前信号屏蔽字。SIG_BLOCK是或操作,SIG_SETMASK则是赋值操作
函数sigpending
sigpending函数返回一信号集,对于调用进程而言,其中的各信号是阻塞不能递送的,因而也一定是当前未决的。
#include <signal.h> ing sigpending(sigset_t *set);
下面展示信号设置和sigprocmask实例
#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"); /* * Block SIGQUIT and save current signal mask. */ sigemptyset(&newmask); sigaddset(&newmask, SIGQUIT); if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0) err_sys("SIG_BLOCK error"); sleep(5); /* SIGQUIT here will remain pending */ if (sigpending(&pendmask) < 0) err_sys("sigpending error"); if (sigismember(&pendmask, SIGQUIT)) printf("\nSIGQUIT pending\n"); /* * Restore signal mask which unblocks SIGQUIT. */ if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0) err_sys("SIG_SETMASK error"); printf("SIGQUIT unblocked\n"); sleep(5); /* SIGQUIT here will terminate with core file */ 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"); }
进程开始阻塞SIGQUIT信号,保存了当前信号屏蔽字(以便以后恢复),然后休眠5秒。在此期间所产生的退出信号SIGQUIT都被阻塞,不递送至该进程。
5秒休眠后,检查该信号是否是未决的,然后将SIGQUIT设置为不再阻塞。
运行程序,在5s之内键入退出字符Ctril+\(产生SIGQUIT信号),然后在第二个5s之内再次键入退出字符。
函数sigaction
sigaction函数的功能是检查或修改与制定信号相关联的处理动作。此函数取代了UNIX早期版本使用的signal函数。
#include <signal.h> int sigaction(int signo,const struct sigction *restrict act,struct sigaction *restrict oact);
参数signo是要检测或修改其具体动作的信号编号。若act指针非空,则根据参数act修改其动作。若oact指针非空,则由oact指针返回该信号的上一个动作。
此函数使用下列结构:
struct sigaction { void (*sa_handler)(int); void (*sa_sigaction)(int, siginfo_t *, void *); sigset_t sa_mask; int sa_flags; };
sa_handler字段包含一个信号捕捉函数的地址。
sa_mask字段说明了一个信号集,在调用该信号捕捉函数之前,这一信号集要加到进程的信号屏蔽字中。仅当从信号捕捉函数返回时将进程的信号屏蔽字恢复为原先值。
sa_flags字段指定对信号进行处理的各个选项。
sa_sigaction字段是一个替代的信号处理程序,当sa_flags设置为SA_SIGINFO时,使用该信号处理程序。
通常按下列方式调用信号处理程序:
void handler(int signo);
在设置了SA_SIGINFO标志,那么按下列凡是调用信号处理程序:
void handler(int signo,siginfo_t *info,void *context);
下面使用sigaction实现signal函数,它力图阻止被中断的系统调用重启动
typedef void Sigfunc(int);Sigfunc* mysignal(int signo,Sigfunc *func) { struct sigaction act,oact; act.sa_handler = func; sigemptyset(&act.sa_mask); act.sa_flags = 0; 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); }
函数sigsetjmp和siglongjmp
之前说明了setjmp和longjmp函数可以用户非局部转移,sigsetjmp跟siglongjmp指定了对信号屏蔽字的作用。
在信号处理程序中进行非局部转移时应当使用这两个函数。
#include <setjmp.h> int sigsetjmp(sigjmp_buf env,int savemask); void siglongjmp(sigjmp_buf env,int val);
与setjmp和longjmp函数唯一的区别是sigsetjmp增加了一个参数savemask。
如果savemask非0,则sigsetjmp在env中保存在env中保存进程的当前信号屏蔽字。调用siglongjmp时,从已经保存的env中恢复保存的信号屏蔽字。
函数sigsuspend
sigsuspend用于在接收到某个信号之前,临时用sigmask替换进程的信号屏蔽字,并暂停进程执行,直到捕捉到一个信号而且从该信号处理程序返回,并且进程的信号屏蔽字设置为调用sigsuspend之前的值。
#include <signal.h> int sigsuspend(const sigset_t *sigmask);
下面显示了保护代码临界区,使其不被特定信号中断的正确方法
#include "apue.h" static void sig_int(int); int main(void) { sigset_t newmask, oldmask, waitmask; pr_mask("program start: "); if (signal(SIGINT, sig_int) == SIG_ERR) err_sys("signal(SIGINT) error"); sigemptyset(&waitmask); sigaddset(&waitmask, SIGUSR1); sigemptyset(&newmask); sigaddset(&newmask, SIGINT); /* * Block SIGINT and save current signal mask. */ if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0) err_sys("SIG_BLOCK error"); /* * 代码临界区 */ pr_mask("in critical region: "); /* * Pause, allowing all signals except SIGUSR1. */ if (sigsuspend(&waitmask) != -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 ... */ pr_mask("program exit: "); exit(0); } static void sig_int(int signo) { pr_mask("\nin sig_int: "); }
下面是程序运行结果:
函数abort
abort函数的功能是使程序异常终止
#include <stdlib.h> void abort(void);
此函数将SIGABRT信号发送给调用进程。让进程捕捉SIGABRT信号目的是在进程终止之前由其执行所需的清理操作。默认情况是终止调用进程。
函数system
POSIX.1要求system函数忽略SIGINT和SITQUIT信号,阻塞SIGCHLD。
函数sleep
此函数使调用进程被挂起,直到满足下列条件之一:
(1)已经经过seconds所指定的墙上时钟时间。
(2)调用进程捕捉到一个信号并从信号处理程序返回。