【Linux 系统】信号保存

前言

进程对于信号的处理和我们现实生活中的行为是一致的:

  1. 进程必须能识别对应的信号,同时也能合理的处理信号。也就是意味着:进程本身是具备处理信号的能力的,这是进程内置功能的一部分

  2. 当一个进程受到了信号,进程可能不会马上处理这个信号,而是在一个“合适的时候”对信号进行处理。

  3. 进程在收到信号和处理信号的时间内,一定需要有一个时间窗口,在此期间。进程有能将信号进行保存的能力

  • 在Linux下的信号查看 kill -l

    在这里插入图片描述

    我们主要探讨的信号是 1-31 号的信号。

在这篇文章,我们将先带大家认识信号,了解一个信号的保存

1. PCB中的信号保存

在 Linux 中操作系统会为每一个进程创建一个 PCB 结构体:struct task_struct。其中包含一个字段来描述当前进程的收到的信号

struct task_struct
{
    int sign;
}

一个整型有32位:0000 0000 0000 0000 0000 0000 0000 0000。其中我们管理下层的1 ~ 32号的信号只需要采用位图的形式:1 ~ 31下标的比特位对应的信号编号,其中 0代表未收到该信号;1代表收到了该信号

小结

  • 比特位的0/1表示是否收到信号
  • 比特位的下标表示收到了几号信号
  • 所谓发送信号,在进程看来:就是操作系统修改 struct task_struct 中的这个字段的某一个比特位设置为了1。注意:修改PCB的状态只能由操作系统来完成,因为用户无权修改。操作系统是管理者!(这也解释了为什么需要陷入内核态)
  • 同时:这样有一个弊端,如果收到了多个信号,进程只会处理一次。

为什么要保存信号呢?

  • 因为我们的进程在收到信号之后不会立即处理信号!需要等到一个“合适的时候”才会处理信号。

2. 信号在内核中的存储

  • 信号在内核中的存储有三个重要字段

    1. block阻塞,也被称为信号掩码(signal mask)。表示当前的信号是否是阻塞的。在阻塞状态下的信号不会执行对应的方法。主要目的是:避免 “关键操作被信号打断”。例如,进程在执行磁盘写入、数据同步等原子操作时,若被 SIGINT 打断可能导致数据损坏,此时可将 SIGINT 加入 block 状态,暂时屏蔽该信号

      注意:block 不是 “丢弃信号”,而是 “延迟处理”—— 被阻塞的信号若在阻塞期间收到,会进入 pending 集(未决状态),直到信号被从 block 集中移除(解除阻塞),才会被处理。

    2. pending未决。 是进程 “已收到但未处理” 信号的 “暂存区”。是进程维护的另一个信号状态,表示 “进程已经收到,但因以下原因暂未处理” 的信号:a、信号处于 block 状态 b、进程正在处理另一个信号(同一时间只能处理一个信号,新收到的信号会暂存到 pending)。

      注意:1 ~ 31 号信号虽然进程可能受到多次,但是只可能处理一次。这样的触发机制被称为:边沿触发。32 ~ 64号信号是电平触发,多次收到会记录次数(处理多次)

    3. handler信号处理函数。handler 是进程为某个信号注册的处理函数,其定义了 当进程收到该信号时,应该执行什么操作。Linux 中,每个信号(如 SIGINTSIGQUIT)都有一个默认的 handler(默认行为),进程也可以通过 signal()sigaction() 函数自定义 handler。

    注意:该字段的类型就是一个函数指针。其中默认定义了三个宏:

    在这里插入图片描述
    其中:0表示使用内核的默认处理函数,1表示忽略该信号,表示不做任何处理。当然操作系统是能够识别这个handler字段是0/1/用户程序。如果是0,那么操作系统就会调用自己的方法(比如说:维护了一张表存储信号编号和默认处理方法的映射);如果是1,那么操作系统就不再做处理;如果是用户的处理方法,就直接调用即可(存的是用户的代码的)。

普通信号的范围是:[1, 31]。所以对于不同的信号,我们利用数组进行维护就非常不错了:

在这里插入图片描述
进程PCB管理信号就是利用三张表。这三张表分别表示着上面我们介绍的内核维护信号的三个字段。Linux 中的 struct task_struct 其中有字段,就是指向了这三张表,通过这样的管理手段,就能很好地管理好进程收到的信号了。

3. 信号集操作函数

Linux 允许用户能够利用信号集设置阻塞,但是不允许用户利用信号集设置未决。因为我们的 Linux 的进程管理信号是通过编号/下标的方式,所以我们的内核提供了一种类型:sigset_t。通过这个函数设置标志位0/1表示取消/启动……而这样的性质不就是我们的信号屏蔽字表所需要的吗?

这个类型还配有相应的函数,来实现类似于位图的操作
在这里插入图片描述
正如上面说谈:sigset_t 是一个被包装的类型。是不允许进行位运算的。只能通过系统提供的运算方式。

3.1 sigprocmask

  • sigprocmask:可用读取/修改当前进程的信号的 block (信号屏蔽字)

    在这里插入图片描述

    • how:设置的方式

      SIG_BLOCK:根据当前传入的信号集新增当前进程的屏蔽字。相当于:mask = mask | set

      SIG_UNBLOCK:根据当前传入的信号集取消当前进程的屏蔽字。相当于:mask = mask & ~set

      SIG_SETMASK:根据当前传入的信号集设置当前进程的屏蔽字。相当于:mask = set

    • set:输入型参数,表示需要传入的信号集。

    • oldset:输出型参数,表示执行 sigprocmask 函数之前的信号集。

3.2 sigpending

  • sigpending

    在这里插入图片描述

demo:

//测试:将某某信号阻塞,然后发送对应信号,观察pending表的变化

 void printpending(const sigset_t& pending)
 {
     for(int i = 31; i >= 1; i--)
     {
         if(sigismember(&pending, i))
             cout << 1;
         else
             cout << 0;
     }
     cout << endl << endl;
 }

 int main()
 {
     //1、做数据的预备工作
     sigset_t bset, oset;
     sigemptyset(&bset); //全部置为0
     sigemptyset(&oset);

     sigaddset(&bset, 2); //设置2号信号为1

     //2、将数据加载到内核中
     sigprocmask(SIG_SETMASK, &bset, &oset); //屏蔽2号信号

     //3、重复打印,观察现象:
     sigset_t pending;
     while(true)
     {
         int n = sigpending(&pending); //获取当前进程的pending表
         if(n < 0) continue;

         printpending(pending); //打印信号
         sleep(1);
     }
     return 0;
}

上面的代码,我们将信号2进行了屏蔽:
现象:当我们打印的时候,开始信号2的未决表是为空的,然后我们对其进行 ctrl c。我们就能观察到信号2的位置由0变1的现象了。

完。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值