Linux进程信号(二)

news2024/12/28 4:29:43

信号保存与捕捉

  • 1.相关概念
  • 2.信号在内核中的示意图
  • 3.信号集
  • 4.信号集操作函数
  • 5.内核态与用户态
  • 6.信号捕捉
  • 7.sigaction
  • 8.可重入函数
  • 8.volatile
  • 9.SIGCHLD信号

🌟🌟hello,各位读者大大们你们好呀🌟🌟
🚀🚀系列专栏:【Linux的学习】
📝📝本篇内容:信号在内核中的情况;信号集;信号集操作函数;内核态和用户态;信号捕捉;sigaction;可重入函数;volatile;SIGCHLD信号
⬆⬆⬆⬆上一篇: 信号产生
💖💖作者简介:轩情吖,请多多指教(>> •̀֊•́ ) ̖́-

1.相关概念

①实际执行信号的处理动作称为信号递达
②信号从产生到递达之间的状态,称为信号未决
③进程可以选择阻塞(Block)某个信号
④被阻塞的信号产生时将保持在未决状态,直到进程解决对此信号的阻塞,才执行递达的动作
⑤注意,阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作

2.信号在内核中的示意图

在这里插入图片描述

①pending表:位图结构,比特位的位置,表示哪一个信号,比特位的内容代表是否收到信号,设置方法类似于:uint32_t pending=0;pending|=(1<<(signo-1))
②block表:位图结构,比特位的位置,表示哪一个信号,比特位的内容,代表是否对应的信号被阻塞
③handler表:函数指针数组,该数组下标,表示信号编号,数组的特定下标内容,表示该信号的递达动作,其中SIG_DFL是默认处理方法,SIG_IGN是忽略,可以作为signal的参数传递
④每个信号都有两个标志位分别表示阻塞和未决,还有一个函数指针表示处理动作。信号产生时,内核在进程控制块中设置该信号的未决标志,直到信号递达才清除该标志位
⑤如果在进程解除对某信号的阻塞前这种信号产生过多次,POSIX.I允许系统递送该信号一次或多次。Linux是这样实现的:常规信号在递达前产生多次只记一次,而实时信号在递达前产生多次,可以依次放在一个队列中(我们这里不考虑实时信号)。

3.信号集

每个信号只有一个bit的未决标志,非0即1,不记录该信号产生了多少次,阻塞标志也是这样表示的。
因此,未决和阻塞标志可以用相同的数据类型sigset_t来存储,sigset_t称为信号集,这个类型可以表示每个信号的“有效”或“无效”状态,在阻塞信号集中“有效”和“无效”的含义是该信号是否被阻塞,而在未决信号集中“有效”和“无效”的含义是该信号是否处于未决状态。

4.信号集操作函数

#include <signal.h>
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);
函数sigemptyset初始化set所指向的信号集,使其中所有信号的对应bit清零,表示该信号集不包含 任何有效信号。
函数sigfillset初始化set所指向的信号集,使其中所有信号的对应bit置位,表示该信号集的有效信号包括系统支持的所有信号。
注意,在使用sigset_ t类型的变量之前,一定要调用sigemptyset或sigfillset做初始化,使信号集处于确定的状态。初始化sigset_t变量之后就可以在调用sigaddset和sigdelset在该信号集中添加或删除某种有效信号。
这四个函数都是成功返回0,出错返回-1。sigismember是一个布尔函数,用于判断一个信号集的有效信号中是否包含某种信号,若包含则返回1,不包含则返回0,出错返回-1。

