Linux知识点 -- 进程信号(二)

Linux知识点 – 进程信号(二)

文章目录

  • Linux知识点 -- 进程信号(二)
  • 一、信号保存
    • 1.相关概念
    • 2.信号保存的相关接口
    • 3.对所有的信号都进行自定义捕捉
    • 4.将2号信号block,并打印pending信号集
    • 5.将所有信号都block
  • 二、处理信号
    • 1.信号处理的时机
    • 2.信号处理的流程
    • 3.sigaction
  • 三、可重入函数
  • 四、volatile关键字
  • 五、SIGCHILD信号


一、信号保存

1.相关概念

  • 信号递达(Delivery):实际执行信号的处理动作;

  • 信号未决(Pedning):信号从产生到递达之间的状态;信号未决就是进程收到了一个信号,但是未处理,就是临时保存到了进程PCB中的对应的位图中;

  • 进程可以选择阻塞(block)某个信号;

  • 被阻塞的信号产生时将保持在未决状态,直到进程解决对此信号的阻塞,才执行递达的动作;

  • 注意,阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是在抵达之后可选的一种处理动作;

  • 在进程PCB内部有3张表:
    在这里插入图片描述
    其中,pending就是信号未决的位图,进程在收到一个信号后,会将pending表中相应的位置位;
    handler是函数指针数组 – 数组下标对应信号的编号,就是对应信号的处理方式;signal自定义捕捉就是将信号对应的方法填入handler表;

    在这里插入图片描述
    也可以设置信号的忽略和默认;IGN是忽略;DFL是默认;
    block表是阻塞表,结构和pending一摸一样,代表的含义是对应的信号是否被阻塞;

  • 信号的处理过程:
    进程在接受一个信号后,会将pending表中相应的位置位,然后先去block表中查看该进程是否被阻塞,如果被阻塞,就不做任何动作,如果没有阻塞,再去handler表中查询处理方法;

2.信号保存的相关接口

(1)语言会为我们提供.h.hpp和语言的自定义类型;
同时,操作系统也会给我们提供.h和自定义类型;

(2)OS向我们提供了接口,一定要提供相对应的类型;
语言提供了访问系统调用的接口,也一定会提供相对应的类型;

  • sigset_t类型:
    未决和阻塞标志可以使用相同的数据类型(位图),sigset_t称为信号集,这个类型可以表示每个信号的有效或无效状态;
    在阻塞信号集中有效和无效的含义是该信号是否被阻塞,阻塞信号集也叫做信号屏蔽字
    而在未决信号集中有效和无效的含义是该信号是否处于未决状态;

    注:
    sigset_t不允许用户自己进行位操作,OS为我们提供了对应的操作方法;
    sigset_t使用者可以直接使用该类型,和用内置类型、自定义类型没有任何差别;
    sigset_t一定需要对应的系统接口,来完成对应的功能,其中系统接口需要的参数,可能就包含了sigset_t定义的变量或者对象;

  • OS提供的对sigset_t操作的接口:
    在这里插入图片描述
    分别是:
    全部位清0;
    全部位置1;
    某个信号置位;
    某个信号复位;
    判断信号是否存在;

    在这里插入图片描述
    sigpending函数获取当前调用进程的pending信号集;
    set是输出型参数;
    成功返回0,失败返回-1;

    在这里插入图片描述
    sigprocmask函数检查并更改block信号集;
    how参数:
    在这里插入图片描述
    set:根据how的不同的宏,有不同的功能;
    oldset:输出型参数,返回老的信号屏蔽字,不需要可以传空指针;

3.对所有的信号都进行自定义捕捉

#include<iostream>
#include<unistd.h>
#include<signal.h>using namespace std;void catchSig(int signum)
{cout << "获得了一个信号:" << signum << endl;
}int main()
{for(int i = 1; i <= 31; i++){signal(i, catchSig);}while(true){sleep(1);}return 0;
}

运行结果:
在这里插入图片描述
可以发现,其他信号都被自定义捕捉了,只有9号信号杀死了该进程,因为9号信号是不能被捕捉的

4.将2号信号block,并打印pending信号集

