信号机制是Linux编程中十分重要的部分——所有人都是这么说的,虽然还没理解到这一点,但是要重视的学习。
信号,其实就是软中断。中断的重要性,是显然的。无论是硬件驱动还是网络应用。
信号是进程间通信机制中唯一的异步通信机制,可以看作是异步通知,通知接收信号的进程有哪些事情发生了。信号机制经过POSIX实时扩展后,功能更加强大,除了基本通知功能外,还可以传递附加信息。
信号的种类
可以从两个不同的分类角度对信号进行分类:(1)可靠性方面:可靠信号与不可靠信号;(2)与时间的关系上:实时信号与非实时信号。在《Linux环境进程间通信(一):管道及有名管道》的附1中列出了系统所支持的所有信号。
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安装的信号却不能向信号处理函数传递信息。对于信号发送函数来说也是一样的。
2、实时信号与非实时信号
早期Unix系统只定义了32种信号,Ret hat7.2支持64种信号,编号0-63(SIGRTMIN=31,SIGRTMAX=63),将来可能进一步增加,这需要得到内核的支持。前32种信号已经有了预定义值,每个信号有了确定的用途及含义,并且每种信号都有各自的缺省动作。如按键盘的CTRL ^C时,会产生SIGINT信号,对该信号的默认反应就是进程终止。后32个信号表示实时信号,等同于前面阐述的可靠信号。这保证了发送的多个实时信号都被接收。实时信号是POSIX标准的一部分,可用于应用进程。
非实时信号都不支持排队,都是不可靠信号;实时信号都支持排队,都是可靠信号。
信号屏蔽,信号阻塞,信号忽略,信号未决
信号屏蔽是指信号不被处理,在很多地方,信号屏蔽指信号阻塞。在概念上屏蔽是mask, 阻塞是block,而setprocmask函数负责处理屏蔽信号,它的选项SIG_BLOCK和SIG_UNBLOCK就是处理信号阻塞。所以,屏蔽就是不处理(可以意味着这个信号对内核是透明的),当信号被阻塞的时候,内核将暂时处理这个信号。信号存在队列中等待延迟处理。
而信号忽略,某种意义上,也是信号屏蔽,不过这个信号不会存在队列中,它将丢失。
“从kernel的角度,每个进程都有个一个blocked数据成员,描述阻塞的信号集 ,而这个信号集 是一个 unsiged long (32bit的值,1-31不可靠的信号) , 它的用处是当进程运行时 , 它暂时的阻塞了一些信号, 在kernel集中处理信号的时候, kernel不会处理blocked的信号, 但是当解除了对某个信号的阻塞, 当kernel进行下一次的信号集中处理的时候就会处理它 。 记住阻塞时暂时的, 但是阻塞的信号不排队 ,系统在解除阻塞后, 会这样的信号只有一个!
屏蔽信号, 是指 sigcation->sa_handler = SIG_IGN , 从代码的角度 在send_sig_info ==>ignore_signal() 就被忽略了。 是永久的”
信号未决,pend,和信号阻塞应该是同义的,也就是信号因为阻塞而未处理。
一些有助于理解的示例:
example1:
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
void sig_handle(int signo)
{
printf("SIGQUIT CAUGHT\n");
signal(signo, SIG_DFL);
}
int main()
{
sigset_t newset, oldset, pendset;
signal(SIGQUIT, sig_handle);
sigemptyset(&newset);
sigaddset(&newset, SIGQUIT);
sigprocmask(SIG_BLOCK, &newset, &oldset);//阻塞信号
sleep(5);
sigpending(&pendset);//测试信号是否阻塞,这里获得阻塞的信号集
if(sigismember(&pendset, SIGQUIT))
{
printf("SIGQUIT pending\n");
}
sigprocmask(SIG_SETMASK, &oldset, NULL);//解除阻塞
printf("SIGQUIT UNBLOCKED\n");
} //PS:按下CTRL-\发送SIGQUIT信号给进程
|
example2:
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
int sig_recieved = 0;
struct sigaction osigaction;
void sig_handle(int signo)
{
printf("recieved signal: SIGINT\n");
if(sig_recieved == 1)
{
printf("program terminated\n");
exit(0);
}
printf("stop here\n");
sig_recieved = 1;
return;
}
int main()
{
struct sigaction nsigaction;
nsigaction.sa_handler = sig_handle;
sigemptyset(&nsigaction.sa_mask);
nsigaction.sa_flags = 0;
if(sigaction(SIGINT, &nsigaction, &osigaction) < 0)
{
printf("error:sigaction\n");
exit(1);
}
while(sig_recieved == 0)
pause();//挂起进程。pause是当有信号处理函数执行并且返回的时候返回,
sleep(5); //包括使用默认的信号处理,但是不包括被忽略的信号。
return(0);
}
|
example3:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
int sig_recieved = 0;
struct sigaction osigaction;
void sig_handle(int signo)
{
printf("recieved signal: SIGINT\n");
if(sig_recieved == 1)
{
printf("program terminated\n");
exit(0);
}
printf("stop here\n");
sig_recieved = 1;
return;
}
int main()
{
struct sigaction nsigaction;
sigignore(SIGQUIT); //忽略信号SIGQUIT
nsigaction.sa_handler = sig_handle;
sigemptyset(&nsigaction.sa_mask);
nsigaction.sa_flags = 0;
if(sigaction(SIGINT, &nsigaction, &osigaction) < 0)
{
printf("error:sigaction\n");
exit(1);
}
pause();
pause();
sleep(5);
return(0);
}
//当你按下CTRL-\的时候,pause是不会返回的。如果你不忽略,就会返回。
//当你按下CTRL-Z的时候,pause返回,和按下CTRL-C没有分别。这就是要检测 //received的原因。 |
example4:
可能发生这种情况:
在检测了received == 0后,想要等待的信号释放了,这时候pause还没有调用,那么pause将不会返回。需要等待另一个信号到来。所以使用sigsuspend函数可以解决这个问题。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
int received = 0;
struct sigaction osigaction;
void sig_handle(int signo)
{
printf("receive signal\n");
sleep(5);
if(received == 1)
{
printf("exit now\n");
exit(0);
}
else
{
printf("stop here\n");
received = 1;
}
}
int main()
{
struct sigaction nsigaction;
sigset_t newset, oldset;
sigprocmask(SIG_SETMASK, NULL, &newset);
sigprocmask(SIG_SETMASK, NULL, &oldset);
sigaddset(&newset, SIGINT);
sigprocmask(SIG_SETMASK, &newset, NULL);
nsigaction.sa_handler = sig_handle;
sigemptyset(&nsigaction.sa_mask);
nsigaction.sa_flags = 0;
if(sigaction(SIGINT, &nsigaction, &osigaction) < 0)
{
perror("sigaction");
exit(1);
}
while(received == 0)
{
sigsuspend(&oldset);
}
sigprocmask(SIG_SETMASK, &oldset, NULL);
sleep(5);
return(0);
}
//为了解释清楚,我们首先要理解的是sigprocmask函数的作用,是设置这个进 //程的block域(内核会根据这个域确定该进程是否忽略阻塞某些信号)。当 //sigsuspend函数被调用的时候,它将参数集合临时作为该进程的block域,那么 //这个程序里当sigsuspend函数被调用的时候,sigsuspend可以监测得到SIGINT, //而当这个函数返回的时候,将恢复原有的block域,这时候,程序的其他部分就不能 //监测到SIGINT,因为SIGINT被阻塞了。所以在检查while之后,SIGINT不会被释放, //因此,sigsuspend总是能够根据SIGINT而返回。 //PS:sigaction函数可以设置信号处理函数的block域,即sigaction.sa_mask,这样 //信号处理函数就不会被某些信号中断。但是这个信号会存在队列中,当信号处理 //函数返回的时候,它将被要求处理。 |
一组函数可以方便我们操作信号:sig_ignore, sig_hold, sig_relse。
sig_hold是保持某个信号,事实上就是阻塞某个信号,使它保持在队列中。而sig_relse则会从队列中释放这个信号。
example5:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
int received = 0;
struct sigaction osigaction;
void sig_handle(int signo)
{
sighold(SIGQUIT); //保持这个信号在队列中
printf("receive signal\n");
sleep(5);
if(received == 1)
{
printf("exit now\n");
exit(0);
}
else
{
printf("stop here\n");
received = 1;
}
sigrelse(SIGQUIT); //释放这个信号,因此下面的printf将不会被执行
printf("leaving sig_handle\n");
}
int main()
{
struct sigaction nsigaction;
sigset_t newset, oldset;
sigprocmask(SIG_SETMASK, NULL, &newset);
sigprocmask(SIG_SETMASK, NULL, &oldset);
sigaddset(&newset, SIGINT);
sigprocmask(SIG_SETMASK, &newset, NULL);
nsigaction.sa_handler = sig_handle;
sigemptyset(&nsigaction.sa_mask);
sigaddset(&nsigaction.sa_mask, SIGQUIT);
nsigaction.sa_flags = 0;
if(sigaction(SIGINT, &nsigaction, &osigaction) < 0)
{
perror("sigaction");
exit(1);
}
while(received == 0)
{
sigsuspend(&oldset);
}
sleep(5);
sigprocmask(SIG_SETMASK, &oldset, NULL);
return(0);
}
|
事实上,汇编语言的东西,还是有价值的吧。据说,Linux内核里有部分是用汇编写的(废话!操作系统的东西离得开汇编吗?)我们看看两个和汇编相关的函数,跳转指令。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <setjmp.h>
int jmp_ok = 0;
sigjmp_buf jmpbuf; //用于保存上下文的区域
void sig_handle(int signo)
{
if(jmp_ok == 0) //保证siglongjmp跳转的时候,sigsetjmp已经设置好了标号
{
return;
}
siglongjmp(jmpbuf, 2);
}
int main()
{
int count;
signal(SIGINT, sig_handle);
if(sigsetjmp(jmpbuf, 1) == 2)//siglongjmp返回,sigsetjmp将返回它的设置参 //数
{
printf("now restart\n");
}
jmp_ok = 1;
count = 0;
for(;;)
{
printf("count = %d\n", count++);
sleep(1);
}
}
|
阅读(747) | 评论(0) | 转发(0) |