您现在的位置是:首页 > 正文

Linux系统编程:进程信号的保存和阻塞

2024-04-01 04:48:19阅读 3

目录

一. 信号保存和阻塞的相关概念

二. 进程信号的表示

2.1 进程信号在内核中的表示

2.2 sigset_t 类型

三. 信号集操作相关函数

3.1 sigset_t 类型参数设置相关函数

3.2 sigprocmask 函数

3.3 sigpending 函数

四. 演示代码

4.1 将所有信号的处理方式都注册为不退出进程

4.2 显示未决信号集

4.3 所有信号设置为阻塞

五. 总结


一. 信号保存和阻塞的相关概念

  • 信号递达:进程实际执行信号处理的动作叫做信号的递达(Delivery)
  • 信号未决:信号从实际产生到递达之间的状态,称为信号未决(Pending)
  • 进程可以阻塞某个信号(block),即:即使产生了某个信号也不执行相应的处理动作。
  • 被阻塞信号在产生后,只要不接触阻塞状态,就一直不会递达。
  • 信号阻塞和忽略不是一个概念,信号阻塞是不对进行处理,信号处于未决状态,而忽略信号是指进程接收到了信号,信号也会递达,只是没有实质性的处理动作。

二. 进程信号的表示

2.1 进程信号在内核中的表示

每一个进程的PCB中,都会存储三张用于表示信号状态的表(如图2.1所示),他们分别为block、pending和handler,其中:

  • block:阻塞状态表,其底层实现是位图,如果设置某个进程信号阻塞,block中对应的二进制位就由0变1,block标志位如果为1,信号就不能够递达。
  • pending:未决信号,底层实现也是位图,如果OS检测到了信号,但还没有对信号进行处理,那么在其pending位图中的对应bit位就会被设置为1,如果信号递达,pending位图就会由1变0,如果其block中的二进制位也为1,那么信号就不会递达,pending中的二进制位永远都会为1,直到阻塞状态解除信号递达。
  • handler:是一个函数指针数组,指向其对应的信号处理方法的函数指针,图中SIG_DEF和SIG_IGN分别对应默认处理方式和忽略信号。
  • SIG_DFL和SIG_IGN在源码中的定义为:#define SIG_DEF ((__sighandler_t ) 0) 和 #define SIG_IGN ((__sighandler_t ) 0),进程收到信号,在递达是判断用哪个函数对信号进行处理的流程大概为:先通过 if 和 else if 判断是否选取默认处理方法或者忽略,如果都不是,则走到else执行用于自定义的处理方法。
图2.1 进程信号在内核中的表示

2.2 sigset_t 类型

OS不允许用于直接对block和pending位图中的二进制位进行修改,因此提供了一个OS类型sigset专门用于设置用于进程信号表示的相关位图,sigset_t 可以被称为信号集,sigset_t 的底层是位图结果,用 0/1 来表示状态。

阻塞信号集也叫做当前进程的信号屏蔽字(Signal Mask)。

三. 信号集操作相关函数

3.1 sigset_t 类型参数设置相关函数

  • int sigemptyset(sigset_t *set) -- 让set中不包含任何信号,即全部二进制位都是0。
  • int sigfillset(sigset_t *set) -- 让set包含全部信号,即让全部二进制位都是1。
  • int sigaddset(sigset_t *set, int signo) -- 将set指定信号的状态设置为有效1。
  • int sigdelset(sigset_t *set, int signo) -- 删除set指定信号的状态无效0。
  • int sigismember(sigset_t *set, int signo) -- 判断set中某个信号的状态是否为有效1,如果有效,函数返回1,无效返回0。

上面的这些函数,都是成功执行返回0,失败返回-1。 

3.2 sigprocmask 函数

sigprocmask函数 -- 读取或更改信号的阻塞状态

函数原型:int sigprocmask( int how,  sigset_t *set, sigset_t *obset )

函数参数:

        how -- 方法选择,添加阻塞、删除阻塞或设置阻塞

        set -- 如果选择设置阻塞,则用set中的信息设置

        obset -- 输出型参数,读取原来的阻塞信息

返回值:成功返回0,失败返回-1

在sigprocmask函数中,参数 how 用于方法的选择,how有三个可选项,分别为:SIG_BLOCK、SIG_UNBLOCK 和 SIG_SETMASK。

表3.1 sigprocmask函数参数how的选择及对应功能
how 功能
SIG_BLOCK 添加对特定信号的阻塞状态,相当于mask | set
SIG_UNBLOCK 取消对特定信号的阻塞状态,相当于mask & ~set
SIG_SETMASK 使用set设置信号阻塞状态,相当于mask = set