sigprocmask
调用可以读取或者更改进程的阻塞信号集
阻塞信号集也叫作当前进程的信号屏蔽字
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oset);
返回值:若成功则为0,若出错则为-1
如果oset是非空指针,则读取进程的当前信号屏蔽字通过oset参数传出。如果set是非空指针,则更改进程的信号屏蔽字,参数how指示如何更改。如果oset和set都是非空指针,则先将原来的信号屏蔽字备份到oset里,然后根据set和how参数更改信号屏蔽字。假设当前的信号屏蔽字为mask,下面说明了how参数的可选值
SIG_BLOCK:set包含了我们希望添加到当前信号屏蔽字的信号,相当于mask=mask|set
SIG_UNBLOCK:set包含了我们希望从当前信号屏蔽字中解除阻塞的信号,相当于mask=mask&~set
SIG_SETMASK:设置当前信号屏蔽字为set所指向的值,mask=set

#include <signal.h>
sigpending
读取当前进程的未决信号集,通过set参数传出。调用成功则返回0,出错则返回-1。

    #include <iostream>
    #include <signal.h>
    #include <unistd.h>
    using namespace std;
    
    void handle(int signo)
    {
    cout<<"我是对应的"<<signo<<"信号"<<endl;
    }


    int main()
    {
        signal(2,handle);
        sigset_t set,oset;
        sigemptyset(&set);//初始化
        sigemptyset(&oset);
        sigaddset(&set,2);
        sigprocmask(SIG_SETMASK,&set,&oset);//屏蔽掉2号信号
        while(1)
        {
            cout<<"我是一个进程,我在运行......"<<endl;
            sleep(1);
        }
        return 0;
    }

在这里插入图片描述
可以发现2号信号已经被屏蔽了

5.内核态与用户态

在这里插入图片描述

用户态:执行自己写的代码的时候,进程所处的状态
内核态:执行OS的代码的时候,进程所处的状态
所有的进程0~3GB是不同的,每一个进程都要有自己的用户级页表
所有的进程3~4GB是一样的,每一个进程都可以看到同一张内核级页表,所有进程都可以通过统一的窗口看到同一个OS
把物理内存中的OS数据和代码与虚拟地址中的OS代码和数据通过页表建立映射,因此,OS运行的本质其实都是在进程的地址空间内运行的,所以说所谓的系统调用的本质就是如果调用动态库中的方法,在自己的地址空间中进行函数跳转并返回
为了防止出现随意访问OS的数据和代码才出现了用户态和内核态,用户无法直接更改,OS提供的所有的函数调用,内部在正式执行调用逻辑时,会去修改执行级别
CPU中有一个寄存器叫做CR3寄存器:
3:表征正在运行的进程执行的级别是用户态
0:表征正在运行的进程执行的级别是内核态
进程调度:OS是一个软件,本质上是一个死循环,有一个硬件叫做OS时钟硬件,它的作用是记录时间,这就是为什么关机了在开机,右下角的时间还是准确的,它是一直在工作的,OS时钟硬件每隔很短的时间会向OS发送时钟中断,此时OS要执行对应的中断方法,检测当前进程的时间片,如果时间到了,就调用schedule()系统函数来将对应的进程的上下文等进行保存并切换下一个进程。
因此无论进程如何切换,3~4GB是不变的,看到OS内容,与进程切换无关。
切换到内核态:
①进程时间片到了,需要切换,就要执行进程的切换逻辑
②系统调用
③中断
④异常

回归到信号:
当进程从内核态切换到用户态的时候,进程会在OS指导下,进行信号的检测与处理
如果一个信号之前被block(屏蔽),当他解除block的时候,对应的信号会被立即递达

6.信号捕捉

在这里插入图片描述
如果信号的处理动作是用户自定义函数,在信号递达时就调用这个函数,称为信号捕捉

7.sigaction

在这里插入图片描述

