Linux:进程通信(二)信号的保存

目录

一、信号的处理是否是立即处理的?

 二、信号如何保存

1、阻塞、未决、递达

2、信号集

3、信号集操作函数

4、sigprocmask函数

5、sigpending 函数


上篇文章我们讲解了信号的产生:Linux:进程信号(一)信号的产生

接下来我们来看一下信号的保存

一、信号的处理是否是立即处理的?

        信号的处理是否立即进行,取决于信号的类型、进程的当前状态,以及进程对信号的处理策略。

  1. 信号的优先级:某些信号需要立即处理,比如 SIGKILL(强制终止进程)和 SIGSTOP(暂停进程)。这些信号一旦被发送给目标进程,操作系统会立即采取行动。其他信号,比如 SIGTERMSIGUSR1,可能会被延迟处理,特别是在进程有更高优先级的任务在进行时。

  2. 进程的状态:如果进程正在内核态(如系统调用期间),信号的处理可能被延迟,直到进程回到用户态。此外,如果进程正在被其他信号阻塞,或处于某种临界区,信号也可能被暂时推迟。

  3. 信号的阻塞和掩码:进程可以通过信号掩码来阻止某些信号的立即处理。操作系统将这些阻塞的信号放入进程的信号队列中,直到进程解除阻塞。

  4. 信号处理函数:进程可以设置信号处理函数。当信号到达时,如果进程当前正在运行中,它可能会等到适当的时机(如下一个调度周期)才调用信号处理函数。

 二、信号如何保存

信号需要被进程记录下来,以确保即使在信号到达时进程无法立即处理时,信号也不会丢失。这种记录通常发生在进程的信号队列中。

信号队列是内核为每个进程维护的一个数据结构,用于存储已经到达但尚未被处理的信号。当进程接收到信号时,内核将信号放入进程的信号队列中,然后等待进程在适当的时机处理它们。这样做可以确保即使进程在信号到达时处于某种阻塞状态,也能够在稍后的时候处理这些信号。

信号队列通常存储在内核的内存中,并且对于每个进程都有一个单独的信号队列。进程可以使用系统调用来检查它的信号队列,并且可以对信号队列进行操作,比如阻塞或解除阻塞某些信号。

1、阻塞、未决、递达

实际执行信号的处理动作称为信号递达(Delivery)
信号从产生到递达之间的状态,称为信号未决(Pending)
进程可以选择阻塞 (Block ) 某个信号。
被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作.
注意 , 阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作。

 如下图所示

比特位的位置表示信号的编号,比如图中是三号信号,三号信号产生后,未决信号集对应位置为1,内核来判断是否应该被处理,如果被处理则置为0 。而如果该信号被阻塞,那么就不会被处理,直到阻塞解除。

那么信号集如何管理呢:

2、信号集

sigset_t

sigset_t 用于表示一组信号,通常用于表示被阻塞的信号集合或者待处理的信号集合。

在内部实现上,sigset_t 可以被看作是一个位图,其中每个位代表一个信号。如果某个位被设置为 1,则表示对应的信号被包含在集合中;如果被设置为 0,则表示对应的信号不在集合中。

这种位图的表示方式非常高效,因为它可以用很小的内存空间表示很多信号。通常情况下,sigset_t 的大小被限制在一个字(通常是 32 位或 64 位)的大小范围内,所以它适用于表示系统支持的信号数量。

sigset_t可以本质上被视为一个位图 ,关于位图我们前面也介绍过:C++:Hash应用【位图与布隆过滤器】

3、信号集操作函数

sigset_t 类型对于每种信号用一个 bit 表示 有效 无效 状态 , 至于这个类型内部如何存储这些 bit 则依赖于系统实现, 从使用者的角度是不必关心的 , 使用者只能调用以下函数来操作 sigset_ t 变量
#include <signal.h>
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset (sigset_t *set, int signo);
int sigdelset(sigset_t *set, int signo);
int sigismember(const sigset_t *set, int signo);
  • sigemptyset(sigset_t *set): 此函数用于将信号集合 set 清空,即将所有信号从集合中移除,使得 set 表示一个空集。调用此函数后,set 中不包含任何信号。

  • sigfillset(sigset_t *set): 此函数用于将信号集合 set 填满,即将所有信号添加到集合中,使得 set 包含系统支持的所有信号。调用此函数后,set 中包含所有可能的信号。

  • sigaddset(sigset_t *set, int signo): 此函数用于向信号集合 set 中添加指定的信号 signo。添加成功后,set 中将包含 signo 代表的信号。

  • sigdelset(sigset_t *set, int signo): 此函数用于从信号集合 set 中删除指定的信号 signo。删除成功后,set 中将不再包含 signo 代表的信号。

  • sigismember(const sigset_t *set, int signo): 此函数用于检查指定的信号 signo 是否包含在信号集合 set 中。如果 signo 存在于 set 中,则返回非零值;否则返回 0。