#include<iostream>
#include<unistd.h>
#include<signal.h>
#include<cassert>using namespace std;static void showPending(sigset_t &pending)
{for(int sig = 1; sig <= 31; sig++){if(sigismember(&pending, sig)){cout << "1";}else{cout << "0";}}cout << endl;
}int main()
{//1.定义信号集对象sigset_t bset, obset;sigset_t pending;//2.初始化sigemptyset(&bset);sigemptyset(&obset);sigemptyset(&pending);//3.添加要进行屏蔽的信号sigaddset(&bset, 2);//4.设置set到内核中对应的进程内部int n = sigprocmask(SIG_BLOCK, &bset, &obset);assert(n == 0);(void)n;cout << "block 2号信号成功 " << endl;//5.重复打印当前进程的pending信号集while(true){//获取当前进程的pending信号集sigpending(&pending);//显示当前进程的pending信号集showPending(pending);sleep(1);}return 0;
}

运行结果:
在这里插入图片描述
在这里插入图片描述
当发送了2号信号后,pending表中对应的位置1了,2号信号是被阻塞了,应该一直在pending表中,无法被递达;

在一定时间后恢复2号信号的block

#include<iostream>
#include<unistd.h>
#include<signal.h>
#include<cassert>using namespace std;static void showPending(sigset_t &pending)
{for(int sig = 1; sig <= 31; sig++){if(sigismember(&pending, sig)){cout << "1";}else{cout << "0";}}cout << endl;
}int main()
{//1.定义信号集对象sigset_t bset, obset;sigset_t pending;//2.初始化sigemptyset(&bset);sigemptyset(&obset);sigemptyset(&pending);//3.添加要进行屏蔽的信号sigaddset(&bset, 2);//4.设置set到内核中对应的进程内部int n = sigprocmask(SIG_BLOCK, &bset, &obset);assert(n == 0);(void)n;cout << "block 2号信号成功 " << endl;//5.重复打印当前进程的pending信号集int count = 0;while(true){//获取当前进程的pending信号集sigpending(&pending);//显示当前进程的pending信号集showPending(pending);sleep(1);count++;if(count == 20){int n = sigprocmask(SIG_SETMASK, &obset, nullptr);//将原来的信号集附上去assert(n == 0);(void)n;cout << "接触对2号信号的block " << endl;}}return 0;
}

运行结果:
在这里插入图片描述
结果是没有看到pending表从1变为0;
默认情况下,回复对于2号信号block的时候,确实会进行递达;
但是2号信号的默认处理动作是终止进程,将进程直接终止;

我们需要对2号信号进行捕捉:

#include<iostream>
#include<unistd.h>
#include<signal.h>
#include<cassert>using namespace std;void catchSig(int signum)
{cout << "获得了一个信号:" << signum << endl;
}static void showPending(sigset_t &pending)
{for(int sig = 1; sig <= 31; sig++){if(sigismember(&pending, sig)){cout << "1";}else{cout << "0";}}cout << endl;
}int main()
{signal(2, catchSig);//1.定义信号集对象sigset_t bset, obset;sigset_t pending;//2.初始化sigemptyset(&bset);sigemptyset(&obset);sigemptyset(&pending);//3.添加要进行屏蔽的信号sigaddset(&bset, 2);//4.设置set到内核中对应的进程内部int n = sigprocmask(SIG_BLOCK, &bset, &obset);assert(n == 0);(void)n;cout << "block 2号信号成功 " << endl;//5.重复打印当前进程的pending信号集int count = 0;while(true){//获取当前进程的pending信号集sigpending(&pending);//显示当前进程的pending信号集showPending(pending);sleep(1);count++;if(count == 20){int n = sigprocmask(SIG_SETMASK, &obset, nullptr);//将原来的信号集附上去assert(n == 0);(void)n;cout << "接触对2号信号的block " << endl;}}return 0;
}

在这里插入图片描述
注:
没有一个接口时用来设置pending位图的,这是因为所有信号的发送方式,都是修改pending位图的过程;

