signal机制在linux中是一个非常常用的进程间通信机制,很多人在使用的时候不会考虑该机制是具体如何实现的。signal机制可以被理解成进程的软中断,因此,在实时性方面还是相对比较高的。linux中signal机制的模型可以采用下图进行描述。
个进程都会采用一个进程控制块对其进行描述,进程控制块中设计了一个signal的位图信息,其中的每位与具体的signal相对应,这与中断机制是保持一致的。当系统中一个进程a通过signal系统调用向进程b发送signal时,设置进程b的对应signal位图,类似于触发了signal对应中断。发送signal只是“中断”触发的一个过程,具体执行会在两个阶段发生:
1、 system call返回。进程b由于调用了system call后,从内核返回用户态时需要检查他拥有的signal位图信息表,此时是一个执行点。
2、 中断返回。进程被系统中断打断之后,系统将cpu交给进程时,需要检查即将执行进程所拥有的signal位图信息表,此时也是一个执行点。
综上所述,signal的执行点可以理解成从内核态返回用户态时,在返回时,如果发现待执行进程存在被触发的signal,那么在离开内核态之后(也就是将cpu切换到用户模式),执行用户进程为该signal绑定的signal处理函数,从这一点上看,signal处理函数是在用户进程上下文中执行的。当执行完signal处理函数之后,再返回到用户进程被中断或者system call(软中断或者指令陷阱)打断的地方。
signal机制实现的比较灵活,用户进程由于中断或者system call陷入内核之后,将断点信息都保存到了堆栈中,在内核返回用户态时,如果存在被触发的signal,那么直接将待执行的signal处理函数push到堆栈中,在cpu切换到用户模式之后,直接pop堆栈就可以执行signal处理函数并且返回到用户进程了。signal处理函数应用了进程上下文,并且应用实际的中断模拟了进程的软中断过程。
最近写程序,各种bug各种错,有一回程序莫名退出,没报错,也没产生日志和core文件,貌似正常退出一样。
但又不是在程序全部走完后退出,中途莫名退出,这就叫我想到了signal,应该是某些函数错误后发送kill信号给主进程,然后退出。
现在总结下signal各种类型:
signal |
description |
sigabrt |
由调用abort函数产生,进程非正常退出 |
sigalrm |
用alarm函数设置的timer超时或setitimer函数设置的interval timer超时 |
sigbus |
某种特定的硬件异常,通常由内存访问引起 |
sigcancel |
由solaris thread library内部使用,通常不会使用 |
sigchld |
进程terminate或stop的时候,sigchld会发送给它的父进程。缺省情况下该signal会被忽略 |
sigcont |
当被stop的进程恢复运行的时候,自动发送 |
sigemt |
和实现相关的硬件异常 |
sigfpe |
数学相关的异常,如被0除,浮点溢出,等等 |
sigfreeze |
solaris专用,hiberate或者suspended时候发送 |
sighup |
发送给具有terminal的controlling process,当terminal被disconnect时候发送 |
sigill |
非法指令异常 |
siginfo |
bsd signal。由status key产生,通常是ctrl t。发送给所有foreground group的进程 |
sigint |
由interrupt key产生,通常是ctrl c或者delete。发送给所有foreground group的进程 |
sigio |
异步io事件 |
sigiot |
实现相关的硬件异常,一般对应sigabrt |
sigkill |
无法处理和忽略。中止某个进程 |
siglwp |
由solaris thread libray内部使用 |
sigpipe |
在reader中止之后写pipe的时候发送 |
sigpoll |
当某个事件发送给pollable device的时候发送 |
sigprof |
setitimer指定的profiling interval timer所产生 |
sigpwr |
和系统相关。和ups相关。 |
sigquit |
输入quit key的时候(ctrl \)发送给所有foreground group的进程 |
sigsegv |
非法内存访问 |
sigstkflt |
linux专用,数学协处理器的栈异常 |
sigstop |
中止进程。无法处理和忽略。 |
sigsys |
非法系统调用 |
sigterm |
请求中止进程,kill命令缺省发送 |
sigthaw |
solaris专用,从suspend恢复时候发送 |
sigtrap |
实现相关的硬件异常。一般是调试异常 |
sigtstp |
suspend key,一般是ctrl z。发送给所有foreground group的进程 |
sigttin |
当background group的进程尝试读取terminal的时候发送 |
sigttou |
当background group的进程尝试写terminal的时候发送 |
sigurg |
当out-of-band data接收的时候可能发送 |
sigusr1 |
用户自定义signal 1 |
sigusr2 |
用户自定义signal 2 |
sigvtalrm |
setitimer函数设置的virtual interval timer超时的时候 |
sigwaiting |
solaris thread library内部实现专用 |
sigwinch |
当terminal的窗口大小改变的时候,发送给foreground group的所有进程 |
sigxcpu |
当cpu时间限制超时的时候 |
sigxfsz |
进程超过文件大小限制 |
sigxres |
solaris专用,进程超过资源限制的时候发送 |
signal对应的值:
posix.1中列出的信号:
sighup 1 a 终端挂起或者控制进程终止
sigint 2 a 键盘中断(如break键被按下)
sigquit 3 c 键盘的退出键被按下
sigill 4 c 非法指令
sigabrt 6 c 由abort(3)发出的退出指令
sigfpe 8 c 浮点异常
sigkill 9 aef kill信号
sigsegv 11 c 无效的内存引用
sigpipe 13 a 管道破裂: 写一个没有读端口的管道
sigalrm 14 a 由alarm(2)发出的信号
sigterm 15 a 终止信号
sigusr1 30,10,16 a 用户自定义信号1
sigusr2 31,12,17 a 用户自定义信号2
sigchld 20,17,18 b 子进程结束信号
sigcont 19,18,25 进程继续(曾被停止的进程)
sigstop 17,19,23 def 终止进程
sigtstp 18,20,24 d 控制终端(tty)上按下停止键
sigttin 21,21,26 d 后台进程企图从控制终端读
sigttou 22,22,27 d 后台进程企图从控制终端写
没在posix.1中列出,而在susv2列出
sigbus 10,7,10 c 总线错误(错误的内存访问)
sigpoll a sys v定义的pollable事件,与sigio同义
sigprof 27,27,29 a profiling定时器到
sigsys 12,-,12 c 无效的系统调用 (svid)
sigtrap 5 c 跟踪/断点捕获
sigurg 16,23,21 b socket出现紧急条件(4.2 bsd)
sigvtalrm 26,26,28 a 实际时间报警时钟信号(4.2 bsd)
sigxcpu 24,24,30 c 超出设定的cpu时间限制(4.2 bsd)
sigxfsz 25,25,31 c 超出设定的文件大小限制(4.2 bsd)
(对于sigsys,sigxcpu,sigxfsz,以及某些机器体系结构下的sigbus,linux缺省的动作是a (terminate),susv2 是c (terminate and dump core))。
下面是其它的一些信号
信号 值 处理动作 发出信号的原因
----------------------------------------------------------------------
sigiot 6 c io捕获指令,与sigabrt同义
sigemt 7,-,7
sigstkflt -,16,- a 协处理器堆栈错误
sigio 23,29,22 a 某i/o操作现在可以进行了(4.2 bsd)
sigcld -,-,18 a 与sigchld同义
sigpwr 29,30,19 a 电源故障(system v)
siginfo 29,-,- a 与sigpwr同义
siglost -,-,- a 文件锁丢失
sigwinch 28,28,20 b 窗口大小改变(4.3 bsd, sun)
sigunused -,31,- a 未使用的信号(will be sigsys)
(在这里,- 表示信号没有实现;有三个值给出的含义为,第一个值通常在alpha和sparc上有效,中间的值对应i386和ppc以及sh,最后一个值对应mips。信号29在alpha上为siginfo / sigpwr ,在sparc上为siglost。)
处理动作一项中的字母含义如下
a 缺省的动作是终止进程
b 缺省的动作是忽略此信号
c 缺省的动作是终止进程并进行内核映像转储(dump core)
d 缺省的动作是停止进程
e 信号不能被捕获
f 信号不能被忽略
代码测试
#include
#include
#include
#include
void when_alarm();
void when_sigint();
void when_sigchld(int);
void when_sigusr1();
void when_sigio();
int main()
{
int childpid;//子程序进程id号
printf("程序已经开始运行,5秒钟后将接收到时钟信号。/n");
if ((childpid=fork())>0)//父进程
{
signal(sigalrm,when_alarm); //当接收到sigalrm信号时,调用when_alarm函数
signal(sigint,when_sigint); //当接收到sigint信号时,调用when_sigint函数
signal(sigchld,when_sigchld);//当接收到sigchld信号时,调用when_sigchld函数
signal(sigusr1,when_sigusr1);//当接收到sigusr1信号时,调用when_sigusr1函数
signal(sigio,when_sigio);//当接收到sigio信号时,调用when_sigio函数
alarm(5); //5秒钟之后产生sigalrm信号
raise(sigio); //向自己发送一个sigio信号
pause(); //将父进程暂停下来,等待sigalrm信号到来
pause(); //将父进程暂停下来,等待sigusr1信号到来
pause(); //将父进程暂停下来,等待sigchld信号到来
printf("------此时程序会停下来等待,请按下ctrl c送出sigint信号-------/n");
pause(); //将父进程暂停下来,等待sigint信号到来
}
else if(childpid==0) //子进程
{
int timer;
for(timer=7;timer>=0;timer--) //时钟计时5秒产生sigalrm信号,再过2秒子进程退出,产生sigchld信号
{
if(timer>2)
printf("距离sigalrm信号到来还有%d秒。/n",timer-2);
if(timer==4)
kill(getppid(),sigusr1); //向父进程发送一个sigusr1信号
if((timer<=2)&&(timer>0))
printf("子进程还剩%d秒退出,届时会产生sigchld信号。/n",timer);
if(timer==0) //子进程退出,产生sigchld信号
raise(sigkill); //子进程给自己发一个结束信号
sleep(1); //每个循环延时1秒钟
}
}
else
printf("fork()函数调用出现错误!/n");
return 0;
}
void when_alarm()
{
printf("5秒钟时间已到,系统接收到了sigalrm信号!/n");
}
void when_sigint()
{
printf("已经接收到了sigint信号,程序将退出!/n");
exit(0);
}
void when_sigchld(int sigchld_num)
{
printf("收到sigchld信号,表明我的子进程已经中止,sigchld信号的数值是:%d。/n",sigchld_num);
}
void when_sigusr1()
{
printf("系统接收到了用户自定义信号sigusr1。/n");
}
void when_sigio()
{
printf("系统接收到了sigio信号。/n");
}