进程信号(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 8迁移Anolis OS 8
Anolis OS 8在做出差异性开发同时,在生态上和依赖管理上保持跟CentOS 8.x兼容,本文为您介绍如何通过AOMS迁移工具实现CentOS 8.x到Anolis OS 8的迁移。
目录
相关文章
|
1月前
|
消息中间件 存储 网络协议
从零开始掌握进程间通信:管道、信号、消息队列、共享内存大揭秘
本文详细介绍了进程间通信(IPC)的六种主要方式:管道、信号、消息队列、共享内存、信号量和套接字。每种方式都有其特点和适用场景,如管道适用于父子进程间的通信,消息队列能传递结构化数据,共享内存提供高速数据交换,信号量用于同步控制,套接字支持跨网络通信。通过对比和分析,帮助读者理解并选择合适的IPC机制,以提高系统性能和可靠性。
129 14
|
2月前
|
算法 Linux 调度
深入理解Linux操作系统的进程管理
本文旨在探讨Linux操作系统中的进程管理机制,包括进程的创建、执行、调度和终止等环节。通过对Linux内核中相关模块的分析,揭示其高效的进程管理策略,为开发者提供优化程序性能和资源利用率的参考。
109 1
|
3天前
|
存储 网络协议 Linux
【Linux】进程IO|系统调用|open|write|文件描述符fd|封装|理解一切皆文件
本文详细介绍了Linux中的进程IO与系统调用,包括 `open`、`write`、`read`和 `close`函数及其用法,解释了文件描述符(fd)的概念,并深入探讨了Linux中的“一切皆文件”思想。这种设计极大地简化了系统编程,使得处理不同类型的IO设备变得更加一致和简单。通过本文的学习,您应该能够更好地理解和应用Linux中的进程IO操作,提高系统编程的效率和能力。
50 34
|
6天前
|
Linux
Linux编程: 在业务线程中注册和处理Linux信号
本文详细介绍了如何在Linux中通过在业务线程中注册和处理信号。我们讨论了信号的基本概念,并通过完整的代码示例展示了在业务线程中注册和处理信号的方法。通过正确地使用信号处理机制,可以提高程序的健壮性和响应能力。希望本文能帮助您更好地理解和应用Linux信号处理,提高开发效率和代码质量。
38 17
|
15天前
|
Linux
Linux编程: 在业务线程中注册和处理Linux信号
通过本文,您可以了解如何在业务线程中注册和处理Linux信号。正确处理信号可以提高程序的健壮性和稳定性。希望这些内容能帮助您更好地理解和应用Linux信号处理机制。
50 26
|
7天前
|
消息中间件 Linux C++
c++ linux通过实现独立进程之间的通信和传递字符串 demo
的进程间通信机制,适用于父子进程之间的数据传输。希望本文能帮助您更好地理解和应用Linux管道,提升开发效率。 在实际开发中,除了管道,还可以根据具体需求选择消息队列、共享内存、套接字等其他进程间通信方
39 16
|
1月前
|
消息中间件 Linux
Linux:进程间通信(共享内存详细讲解以及小项目使用和相关指令、消息队列、信号量)
通过上述讲解和代码示例,您可以理解和实现Linux系统中的进程间通信机制,包括共享内存、消息队列和信号量。这些机制在实际开发中非常重要,能够提高系统的并发处理能力和数据通信效率。希望本文能为您的学习和开发提供实用的指导和帮助。
117 20
|
2月前
|
存储 监控 Linux
嵌入式Linux系统编程 — 5.3 times、clock函数获取进程时间
在嵌入式Linux系统编程中,`times`和 `clock`函数是获取进程时间的两个重要工具。`times`函数提供了更详细的进程和子进程时间信息,而 `clock`函数则提供了更简单的处理器时间获取方法。根据具体需求选择合适的函数,可以更有效地进行性能分析和资源管理。通过本文的介绍,希望能帮助您更好地理解和使用这两个函数,提高嵌入式系统编程的效率和效果。
120 13
|
2月前
|
SQL 运维 监控
南大通用GBase 8a MPP Cluster Linux端SQL进程监控工具
南大通用GBase 8a MPP Cluster Linux端SQL进程监控工具
|
2月前
|
运维 监控 Linux
Linux操作系统的守护进程与服务管理深度剖析####
本文作为一篇技术性文章,旨在深入探讨Linux操作系统中守护进程与服务管理的机制、工具及实践策略。不同于传统的摘要概述,本文将以“守护进程的生命周期”为核心线索,串联起Linux服务管理的各个方面,从守护进程的定义与特性出发,逐步深入到Systemd的工作原理、服务单元文件编写、服务状态管理以及故障排查技巧,为读者呈现一幅Linux服务管理的全景图。 ####