信号是软件中断,是在软件层次上对中断机制的一种模拟,在原理上,一个进程收到一个信号与处理器收到一个中断请求可以说是一样的。信号是异步的,一个进程不必通过任何操作来等待信号的到达,事实上,进程也不知道信号到底什么时候到达。
信号是进程间通信机制中唯一的异步通信机制,可以看作是异步通知,通知接收信号的进程有哪些事情发生了。信号机制经过POSIX实时扩展后,功能更加强大,除了基本通知功能外,还可以传递附加信息。
1、可靠信号与不可靠信号
"不可靠信号"
Linux信号机制基本上是从Unix系统中继承过来的。早期Unix系统中的信号机制比较简单和原始,后来在实践中暴露出一些问题,因此,把那些建立在早期机制上的信号叫做"不可靠信号",信号值小于SIGRTMIN(Red hat 7.2中,SIGRTMIN=32,SIGRTMAX=63)的信号都是不可靠信号。
这就是"不可靠信号"的来源。它的主要问题是:
进程每次处理信号后,就将对信号的响应设置为默认动作。在某些情况下,将导致对信号的错误处理;因此,用户如果不希望这样的操作,那么就要在信号处理函数结尾再一次调用signal(),重新安装该信号。
信号可能丢失,后面将对此详细阐述。 如果在进程对某个信号进行处理时,这个信号发生多次,对后到来的这类信号不排队,那么仅传送该信号一次,即发生了信号丢失。
因此,早期unix下的不可靠信号主要指的是进程可能对信号做出错误的反应以及信号可能丢失。
Linux支持不可靠信号,但是对不可靠信号机制做了改进:在调用完信号处理函数后,不必重新调用该信号的安装函数(信号安装函数是在可靠机制上的实现)。因此,Linux下的不可靠信号问题主要指的是信号可能丢失。
"可靠信号"
随着时间的发展,实践证明了有必要对信号的原始机制加以改进和扩充。所以,后来出现的各种Unix版本分别在这方面进行了研究,力图实现"可靠信号"。由于原来定义的信号已有许多应用,不好再做改动,最终只好又新增加了一些信号,并在一开始就把它们定义为可靠信号,这些信号支持排队,不会丢失。同时,信号的发送和安装也出现了新版本:信号发送函数sigqueue()及信号安装函数sigaction()。POSIX.4对可靠信号机制做了标准化。但是,POSIX只对可靠信号机制应具有的功能以及信号机制的对外接口做了标准化,对信号机制的实现没有作具体的规定。
信号值位于SIGRTMIN和SIGRTMAX之间的信号都是可靠信号,可靠信号克服了信号可能丢失的问题。Linux在支持新版本的信号安装函数sigation()以及信号发送函数sigqueue()的同时,仍然支持早期的signal()信号安装函数,支持信号发送函数kill()。
注:不要有这样的误解:由sigqueue()发送、sigaction安装的信号就是可靠的。事实上,可靠信号是指后来添加的新信号(信号值位于SIGRTMIN及SIGRTMAX之间);不可靠信号是信号值小于SIGRTMIN的信号。信号的可靠与不可靠只与信号值有关,与信号的发送及安装函数无关。目前linux中的signal()是通过sigation()函数实现的,因此,即使通过signal()安装的信号,在信号处理函数的结尾也不必再调用一次信号安装函数。同时,由signal()安装的实时信号支持排队,同样不会丢失。
对于目前linux的两个信号安装函数:signal()及sigaction()来说,它们都不能把SIGRTMIN以前的信号变成可靠信号(都不支持排队,仍有可能丢失,仍然是不可靠信号),而且对SIGRTMIN以后的信号都支持排队。这两个函数的最大区别在于,经过sigaction安装的信号都能传递信息给信号处理函数(对所有信号这一点都成立),而经过signal安装的信号却不能向信号处理函数传递信息。对于信号发送函数来说也是一样的。
接下来,用两个例程来说明可靠信号和不可靠信号的排队问题:
一个发送进程,发送三次不可靠信号SIGINT,发送三次可靠信号SIGRTMIN(34); 再发送一次SIGUSR1信号。接收程序中对SIGINT和SIGRTMIN阻塞,然后在接受SIGUSR1的信号处理函数中再解除对信号SIGINT和SIGRTMIN的阻塞,查看排队丢失情况。
1 #include<unistd.h> 2 #include<sys/types.h> 3 #include<sys/stat.h> 4 #include<fcntl.h> 5 #include<stdlib.h> 6 #include<stdio.h> 7 #include<errno.h> 8 #include<string.h> 9 10 #include<signal.h> 11 #define ERR_EXIT(m) 12 do 13 { 14 perror(m); 15 exit(EXIT_FAILURE); 16 }while(0) //宏要求一条语句 17 int main(int argc,char*argv[]) 18 { 19 if(argc!=2){ 20 fprintf(stderr,"Usage %s pid ",argv[0]); 21 exit(EXIT_FAILURE); 22 } 23 pid_t pid=atoi(argv[1]);//recv进程运行时的进程号 24 union sigval v; 25 sigqueue(pid,SIGINT,v); 26 sigqueue(pid,SIGINT,v); 27 sigqueue(pid,SIGINT,v); 28 sigqueue(pid,SIGRTMIN,v); 29 sigqueue(pid,SIGRTMIN,v); 30 sigqueue(pid,SIGRTMIN,v); 31 sleep(3); 32 kill(pid,SIGUSR1); 33 return 0; 34 }
下一个是接收进程,接收进程的进程号发给上面的sigqueue函数,接受进程中先对SIGINT,SIGRTMIN阻塞,然后接收处理SIGUSR1信号,在处理程序中解除对上面两个信号的阻塞。最后只收到一个SIGINT信号,收到三次SIGRTMIN信号(支持排队)。
#include<unistd.h> #include<sys/types.h> #include<sys/stat.h> #include<fcntl.h> #include<stdlib.h> #include<stdio.h> #include<errno.h> #include<string.h> #include<signal.h> #define ERR_EXIT(m) do { perror(m); exit(EXIT_FAILURE); }while(0) //宏要求一条语句 void handler(int sig); int main(int argc,char*argv[]) { struct sigaction act; act.sa_handler=handler;//不能使用sa_handler函数了,要使用 void (*sa_sigaction) (int,siginfo_t*,void*) sigemptyset(&act.sa_mask);//针对信号处理程序过程中阻塞 act.sa_flags=0; sigset_t s; sigaddset(&s,SIGINT); sigaddset(&s,SIGRTMIN); sigprocmask(SIG_BLOCK,&s,NULL);//针对进程对信号阻塞。 if(sigaction(SIGINT,&act,NULL)<0) ERR_EXIT("sigaction error "); if(sigaction(SIGRTMIN,&act,NULL)<0) ERR_EXIT("sigaction error "); if(sigaction(SIGUSR1,&act,NULL)<0) ERR_EXIT("sigaction error "); for(;;) pause(); return 0; } void handler(int sig) { if(sig==SIGINT||sig==SIGRTMIN) printf("receive a sig=%d ",sig);//接收到三个SIGRTMIN,只接收到一个SIGINT else if(sig==SIGUSR1) { sigset_t s; sigemptyset(&s); sigaddset(&s,SIGINT); sigaddset(&s,SIGRTMIN); sigprocmask(SIG_UNBLOCK,&s,NULL); } }