进程信号(Linux)下

简介: 进程信号(Linux)

阻塞信号


信号其他相关常见概念


实际执行信号的处理动作称为信号递达

信号从产生到递达之间的状态称为信号未决

进程可以选择阻塞某个信号

被阻塞的信号产生之后将保存在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作

阻塞和忽略是不同的,信号只要被阻塞就不会递达,忽略本质上就是递达之后选择的一种处理动作


在内核中的表示


image.png


每个进程都有两个标志位分别表示阻塞和未决,还有一个函数指针表示处理动作;信号产生时,内核在进程控制块中设置该信号的未决标志,直到信号递达才清除该标志;如果一个信号没有被产生,并不妨碍它可以先被阻塞


sigset_t


每个信号只有一个比特位,非0即1,不记录该信号产生了多少次,阻塞标志亦是如此;未决和阻塞都可以用相同的数据类型sigset_t来存储,sigset_t称为信号集,此类型可以表示每个信号的有效或无效状态;阻塞信号也称当前进程的信号屏蔽字,屏蔽是阻塞而不是忽略


信号集操作函数


sigset_t类型对于每种信号用一个比特位表示有效或无效,至于类型内部如何存储这些比特位则依赖于系统实现


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);


函数 sigempty初始化 set所指向的信号集,使其中所有信号对应的比特位清零,表示该信号集不包括任何有效信号

函数 sigfillset初始化 set所指向的信号集,使其中所有信号对应的比特位置为1,表示该信号集的有效信号包括系统所支持的所有信号

函数 sigaddset将信号添加到信号集中;函数 sigdelset将信号从信号集中删除;函数 sigismember判断某种信号是否在信号集中


sigprocmask


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

如果oldset是非空指针,则读取进程当前的信号屏蔽字通过oldset参数传出,如果set是非空指针,则更改进程当前的信号屏蔽字,参数how指示如何更改;如果oldset和set都是非空指针,则将原来的信号屏蔽字备份到oldset中,然后根据set和how参数更改当前的信号屏蔽字


SIG_BLOCK set包含了所有待添加到信号集中的信号
SIG_UNBLOCK set包含了所有待从信号集中删除的信号
SIG_SETMASK 设置当前信号屏蔽字为set所指向的值


sigpending


int sigpending(sigset_t *set);

读取当前进程的未决信号集,通过set参数传出


默认情况下所有信号都不是阻塞的,如果信号被屏蔽,则该信号不会被递达

代码实现:屏蔽信号2


#include<iostream>
#include<signal.h>
#include<unistd.h>
#define BLOCK_SIGNAL 2
#define MAX_SIGNUM 31
using namespace std;
void show_pending(const sigset_t& pending)
{
    for(int signo=MAX_SIGNUM;signo>=1;signo--)
    {
        if(sigismember(&pending,signo))
        {
            cout<<"1";
        }
        cout<<"0";
    }
    cout<<"\n";
}
int main()
{
    //1.屏蔽指定信号
    sigset_t block,oldblock,pending;
    //1.1初始化
    sigemptyset(&block);
    sigemptyset(&oldblock);
    sigemptyset(&pending);
    //1.2添加要屏蔽的信息
    sigaddset(&block,BLOCK_SIGNAL);
    //1.3开始屏蔽
    sigprocmask(SIG_SETMASK,&block,&oldblock);
    //2.遍历打印pending信号集
    while(true)
    {
        //2.1初始化
        sigemptyset(&pending);
        //2.2获取
        sigpending(&pending);
        //2.3打印
        show_pending(pending);
        //间隔一秒
        sleep(1);
    }
    return 0;
}


3532e111238230d115f002022c60e4f4_bd8b789d311c40c48e18f25027eba018.png


捕捉信号


内核如何实现信号的捕捉


操作系统中进程存在两种状态:用户态,内核态;用户态一般会访问操作系统的资源和硬件资源,为达这一目的,必须通过系统提供的系统调用接口,而且执行系统调用的身份必须是内核,为什么用户可以访问系统调用接口呢???