3.3 sigpending 函数

sigpending函数 -- 获取进程当前的未决信号集

函数原型:int sigpending(sigset_t *set)

函数参数:set为输出型参数,用于获取当前进程的未决信号集

返回值:运行成功返回0,失败返回-1

四. 演示代码

4.1 将所有信号的处理方式都注册为不退出进程

如果我们在程序中将所有信号的处理方法都设置为不退出,并在之后执行死循环,那么,是不是进程就无法被杀死了呢?

答案显然是否定的,OS的设计者早就考虑到了这一点,9号信号SIGKILL为管理员权限信号,用户不可以重新注册其处理方法,代码和运行结果如下。

代码4.1:所有信号重新注册方法为不退出

#include <iostream>
#include <signal.h>
#include <unistd.h>

// 用户自定义的信号处理函数
void siganl_handler(int sig)
{
    std::cout << "recieve a signal, signum:" << sig << std::endl;
}

int main()
{
    std::cout << "this is a process, pid:" << getpid() << ", ppid:" << getppid() << std::endl;

    // 1. 将全部普通信号处理函数重新注册
    for(int sig = 1; sig <= 31; ++sig)
    {
        signal(sig, siganl_handler);
    }

    // 2. 执行死循环
    while(true) 
    { }

    return 0;
}
图4.1 代码4.1的运行结果

4.2 显示未决信号集

代码4.2通过sigprocmask函数,在第5s设置2号SIGINT信号为阻塞状态,,在第8s通过raise函数向进程发送2号SIGINT信号,在第15s取消SIGINT信号的阻塞状态,并实时输出未决状态信息,代码运行结果如图4.2,2号信号的阻塞状态由0至1,并在阻塞状态取消后,执行用户自定义的2号SIGINT信号处理函数,退出进程。

代码4.2:未决信号集的打印

#include <iostream>
#include <signal.h>
#include <unistd.h>

// 用户自定义的信号处理函数
void siganl_handler(int sig)
{
    std::cout << "recieve a signal, signum:" << sig << std::endl;
    exit(0);
}

// 未决状态打印函数
void showPending(const sigset_t& pending)
{
    for(int sig = 1; sig <= 31; ++sig)
    {
        if(sigismember(&pending, sig)) std::cout << 1;
        else std::cout << 0;
    }    
    std::cout << std::endl;
}

int main()
{
    // 重新注册2号信号的处理方法
    signal(SIGINT, siganl_handler);

    // bset用于设置阻塞状态,obset用于接收原阻塞状态
    sigset_t bset, obset; 
    // pending用于获取阻塞集
    sigset_t pending;  

    //对信号集设置初始状态
    sigemptyset(&bset);
    sigaddset(&bset, SIGINT);

    int count = 0;
    while(true)
    {
        sleep(1);
        std::cout << ++count << ": " << std::flush;
        
        sigpending(&pending);   // 接收阻塞状态
        showPending(pending);   // 阻塞状态打印

        if(count == 5)
        {
            std::cout << "设置SIGINT信号处于阻塞状态 ..." << std::endl; 
            sigprocmask(SIG_BLOCK, &bset, &obset);
        }

        if(count == 8)
        {
            std::cout << "发送SIGINT信号" << std::endl;
            raise(SIGINT);
        }

        if(count == 15)
        {
            std::cout << "取消对SIGINT信号的阻塞" << std::endl;
            sigprocmask(SIG_SETMASK, &obset, nullptr);
        }
    }

    return 0;
}
图4.2 代码4.2的运行结果

4.3 所有信号设置为阻塞

设置所有信号阻塞,然后死循环,是不是发送任何信号都无法终止进程的运行?

答案当然也不是,9号SIGKILL信号和19号SIGSTOP信号都无法被阻塞,在19号信号使进程终止运行期间,18号SIGCONT信号也无法被阻塞。

代码4.3:验证9号SIGKILL信号无法被阻塞

#include <iostream>
#include <signal.h>
#include <unistd.h>

// 未决状态打印函数
void showPending(const sigset_t& pending)
{
    for(int sig = 1; sig <= 31; ++sig)
    {
        if(sigismember(&pending, sig)) std::cout << 1;
        else std::cout << 0;
    }    
    std::cout << std::endl;
}