5.将所有信号都block

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <cassert>using namespace std;static void showPending(sigset_t &pending)
{for (int sig = 1; sig <= 31; sig++){if (sigismember(&pending, sig)){cout << "1";}else{cout << "0";}}cout << endl;
}static void blockSig(int sig)
{sigset_t bset;sigemptyset(&bset);sigaddset(&bset, sig);int n = sigprocmask(SIG_BLOCK, &bset, nullptr);assert(n == 0);(void)n;
}int main()
{for(int sig = 1; sig <= 31; sig++){blockSig(sig);}sigset_t pending;while(true){sigpending(&pending);showPending(pending);sleep(1);}return 0;
}

在这里插入图片描述
当发到9号信号的时候,进程停止,9号信号是不能被屏蔽的;
在这里插入图片描述
跳过9号信号:
在这里插入图片描述
19号也是无法屏蔽
在这里插入图片描述

二、处理信号

1.信号处理的时机

  • 信号产生之后,可能无法被立即处理,要在合适的时候处理;
  • 因为信号的相关数据字段都是在进程PCB内部,这属于内核范畴,进程在运行时会从内核范畴 -> 内核状态 -> 用户态 -> 内核状态 -> 内核范畴;
    在内核态中,从内核态返回用户态的时候,进行信号的检测和处理
  • 当我们进行系统调用的时候,比如缺陷异常等,会进入内核态;int 80是一个系统中断语句,可以陷入内核;
  • 用户态是一个受管控的状态,内核态是一个操作系统执行自己代码的一个状态,具备非常高的优先级;
  • CPU的寄存器是由两套的,一套用户可见,另一套不可见,CPU自用;
  • CR3表示当前CPU的执行权限,1表示内核,3表示用户;
  • 在进程地址空间中,不光有用户地址空间,还有内核地址空间,内核地址空间使用的是内核级的页表,该页表是整个OS只有一份的,能够被所有的进程看到,因此所有进程看到的都是一个操作系统;
    在这里插入图片描述
    当我们进程需要调用系统接口时,就跳转到进程的内核地址空间,根据内核级页表,在内存中找到系统调用的相关方法;
  • 当我们有权限进入内核态时,进程使用的页表就是内核级页表了,就能够访问 OS的方法了,这也就意味着进程进入了内核态,可以处理信号了;

2.信号处理的流程

在这里插入图片描述

  • 注意:
    (1)在第二步时,进程在内核态处理完成系统任务后,会在重回用户态的时候进行信号的检测和处理;
    (2)在第三步检测到信号,并处理时,如果信号的处理方式时系统默认方式,就直接在内核态处理了,然后返回用户态的执行流继续执行;如果信号的处理方式是用户自定义的,就需要返回用户态去执行相应的方法;这时进程的状态时用户态,能够执行自定义信号处理,但是系统不会去在内核态执行用户代码,因为涉及到系统安全问题;
    (3)在第四步返回用户态执行信号处理后,进程会再次进入内核态,从内核态在返回用户态进程中断处继续执行;
    (4)一共四次状态切换;

3.sigaction

在这里插入图片描述

  • 参数:
    signum:信号编号;
    act:信号处理动作;struct sigaction是一个结构体,里面包含用户自定义的信号处理方式的函数指针等数据;
    在这里插入图片描述
    oldact:信号过去的处理方式;
#include<iostream>
#include<signal.h>
#include<unistd.h>using namespace std;void handler(int signum)
{cout << "获取了一个信号:" << signum << endl;
}int main()
{//内核数据类型,用户栈定义的struct sigaction act, oact;act.sa_flags = 0;sigemptyset(&act.sa_mask);act.sa_handler = handler;//设置进当前调用进程的PCB中sigaction(2, &act, &oact);while(true) sleep(1);return 0;
}

运行结果:
在这里插入图片描述
捕获2号信号并执行自定义处理方式;

  • 当某个信号的处理函数被调用时,内核自动将当前信号加入进程的信号屏蔽字,当信号处理函数返回时自动恢复原来的的信号屏蔽字,这样就保证了在处理某个信号时,如果该信号再次产生,那么它就会被阻塞到当前信号处理结束为止;如果在调用信号处理函数时,还希望屏蔽除当前信号的其他信号,就可以使用sigaction函数的sa_mask参数,来指定希望额外屏蔽的信号;