在CPU中存在着许多的寄存器分为:可见寄存器,不可见寄存器,只要是和进程强相关的都是保存着进程的上下文的数据;名为CR3的寄存器保存着当前进程的运行级别:0表示内核态,3表示用户态,在系统调用接口的起始位置,存在着int 80汇编,会将用户态修改为内核态,从而可以以内核态的身份访问系统调用接口


进程以内核身份访问系统调用接口的具体过程又是怎么样的呢???

在之前进程空间中学习过,进程空间包括用户空间和内核空间,系统调用接口就与这内核空间有关:每个进程都有自己的进程空间,用户空间独享,内核空间只有一份,也就是说所有进程共享同一份内核空间;当进程访问接口时,只需要在进程空间上进行跳转即可,就类似与动态库加载到进程空间


图解:


0d112ffa46fda1261c85728dcf136138_43aac03b963f4d43a70176cae56d0ee8.png


当开机时,操作系统会从磁盘加载到内存中的内核区中,当进程以内核态身份访问系统调用时,会在进程空间中跳转到内核空间通过内核级页表映射到内存中操作系统完成相应的调用,完毕之后再跳转回用户空间


信号在产生时,并不会被立刻处理,而是在合适的时间被操作系统处理,这个合适的时间就是从内核态返回用户态时;所以,进程在进程切换或者系统调用时先进入内核态,在内核态中进行信号检测,也就是进程中的两个标志位(pending/block)和函数指针(handler*):如果信号未决且未被阻塞,查找函数指针是否有对应的自定义处理方法,若有,将进程内核态身份修改为用户身份完成对应的处理方法,再还原为内核身份,完成剩余的系统调用,待系统调用结束后,最后将身份修改为用户态继续执行后续的代码


图解:


cacf5ef9ff541a76f076e2493cfa26d3_8b8b092a5665401bb22e807eb999f7a0.png


需要注意的是:不能以内核态的身份执行用户态的代码,因为操作系统不相信任何人,以免有人进行恶意破坏


sigaction


int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);


signum:待捕获的信号编号

act:结构体指针


682eb45148a1235a5c43da09099ff06d_35c0b81d63ac4c13abf0b4e2fd6ed768.png

其中包含处理方法sa_handler和信号集sa_mask


举个栗子,通过此函数捕获2号信号,捕获信号后休息20秒,多次向进程发送2号信号,观察进程运行结果


void Count(int cnt)
{
    while(cnt)
    {
        printf("cnt:%2d\r",cnt);
        fflush(stdout);
        cnt--;
        sleep(1);
    }
    printf("\n");
}
void handler(int signo)
{
    cout<<"get a signo"<<signo<<"正在处理..."<<endl;
    Count(20);
}
int main()
{
    struct sigaction act,oldact;
    act.sa_handler=handler;
    act.sa_flags=0;
    sigemptyset(&act.sa_mask);
    sigaction(SIGINT,&act,&oldact);
    while(true)
    sleep(1);
    return 0;
}


0b56a85943a8a2b47a8093adc6ac2c2e_8fb296beec08470cbb662fef2a9c13b1.png

e0df86dccce53a874f74836e91622619_e99e13984bc041909d0ad75e686307f8.png


虽然向进程发送多次同一种信号,但是进程只捕获了两次,因为当进程正在递达某一个信号时,将同种类型的信号是无法被抵达的,系统会自动将当前信号添加到屏蔽字中,将pending位图中该信号所在位置修改为0,再次发送同一信号,会将pending位图中该信号所在位置修改为1;当进程完成信号的递达时,系统会自动解除对该信号的屏蔽,所以系统会立即递达pending位图中的信号,也就是捕获第二次信号


当我们正在处理2号信号时,还想屏蔽3号信号,此时只需要将3号信号加入sa_mask信号集即可


void Count(int cnt)
{
    while(cnt)
    {
        printf("cnt:%2d\r",cnt);
        fflush(stdout);
        cnt--;
        sleep(1);
    }
    printf("\n");
}
void handler(int signo)
{
    cout<<"get a signo"<<signo<<"正在处理..."<<endl;
    Count(20);
}
int main()
{
    struct sigaction act,oldact;
    act.sa_handler=handler;
    act.sa_flags=0;
    sigemptyset(&act.sa_mask);
    //添加3号信号
    sigaddset(&act.sa_mask,3);
    sigaction(SIGINT,&act,&oldact);
    while(true)
    sleep(1);
    return 0;
}