int main()
{
    // 设置所有信号阻塞
    sigset_t bset, obset;
    sigset_t pending;
    sigfillset(&bset);

    sigprocmask(SIG_BLOCK, &bset, &obset);

    // 依次向进程发送1 - 31号信号,如何输出未决状态
    for(int sig = 1; sig <= 31; ++sig)
    {   
        std::cout << "kill -" << sig << std::endl;
        raise(sig);

        // 获取阻塞信号集并打印
        sigpending(&pending);
        showPending(pending);

        sleep(1);
    }

    return 0;
}
图4.3 代码4.3的运行结果

五. 总结

  • 进程处理信号的动作叫做信号递达、信号从产生到递达的状态叫做信号未决、被阻塞的信号无法递达。
  • 进程PCB中有三张表:block、pending、handler,block和pending为阻塞和未决信号集,handler为指向信号处理函数的函数指针数组。
  • Linux操作系统提供了内置信号集类型sigset_t,用于信号状态的设置,sigprocmask 函数用于读取或设置阻塞信号集,sigpending用于获取阻塞信号集。
  • 9号SIGKILL信号具有管理员权限,用户无法自定义其处理动作,也无法被阻塞。

网站文章

  • xss攻击

    xss攻击

    之前介绍过csrf攻击,那个是通过编写恶意页面来通过跨域请求来调用用户的api现在介绍的是xss攻击,这种攻击和csrf不同的是,恶意脚本是注入到了用户要访问页面的本身,而不是一个恶意页面xss攻击按攻击方式可以分为2类:通过url和通过数据库1.非持久性(一般通过url)举个栗子:正常发送消息:http://www.test.com/message.php?se...

    2024-04-01 04:48:12
  • 谷歌翻译可用地址

    谷歌翻译可用地址

    谷歌翻译可用地址

    2024-04-01 04:47:32
  • RabbitMQ实现延迟消息

    RabbitMQ实现延迟消息

    本文主要讲解mall整合RabbitMQ实现延迟消息的过程,以发送延迟消息取消超时订单为例。

    2024-04-01 04:47:24
  • CentOS7查看开放端口命令及开放端口号

    CentOS7查看开放端口命令及开放端口号

    2024-04-01 04:47:17
  • 遇到报错 ImportError: /usr/lib/x86_64-linux-gnu/libstdc++.so.6: version `GLIBCXX_3.4.26‘ not found

    遇到报错 ImportError: /usr/lib/x86_64-linux-gnu/libstdc++.so.6: version `GLIBCXX_3.4.26‘ not found

    大多解决方法:需要将/usr/lib/x86_64-linux-gnu/libstdc++.so.6下对应的软连接进行修改替换。这里find后面的路径一般是根据自己服务器中,用户文件所在路径进行修改,...

    2024-04-01 04:46:40
  • Android——UI篇:关于Fragment的构造参数打包release版本报错的问题

    最近在做项目的途中遇到了问题,本来想着在fragment中创建一个构造参数来传递数据,获取Activity中的数据,如下: public class FaultVideoFragment extends android.app.Fragment { private FaultEnclosureModel model2; private FaultDetailsActivity

    2024-04-01 04:46:33
  • vue3系列--reactive实现细节

    vue3系列--reactive实现细节

    vue3reactive实现细节

    2024-04-01 04:46:26
  • java 外观设计模式

    java 外观设计模式

    外观模式(Facade),为子系统中的一组接口提供一个一致的界面,此模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。使用时间: 客户端不需要知道系统内部的复杂联系,整个系统只需提供一个"接...

    2024-04-01 04:46:20
  • C++的public继承中的public、private和protected

    C++的public继承中的public、private和protected

        C++语言是对C语言的一种增强,而其主要贡献在于,为C语言增加了类和模板等功能,可以帮助实现面向对象编程和代码复用等更方便的功能。    C++语言的类,一般都包含两种成员,分别是成员变量和成员函数。成员变量可以用来表示该类的某些状态,而成员函数则可以用来对这些成员变量进行操作。而对于这些成员而言,最核心的概念当属类的封装和继承的概念。1. 封装    所谓封装,就是编写一个类...

    2024-04-01 04:45:38
  • 假脱机技术Spooling和守护进程

    假脱机技术Spooling和守护进程

    总结:在假脱机打印系统下,对于每个用户而言,系统并非即时执行其程序输出数据的真实打印操作,而只是即时将数据输出到缓冲区,这时的数据并未真正被打印,只是让用户感觉系统正在为自己打印;当系统引入多道程序技...

    2024-04-01 04:45:30