二 Linux进程间通信( 三 )

代码示例:
#include <stdio.h>#include <unistd.h>#include <signal.h>#include <stdlib.h>//信号处理函数void fun1(int signum){printf("捕捉到信号:%d\n",signum);}//信号处理函数2void fun2(int signum){printf("捕捉到信号:%d\n",signum);}//信号注册函数int main(){//信号注册//ctrl+csignal(SIGINT,fun1);//ctrl+\signal(SIGQUIT,fun2);while(1){sleep(1);}return 0;}运行结果如下:

二 Linux进程间通信

文章插图
信号先注册,进程不要退出 , 然后等待信号的到达,信号到达之后就会执行信号处理函数 。
阻塞信号了解几个概念:
  • 实际执行信号的处理动作称为信号递达
  • 信号递达的三种方式:默认、忽略和自定义捕捉
  • 信号从产生到递达之间的状态,称为信号未决(Pending)
  • 进程可以选择阻塞 (Block )某个信号
  • 被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作
    注意:
    • 阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作
    • OS发生信号给一个进程,此信号不是立即被处理的,那么这个时间窗口中,信号就需要被记录保存下来 , 那么信号是如何在内核中保存和表示的呢?

二 Linux进程间通信

文章插图
每个进程都存在一个PCB,在PCB中存在两个集合 , 一个是未决信号集,一个是阻塞信号集 。
以SIGINT为例说明未决信号集和阻塞信号集的关系:
  • 当进程收到SIGINT信号(信号编号为2),首先这个信号会保存在未决信号集合中,此时对应的2号编号的这个位置上置为1,表示处于未决状态;在这个信号需要被处理之前首先要在阻塞信号集中的编号2的位置上区检查该值是否为1
  • 如果是1,表示SIGINT信号被当前进程阻塞了,这个信号暂时不被处理,所以未决集上该位置上的值保持为1,表示该信号处于未决状态
  • 如果是0,表示SIGINT信号没有被当前进程阻塞,这个信号需要被处理,内核会对SIGINT信号进行处理(执行默认动作,忽略或者执行用户自定义的信号处理函数),并将未决信号集合中编号2的位置将1置为0,表示该信号已经被处理了,这个时间非常短
  • 当SIGINT信号从阻塞信号集中解除阻塞之后,该信号就会被处理
注意:
未决信号集在内核中 , 要对内核进行操作只能通过系统调用 , 但是没有提供这样的方法,所以只能对未决信号集进行读操作,但是可以对阻塞信号集进行读写操作 。
问题1:所有信号的产生都要由OS来进行执行 , 这是为什么?
信号的产生涉及到软硬件,且OS是软硬件资源的管理者,还是进程的管理者 。
问题2:进程在没有收到信号的时候,能否知道自己应该如何对合法信号进行处理呢?
答案是能知道的 。每个进程都可以通过task_struct找到表示信号的三张表 。此时该进程的未决信号集表中哪些信号对应的那一位比特位是为0的,且进程能够查看阻塞信号集表知道如果收到该信号是否需要阻塞,可以查看handler表知道对该信号的处理动作 。
问题3:OS如何发生信号?
OS给某一个进程发送了某一个信号后,OS会找到信号在进程中未决信号集表对应的那一位比特位,然后把那一位比特位由0置1,这样OS就完成了信号发送的过程 。
信号集操作函数sigset_t: 未决和阻塞标志可以用相同的数据类型sigset_t来存储,sigset_t称为信号集,也被定义为一种数据类型 。这个类型可以表示每个信号状态处于何种状态(是否被阻塞,是否处于未决状态) 。阻塞信号集也叫做当前进程的信号屏蔽字,这里的“屏蔽”应该理解为阻塞而不是忽略 。
实际上两个信号集在都是内核使用位图机制来实现的,想了解的可以自己去了解下,但是操作系统不允许我们直接对其操作 。而需要自定义另外一个集合,借助于信号集操作函数来对PCB中的这两个信号集进行修改 。
信号集操作函数: sigset_t类型对于每种信号用一个bit表示“有效”或“无效”状态 , 至于这个类型内部如何存储这些bit则依赖于系统实现,从使用者的角度是不必关心的,使用者只能调用以下函数来操作sigset_ t变量,而不应该对它的内部数据做任何解释 。注意: 对应sigset类型的变量,我们不可以直接使用位操作来进行操作,而是一个严格实现系统给我们提供的库函数来对这个类型的变量进行操作 。

推荐阅读