需要注意的是在使用 sigset_ t 类型的变量之前 , 一定要调 用 sigemptyset sigfillset 做初始化 , 使信号集处于确定的状态。初始化sigset_t 变量之后就可以在调用 sigaddset sigdelset 在该信号集中添加或删除某种有效信号。

4、sigprocmask函数

调用函数 sigprocmask 可以读取或更改进程的信号屏蔽字 ( 阻塞信号集 )
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oset); 
返回值:若成功则为0,若出错则为-1
如果调用 sigprocmask 解除了对当前若干个未决信号的阻塞 , 则在 sigprocmask 返回前 , 至少将其中一个信号递达。
通过man sigprocmask命令可以看到

how参数的取值:

我们通过代码来测试一下:

#include<signal.h>
#include<iostream>
#include <sys/types.h>
#include <unistd.h>


int main()
{
    sigset_t set,oset;
    sigemptyset(&set);
    sigemptyset(&oset);
    sigaddset(&set,SIGINT);//屏蔽2号信号
    sigprocmask(SIG_BLOCK,&set,&oset);
    while(true)
    {
        std::cout<<"我是"<<getpid()<<"号进程"<<std::endl;
        sleep(1);
    }
    return 0;
}

运行代码后我们使用Ctrl+c发出2号信号,没有停止

因为2号信号已经被屏蔽,就算使用kill命令发出也是一样的:

 

那么我们来解除阻塞后会不会恢复正常呢:

#include<signal.h>
#include<iostream>
#include <sys/types.h>
#include <unistd.h>


int main()
{
    sigset_t set,oset;
    sigemptyset(&set);
    sigemptyset(&oset);
    sigaddset(&set,SIGINT);//屏蔽2号信号
    sigprocmask(SIG_BLOCK,&set,&oset);
    while(true)
    {
        std::cout<<"我是"<<getpid()<<"号进程"<<std::endl;
        sleep(20);
        sigprocmask(SIG_UNBLOCK,&set,&oset);//20秒后解除阻塞
    }
    return 0;
}

可以看到

在未解除前,我们发送2号信号是没用的,但在20秒后,会自动处理2号信号退出。

因为阻塞并不是处理,信号依旧保持未决,暂时不递达,直到解除对信号的阻塞。

需要注意的是,9号信号是不会被屏蔽的,大家可以在上面代码的基础上测试。

5、sigpending 函数

sigpending 函数用于获取当前被阻塞但是仍然排队等待传递给进程的信号集合。也就是未决信号集

int sigpending(sigset_t *set);
调用成功则返回 0, 出错则返回 -1。
我们使用下列代码来测试:
#include<signal.h>
#include<iostream>
#include <sys/types.h>
#include <unistd.h>

void printsigset(sigset_t *set)
{
    for(int i=31;i>=0;i--)
    {
        if(sigismember(set,i))
        {
            std::cout<<"1";
        }
        else
        {
            std::cout<<"0";
        }
    }
    std::cout<<std::endl;
}
int main()
{
    sigset_t set,oset;
    sigemptyset(&set);
    sigemptyset(&oset);
    sigaddset(&set,SIGINT);//屏蔽2号信号
    sigprocmask(SIG_BLOCK,&set,&oset);
    while(true)
    {
        std::cout<<"我是"<<getpid()<<"号进程"<<std::endl;
        sigpending(&oset);
        printsigset(&oset);
        sleep(1);
    }
    return 0;
}

运行后可以看到:

当收到2号进程后,信号集对应的位置置为1,但是没有被执行,因为2号信号被阻塞了,按Ctrl-\仍然可以终止程序,因为SIGQUIT信号没有阻塞。

  • 13
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值