sigaction函数可以读取和修改与指定信号相关联的处理动作。调用成功则返回0,出错则返回- 1。signo是指定信号的编号。若act指针非空,则根据act修改该信号的处理动作。若oact指针非空,则通过oact传出该信号原来的处理动作。
act和oact指向sigaction结构体:
将sa_handler赋值为常数SIG_IGN传给sigaction表示忽略信号,赋值为常数SIG_DFL表示执行系统默认动作,赋值为一个函数指针表示用自定义函数捕捉信号,或者说向内核注册了一个信号处理函数,该函数返回值为void,可以带一个int参数,通过参数可以得知当前信号的编号,这样就可以用同一个函数处理多种信号。显然,这也是一个回调函数,不是被main函数调用,而是被系统所调用。
当某个信号的处理函数被调用时,内核自动将当前信号加入进程的信号屏蔽字,当信号处理函数返回时自动恢复原来的信号屏蔽字,这样就保证了在处理某个信号时,如果这种信号再次产生,那么它会被阻塞到当前处理结束为止。如果在调用信号处理函数时,除了当前信号被自动屏蔽之外,还希望自动屏蔽另外一些信号,则用sa_mask字段说明这些需要额外屏蔽的信号,当信号处理函数返回时自动恢复原来的信号屏蔽字。
sa_flags字段包含一些选项,但我们设为0即可,sa_sigaction是实时信号的处理函数,设为nullptr即可

#include <iostream>
#include <signal.h>
#include <unistd.h>
#include <cstring>
using namespace std;
void handle(int signo)
{
    sigset_t set;
    cout << "我是被捕捉的" << signo << "号信号" << endl;
    int cnt = 30;
    while (cnt--)
    {
        sigpending(&set);
        for (int i = 1; i <= 31; i++)
        {
            // 打印一下对应的pending表
            if (sigismember(&set, i))
            {
                cout << 1;
            }
            else
                cout << 0;
        }
        cout<<endl;
        sleep(1);
    }
}
int main()
{
    cout << "pid:" << getpid() << endl;
    sigset_t set;
    sigaddset(&set, 3);
    sigaddset(&set, 4);
    sigaddset(&set, 5);
    struct sigaction act;
    struct sigaction oact;
    memset(&act, 0, sizeof(act));
    act.sa_handler = handle;
    act.sa_mask = set; // 在执行2号命令的处理函数时,同时也屏蔽掉3,4,5号信号
    memset(&oact, 0, sizeof(oact));
    sigaction(2, &act, &oact);
    while (1)
    {
    }
    return 0;
}

在这里插入图片描述
在这里插入图片描述

8.可重入函数

在这里插入图片描述

main函数调用insert函数向一个链表head中插入节点node1,插入操作分为两步,刚做完第一步的 时候,因为硬件中断使进程切换到内核,再次回用户态之前检查到有信号待处理,于是切换 到sighandler函
数,sighandler也调用insert函数向同一个链表head中插入节点node2,插入操作的 两步都做完之后从
sighandler返回内核态,再次回到用户态就从main函数调用的insert函数中继续 往下执行,先前做第一步
之后被打断,现在继续做完第二步。结果是,main函数和sighandler先后 向链表中插入两个节点,而最后只有一个节点真正插入链表中了。
像上例这样,insert函数被不同的控制流程调用,有可能在第一次调用还没返回时就再次进入该函数,这称
为重入,insert函数访问一个全局链表,有可能因为重入而造成错乱,像这样的函数称为 不可重入函数,反之,如果一个函数只访问自己的局部变量或参数,则称为可重入(Reentrant) 函数。

总结:不同执行流中同一个函数被重复进入,如果没问题 ,该函数就是可重入函数,如果有问题就是不可重入函数

8.volatile

#include <iostream>
#include <signal.h>
#include <unistd.h>
#include <cstring>
using namespace std;
int flag=0;
void handle(int)
{
    cout<<"change flag 0 to 1"<<endl;;
    flag=1;
}
int main()
{
    signal(2,handle);
    while(!flag);
    cout<<"The process exits success";
    return 0;
}

