二 Linux进程间通信( 四 )

  • 当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类型的变量,我们不可以直接使用位操作来进行操作,而是一个严格实现系统给我们提供的库函数来对这个类型的变量进行操作 。
    下面是信号集操作函数的原型:
    #include <signal.h>int sigemptyset(sigset_t *set);int sigfillset(sigset_t *set);int sigaddset(sigset_t *set, int signum);int sigdelset(sigset_t *set, int signum);int sigismember(const sigset_t *set, int signum);
    • sigemptyset: 初始化set指向的信号集,将所有比特位置0
    • sigfillset: 初始化set指向的信号集,将所有比特位置1
    • sigaddset: 把set指向的信号集中signum信号对应的比特位置1
    • sigdelset: 把set指向的信号集中signum信号对应的比特位置0
    • sigismember: 判断signum信号是否存在set指向的信号集中(本质是信号判断对应位是否为1)
    注意: 在实现这些函数之前,需要使用sigemptyset 或sigfillset对信号集进行初始化 。前四个函数的返回值是成功返回0,失败返回-1 。最后一个函数的返回值是真返回1,假返回-1
    阻塞信号集操作函数——sigprocmask:
    #include<signal.h>int sigprocmask(int how,const sigset_t *set,sigset_t *oldset);功能:检查或修改信号阻塞集,根据how指定的方法对进程的阻塞集合进行修改,新的信号阻塞集由set指定,而原先的信号阻塞集合由oldset保存 。参数:how:信号阻塞集合的修改方法,有3种情况:SIG_BLOCK:向信号阻塞集合中添加set信号集,新的信号掩码是set和旧信号掩码的并集 。相当于mask=mask丨set 。SIG_UNBLOCK:从信号阻塞集合中删除set信号集,从当前信号掩码中去除set中的信号 。相当于mask=mask&~set 。SIG_SETMASK:将信号阻塞集合设为set信号集,相当于原来信号阻塞集的内容清空,然后按照set中的信号重新设置信号阻塞集 。相当于mask=set 。set:要操作的信号集地址 。若set为NULL,则不改变信号阻塞集合,函数只把当前信号阻塞集合保存到oldset中 。oldset: 保存原先信号阻塞集地址 。返回值:成功:0失败:-1

    经验总结扩展阅读