初识Linux · 信号保存

目录

前言:

[Block pending handler表](#Block pending handler表)

信号保存


前言:

前文我们已经介绍了信号产生,在时间的学习线上,信号的学习分为预备知识,信号产生,信号保存,信号处理,本文我们学习信号保存,在前言部分,我们介绍几个信号保存中的概念。

信号递达:实际执行信号的处理动作。 信号未决:信号从产生到递达之间的状态。

对于信号产生之后,在递达的这个过程,成为未决,就像老师给你布置了作业,你接受到了做作业的这个信号,但是因为贪玩,不想做,这个状态,就是未决。


Block pending handler表

除了前言部分介绍的两个信号,还有一个概念是阻塞,其实就是,接受到了信号,但是对于信号排外的这个动作,它和忽略不是一样的,这要注意。

对于阻塞来说,一个信号如果阻塞了,和它有没有未决有关系吗?

当然是没有的,就像老师一直给你布置作业,但是你一直不想写,虽然接受到了这个信号,但是不鸟他,欸对吧,这就是阻塞。

对于进程来说,可以选择阻塞某个信号,而对于阻塞来说,如果进程阻塞了某个信号,那么对于该信号来说,就是永不递达,直到接触阻塞。

现在我们来介绍进程中的三张表:分别是Block pending handler表:

进程中存在三张表,block pending handler,他们都是位图,比特位的位置用来表示信号的某个状态,比如block位图表示的是是否被阻塞,pending位图表示的是是否接收到了该信号。

对于handler来说,首先我们思考一个问题,我们是否考虑过为什么signal使用的函数指针呢?因为task_struct中使用的handler就是函数指针,也就是说handler本质上是一个函数指针数组,当接收到了信号之和,通过该数组找到函数的地址并执行。

所以进程是如何识别信号的呢?

两个位图 + 一个函数指针数 == 让进程接收到信号

这里需要注意,忽略是递达之后的一种可选动作,阻塞是信号一旦被阻塞就不会递达。所以被阻塞的信号一直处于未决的状态,直到解除了阻塞状态。

那么说了这么多,我们是不是应该见见这三张表啊?所以进入下一个话题,语言层面操作两个位图。


信号保存

对于函数的调用,我们需要认识的是这5个函数,分别是sigemptyset, sigfillset, sigaddset, sigdelset,sigismember。

其实从名字来看,我们就能分析出来是干啥的,sigemptyset,将信号结构体置为空,fill填充信号,add添加信号,del删除信号,ismember判断是否有这个信号。

上文提到了这个信号结构体,可是我们好像没有看到过这个信号结构体?

那我们看看:

当定义一个sigset对象之后,我们转到定义,就会发现是这个结构体实际上就是一个无符号的长整型数组,说白了,它就是一个位图而已。通过/和%运算可以定义到信号的位置。

再介绍几个函数:

对于函数sigpending来说,它的参数set是一个输出型参数,获取当前pending位图。

对于函数sigprocmask的参数来说:

  • how:指定如何更改当前的信号屏蔽字。它可以是以下三个值之一:

    • SIG_BLOCK:将 set 中指定的信号添加到当前信号屏蔽字中。
    • SIG_UNBLOCK:从当前信号屏蔽字中移除 set 中指定的信号。
    • SIG_SETMASK:将当前信号屏蔽字设置为 set,忽略其当前值。
  • set:指向一个 sigset_t 类型的变量,该变量包含要更改的信号集。如果 setNULL,则不修改信号屏蔽字,但 oldset 仍然会被设置为当前的信号屏蔽字。

  • oldset:如果非空,则指向一个 sigset_t 类型的变量,该变量将被设置为调用 sigprocmask 之前的信号屏蔽字。

对于oldset来说,就是存储之前信号,how参数的block参数是将set的信号添加到当前进程,un就是取消,setmask是将信号直接换成set,不要之前信号屏蔽值了。

所以,对于信号来说,因为能修改信号的只有OS,那么OS要提供给用户系统调用函数,但是不能直接修改,直接修改风险太大了,毕竟直接修改简直让我们和OS没有两样,所以提供了一个结构体,叫做sigset_t,并且使用函数,可以通过修改sigset_t的对象,间接修改当前进程。

现在我们试试:

cpp 复制代码
void PrintPending(sigset_t pending)
{
   std::cout << "curr process[" << getpid() << "]pending: ";
    for (int signo = 31; signo >= 1; signo--)
    {
        if (sigismember(&pending, signo))
            std::cout << 1;
        else
            std::cout << 0;
    }
    std::cout << std::endl;

}

int main()
{

    sigset_t block_set, old_set;
    //初始化sigset_t对象
    sigemptyset(&block_set);
    sigemptyset(&old_set);
    //添加2号信号
    sigaddset(&block_set,2);
    //修改当前进程的信号
    sigprocmask(SIG_BLOCK,&block_set,&old_set);

    while(true)
    {
        sigset_t pending;
        sigpending(&pending);
        // 打印pending位图
        PrintPending(pending);

        sleep(1);
    }
    return 0;
} 

我们通过定义两个sigset_t对象,然后初始化一下,对1号对象添加信号,此时,我们并没有完成对当前进程修改信号这个过程,当我们调用sigprocmask之后,我们就可以修改进程信号了,并且,我们可以打印pending出来看看。

我们往1507065进程发送信号2,发现pending位图的第二个比特位变成了1,也就是它接收到了2号信号,所以变成了1。

现在我们看到了0到1的变化,那么如果我们想看1到0的变换呢?我们只需要修改how的参数,SIG_SETMASK,这里我们可以直接复用原来的oldset:

cpp 复制代码
       cnt--;
        if (cnt == 0)
        {
            std::cout << "解除对2号信号的屏蔽!!!" << std::endl;
            sigprocmask(SIG_SETMASK, &old_set, &block_set);
        }

加入以上逻辑就发现,时间一到直接就停止了,所以我们应该自定义,取消默认行为:

cpp 复制代码
void handler(int signo)
{
    std::cout << "Hello SIGINT " << std::endl;
}

那么这里有新问题了,信号从0变成1的这个过程,是递达之前还是递达之后呢?

我们打印出来看看:

cpp 复制代码
void handler(int signo)
{
    std::cout << signo << "号信号被递达!!!" << std::endl;
    std::cout << "-------------------------------" << std::endl;
    sigset_t pending;
    sigpending(&pending);
    PrintPending(pending);
    std::cout << "-------------------------------" << std::endl;
}

因为是在函数里面发生改变的,所以应该是递达之前改变的。

对于已经屏蔽的信号,如果解除之后,那么就会立即执行。

以上的操作都是修改的pending,没有直观看到block,这段代码对于block修改较为直观:

cpp 复制代码
void handler(int signum) {
    printf("Caught signal %d\n", signum);
}
 
int main() {
    sigset_t set, oldset;
 
    // 设置信号处理程序
    signal(SIGINT, handler);
 
    // 初始化信号集
    sigemptyset(&set);
    sigaddset(&set, SIGINT);
 
    // 阻塞 SIGINT 信号
    if (sigprocmask(SIG_BLOCK, &set, &oldset) == -1) {
        perror("sigprocmask");
        exit(EXIT_FAILURE);
    }
 
    printf("SIGINT is blocked. Press Ctrl+C (SIGINT) now...\n");
    sleep(5);
 
    // 解除阻塞 SIGINT 信号
    if (sigprocmask(SIG_UNBLOCK, &set, NULL) == -1) {
        perror("sigprocmask");
        exit(EXIT_FAILURE);
    }
 
    printf("SIGINT is unblocked. Press Ctrl+C (SIGINT) again...\n");
    sleep(5);
 
    // 恢复原来的信号屏蔽字
    if (sigprocmask(SIG_SETMASK, &oldset, NULL) == -1) {
        perror("sigprocmask");
        exit(EXIT_FAILURE);
    }
 
    printf("Original signal mask restored.\n");
 
    // 继续执行其他操作
    sleep(5);
 
    return 0;
}

感谢阅读!

相关推荐
zzzzzz31016 小时前
9K Star 炸裂开源!这个 C 语言写的代码知识图谱,把 Linux 内核索引压缩到了 3 分钟
linux·服务器·sql
XIAOHEZIcode16 小时前
Linux系统鼠标偏移常见原因以及修复方案
linux·运维·游戏
用户0328472220701 天前
如何搭建本地yum源(上)
运维
大树884 天前
金刚石散热越强,管路越先见顶
大数据·运维·服务器·人工智能·ai
摇滚侠4 天前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
霸道流氓气质4 天前
领域驱动设计(DDD)在 Spring Boot 微服务中的实践指南
运维·spring boot·微服务
小宇宙Zz4 天前
Maven依赖冲突
java·服务器·maven
Inhand陈工4 天前
基于台达PLC与映翰通IG502的智慧水产养殖精准投喂与远程运维解决方案
运维·人工智能·物联网·阿里云·信息与通信
酣大智4 天前
ARP代理--工作原理
运维·网络·arp·arp代理
shushangyun_4 天前
2026年快消品B2B系统推荐:支持终端门店订货、促销政策自动化的工具?
java·运维·网络·数据库·人工智能·spring·自动化