大家看这个代码,按照正常的来说,当你使用2号信号的时候他就会正常运行结束,但是如果我们使用编译器的时候进行一个优化呢?
在这里插入图片描述
在这里插入图片描述
可以发现经过优化后,并没有正常终止,也就是说flag的值没有变化
解释:
由于flag的值在main函数中从来没有发生过任何变化,对于编译器来说,因此这样循环每次从内存中去取值太过于麻烦了,所以进行了优化,把value放在一个寄存器里面,每次while时,从寄存器拿,而寄存器一直为0,因此不断循环
加上volatile保证内存可见性,告诉编译器,保证每次检测都从内存中进行数据读取,不要用寄存器的数据,让内存数据可见
本质上编译器优化就是通过对代码进行了修改而已,CPU只会执行代码

9.SIGCHLD信号

用wait和waitpid函数清理僵尸进程,父进程可以阻塞等待子进程结束,也可以非阻 塞地查询是否有子进
程结束等待清理(也就是轮询的方式)。采用第一种方式,父进程阻塞了就不 能处理自己的工作了;采用第二种方式,父进程在处理自己的工作的同时还要记得时不时地轮询一 下,程序实现复杂。
其实,子进程在终止时会给父进程发SIGCHLD信号,该信号的默认处理动作是忽略,父进程可以自定义SIGCHLD信号的处理函数,这样父进程只需专心处理自己的工作,不必关心子进程了,子进程 终止时会通知父进程,父进程在信号处理函数中调用waitpid清理子进程即可。

#include <iostream>
#include <signal.h>
#include <unistd.h>
#include <cstring>
#include <sys/wait.h>
#include <sys/types.h>
using namespace std;
pid_t pid=0;
void handle(int signo)
{
    //回收对应的子进程
    waitpid(pid,nullptr,0);
    exit(2);
}
int main()
{
    signal(SIGCHLD,handle);//当父进程收到子进程发来的信号时,进行捕捉
    pid=fork();
    if(pid==0)
    {
        //子进程
        while(1);
        {
            //让子进程不终止
        }
    }
    cout<<pid<<endl;
    while (1)
    {
        //让父进程不终止
    }
    return 0;
}

在这里插入图片描述

在这里插入图片描述

事实上,由于UNIX 的历史原因,要想不产生僵尸进程还有另外一种办法:父进程调 用sigaction将SIGCHLD的处理动作置为SIG_IGN,这样fork出来的子进程在终止时会自动清理掉,不会产生僵尸进程,也不会通知父进程。系统默认的忽略动作和用户用sigaction函数自定义的忽略 通常是没有区别的,但这是一个特例。此方法对于Linux可用,但不保证在其它UNIX系统上都可用。

#include <iostream>
#include <signal.h>
#include <unistd.h>
#include <cstring>
#include <sys/wait.h>
#include <sys/types.h>
using namespace std;
pid_t pid=0;
/*void handle(int signo)
{
    //回收对应的子进程
    waitpid(pid,nullptr,0);
    exit(2);
}*/
int main()
{
    signal(SIGCHLD,SIG_IGN);//当父进程收到子进程发来的信号时,进行捕捉
    pid=fork();
    if(pid==0)
    {
        //子进程
        while(1);
        {
            //让子进程不终止
        }
    }
    cout<<pid<<endl;
    while (1)
    {
        sleep(1);
        cout<<"我是父进程,我在进行工作...."<<endl;
    }
    return 0;
}

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
可以看到子进程结束后并没有产生僵尸进程也没有影响父进程的工作。

🌸🌸信号保存和捕捉的知识大概就讲到这里啦,博主后续会继续更新更多Linux的相关知识,干货满满,如果觉得博主写的还不错的话,希望各位小伙伴不要吝啬手中的三连哦!你们的支持是博主坚持创作的动力!💪💪

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/747226.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

hbuilderX自定义主题仿vscode暗黑主题

目录 hbuilderX自定义主题仿vscode暗黑主题 效果图 设置方式&#xff08;把主题代码复制进来即可&#xff09; 是基于雅蓝主题自定义的 设置好后需要切换到雅蓝主题 hbuilderx保存时自动整理代码 1.首先在顶部栏&#xff0c;找到工具&#xff0c;打开设置 2.点击旁边的编…

uniapp做微信小程序,自定义checkbox和radio的样式

