c语言信号处理.docx
《c语言信号处理.docx》由会员分享,可在线阅读,更多相关《c语言信号处理.docx(16页珍藏版)》请在冰豆网上搜索。
c语言信号处理
第一章信号
一、信号的概念
信号机制是进程之间相互传递消息的一种方法,信号全称为软中断信号,也有人称作软中断。
从它的命名可以看出,它的实质和使用很象中断。
所以,信号可以说是进程控制的一部分。
1.基本概念
软中断信号(signal,又简称为信号)用来通知进程发生了异步事件。
进程之间可以互相通过系统调用kill发送软中断信号。
内核也可以因为内部事件而给进程发送信号,通知进程发生了某个事件。
注意,信号只是用来通知某进程发生了什么事件,并不给该进程传递任何数据。
收到信号的进程对各种信号有不同的处理方法。
处理方法可以分为三类:
第一种是类似中断的处理程序,对于需要处理的信号,进程可以指定处理函数,由该函数来处理。
第二种方法是,忽略某个信号,对该信号不做任何处理,就象未发生过一样。
第三种方法是,对该信号的处理保留系统的默认值,这种缺省操作,对大部分的信号的缺省操作是使得进程终止。
进程通过系统调用signal来指定进程对某个信号的处理行为。
在进程表的表项中有一个软中断信号域,该域中每一位对应一个信号,当有信号发送给进程时,对应位置位。
由此可以看出,进程对不同的信号可以同时保留,但对于同一个信号,进程并不知道在处理之前来过多少个。
2.信号的类型
发出信号的原因很多,这里按发出信号的原因简单分类,以了解各种信号:
(1)与进程终止相关的信号。
当进程退出,或者子进程终止时,发出这类信号。
(2)与进程例外事件相关的信号。
如进程越界,或企图写一个只读的内存区域(如程序正文区),或执行一个特权指令及其他各种硬件错误。
(3)与在系统调用期间遇到不可恢复条件相关的信号。
如执行系统调用exec时,原有资源已经释放,而目前系统资源又已经耗尽。
(4)与执行系统调用时遇到非预测错误条件相关的信号。
如执行一个并不存在的系统调用。
(5)在用户态下的进程发出的信号。
如进程调用系统调用kill向其他进程发送信号。
(6)与终端交互相关的信号。
如用户关闭一个终端,或按下break键等情况。
(7)跟踪进程执行的信号。
Linux支持的信号列表如下。
很多信号是与机器的体系结构相关的,首先列出的是POSIX.1中列出的信号:
信号值处理动作发出信号的原因
----------------------------------------------------------------------
SIGHUP1A终端挂起或者控制进程终止
SIGINT2A键盘中断(如break键被按下)
SIGQUIT3C键盘的退出键被按下
SIGILL4C非法指令
SIGABRT6C由abort(3)发出的退出指令
SIGFPE8C浮点异常
SIGKILL9AEFKill信号
SIGSEGV11C无效的内存引用
SIGPIPE13A管道破裂:
写一个没有读端口的管道
SIGALRM14A由alarm
(2)发出的信号
SIGTERM15A终止信号
SIGUSR130,10,16A用户自定义信号1
SIGUSR231,12,17A用户自定义信号2
SIGCHLD20,17,18B子进程结束信号
SIGCONT19,18,25进程继续(曾被停止的进程)
SIGSTOP17,19,23DEF终止进程
SIGTSTP18,20,24D控制终端(tty)上按下停止键
SIGTTIN21,21,26D后台进程企图从控制终端读
SIGTTOU22,22,27D后台进程企图从控制终端写
下面的信号没在POSIX.1中列出,而在SUSv2列出
信号值处理动作发出信号的原因
--------------------------------------------------------------------
SIGBUS10,7,10C总线错误(错误的内存访问)
SIGPOLLASysV定义的Pollable事件,与SIGIO同义
SIGPROF27,27,29AProfiling定时器到
SIGSYS12,-,12C无效的系统调用(SVID)
SIGTRAP5C跟踪/断点捕获
SIGURG16,23,21BSocket出现紧急条件(4.2BSD)
SIGVTALRM26,26,28A实际时间报警时钟信号(4.2BSD)
SIGXCPU24,24,30C超出设定的CPU时间限制(4.2BSD)
SIGXFSZ25,25,31C超出设定的文件大小限制(4.2BSD)
(对于SIGSYS,SIGXCPU,SIGXFSZ,以及某些机器体系结构下的SIGBUS,Linux缺省的动作是A(terminate),SUSv2是C(terminateanddumpcore))。
下面是其它的一些信号
信号值处理动作发出信号的原因
----------------------------------------------------------------------
SIGIOT6CIO捕获指令,与SIGABRT同义
SIGEMT7,-,7
SIGSTKFLT-,16,-A协处理器堆栈错误
SIGIO23,29,22A某I/O操作现在可以进行了(4.2BSD)
SIGCLD-,-,18A与SIGCHLD同义
SIGPWR29,30,19A电源故障(SystemV)
SIGINFO29,-,-A与SIGPWR同义
SIGLOST-,-,-A文件锁丢失
SIGWINCH28,28,20B窗口大小改变(4.3BSD,Sun)
SIGUNUSED-,31,-A未使用的信号(willbeSIGSYS)
(在这里,-表示信号没有实现;有三个值给出的含义为,第一个值通常在Alpha和Sparc上有效,中间的值对应i386和ppc以及sh,最后一个值对应mips。
信号29在Alpha上为SIGINFO/SIGPWR,在Sparc上为SIGLOST。
)
处理动作一项中的字母含义如下
A缺省的动作是终止进程
B缺省的动作是忽略此信号
C缺省的动作是终止进程并进行内核映像转储(dumpcore)
D缺省的动作是停止进程
E信号不能被捕获
F信号不能被忽略
上面介绍的信号是常见系统所支持的。
以表格的形式介绍了各种信号的名称、作用及其在默认情况下的处理动作。
各种默认处理动作的含义是:
终止程序是指进程退出;忽略该信号是将该信号丢弃,不做处理;停止程序是指程序挂起,进入停止状况以后还能重新进行下去,一般是在调试的过程中(例如ptrace系统调用);内核映像转储是指将进程数据在内存的映像和进程在内核结构中的部分内容以一定格式转储到文件系统,并且进程退出执行,这样做的好处是为程序员提供了方便,使得他们可以得到进程当时执行时的数据值,允许他们确定转储的原因,并且可以调试他们的程序。
注意信号SIGKILL和SIGSTOP既不能被捕捉,也不能被忽略。
信号SIGIOT与SIGABRT是一个信号。
可以看出,同一个信号在不同的系统中值可能不一样,所以建议最好使用为信号定义的名字,而不要直接使用信号的值。
3.常见信号功能说明
1)SIGHUP本信号在用户终端连接(正常或非正常)结束时发出,通常是在终端的控制进程结束时,通知同一session内的各个作业,这时它们与控制终端不再关联.
2)SIGINT程序终止(interrupt)信号,在用户键入INTR字符(通常是Ctrl-C)时发出
3)SIGQUIT和SIGINT类似,但由QUIT字符(通常是Ctrl-\)来控制.进程在因收到SIGQUIT退出时会产生core文件,在这个意义上类似于一个程序错误信号.
4)SIGILL执行了非法指令.通常是因为可执行文件本身出现错误,或者试图执行数据段.堆栈溢出时也有可能产生这个信号.
5)SIGTRAP由断点指令或其它trap指令产生.由debugger使用.
6)SIGABRT程序自己发现错误并调用abort时产生.
7)SIGIOT在PDP-11上由iot指令产生,在其它机器上和SIGABRT一样.
8)SIGBUS非法地址,包括内存地址对齐(alignment)出错.eg:
访问一个四个字长的整数,但其地址不是4的倍数.
9)SIGFPE在发生致命的算术运算错误时发出.不仅包括浮点运算错误,还包括溢出及除数为0等其它所有的算术的错误.
10)SIGKILL用来立即结束程序的运行.本信号不能被阻塞,处理和忽略.
11)SIGUSR1留给用户使用
12)SIGSEGV试图访问未分配给自己的内存,或试图往没有写权限的内存地址写数据.
13)SIGUSR2留给用户使用
14)SIGPIPEBrokenpipe
15)SIGALRM时钟定时信号,计算的是实际的时间或时钟时间.alarm函数使用该信号.
16)SIGTERM程序结束(terminate)信号,与SIGKILL不同的是该信号可以被阻塞和处理.通常用来要求程序自己正常退出.shell命令kill缺省产生这个信号.
17)SIGCHLD子进程结束时,父进程会收到这个信号.
18)SIGCONT让一个停止(stopped)的进程继续执行.本信号不能被阻塞.可以用一个handler来让程序在由stopped状态变为继续执行时完成特定的工作.例如,重新显示提示符
19)SIGSTOP停止(stopped)进程的执行.注意它和terminate以及interrupt的区别:
该进程还未结束,只是暂停执行.本信号不能被阻塞,处理或忽略.
20)SIGTSTP停止进程的运行,但该信号可以被处理和忽略.用户键入SUSP字符时(通常是Ctrl-Z)发出这个信号
21)SIGTTIN当后台作业要从用户终端读数据时,该作业中的所有进程会收到SIGTTIN信号.缺省时这些进程会停止执行.
22)SIGTTOU类似于SIGTTIN,但在写终端(或修改终端模式)时收到.
23)SIGURG有紧急数据或out-of-band数据到达socket时产生.
24)SIGXCPU超过CPU时间资源限制.这个限制可以由getrlimit/setrlimit来读取/改变
25)SIGXFSZ超过文件大小资源限制.
26)SIGVTALRM虚拟时钟信号.类似于SIGALRM,但是计算的是该进程占用的CPU时间.
27)SIGPROF类似于SIGALRM/SIGVTALRM,但包括该进程用的CPU时间以及系统调用的时间.
28)SIGWINCH窗口大小改变时发出.
29)SIGIO文件描述符准备就绪,可以开始进行输入/输出操作.
30)SIGPWRPowerfailure
当用户按下ctrl+c时,进程被中断,catch()被执行.中断处理函数处理完毕后,转回断点执行下面的指令.
当编写自己的中断处理函数时,注意下面两点:
1.信号不能打断系统调用.
2.信号不能打断信号处理函数.
Signal机制在Linux中是一个非常常用的进程间通信机制,很多人在使用的时候不会考虑该机制是具体如何实现的。
signal机制可以被理解成进程的软中断,因此,在实时性方面还是相对比较高的。
每个进程都会采用一个进程控制块对其进行描述,进程控制块中设计了一个signal的位图信息,其中的每位与具体的signal相对应,这与中断机制是保持一致的。
当系统中一个进程A通过signal系统调用向进程B发送signal时,设置进程B的对应signal位图,类似于触发了signal对应中断。
发送signal只是“中断”触发的一个过程,具体执行会在两个阶段发生:
1、systemcall返回。
进程B由于调用了systemcall后,从内核返回用户态时需要检查他拥有的signal位图信息表,此时是一个执行点。
2、中断返回。
进程被系统中断打断之后,系统将CPU交给进程时,需要检查即将执行进程所拥有的signal位图信息表,此时也是一个执行点。
综上所述,signal的执行点可以理解成从内核态返回用户态时,在返回时,如果发现待执行进程存在被触发的signal,那么在离开内核态之后(也就是将CPU切换到用户模式),执行用户进程为该signal绑定的signal处理函数,从这一点上看,signal处理函数是在用户进程上下文中执行的。
当执行完signal处理函数之后,再返回到用户进程被中断或者systemcall(软中断或者指令陷阱)打断的地方。
Signal机制实现的比较灵活,用户进程由于中断或者systemcall陷入内核之后,将断点信息都保存到了堆栈中,在内核返回用户态时,如果存在被触发的signal,那么直接将待执行的signal处理函数push到堆栈中,在CPU切换到用户模式之后,直接pop堆栈就可以执行signal处理函数并且返回到用户进程了。
Signal处理函数应用了进程上下文,并且应用实际的中断模拟了进程的软中断过程。
4.SIGCLD处理方式
APUE上SIGCLD语义写的有点不清楚,到底我们的系统是如何来处理SIGCLD信号呢?
1)SIG_DFL
默认的处理方式是不理会这个信号,但是也不会丢弃子进行状态,所以如果不用wait,waitpid
对其子进行进行状态信息回收,会产生僵尸进程。
2)SIG_IGN
忽略的处理方式,这个方式和默认的忽略是不一样的语意,暂且我们把忽略定义为SIG_IGN,在这种方式下,子进程状态信息会被丢弃,也就是自动回收了,所以不会产生僵尸进程,但是问题也就来了,wait,waitpid却无法捕捉到子进程状态信息了,如果你随后调用了wait,那么会阻塞到所有的子进程结束,并返回错误ECHILD,也就是没有子进程等待。
APUE中P248叙述SIGCHLD如果配置成SIG_IGN也不会产生僵尸进程。
是否系统SIG_IGN配置下,对SIGCLD,SIGCHLD做出的处理方式是相同的。
3)自定义处理方式
SIGCLD会立即检查是否有子进程准备好被等待,这便是SIGCLD最大漏洞了,一旦在信号处理函数中加入了信号处理方式重建的步骤,那么每次设置SIGCLD处理方式时,都会去检查是否有信号到来,如果此时信号的确到来了,先是调用自定义信号处理函数,然后是调用信号处理方式重建函数,在重建配置的时候,会去检查信号是否到来,此时信号未被处理,会再次触发自定义信号处理函数,一直循环。
所以在处理SIGCLD时,应该先wait处理掉了信号信息后,再进行信号处理方式重建。
SIGCHLD在配置信号处理方式时,是不会立即检查是否有子进程准备好被扽带,也不会在此时调用信号处理函数。
二、Linux下的信号事件
1.信号的产生
Linux下的信号可以类比于DOS下的INT或者是Windows下的事件.在有一个信号发生时候相信的信号就会发送给相应的进程.在Linux下的信号有以下几个.
我们使用kill-l命令可以得到以下的输出结果:
1)SIGHUP2)SIGINT3)SIGQUIT4)SIGILL5)SIGTRAP6)SIGABRT7)SIGBUS8)SIGFPE9)SIGKILL10)SIGUSR111)SIGSEGV12)SIGUSR213)SIGPIPE14)SIGALRM15)SIGTERM17)SIGCHLD18)SIGCONT19)SIGSTOP20)SIGTSTP21)SIGTTIN22)SIGTTOU23)SIGURG24)SIGXCPU25)SIGXFSZ26)SIGVTALRM27)SIGPROF28)SIGWINCH29)SIGIO30)SIGPWR
关于这些信号的详细解释请查看man7signal的输出结果。
信号事件的发生有两个来源:
●一个是硬件的原因(比如我们按下了键盘);
●一个是软件的原因(比如我们使用系统函数或者是命令发出信号).
最常用的四个发出信号的系统函数是kill,raise,alarm和setitimer函数.setitimer函数我们在计时器的使用那一章再学习.
#include
#include
#include
intkill(pid_tpid,intsig);
intraise(intsig);
unisignedintalarm(unsignedintseconds);
1)kill系统调用负责向进程发送信号sig
●如果pid是正数,那么信号sig被发送到进程pid。
●如果pid等于0,那么信号sig被发送到所有和pid进程在同一个进程组的进程。
●如果pid等于-1,那么信号发给所有的进程表中的进程,除了最大的那个进程号。
●如果pid小于-1,和0一样,只是发送进程组是-pid。
我们用最多的是第一个情况.还记得我们在守护进程那一节的例子吗?
我们那个时候用这个函数杀死了父进程守护进程的创建
2)raise系统调用向自己发送一个sig信号
我们可以用上面那个函数来实现这个功能的.
3)alarm函数
alarm函数和时间有点关系了,这个函数可以在seconds秒后向自己发送一个SIGALRM信号.下面这个函数会有什么结果呢?
#include
main()
{
unsignedinti;
alarm
(1);
for(i=0;1;i++)
printf("I=%d",i);
}
SIGALRM的缺省操作是结束进程,所以程序在1秒之后结束。
2.信号操作
有时候我们希望进程正确的执行,而不想进程受到信号的影响,比如我们希望上面那个程序在1秒钟之后不结束.这个时候我们就要进行信号的操作了.信号操作最常用的方法是信号屏蔽.信号屏蔽要用到下面的几个函数.
#include
intsigemptyset(sigset_t*set);
intsigfillset(sigset_t*set);
intsigaddset(sigset_t*set,intsigno);
intsigdelset(sigset_t*set,intsigno);
intsigismember(sigset_t*set,intsigno);
intsigprocmask(inthow,constsigset_t*set,sigset_t*oset);
1)sigemptyset函数
初始化信号集合set,将set设置为空。
2)sigfillset函数
也初始化信号集合,只是将信号集合设置为所有信号的集合.
3)sigaddset函数
将信号signo加入到信号集合之中。
4)sigdelset函数
将信号从信号集合中删除.
5)sigismember函数
查询信号是否在信号集合之中.
6)sigprocmask函数
是最为关键的一个函数.在使用之前要先设置好信号集合set.这个函数的作用是将指定的信号集合set加入到进程的信号阻塞集合之中去,如果提供了oset那么当前的进程信号阻塞集合将会保存在oset里面.参数how决定函数的操作方式.
◆SIG_BLOCK:
增加一个信号集合到当前进程的阻塞集合之中.
◆SIG_UNBLOCK:
从当前的阻塞集合之中删除一个信号集合.
◆SIG_SETMASK:
将当前的信号集合设置为信号阻塞集合.
以一个实例来解释使用这几个函数.
#include
#include
#include
#include
intmain(intargc,char**argv)
{
doubley;
sigset_tintmask;
inti,repeat_factor;
if(argc!
=2)
{
fprintf(stderr,"Usage:
%srepeat_factorna",argv[0]);
exit
(1);
}
if((repeat_factor=atoi(argv[1]))<1)repeat_factor=10;
sigemptyset(&intmask);
sigprocmask(SIG_UNBLOCK,&intmask,NULL);
fprintf(stderr,"SIGINTsignalunblockedn");
for(i=0;ifprintf(stderr,"Unblockedcalculationisfinishedn");
}
exit(0);
}
7)sigaction函数
程序在运行的时候我们要使用Ctrl+C来结束.如果我们在第一计算的时候发出SIGINT信号,由于信号已经屏蔽了,所以程序没有反映.只有到信号被取消阻塞的时候程序才会结束.
注意我们只要发出一次SIGINT信号就可以了,因为信号屏蔽只是将信号加入到信号阻塞集合之中,并没有丢弃这个信号.一旦信号屏蔽取消了,这个信号就会发生作用.
有时候我们希望对信号作出及时的反映的,比如当按下Ctrl+C时,我们不想什么事情也不做,我们想告诉用户你的这个操作不好,请不要重试,而不是什么反映也没有的.这个时候我们要用到sigaction函数.
#include
intsigaction(intsigno,conststructsigaction*act,structsigaction*oact);
structsigaction{
void(*sa_handler)(intsigno);
void(*sa_sigaction)(intsiginfo_t*info,void*act);
sigset_tsa_mask;
intsa_flags;
void(*sa_restore)(void);
}
这个函数和结构看起来是不是有点恐怖呢.不要被这个吓着了,其实这个函数的使用相当简单的.我们先解释一下各个参数的含义.
●signo很简单就是我们要处理的信号了,可以是任何的合法的信号.有两个信号不能够使用(SIGKILL和SIGSTOP).
●act包含我们要对这个信号进行如何处理的信息.
●oact更简单了就是以前对这个函数的处理信