#include<iostream>
#include<signal.h>
#include<unistd.h>using namespace std;static void showPending(sigset_t &pending)
{for (int sig = 1; sig <= 31; sig++){if (sigismember(&pending, sig)){cout << "1";}else{cout << "0";}}cout << endl;
}void handler(int signum)
{cout << "获取了一个信号:" << signum << endl;sigset_t pending;int c = 10;while(true){sigpending(&pending);showPending(pending);c--;if(!c){break;}sleep(1);}
}int main()
{//内核数据类型,用户栈定义的struct sigaction act, oact;act.sa_flags = 0;sigemptyset(&act.sa_mask);act.sa_handler = handler;//设置进当前调用进程的PCB中sigaction(2, &act, &oact);while(true) sleep(1);return 0;
}

运行结果:
在这里插入图片描述
第二次获取二号信号的时候,就进行了屏蔽;

如果需要同时添加对其他信号的屏蔽:

#include<iostream>
#include<signal.h>
#include<unistd.h>using namespace std;static void showPending(sigset_t &pending)
{for (int sig = 1; sig <= 31; sig++){if (sigismember(&pending, sig)){cout << "1";}else{cout << "0";}}cout << endl;
}void handler(int signum)
{cout << "获取了一个信号:" << signum << endl;sigset_t pending;int c = 10;while(true){sigpending(&pending);showPending(pending);c--;if(!c){break;}sleep(1);}
}int main()
{//内核数据类型,用户栈定义的struct sigaction act, oact;act.sa_flags = 0;sigemptyset(&act.sa_mask);act.sa_handler = handler;//同时添加对其他信号的屏蔽sigaddset(&act.sa_mask, 3);sigaddset(&act.sa_mask, 4);sigaddset(&act.sa_mask, 5);sigaddset(&act.sa_mask, 6);sigaddset(&act.sa_mask, 7);//设置进当前调用进程的PCB中sigaction(2, &act, &oact);while(true) sleep(1);return 0;
}

运行结果:
在这里插入图片描述

三、可重入函数

在这里插入图片描述
在main函数调用insert方法时,信号来了,调用handler,handler也去调用insert,那么像这样被多个执行流调用insert就叫做函数重入
在这里插入图片描述
函数重入出问题的叫做不可重入函数;
不出问题的叫做可重入函数;

函数的可重入性是函数的一种特征,我们目前使用的大多数函数,都是不可重入的;

四、volatile关键字

当接收到2号信号时,将flag置1,进程退出;
在这里插入图片描述
运行结果:
在这里插入图片描述
如果我们更改编译选项,让g++对代码作出一定的优化:
在这里插入图片描述
运行结果:
在这里插入图片描述
现在进程就无法退出了,但是flag还是变成了1;

这是因为在优化了代码之后,后面的语句没有更改flag,在后面检测flag的时候,就不访问内存中的flag了,而是检测寄存器edx中的flag;而寄存器中的flag是第一次读取的0,因此进程就不会退出了;
在变量定义的时候加上volatile关键字:
在这里插入图片描述
这个关键字的作用是**保持变量在内存中的可见性;**
运行结果:
在这里插入图片描述
注:优化是在编译时就完成的;

五、SIGCHILD信号

在这里插入图片描述
在这里插入图片描述
如果我们需要等待子进程退出,10个子进程5个退出,后面的信号还需要进行wait检测是否退出;
因为5个进程都发送了sigchild信号,但是OS只能收到一个;
这时主进程只能阻塞等待该子进程退出;
我们也可使用vector保存进程pid,来进行非阻塞遍历所有进程,这样不会被阻塞;
也可以在waitpid时候传入-1, 就可以等待任意一个退出的进程,进程也不会被阻塞;

  • 如果我们不想等待子进程,还想在子进程退出之后,自动释放僵尸子进程:可以设置对SIGCHILD信号的忽略
    在这里插入图片描述
    运行结果:
    子进程退出后自动回收僵尸子进程;
    在这里插入图片描述
    sigchild的默认动作就是忽略,但是为什么要再加一个忽略呢?
    因为这两个忽略时不同等级的,OS的忽略就是默认动作,不会回收子进程,会形成僵尸进程;
    而自己设置的忽略,告诉OS不光要忽略子进程,还要回收资源;

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