用uniapp做个微信小程序&#xff0c;其中有用到自定义checkbox和radio的样式&#xff1b;代码记录如下&#xff1a; 自定义checkbox 在App.vue中写入样式&#xff1a; checkbox.red .wx-checkbox-input,checkbox.red .uni-checkbox-input {background-image: url(/static/ima…

MySQL锁定:死锁及其避免方法

文章目录 一、MySQL锁定和并发控制的重要性二、MySQL锁机制回顾2.1 锁类型&#xff1a;共享锁和排他锁2.2 锁粒度&#xff1a;行级锁和表级锁 三、死锁的原因和场景四、检测和解决死锁4.1 死锁检测算法4.2 死锁处理策略4.3 手动处理死锁 五、死锁的预防和避免分布式环境下&…

驱动程序设计 利用RTC显示年月日时分秒到终端 7.11

驱动控制RTC 用户需求&#xff1a;写一个RTC时钟控制的驱动&#xff0c;实现RTC显示年月日时分秒到用户端 驱动需求&#xff1a;设备&#xff1a;RTC模块 功能&#xff1a;打印显示 寄存器操作&#xff1a; 原理图&#xff1a; 设备–>pin–>设备控制芯片 ​ 驱动需求 …

信号波形时序图常用工具推荐

Refer: 下载&#xff1a;画时序图的四大神器_可编程器件-面包板社区 (eet-china.com) 软件工程师&#xff0c;习惯使用StarUML画Timing Diagram, 硬件工程师建议使用一下软件。 1、AndyTiming 免费的&#xff0c;这个有一个知乎的博文可以参考 https://zhuanlan.zhihu.com/p…

Android 中利用多个Button组合实现选项切换效果

效果图&#xff1a; xml布局: <LinearLayoutandroid:orientation"horizontal"android:layout_width"match_parent"android:layout_height"50dp"android:gravity"center"android:background"color/White">​<Linear…

SpringCloud Alibaba微服务分布式架构组件演变

文章目录 1、SpringCloud版本对应1.1 技术选型依据1.2 cloud组件演变&#xff1a; 2、Eureka2.1 Eureka Server &#xff1a; 提供服务注册服务2.2 EurekaClient &#xff1a; 通过注册中心进行访问2.3 Eureka自我保护 3、Eureka、Zookeeper、Consul三个注册中心的异同点3.1 CP…

Mysql:常见的面试题和答案

1. 数据库事务的特性&#xff1f; 原子性&#xff1a;即不可分割性&#xff0c;事务要么全部被执行&#xff0c;要么就全部不被执行。一致性或可串性。事务的执行使得数据库从一种正确状态转换成另一种正确状态隔离性。在事务正确提交之前&#xff0c;不允许把该事务对数据的任…

华为数通智选交换机S5735S-L24T4S-QA2无法SSH远程访问

以前都是按照华为S5700交换机开启SSH远程访问方法配置不同网段通过静态路由实现互通,华为S5700交换机开启ssh远程登陆,现在新买的华为数通智选交换机S5735S-L24T4S-QA2,也是按照这步骤配置,令人不解的是,竟然无法ssh访问,仔细看了配置也没有发现问题,在华为eNSP模拟器上验…

FreeSwitch 1.10.9 在CentOS7.9编译spandsp版本匹配问题

最近FreeSwitch 1.10.9 在CentOS7.9编译mod_spandsp出问题, making all mod_spandsp make[4]: Entering directory /usr/local/src/freeswitch-1.10.9.-release/src/mod/applications/mod_spandspCC mod_spandsp_la-mod_spandsp.loCC mod_spandsp_la-udptl.loCC …

HTTP以及Servlet的学习

HTTP和Servlet 联系&#xff1a; HTTP是一个通信协议&#xff0c;而Servlet是服务器端程序&#xff0c;用于处理HTTP请求。Servlet通常用于处理HTTP请求&#xff0c;在服务器上生成动态内容&#xff0c;并生成HTTP响应。HTTP协议就是Servlet处理的基础。 区别&#xff1a; …

