深入了解linux系统—— 进程信号的产生

发布于:2025-07-15 ⋅ 阅读:(20) ⋅ 点赞:(0)

前言

进程在收到信号之后,可以立即处理,也可以在合适的时间再处理(1-31号普通信号可以不被立即处理)

信号不是被立即处理,信号就要被保存下来,让进程在合适的时间再去处理。

相关概念

在了解进程是如何保存信号之前,先了解一下一些概念:

  • 信号递达:进程实际执行信号的处理动作
  • 信号未决:信号从产生到递达的状态
  • 阻塞:进程可以阻塞某一个信号;(被阻塞的信号不会被递达,直到解除对信号的阻塞,该信号才会被递达)
  • 忽略:是进程对于已递达的信号的一种处理方式。

信号保存

进程要将信号保存下来,在合适的时候处理;保存信号不仅要保存是否存在信号,也要保存进程是否阻塞信号以及进程对于信号的处理方式。

Linux内核中,对于信号存在三张表分别是:blockpendinghandler;它们分别存储了进程对于信号的阻塞信号,收到信号以及进程对于信号的处理方式。

每一个信号都有阻塞、未决两个标识位以及处理动作,这分别和blockpendinghandler表一一对应。

  1. block表,表示进程对于信号的阻塞情况;
  2. pending表,表示处于未决的信号情况;
  3. handler表,函数指针表,存储着进程对于信号的处理方式。

对于blockpending,可以简单理解为位图,对于1-31号信号,每一个信号对应一个bit位;

blockbit位为1表示进程阻塞该信号,pendingbit位为1表示该信号处于未决状态(进程收到该信号)。

对于handler表,其存储的是进程对于信号的处理方式。

在这里插入图片描述

了解了信号的保存方式blockpendinghandler表;

那发送信号的本质就是将进程的task_struct中的pending表对应的bit位置1(修改内核数据结构对象)

进程处理信号本质就是检查进程task_structblock表是否阻塞信号、pending表是否存在信号,然后进行调用handler表中进程对于信号的处理方法。

信号集sigset_t

信号保存的blockpending表,都是以位图的方式来保存信号阻塞和未决的状态;01

对于阻塞和未决标识都可以使用sigset_t(信号集)这一数据类型来存储。

信号集sigset_t,可以表示信号有效无效的状态。

在阻塞信号集中,有效无效表示信号是否被阻塞。

在未决信号集中,有效无效表示信号是否处于未决状态。

信号集操作函数

对于sigset_t信号集,我们不清楚里面有什么;不能直接对信号集进程操作,需要用到特定的函数接口。

在这里插入图片描述

相关信号集操作函数,例如初始化sigemptyset(设置为0)、sigfillset(设置为1)、sigaddset(新增信号)、sigdelset(删除信号)等等。

初始化信号集

int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);

对于初始化信号集,分为两种:一是所有bit位设置为0、二是所有bit位设置为1

sigemptyset,初始化信号集,所有bit位设置为0

sigfillset,初始化信号集,所有bit位设置为1

新增信号

int sigaddset(sigset_t* set, int signum);

在信号集中,新增一个信号signum

删除信号

int sigdelsets(sigset_t* set, int signum);

在信号集set这,删除信号signum

对于上述的sigemptysetsigfilletsigaddsetsigdelset,这些函数返回值:

如果函数调用成功,返回0

函数调用失败,返回-1

判断信号是否存在

int sigismember(sigset_t* set, int signum);

sigismember函数用来判断信号集sig中是否存在信号signum

如果存在就返回1;如果不存在就返回0;如果函数调用失败就返回-1

系统调用

上述信号集操作函数,是修改当前已有信号集sigset_t

而在Linux内核中,存在block表和pending表来记录进程对于信号的屏蔽和未决状态;那我们能否修改内核中的block表呢?能否获取内核中的blockpending表呢?

当然是可以的,我们可以通过系统调用sigprocmasksigpending来对内核中进程的阻塞信号集未决信号集进行相关操作。

sigprocmask

调用该函数,可以修改或者读取信号屏蔽字

int sigprocmask(int how, const sigset_t *_Nullable restrict set, sigset_t *_Nullable restrict oldset);

对于sigprocmask函数,一共存在三个参数:

第一个参数表示要对内核中的block(阻塞信号集)进行什么操作;how的取值如下图:

在这里插入图片描述

第二个参数set指向当前信号集,我们需要自己创建信号集;

第三个参数oldset是一个输出型参数,当调用sigprocmask修改内核阻塞信号集时,会将内核阻塞信号集输出到oldset指向的信号集中。

测试:这里简单调用setprocmask为进程添加屏蔽2号信号

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

int main()
{
    sigset_t set;
    sigemptyset(&set);//初始化信号集
    sigaddset(&set, 2);//在信号集中添加2号信号
    sigset_t oldset;
    sigprocmask(SIG_BLOCK, &set, &oldset);
    while (true)
    {
    }
    return 0;
}

在这里插入图片描述

sigpending

int sigpending(sigset_t *set);

调用sigpending函数可以获取进程的pending表(未决信号集)。

在内核中pending表是记录进程2未决信号的状态的,给进程发信号的本质就是修改内核中进程的pending表。

在获取了进程的未决信号集之后,我们可以使用信号集相关操作函数来判断该信号集中是否存在未决的信号。

这里简单验证:阻塞的信号是不会被递达的;

void print_pending()
{
    // 获取pending
    sigset_t set;
    sigpending(&set);
    //输出位图
    for (int i = 31; i >= 1; i--)
    {
        if (sigismember(&set, i))
            std::cout << '1';
        else
            std::cout << '0';
    }
    std::cout << std::endl;
}
void handler(int sig)
{
    std::cout << "receive sig : " << sig << std::endl;
}

int main()
{
    //自定义捕捉2号信号
    signal(2, handler);
    //屏蔽2号信号
    sigset_t set, oldset;
    sigemptyset(&set);
    sigaddset(&set,2);
    sigprocmask(SIG_BLOCK, &set, &oldset);

    int cnt = 10;
    while(cnt--)
    {
        //输出pending表
        print_pending();

        if(cnt == 0)
        {
            //解除对2号信号的屏蔽
            sigemptyset(&set);
            sigprocmask(SIG_SETMASK, &oldset,nullptr);
        }
        sleep(1);
    }
    return 0;
}

在这里插入图片描述

总结

简单总结上述内容:

信号保存:block信号屏蔽表、pending信号未决表、handler信号处理方式(函数指针表)

信号集sigset_t,相关操作:初始化、新增、删除、判断是否存在等等

系统调用:sigprocmask:获取或设置内核blcok表、sigpending获取内核pending表。


网站公告

今日签到

点亮在社区的每一天
去签到