.NET6使用SqlSugar操作数据库

1.//首先引入SqlSugarCore包 2.//新建SqlsugarSetup类 public static class SqlsugarSetup{public static void AddSqlsugarSetup(this IServiceCollection services, IConfiguration configuration,string dbName "ConnectString"){SqlSugarScope sqlSugar new Sq…

bilibili倍数脚本,油猴脚本

一. 内容简介 bilibili倍数脚本&#xff0c;油猴脚本 二. 软件环境 2.1 Tampermonkey 三.主要流程 3.1 创建javascript脚本 点击添加新脚本 就是在 (function() {use strict;// 在这编写自己的脚本 })();倍数脚本&#xff0c;含解析 // UserScript // name bi…

Leetcode链表篇 Day2

203. 移除链表元素 - 力扣&#xff08;LeetCode&#xff09; 1.暴力移除&#xff1a;分删除的为头结点和不为头节点 while删除头节点时&#xff1a;直接从下一个结点开始&#xff0c;headhead->next while不是头节点时&#xff1a;从head开始遍历(需记录的为 前继结点pre) 虚…

计算机竞赛 opencv 图像识别 指纹识别 - python

0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 基于机器视觉的指纹识别系统 &#x1f947;学长这里给一个题目综合评分(每项满分5分) 难度系数&#xff1a;3分工作量&#xff1a;3分创新点&#xff1a;4分 该项目较为新颖&#xff0c;适…

LeetCode150道面试经典题-- 快乐数(简单)

1.题目 编写一个算法来判断一个数 n 是不是快乐数。 「快乐数」 定义为&#xff1a; 对于一个正整数&#xff0c;每一次将该数替换为它每个位置上的数字的平方和。然后重复这个过程直到这个数变为 1&#xff0c;也可能是 无限循环 但始终变不到 1。如果这个过程 结果为 1&am…

【第一阶段】kotlin中反引号中的函数名特点

在kotlin中可以直接中文定义函数&#xff0c;使用反引号进行调用 eg: fun main() {2023年8月9日定义的函数(5) }private fun 2023年8月9日定义的函数(num:Int){println("反引号的用法$num") }执行结果 在Java中is,in可以定义方法&#xff0c;但是在kotlin中is,in是…

日常BUG——Java使用Bigdecimal类型报错

&#x1f61c;作 者&#xff1a;是江迪呀✒️本文关键词&#xff1a;日常BUG、BUG、问题分析☀️每日 一言 &#xff1a;存在错误说明你在进步&#xff01; 一、问题描述 直接上代码&#xff1a; Test public void test22() throws ParseException {System.out.p…

uniapp开发小程序-有分类和列表时,进入页面默认选中第一个分类

一、效果&#xff1a; 如下图所示&#xff0c;进入该页面后&#xff0c;默认选中第一个分类&#xff0c;以及第一个分类下的列表数据。 二、代码实现&#xff1a; 关键代码&#xff1a; 进入页面时&#xff0c;默认调用分类的接口&#xff0c;在分类接口里做判断&#xff…

分布式 - 消息队列Kafka:Kafka 消费者消息消费与参数配置

文章目录 1. Kafka 消费者消费消息01. 创建消费者02. 订阅主题03. 轮询拉取数据 2. Kafka 消费者参数配置01. fetch.min.bytes02. fetch.max.wait.ms03. fetch.max.bytes04. max.poll.records05. max.partition.fetch.bytes06. session.timeout.ms 和 heartbeat.interval.ms07.…

docker安装达梦数据库

下载安装包 https://eco.dameng.com/download/ 启动达梦数据库 docker run -d -p 5236:5236 --restartalways --name dm8_01 --privilegedtrue -e PAGE_SIZE16 -e LD_LIBRARY_PATH/opt/dmdbms/bin -e INSTANCE_NAMEdm8_01 -v /data/dm8_01:/opt/dmdbms/data dm8_single:v8.…