【MySQL】如何查询MySQL执行过的所有SQL语句

文章目录 1 MySQL 的通常查询日志实验2 参考资料 1 MySQL 的通常查询日志实验 通过 MySQL 的通用查询日志可以找到你指定的每一条 sql 语句。很明显会有严重的性能损耗所以也只会在开发测试时才会使用。 前言&#xff1a;关于本实验与慢日志实验几乎一模一样&#xff0c;故本文…

05-算法部分 (数据结构和算法)

一 排序算法 1.1 冒泡法排序 冒泡排序&#xff08;Bubble Sort&#xff09;是一种简单直观的排序算法。它重复地走访过要排序的数列&#xff0c;一次比较两个元素&#xff0c;如果他们的顺序错误就把他们交换过来。 首先从数组的第一个元素开始到数组最后一个元素为止&#x…

串行FLASH文件系统FatFs-文件系统介绍

目录 串行FLASH文件系统FatFs-文件系统介绍 文件系统介绍 使用SPI FLASH直接存储数据缺点 Windows上的文件系统 磁盘的物理结构 磁盘分区表 文件系统的结构与特性 文件系统的空间示意图 FATFS文件系统 FATFS文件系统简介 1 . 特征 2. 层级结构 FatFs的目录结构 FatF…

文件操作--文件的随机读写、标准输入输出重定向

目录 一、文件的随机读写 二、便准输入输出重定向 一、文件的随机读写 前面的例程执行的都是顺序文件处理&#xff08;Sequential File Processing&#xff09;。在顺序文件处理过程中&#xff0c;数据项是一个接着一个进行读取或者写入的。例如&#xff0c;如果想读取文件中…

HarmonyOS学习路之开发篇—流转(多端协同 一)

多端协同开发 场景介绍 开发者在应用FA中通过调用流转任务管理服务、分布式任务调度的接口&#xff0c;实现多端协同。 主要流程如下&#xff1a; 设备A上的应用FA向流转任务管理服务注册一个流转回调。 Alt1-系统推荐流转&#xff1a;系统感知周边有可用设备后&#xff0c;主…

Leangoo领歌敏捷管理工具标签升级,企业级标签组上线

在Leangoo领歌敏捷工具中&#xff0c;标签通常用作对任务的分类&#xff0c;或任务的优先级区分等。这次我们发布了大家期待已久的“企业级标签组”功能&#xff0c;标签可以统一管理啦&#xff5e; 之前&#xff0c;Leangoo领歌的标签功能只限于单个看板使用&#xff0c;需要…

视频孪生赋能智慧交通综合管理系统的数智化升级

交通是重大民生工程,涉及公共安全和人民群众切身利益,必须树牢安全发展理念,强化企业主体责任落实。根据国家标准《城市轨道交通公共安全防范系统工程技术规范》中要求&#xff1a; 未来的智慧交通系统要建立在安防集成平台的应用上&#xff0c;对各类重要部位进行视频图像与三…

CUDA编程实战(使用Sobel算子对rgb图片进行边缘检测)

写在前面&#xff0c;本篇文章为一个CUDA实例&#xff0c;使用GPU并行计算对程序进行加速。如果不需要看环境如何配置&#xff0c;可以直接到看代码部分:点击直达 关于如何更改代码和理解代码写在这个地方:点击直达 运行环境&#xff1a; 系统:windows10专业版 显卡:NVIDIA …

sharding-jdbc分库连接数优化 | 京东物流技术团队

一.背景: 配运平台组的快递订单履约中心(cp-eofc)及物流平台履约中心(jdl-uep-ofc)系统都使用了ShardingSphere生态的sharding-jdbc作为分库分表中间件, 整个集群采用只分库不分表的设计,共16个MYSQL实例,每个实例有32个库,集群共512个库. 当每增加一台客户端主机,一个MYSQl实…