进程间通信学习
之前学习了使用管道作为进程间通信的方式。如果我们只是简单地想要通过一个进程向另一个进程发送
消息(最常见的场景就是kill另一个进程),那么就可以通过信号来实现。
信号及信号来源
信号是在软件层次上对中断机制的一种模拟,在原理上,一个进程收到一个信号与处理器收到一个中断请求可以说是一样的。
信号是异步的,一个进程不必通过任何操作来等待信号的到达,事实上,进程也不知道信号到底什么时候到达。
信号是进程间通信机制中唯一的异步通信机制,可以看作是异步通知,通知接收信号的进程有哪些事情发生了。
信号机制经过POSIX实时扩展后,功能更加强大,除了基本通知功能外,还可以传递附加信息。
信号事件的发生有两个来源:硬件来源(比如我们按下了键盘或者其它硬件故障);
软件来源,最常用发送信号的系统函数是kill, raise,
alarm和setitimer以及sigqueue函数,软件来源还包括一些非法运算等操作。
信号之Hello world
当一个进程收到一个信号时,其执行流程将被操作系统中断,以便调用信号处理程序。信号通过数字来辨识。
每个信号有默认的信号处理程序。例如,在终端按下Ctrl+C时,将发送一个中断信号,该信号的默认信号
处理程序将退出该程序。
|
|
这个程序会运行一个死循环,然后使用signal函数对注册的信号变量进行处理,比如kill 进程号,
就会打印出 Caught singal 15 SIGTERM 。
二、信号的种类
可以从两个不同的分类角度对信号进行分类:(1)可靠性方面:可靠信号与不可靠信号;(2)与时间的关系上:实时信号与非实时信号。
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()安装的实时信号支持排队,同样不会丢失。
所以,我们之前的Hello world程序就没有再次安装函数。
对于目前linux的两个信号安装函数:signal()及sigaction()来说,
它们都不能把SIGRTMIN以前的信号变成可靠信号(都不支持排队,仍有可能丢失,仍然是不可靠信号),
而且,对SIGRTMIN以后的信号都支持排队。
这两个函数的最大区别在于,经过sigaction安装的信号都能传递信息给信号处理函数(对所有信号这一点都成立),
而经过signal安装的信号却不能向信号处理函数传递信息。对于信号发送函数来说也是一样的。
所以,signal函数第二个参数的函数,只有一个参数?
2.实时信号与非实时信号
早期Unix系统只定义了32种信号,Ret hat7.2支持64种信号,编号0-63(SIGRTMIN=31,SIGRTMAX=63),
将来可能进一步增加,这需要得到内核的支持。
前32种信号已经有了预定义值,每个信号有了确定的用途及含义,并且每种信号都有各自的缺省动作。
如按键盘的CTRL ^C时,会产生SIGINT信号,对该信号的默认反应就是进程终止。
后32个信号表示实时信号,等同于前面阐述的可靠信号。
这保证了发送的多个实时信号都被接收。实时信号是POSIX标准的一部分,可用于应用进程。
非实时信号都不支持排队,都是不可靠信号;实时信号都支持排队,都是可靠信号。
三、进程对信号的响应
进程可以通过三种方式来响应一个信号:
1.忽略信号,即对信号不做任何处理,其中,有两个信号不能忽略:SIGKILL及SIGSTOP;
2.捕捉信号。定义信号处理函数,当信号发生时,执行相应的处理函数;
3.执行缺省操作,Linux对每种信号都规定了默认操作。注意,进程对实时信号的缺省反应是进程终止。
Linux究竟采用上述三种方式的哪一个来响应信号,取决于传递给相应API函数的参数。