第十章 信号
信号是软件中断,它提供了一种处理异步事件的方法。本章对信号机制进行综述,并说明每种信号的一般用法。
1、信号概念
每个信号都有一个名字,这些名字都以3个字符SIG开头。在头文件
1.1 产生信号的条件:
(1)当用户按某写终端按键时,引发终端产生的信号。如:Ctrl+C产生SIGINT信号。
(2)硬件异常产生信号。如除零错,无效的内存应用产生SIGSEGV信号。
(3)进程调用kill函数可将任一信号发送给另一个进程或进程组。
(4)用户可用kill命令将信号发送给其他进程。
(5)当检测到某种软件条件已经发生,并应将其通知有关进程时也产生信号。如SIGPIPE在管道的读进程已终止后,一个进程写此管道。
1.2 在某个信号出现时,可以按下列3种方式之一进行处理:
(1) 忽略该信号。大多数信号都可以使用这种方式进行处理,但有两种信号却决不能被忽略:SIGKILL和SIGSTOP(只能执行系统默认动作)。
(2) 捕获信号。通知内核在某信号发生时,调用一个用户函数对这种时间进行处理。
(3) 执行系统默认动作。对于大多数信号的系统默认动作是终止该进程。
2、信号类型
程序错误类信号:默认动作使进程流产,产生core文件。
SIGABRT: 调用abort函数生成的信号。
SIGFPE: 浮点计算错误。
SIGILL: 非法指令错误。
SIGBUS/SIGSEGV: 硬件错误-非法地址访问。
SIGEMT: 硬件错误
SIGSYS: 非法系统调用。
SIGTRAP: 硬件错误(通常为断点指令)。
程序终止类信号:默认动作使进程终止,我们通常要处理这类信号,做一些清理工作,句柄函数应在结束时为此信号指定默认动作,然后再次生成该信号,使得程序终止。
SIGHUP:终端断开连接时,生成此信号给控制进程。
SIGINT:Ctrl-C或Delete按下时,由终端驱动生成,并发送给前台进程组中的所有进程。
SIGKILL:使程序立即终止,不能被捕获或忽略,也不能被阻塞。
SIGQUIT:Ctrl-\,如SIGINT,并且产生core。
SIGTERM:该信号使程序终止,但是可以阻塞、捕获、忽略。
闹钟类信号:通知定时器到期,默认动作是终止程序,但通常会设置句柄。
SIGALRM:alarm/setitimer函数设置定时到期后,会产生此信号。
SIGPROF:
SIGVTALRM:
I/O类信号:通知进程在描述字上发生了感兴趣事件,支持信号驱动IO。
SIGIO: fd准备执行输入输出时发送此信号。
SIGPOLL:异步I/O信号。
SIGURG:网络收到带外数据时可选择生成此信号。
作业控制类信号:
SIGCHLD: 进程终止或停止时会向其父进程发送该信号,默认动作为忽略。
SIGCONT: 使停止的进程恢复运行。
SIGSTOP: 停止进程。
SIGTSTP/SIGTTIN/SIGTTOU:
操作错误类信号:默认动作终止程序。
SIGPIPE: 管道破裂。
SIGXCPU/SIGXFSZ:
3、函数signal
UNIX系统信号机制最简单的接口是signal函数,不过最好使用sigaction代替该函数。
#include
void (*signal(int signo,void (*func)(int)))(int);
//返回值:若成功,返回以前的信号处理配置;若出错,返回SIG_ERR。
//可以使用typedef使其变得简单一点
typedef void Sigfunc(int);
Sigfunc *signal(int,Sigfunc *);
第一个参数指定要捕获的信号(整形常量),第二个参数指定针对前面信号值的处理handler可以是SIG_IGN(忽略该信号)、SIG_DFL(系统默认动作)或者是接到此信号后要调用的函数地址。我们称这种处理为捕捉该信号,称此函数为信号处理程序或信号捕捉函数,第二个参数是一个函数指针(处理函数),该函数指针指向的函数返回值是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命令将信号发送给它:
4、中断的系统调用
早期UNIX系统的一个特性是:如果进程在执行一个低速系统调用而阻塞期间捕获到一个信号,则该系统调用就被中断不再继续执行。该系统调用返回出错,其errno设置为EINTR。后面的章节会更多的涉及到被中断的系统调用。
5、可重入函数
进程捕捉的信号并对其进行处理时,进程正在执行的正常指令序列就被信号处理程序临时终端,它首先执行该信号处理程序中的指令,如果从信号处理程序返回,则继续执行在捕捉的信号时进程正在执行的正常指令序列。但是在返回控制时不会出现任何错误,就叫可重入函数。而不可重入的函数由于使用了一些系统资源,比如全局变量区,中断向量表等,所以它如果被中断的话,可能会出现问题,这类函数是不能运行在多任务环境下的。
在信号处理函数中调用某些函数可能对导致安全问题(其结果是不可预知的),下面列出了这些异步信号安全的函数,没有列入图中的大多数函数是不可重入的。因为(a)已知他们使用静态数据类型;(b)他们调用malloc或free;(c)他们是标准I/O。标准I/O库的很多实现都以不可重入方式使用全局数据结构。
6、函数kill和raise
kill函数将信号发送给进程或进程组,raise函数则允许进程向自身发送信号。
#include
int kill(pid_t pid,int signo);
int raise(int signo);
//两个函数返回值:若成功,返回0;若出错,返回-1.
调用raise(signo);相当于调用kill(getpid(),signo);
Kill的pid参数有以下4种不同的情况:
(1) pid>0 将该信号发送给进程ID为pid的进程
(2) pid=0 将该信号发送给于发送进程属于同一个进程组的所有进程
(3) pid<0 将该信号发送给其进程组ID等于pid绝对值,而且发送进程具有权限向其发送信号的所有进程
(4) pid=-1 将该进程发送给发送进程有权限向它们发送信号的所有进程。
其上所说所有进程不包括系统进程集中的进程。
7、函数alarm和pause
函数alarm设置一个定时器,当定时器超时时,产生SIGALRM信号。
#include
unsigned int alarm(unsigned int seconds);
//返回值:0或以前设置的闹钟时间的余留秒数。
pause函数使调用进程挂起直至捕捉到一个信号
#include
int pause(void);
//返回值:-1,errno设置为EINTR。
只有执行了一个信号处理程序并从其返回时,pause才返回。此时,pause返回-1,errno设置为EINTR。
8、信号集
信号集是能表示多个信号的数据结构(sigset_t),下面列出5个处理信号集的函数
#include
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset(sigset_t *set,int signo);
int sigdelset(sigset_t *set,int signo);
//四个函数返回值:若成功,返回0;若出错,返回-1.
int siggismember(const sigset_t *set,int signo);
//返回值:若真,返回1;若假,返回0.
在使用信号集之前,要对该信号集进行初始化(调用sigemptyset或者sigfillset)。
9、函数sigprocmask
进程的信号屏蔽字规定了当前阻塞而不能递送给该进程的信号集。调用函数sigprocmask可以检测或更改进程的信号屏蔽字。
#include
int sigprocmask(int how,const sigset_t *restrict set,sigset_t *restrict oset);//返回值:若成功,返回0;若出错,返回-1.
首先,若oset是非空指针,那么进程的当前信号屏蔽字通过oset返回。其次,若set是一个非空指针,则参数how指示如何修改当前信号屏蔽字。SIG_BLOCK是或操作,SIG_SETMASK则是赋值操作。不能阻塞SIGKILL和SIGSTOP信号。如果set是空指针,则不改变该进程的信号屏蔽字。
10、函数sigpending
sigpending函数返回一信号集,对于调用进程而言,其中的各信号是阻塞不能递送的,因而也一定是当前未决的。
#include
ing sigpending(sigset_t *set);
//返回值:若成功,返回0;若出错,返回-1.
下面展示信号设置和sigprocmask实例
进程开始阻塞SIGQUIT信号,保存了当前信号屏蔽字(以便以后恢复),然后休眠5秒。在此期间所产生的退出信号SIGQUIT都被阻塞,不递送至该进程。
5秒休眠后,检查该信号是否是未决的,然后将SIGQUIT设置为不再阻塞。
运行程序,在5s之内键入退出字符Ctril+\(产生SIGQUIT信号),然后在第二个5s之内再次键入退出字符。
11、 函数sigaction
sigaction函数的功能是检查或修改与制定信号相关联的处理动作。此函数取代了UNIX早期版本使用的signal函数。
#include
int sigaction(int signo,const struct sigction *restrict act,struct sigaction *restrict oact);//返回值:若成功,返回0;若出错,返回-1.
参数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_sigaction字段是一个替代的信号处理程序,当sa_flags设置为SA_SIGINFO时,使用该信号处理程序。
sa_flags字段指定对信号进行处理的各个选项。
通常按下列方式调用信号处理程序:
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);
}
12、 函数sigsetjmp和siglongjmp
之前说明了setjmp和longjmp函数可以用于非局部转移,sigsetjmp跟siglongjmp指定了对信号屏蔽字的作用。
在信号处理程序中进行非局部转移时应当使用这两个函数。
#include
int sigsetjmp(sigjmp_buf env,int savemask);
//返回值:若直接调用,返回0;若从siglongjmp调用返回,则返回非0.
void siglongjmp(sigjmp_buf env,int val);
与setjmp和longjmp函数唯一的区别是sigsetjmp增加了一个参数savemask。如果savemask非0,则sigsetjmp在env中保存在env中保存进程的当前信号屏蔽字。调用siglongjmp时,从已经保存的env中恢复保存的信号屏蔽字。
13、函数sigsuspend
sigsuspend用于在接收到某个信号之前,临时用sigmask替换进程的信号屏蔽字,并暂停进程执行,直到捕捉到一个信号而且从该信号处理程序返回,并且进程的信号屏蔽字设置为调用sigsuspend之前的值。
#include
int sigsuspend(const sigset_t *sigmask);
//返回值:-1,并将errno设置为EINTR。
进程的屏蔽字设置为由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: ");
}
下面是程序运行结果:
14、函数abort
abort函数的功能是使程序异常终止
#include
void abort(void);
此函数将SIGABRT信号发送给调用进程。让进程捕捉SIGABRT信号目的是在进程终止之前由其执行所需的清理操作。默认情况是终止调用进程。
15、函数system
POSIX.1要求system函数忽略SIGINT和SITQUIT信号,阻塞SIGCHLD。
16、函数sleep
此函数使调用进程被挂起,直到满足下列条件之一:
(1)已经经过seconds所指定的墙上时钟时间。
(2)调用进程捕捉到一个信号并从信号处理程序返回。
#include
unsigned int sleep(unsigned int seconds);
17、函数sigqueue
之前学过kill,raise,alarm,abort等功能稍简单的信号发送函数,现在我们学习一种新的功能比较强大的信号发送函数sigqueue.
#include
#include
int sigqueue(pid_t pid, int sig, const union sigval val)
//调用成功返回 0;否则,返回 -1。
sigqueue()是比较新的发送信号系统调用,主要是针对实时信号提出的(当然也支持前32种),支持信号带有参数,与函数sigaction()配合使用。
sigqueue的第一个参数是指定接收信号的进程ID,第二个参数确定即将发送的信号,第三个参数是一个联合数据结构union sigval,指定了信号传递的参数,即通常所说的4字节值。
typedef union sigval {
int sival_int;
void *sival_ptr;
}sigval_t;
sigqueue()比kill()传递了更多的附加信息,但sigqueue()只能向一个进程发送信号,而不能发送信号给一个进程组。如果signo=0,将会执行错误检查,但实际上不发送任何信号,0值信号可用于检查pid的有效性以及当前进程是否有权限向目标进程发送信号。
在调用sigqueue时,sigval_t指定的信息会拷贝到对应sig 注册的3参数信号处理函数的siginfo_t结构中,这样信号处理函数就可以处理这些信息了。由于sigqueue系统调用支持发送带参数信号,所以比kill()系统调用的功能要灵活和强大得多。
参考:http://www.cnblogs.com/runnyu/p/4641346.html
https://blog.csdn.net/men_wen/article/details/61204064
https://www.cnblogs.com/mickole/p/3191804.html