36597fda135ba7af5b12c837faf993c1_2e272ad820b04e848c2db316905ffb74.png

03ec4aeb7370e90e0442c95613601de6_a8edfd6dbdb6420dba29f74fa697a79c.png


和上面有所不同的是,这里的进程在最后直接退出了,其实是因为,在2号信号被屏蔽后,再次执行2号信号,最后执行3号信号结束进程


进程处理信号的原则是串行处理同类型的信号,不允许递归


相关实践学习
CentOS 7迁移Anolis OS 7
龙蜥操作系统Anolis OS的体验。Anolis OS 7生态上和依赖管理上保持跟CentOS 7.x兼容,一键式迁移脚本centos2anolis.py。本文为您介绍如何通过AOMS迁移工具实现CentOS 7.x到Anolis OS 7的迁移。
目录
相关文章
|
2月前
|
网络协议 Linux
Linux查看端口监听情况,以及Linux查看某个端口对应的进程号和程序
Linux查看端口监听情况,以及Linux查看某个端口对应的进程号和程序
143 2
|
2月前
|
Linux Python
linux上根据运行程序的进程号,查看程序所在的绝对路径。linux查看进程启动的时间
linux上根据运行程序的进程号,查看程序所在的绝对路径。linux查看进程启动的时间
47 2
|
6天前
|
Linux Shell
6-9|linux查询现在运行的进程
6-9|linux查询现在运行的进程
|
1月前
|
Linux C语言
C语言 多进程编程(四)定时器信号和子进程退出信号
本文详细介绍了Linux系统中的定时器信号及其相关函数。首先,文章解释了`SIGALRM`信号的作用及应用场景,包括计时器、超时重试和定时任务等。接着介绍了`alarm()`函数,展示了如何设置定时器以及其局限性。随后探讨了`setitimer()`函数,比较了它与`alarm()`的不同之处,包括定时器类型、精度和支持的定时器数量等方面。最后,文章讲解了子进程退出时如何利用`SIGCHLD`信号,提供了示例代码展示如何处理子进程退出信号,避免僵尸进程问题。
|
1月前
|
NoSQL
gdb中获取进程收到的最近一个信号的信息
gdb中获取进程收到的最近一个信号的信息
|
2月前
|
消息中间件 Linux
Linux进程间通信
Linux进程间通信
35 1
|
2月前
|
Linux 调度
Linux0.11 信号(十二)(下)
Linux0.11 信号(十二)
20 1
|
19天前
|
存储 监控 安全
探究Linux操作系统的进程管理机制及其优化策略
本文旨在深入探讨Linux操作系统中的进程管理机制,包括进程调度、内存管理以及I/O管理等核心内容。通过对这些关键组件的分析,我们将揭示它们如何共同工作以提供稳定、高效的计算环境,并讨论可能的优化策略。
22 0
|
1月前
|
Unix Linux
linux中在进程之间传递文件描述符的实现方式
linux中在进程之间传递文件描述符的实现方式
|
2月前
|
开发者 API Windows
从怀旧到革新:看WinForms如何在保持向后兼容性的前提下,借助.NET新平台的力量实现自我进化与应用现代化,让经典桌面应用焕发第二春——我们的WinForms应用转型之路深度剖析
【8月更文挑战第31天】在Windows桌面应用开发中,Windows Forms(WinForms)依然是许多开发者的首选。尽管.NET Framework已演进至.NET 5 及更高版本,WinForms 仍作为核心组件保留,支持现有代码库的同时引入新特性。开发者可将项目迁移至.NET Core,享受性能提升和跨平台能力。迁移时需注意API变更,确保应用平稳过渡。通过自定义样式或第三方控件库,还可增强视觉效果。结合.NET新功能,WinForms 应用不仅能延续既有投资,还能焕发新生。 示例代码展示了如何在.NET Core中创建包含按钮和标签的基本窗口,实现简单的用户交互。
52 0
下一篇
无影云桌面