一.信号概念
每个信号都以SIG开头。
二.各信号的含义
SIGNAL 调用abort函数时产生的信号。进程异常终止。
SIGALRM 在用alarm函数设置的计时器超时时,产生此信号
SIGBUS 指示一个实现定义的硬件故障
SIGCHLD 在一个进程终止或停止时,将SIGCHLD信号发送给其父进程。一般用wait函数获得子进程ID和终止状态
SIGFPE 此信号表示一个算术运算异常。例如除以0
SIGHUP 如果终端接口检测到一个连接断开,则将此信号发送给与终端相关得控制进程。
SIGILL 此信号指示进程已执行一条非法硬件指令。
SIGINT 当用户按中断键(DELETE或Ctrl+C)时,终端驱动程序产生此信号并送至前台进程组中得每一个进程。
SIGIO 此信号指示一个异步I/O事件。
SIGTERM 这是由kill(1)命令发送得系统默认终止信号
SIGTSTP 交互式停止信号,当用户在终端上按下挂起键(一般为Ctrl+Z)时,终端驱动程序产生此信号。
SIGQUIT 当用户按下退出键(Ctrl+\)时,产生此信号,并送至前台进程组中得所有进程。
SIGUSR1 这是一个用户定义的信号,可用于应用程序
SIGUSR2 另一个用户定义的信号,与SIGUSR1相似
三.signal函数
UNIX系统得信号机制最简单得接口是signal函数
#include<signal.h>
void(*signal(int signo,void(*func)(int)))(int);//返回值:若成功返回信号以前处理配置,出错返回SIG_ERR
signo参数为信号名,func的值是常量SIG_IGN、常量SIG_DFL或当接到此信号后要调用的函数的地址。当指定函数地址时,则在信号发生时,调用该函数,我们称这种处理为"捕捉"该信号,称此函数为信号处理函数或信号捕捉函数。
signal函数需要两个参数,第一个是参数signo是一个整数,第二个参数是函数指针,它指向的函数需要一个整型参数。
例程:
#include "apue.h"
static void sig_user(int signo)
{
if(signo==SIGUSR1)
printf("receive SIGUSR1\n");
else if(signo==SIGUSR2)
printf("receive SIGUSR2\n");
else
err_dump("receive signal %d\n",signo);
}
int main()
{
if(signal(SIGUSR1,sig_user)==SIG_ERR)
err_sys("can't catch SIGUSR1");
if(signal(SIGUSR2,sig_user)==SIG_ERR)
err_sys("can't catch SIGUSR2");
for(;;)
pause();//它使调用进程挂起直至捕捉到一个信号
}
运行结果:
kill -USR1 20768 //向该进程发送SIGUSR1
kill -USR2 20768 //向该进程发送SIGUSR2
在后台调用该程序,用kill(1)命令将信号传送给它,在UNIX中,杀死(kill)这个术语是不恰当的,kill(1)和kill(2)只是将一个信号送给一个进程或进程组。信号是否终止进程却决于信号的类型,以及进程是否安排了捕捉该信号。
四.不可靠信号
信号可能会丢失:一个信号发生了,但进程可能却一直不知道。
五.kill函数和raise函数
kill函数将信号发送给进程或进程组。raise函数则允许进程向自身发送信号。
函数定义:
#include<signal.h>
int kill(pid_t pid,int signo);
int raise(int signo);//两个函数的返回值:成功返回0,出错返回-1
其中:raise(signo);与kill(getpid(),signo);等价,即当前进程
kill的pid参数的4种情况:
pid>0 将该信号发送给进程ID为pid的进程
pid==0 将该信号发送给与发送进程属于同一进程组的所有进程。
pid<0 将该信号发送给其进程组ID等于pid的绝对值。
pid==-1 将该信号发送给发送进程有权限向他们发送信号的系统上的所有进程。
六.alarm和pause函数
使用alarm函数可以设置一个计时器,在某个指定的时间该计时器会超时,此时产生SIGALRM信号。如果不忽略或不捕捉此信号,其默认动作是终止调用该alarm函数的进程。
#include<unistd.h>
unsigned int alarm(unsigned int seconds);//返回值:0或以前设置的闹钟时间的余留秒数
参数seconds的值是秒数,经过seconds秒产生SIGALRM信号。经过指定秒数后,信号由内核产生,由于进程调度的延迟,进程得到控制从而处理该信号还需一些时间。
pause函数将调用进程挂起直至捕捉到一个信号。
#include<unistd.h>
int pause(void);//返回值:-1;并将errno设置为EINTR
只有执行了一个信号处理程序并从其中返回时,pause才返回。
例程:
#include "apue.h"
#include<setjmp.h>
#include<signal.h>
#include<unistd.h>
static jmp_buf env_alrm;
static void sig_alrm(int signo)
{
longjmp(env_alrm,1);//调用后setjmp返回值为1,未调用时setjmp返回值为0
}
unsigned int sleep2(unsigned int nes)
{
if(signal(SIGALRM,sig_alrm)==SIG_ERR)//SIGALRM为计时器超时信号
return(nes);
if(setjmp(env_alrm)==0)//在没有计时器超时情况下,先执行此段语句,超时后执行signal,使setjmp返回值为1
{
alarm(nes);
pause();
}
return(alarm(0));
}
static void sig_int(int signo)
{
int i,j;
volatile int k;//volatile阻止优化编译器丢弃循环语句
printf("sig_int starting\n");
for(i=0;i<4;i++)//使运行时间超过5s
for(j=0;j<4;j++)
k +=i*j;
printf("sig_int finished\n");
}
int main()
{
unsigned int unsleep;
if(signal(SIGINT,sig_int)==SIG_ERR)//SIGINT为中断信号
err_sys("errno");
unsleep=sleep2(5);
printf("sleep2 returned:%u\n",unsleep);
exit(0);
}
运行结果:
前一条运行语句是加了中断得结果,后一条为未加中断得结果
说明:
SIGINT处理程序中包含了for循环,执行时间超过5s,则运行结果中没有打印sig_int finished语句
七.信号集
信号集(sigset_t):一个能表示多个信号的数据类型
#include<signal.h>
int sigemptyset(sigset_t *set);//初始化由set指向得信号集,清除其中的所有信号
int sigfillset(sigset_t *set);//初始化由set指向的信号集,使其包括所有信号
int sigaddset(sigset_t *set,int signo);//增加一个信号到信号集
int sigdelset(sigset_t *set,int signo);//从信号集中删除一个信号
//四个函数得返回值:成功返回0,出错返回-1
int sigismenber(const sigset_t *set,int signo);//返回值:若真返回1,若假返回0,出错返回-1
7.1 sigprocmask函数
告诉内核不允许发生该信号集中的信号。
信号屏蔽字:一个进程的信号屏蔽字规定了当前阻塞而不能递送给该进程的信号集。调用sigprocmask可以检测或更改七信号屏蔽字,或者在一个步骤中同时执行者两个操作。
#include<signal.h>
int sigprocmask(int how,const sigset_t *restrict set,sigset_t *restrict oset);//返回值:成功返回0,出错返回-1
(1)当oset是非空指针,进程当前信号屏蔽字通过oset返回
(2)若set是非空指针,参数how指示如何修改当前信号屏蔽字。
how的值:
SIG_BLOCK 该进程的新的信号屏蔽字是其当前信号屏蔽字和set指向信号集的并集。(或操作)set包含我们希望阻塞的附加信号
SIG_UNBLOCK 该进程的新的信号屏蔽字是其当前信号屏蔽字和set指向信号集的交集。(与操作)set包含我们希望解除阻塞的信号
SIG_SETMASK 改进程新的信号屏蔽字将被set指向的信号集的值代替
7.2 sigpending函数
sigpending函数返回信号集,其中的各个信号对于调用进程是阻塞的而不能递送,因而也一定是当前未决的。该信号集通过set参数返回
#include<signal.h>
int sigpending(sigset_t *set);//返回值:成功返回0,出错返回-1
7.3 sigaction函数
检查或修改与指定信号相关联的处理动作。
#include<signal.h>
int sigaction(int signo,const struct sigaction *restrict act,struct sigaction *restrict oact);
//返回值:成功返回0,出错返回-1
八.sigsetjmp和siglongjmp函数
在信号处理程序中进行非局部转移时应当使用这两个函数。
#include<setjmp.h>
int sigsetjmp(sigjmp_buf env,int savemask);//返回值:直接调用返回0,从siglongjmp调用返回val值
int siglongjmp(sigjmp_buf env,int val);
若savemask为非0,则sigsetjmp再env中保存进程的当前信号屏蔽字,调用siglongjmp时,若带非0savemask的sigsetjmp调用已经保存了env,则siglongjmp从其中恢复保存的信号屏蔽字。
例程:
#include"apue.h"
#include<setjmp.h>
#include<time.h>
#include<errno.h>
static void sig_usr1(int),sig_alrm(int);
void pr_mask(const char *);
static sigjmp_buf jmpbuf;
static volatile sig_atomic_t canjmp;
int main()
{
if(signal(SIGUSR1,sig_usr1)==SIG_ERR)
err_sys("signal SIGUSR1 ERR");
if(signal(SIGALRM,sig_alrm)==SIG_ERR)
err_sys("signal SIGALRM ERR");
pr_mask("starting main:");
if(sigsetjmp(jmpbuf,1))//未调用siglongjmp时,sigsetjmp返回值为0
{
pr_mask("ending main: ");
exit(0);
}
canjmp=1;
while(1)
pause();//抓取信号
}
static void sig_usr1(int signo)
{
time_t starttime;
if(canjmp == 0)
return;
pr_mask("start sig_usr1:");
alarm(3);//3s后计时器超时
starttime = time(NULL);
for(;;)//busy for 5s
if(time(NULL)>starttime + 5)
break;
pr_mask("finishing sig_usr1: ");
canjmp=0;
siglongjmp(jmpbuf,1);//jump to main
}
static void sig_alrm(int signo)
{
pr_mask("in sig_alrm: ");
}
void pr_mask(const char *str)
{
int errno_save;
sigset_t sigset;
errno_save=errno;
if(sigprocmask(0,NULL,&sigset)<0)
err_sys("sigprocmask err");
printf("%s",str);
if(sigismember(&sigset,SIGUSR1))
printf("SIGUSR1 ");
if(sigismember(&sigset,SIGALRM))
printf("SIGALRM ");
printf("\n");
errno = errno_save;
}
运行结果:
九.abort函数
异常程序终止
#include<stdlib.h>
void abort(void);
此函数将SIGABRT信号发送给调用进程。意图:在进程终止之前由其执行所需的清理操作,如果进程并不在信号处理程序中终止自己,abort终止该进程。