freeswitch的mod_xml_curl模块动态获取configuration

概述 freeswitch是一款简单好用的VOIP开源软交换平台。 mod_xml_curl模块支持从web服务获取xml配置&#xff0c;本文介绍如何动态获取acl配置。 环境 centos&#xff1a;CentOS release 7.0 (Final)或以上版本 freeswitch&#xff1a;v1.6.20 GCC&#xff1a;4.8.5 web…

vue3+element-plus表格默认排序default-sort失效问题

场景 在使用动态数据渲染的场景&#xff0c;el-table设置默认属性default-sort失效。 原因 el-table的default-sort属性是针对静态数据的&#xff0c;如果是动态数据&#xff0c;default-sort则无法监听到。 案例&#xff1a;静态数据 <template><el-table:data&…

.gitignore匹配规则

目录 1.直接一个名称2.斜杠 /3.符号 *4.问号 &#xff1f;5.感叹号 &#xff01;6.gitkeep 借鉴抖音账号&#xff1a; 渡一前端提薪课 1.直接一个名称 会忽略目录下的所有该名称文件和文件夹&#xff0c;无论嵌套多深。 2.斜杠 / 1.斜杠在开头(/dist)&#xff1a;忽略和.gitig…

国产数据库-内核特性-低基数全局字典

国产数据库-内核特性-StarRocks低基数全局字典 StarRocks2.0引入了低基数全局字典&#xff0c;可以通过全局字典将字符串的相关操作转换成整型相关操作&#xff0c;大大提升查询性能。 1、低基数字典 对于利用整型替代字符串进行处理&#xff0c;通常使用字典编码进行优化。Sta…

JavaScript版本ES5/ES6及后续版本

JavaScript简史 1995&#xff1a; Brendan Eich在短短10天内创建了JavaScript的第一个版本。它被称为摩卡&#xff0c;但已经具备了现代JavaScript的许多基本特性! 1996&#xff1a; 为了吸引Java开发人员&#xff0c;Mocha先是更改为LiveScript&#xff0c;然后又更改为Ja…

JavaScript进阶 第一天

作用域函数进阶解构赋值 一.作用域 局部作用域全局作用域作用域链JS垃圾回收机制闭包变量提升 1.1 作用域 ① 概念&#xff1a;规定了变量能够被访问的“范围”&#xff0c;离开了这个"范围"&#xff0c;变量不能被访问 ② 分类 局部作用域 &#xff08;1&…

定长内存池设计ConcurrentMemoryPool

原理 还回来的内存用链表串联起来&#xff0c;称为自由链表 内存块自身进行链接&#xff0c;前四个字节存下一个的地址 结构 template<class T> class ObjectPool { public:T* New(){} private:char* _memory nullptr; //方便切割void* _freeList nullptr; };第一步…

探索 C++ 标准库:std::string 库函数用法示例

目录 引言 一、构造函数 1.1 string() 1.2 string (const string& str) 1.3 string (const string& str, size_t pos, size_t len npos) 1.4 string (const char* s) 1.5 string (const char* s, size_t n) 1.6 string (size_t n, char c&#xff09;​ 二、容…

报名小程序PowerActivity配置

https://github.com/zhihuliukanshan/PowerActivity/assets/100545532/9b3e2a3b-f810-4c1f-90d5-9596d99abbd3 导入代码后&#xff0c;需要配置的位置有&#xff1a; 1、miniprogram\setting\setting.js中的CLOUD_ID&#xff1a; module.exports {//### 环境相关 CLOUD_ID: …

如何在iPhone手机上修改手机定位和模拟导航?

如何在iPhone手机上修改手机定位和模拟导航&#xff1f; English 首先&#xff0c;你需要在Mac电脑上下载安装 Location Simulator/定位模拟工具 和 Runner 这两款应用程序。 完成安装后&#xff0c;打开软件&#xff0c;并用USB连接手机设备 修改iPhone手机定位和模拟导航 …