信号同样是用于进程通信的,他是一种异步通信方式。我们之前的管道不是,明显,读进程要等管道里面有数据才能运行,否则他要等待。信号处理则不同,进程不知道什么时候信号会到来,先看一小段代码,看看信号的程序:
[root@liumengli signal]# cat ./signal_recive1.c
#include "stdio.h" #include "signal.h" #include "sys/types.h" #include "unistd.h" void new_op(int, siginfo_t *, void *); int main(int argc, char**argv) { struct sigaction act; //创建新的信号 struct sigaction old_act; //用与记录旧的信号,当然你也可以不用记录 int sig; //信号量,将来我们发送的信号要以这个信号量发送 sig = atoi(argv[1]); sigemptyset(&act.sa_mask); act.sa_flags = SA_SIGINFO; act.sa_sigaction = new_op; //设置信号的响应操作 if(sigaction(sig, &act, &old_act) < 0) { //开始创建信号 printf("install sigal error\n"); return 1; } while(1) { //为了测试写的一个死循环 sleep(2); printf("Now we wait for signal\n"); } } void new_op(int signum, siginfo_t * info, void * myact) { //信号的响应操作 printf("recive signal %d\n", signum); exit(1); }
(我们以15号信号作为新的信号的信号量,我们即将发送就发送15好给目标进程,注意我们这里是用掉了系统的15号信号,这个信号是可以被从写的,但有2个信号KILL和STOP信号,KILL的信号是9,而STOP是根据硬件结构体系而不同,他们是不能被从写的,原因是,内核代码中在安装信号上有这么一句(act && (sig == SIGKILL || sig == SIGSTOP)))
return -EINVAL;同时他们不能被屏蔽也就是不能被不响应,其它信号是可以屏蔽的。这么做的原因是内核设计者为了给进程控制留下的最后底限,绝大部分信号的默认操作是终止进程,这个2个从名字上也看出一个是终止,另一个是暂停。这样我们对一个进程至少有最后的杀死手段就是向进程发送这2种信号,如何法后面会看到)
Now we wait for signal
Now we wait for signal
Now we wait for signal
Now we wait for signal
.......
另开一个终端
[root@liumengli signal]# ps -le
......
0 S 0 30681 28333 0 75 0 - 352 hrtime pts/2 00:00:00 signal_recive1
0 R 0 30828 28245 0 78 0 - 1368 - pts/1 00:00:00 ps
[root@liumengli signal]# kill -15 30681
再返回看看被这个进程掌控的终端的反应
Now we wait for signal
recive signal 15
[root@liumengli signal]#
按照我们预期的结束了。
先解释下kill指令,这是发送信号的一个指令,当然我们可以用函数(linux提供了),我们发送了15号信号,按照我们预期的结束了。通常人们认为 kill是杀死一个进程,其实不是,它是发送一个信号给你指定的进程,觉大部分进程不会去改系统的信号量,如果真的改了,他们改不了STOP和KILL这2个信号量,我改过,运行时候报了信号安装失败。如果你杀不死一个流氓进程的时候,不妨试试 kill -KILL目标进程的PID,kill -STOP是个蛮好玩的信号,可以试试,想恢复就发送kill -CONT.另一个,写过linux/unix环境编程的人会清楚知道,就算进程不是就绪状态,发送信号是会将它改成就绪态,这个看下发送信号的源代码就知道了,他会把目标进程挂到就绪队列中。所以睡眠进程也会响应信号。
从我的程序中看出我没有哪儿有代码去检测一个信号是否到来,既用户并不关心信号什么时候会到来,用户关心的是信号来了我该做些什么。那么谁去检测信号是否到来的,换句话说就是:信号检测的代码写在哪里。它写在
217 ret_with_reschedule:
218 cmpl $0,need_resched(%ebx)
219 jne reschedule
220 cmpl $0,sigpending(%ebx)
221 jne signal_return
这段代码的第220行就是检测信号,以前我们看过进程调度,进程调度代码写在218行。这段代码会在任何返回用户空间时候会被执行,包括系统调用返回,中断返回,异常处理返回从睡眠唤醒的时候返回。
如果你的程序一直在用户空间,而且不会受到任何中断(不太可能实现,至少定时的时钟中断会来骚扰下,而且没有系统调用的支持,直接自己去操作一些硬件也不太可能),那么信号是不会被响应的。
从响应的角度来说,不是我发信号,对方就会立即反应,这中间会有延时,这个不难理解了。
稍微整理后:
#include <stdio.h> #include <stdlib.h> #include "signal.h" #include "sys/types.h" #include "unistd.h" void my_action(int signum, siginfo_t * info, void * myact) { //信号的响应操作 printf("recive signal %d\n", signum); } void CreateSigAndBind(int Sig,void (*fun)(int signum, siginfo_t * info, void * myact)) { struct sigaction act; //创建新的信号 struct sigaction old_act; //用与记录旧的信号,当然你也可以不用记录 sigemptyset(&act.sa_mask); act.sa_flags = SA_SIGINFO; act.sa_sigaction = fun; //设置信号的响应操作 if(sigaction(Sig, &act, &old_act) < 0) { //开始创建信号 printf("install sigal error\n"); return ; } } int main(int argc, char**argv) { CreateSigAndBind(43,&my_action); CreateSigAndBind(44,&my_action); while(1) { //为了测试写的一个死循环 sleep(2); printf("Now we wait for signal\n"); } return 1 ; }