Linux|信号

Linux|信号

  • 信号的概念
  • 信号处理的三种方式
    • 捕捉信号的System Call -- signal
  • 1.产生信号的5种方式
  • 2.信号的保存
    • 2.1 core 标志位
  • 2.信号的保存
    • 2.1 对pending 表 和 block 表操作
    • 2.2 阻塞SIGINT信号 并打印pending表例子
  • 捕捉信号
    • sigaction 函数
    • 验证当前正在处理某信号,则该信号会自动被屏蔽
    • 验证当前信号被处理完之后,会自动解除屏蔽
    • 地址空间中操作系统态
    • 谈谈键盘输入的过程
    • 两个深刻的问题
      • 如何操作系统是怎么运行的
      • 如何理解系统调用
    • 可重入函数
    • volatile
    • sigchild信号

信号的概念

信号:是进程之间异步通知的一种方式,属于软中断。
所谓异步就是 a 和 b 之间没有联系,比如同学a 去上厕所了,老师b还是继续讲课,这称为异步。

信号处理的三种方式

一般情况下是三选一

  1. 忽略此信号
  2. 执行该信号的默认处理动作
  3. 提供一个信号处理函数,要求内核在处理该信号时切换到用户态执行这个处理函数,这种方式称为捕捉(Catch)一个信号

捕捉信号的System Call – signal

每个信号都有一个编号和一个宏定义名称,这些宏定义可以在signal.h中找到,例如其中有定 义 #define SIGINT 2

在这里插入图片描述

sighandler_t signal(int signum, sighandler_t handler);
当我们在键盘中 按ctrl + c 的时候 就会发送一个SIGINT信号,
我们可以用 signal 这个系统调用验证

#include <iostream>
#include <unistd.h>
#include <signal.h>
void hander(int sig)
{std::cout<<"catch sig:"<< sig<<std::endl;
}
int main()
{signal(2,hander);while(true)return 0;
}

有同学会想我把所有的信号都捕捉了,那个这个进程是不是就刀枪不入了?不是的 因为9号信号 无法捕捉

1.产生信号的5种方式

1. 通过 kill 命令,向指定的进程发信号
2. 通过键盘 ctrl + c
3. 系统调用 kill
在这里插入图片描述

raise(sign) 和 kill(getpid(),sign) 是等价的
alrm 也可以产生信号 alrm的返回值是上一个闹钟的剩余时间
同一个进程同一个时间只能有一个闹钟!

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
void hander(int sig)
{std::cout<<"catch sig:"<< sig<<std::endl;
}
int main()
{signal(2,hander);//kill(getpid(),2);raise(2);sleep(3);return 0;
}

4.软件条件
比如 管道 我们读端关闭 , 写端还在写,那么就会产生一个SIGPIPE的信号。
5. 异常
a.

void hander(int sg)
{std:: cout<< "捕捉到:"<<sg<<std::endl;
}
int main()
{signal(8,hander);int b = 10 / 0;return 0;
}

在这里插入图片描述
可能有同学会问为什么会一直死循环打印捕捉到的8号信号呢?
当处理器检测到除法错误时,它会暂停正常的指令流,保存当前的状态(包括程序计数器和其他寄存器的内容),然后跳转到一个预定义的地址来处理这个异常。这个地址指向的是操作系统的异常处理程序,它可以记录错误、终止进程或采取其他恢复措施,由于进程没有退出,又恢复当前的状态,到cpu中 ,cpu中的溢出标记位又置为1了。(这也回答cpu是怎么检测到除以0的)总的来说就是因为进程一直被调度,所以才出现死循环的情况。

终止进程的本质:释放进程的上下文数据,报告溢出标志数据或其他异常数据
b. 野指针问题:
CR3 + MMU : 将虚拟地址转换为物理地址
CR2:保存主要用于存储最近一次发生的页面错误(page fault)时的线性地址。
在这里插入图片描述
当异常的时候,操作系统检测到CR2中的地址,开始发送信号。

2.信号的保存

2.1 core 标志位

在这里插入图片描述
当时在进程控制时 waitpid 函数中的 status参数 core dump 标志位 我们现在就马上知道什么意思了。当程序被信号杀死时,会生成一个core的debug文件。 这个core标记位 ,为0不允许生成,为1运行生成debug文件。
在这里插入图片描述
在云服务上 生成这个core文件的功能默认是被关闭的
ulimit - a 查看core file size 的大小
在这里插入图片描述
ulimit -c 【size】 设置一下就好了
也有 可能 生成的core 文件不在当前目录
echo ./core > /proc/sys/kernel/core_pattern 就欧克啦
在这里插入图片描述
一重启就会生成一个core.进程号的文件 如果无限制的重启 就会生成非常多的core文件 所以云服务器就把这个功能关闭了
在这里插入图片描述
调试的时候,我们core-file core文件 把这个debug文件加载进去,调试器就直接显示出错的那一行了!
在这里插入图片描述

#include <iostream>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>int sum(int star, int end)
{int ret = 0;for (int i = star; i <= end; i++){ret /= 0;ret += i;}return ret;
}
int main()
{pid_t id = fork();if(id == 0){sleep(1);sum(1, 100);exit(0);}int status = 0;pid_t rid = waitpid(id, &status, 0);if(rid > 0){printf("exit code: %d, sig: %d, core dump:%d\n",(status >> 8) &0xff, status &0x7f,(status >> 7) &1);}return 0;
}

在这里插入图片描述
当我们把ulimit -c设置为 0时 coredump 标记位就为0了 表示 不生成core dump(核心转储)文件
在这里插入图片描述

2.信号的保存

信号的保存就保存在这三张表中,block表,peding表,handler表。
每个信号都有两个标志位分别表示阻塞(block)和未决(pending),还有一个函数指针表示处理动作。信号产生时,内核在进程控制块中设置该信号的未决标志,直到信号递达才清除该标志。
执行信号处理的动作称为信号的递达。
信号产生到递达之间称为未决
如果一个信号被阻塞了,那么它永远未决。
在这里插入图片描述
我们用的signal方法sighandler_t signal(int signum, sighandler_t handler); 其中 我们写的handler 就是把函数地址写进对应的handler表下标中 。
两张位图+函数指针数组 == 让进程识别信号

2.1 对pending 表 和 block 表操作

先介绍几个函数

#include <signal.h>
// 清空位图
int sigemptyset(sigset_t *set);
// 所有bit位全为1
int sigfillset(sigset_t *set);
// 把某一bit位置为1
int sigaddset (sigset_t *set, int signo);
// 把某一bit位置为0
int sigdelset(sigset_t *set, int signo);
// 判断某一比特位是不是1
int sigismember(const sigset_t *set, int signo); 

signal.h 给我们提供了 用户级别的位图,这些函数可以用来操作这个位图 sigset_t

//调用函数sigprocmask可以读取或更改进程的信号屏蔽字(阻塞信号集)。
int sigprocmask(int how, const sigset_t *set, sigset_t *oset); 
// 获取pending 表
int sigpending (sigset_t * set);

2.2 阻塞SIGINT信号 并打印pending表例子

// 利用上面的函数,我们就是验证 某一信号被阻塞后,是否一直未决
#include <iostream>
#include <signal.h>
#include <unistd.h>
void PrintPending( sigset_t & pending)
{for(int sig = 31; sig >= 1; sig--){if(sigismember(&pending,sig)){std::cout<<1;}else{std::cout<<0;}}std::cout<<std::endl;
}int main()
{sigset_t block_set , old_set;sigemptyset(&block_set);sigemptyset(&old_set);sigaddset(&block_set , SIGINT);// sigprocmask(SIG_BLOCK,&block_set,&old_set);while(true){sigset_t pending;sigpending(&pending);PrintPending(pending);sleep(1);}return 0;
}

捕捉信号

如果信号的处理动作是用户自定义函数,在信号递达时就调用这个函数,这称为捕捉信号
信号可能不会立即被处理,而是在合适的时候处理
,这个合适的时候指的是,从用户态返回内核态的时候进行处理。
用户态:执行我们自己的数据和代码的时候
内核态:执行操作系统的代码和数据的时候
在这里插入图片描述
当信号的处理动作是自定义的信号处理函数时才返回时先到用户态再从内核态到用户态(因为hander方法 和 main 函数不是调用关系并不能直接返回)。
如果是默认 则直接杀死进程了。 忽略则 除了修改pending 表 由 1 变为 0,其他什么也不干。
举例:
户程序注册了SIGQUIT信号的处理函数sighandler。 当前正在执行main函数,这时发生中断或异常切换到内核态。 在中断处理完毕后要返回用户态的main函数之前检查到有信号
SIGQUIT递达。 内核决定返回用户态后不是恢复main函数的上下文继续执行,而是执行sighandler函 数,sighandler
和main函数使用不同的堆栈空间,它们之间不存在调用和被调用的关系,是 两个独立的控制流程。 sighandler函数返
回后自动执行特殊的系统调用sigreturn再次进入内核态。 如果没有新的信号要递达,这次再返回用户态就是恢复main函数的上下文继续执行了。

sigaction 函数

**int sigaction(int signum, const struct sigaction act, struct sigaction oldact);
和 signal一样都是捕捉信号的,它有一个同名的结构体,但这个结构体我们只关心ssiginfo_t 这个函数指针方法字段在这里插入图片描述

void handler(int signal)
{std::cout<<"捕捉到:"<<signal<<std::endl;// while(true)// {//     sigset_t pending;//     sigpending(&pending);//     Print(pending);//     sleep(1);// }exit(1);
}int main()
{struct sigaction act, oact;act.sa_flags = 0;//在这种情况下,信号处理将遵循默认的行为,//也就是说,信号处理函数将作为一个普通的函数执行,//而不会触发任何 sa_flags 标志所定义的特殊行为。act.sa_handler = handler;sigemptyset(&act.sa_mask);//sigaddset(&act.sa_mask,3); // 顺带屏蔽三号信号sigaction(2,&act,&oact);while(true){std::cout<<"pid: "<<getpid()<<std::endl;sleep(1);}    return 0;
}

在这里插入图片描述

验证当前正在处理某信号,则该信号会自动被屏蔽

我们在hander方法中一直sleep,不退出hander方法,我们再按ctrl + c信号也不会被处理了。这就验证了当前信号正在被处理,则该信号会被自动屏蔽。
在这里插入图片描述

验证当前信号被处理完之后,会自动解除屏蔽

我们设置hander方法睡三秒自动退出。 退出之后又可以捕捉到2号信号则证明了该结论
在这里插入图片描述

地址空间中操作系统态

内核级页表所有进程共享一份用户级页表每一个进程都有一份。操作系统的代码数据都通过内核级页表映射在物理内存中。
在这里插入图片描述

谈谈键盘输入的过程

操作系统怎么知道键盘摁下了? 是一直问键盘吗?当然不是,那不然太浪费cpu资源了
在这里插入图片描述
每一个硬件都有一个中断号,硬盘也不例外,当按下一个键后,通过8529这个芯片向cpu 发出硬件中断,某一个寄存器上就有了键盘的中断号,再在中断向量表中查询对应的键盘读入方法~这样就完成了cpu知道键盘输入的一个过程。
我们学习的信号就是模拟硬件中断实现的!

两个深刻的问题

如何操作系统是怎么运行的

操作系统调用进程谁由来调度操作系统呢?
硬件上有一个时钟,时钟到了就通过中断提醒操作系统该检测进程的时间片,时间片到了就切换进程,否则什么也不做
在这里插入图片描述

如何理解系统调用

  1. 有一个函数指针数组,通过下标 可以找到系统调用,这个下标我们称为系统调用号。
  2. 我们使用系统调用如fork时,会产生内部中断(陷阱),执行系统调用的方法,让cpu找这个函数指针数组。eax 中保存这个函数系统调用号,然后cpu就找到这个系统调用了

可重入函数

在这里插入图片描述
像上例这样,insert函数被不同的控制流程调用,有可能在第一次调用还没返回时就再次进入该函数,这称为重入,insert函数访问一个全局链表,有可能因为重入而造成错乱,像这样的函数称为 不可重入函数

volatile

#include <iostream>
#include <signal.h>
int gflag = 0;
void changeData(int signo)
{std::cout<<"gflg:0 -> 1"<<std::endl;gflag = 1;
}
int main()
{signal(2,changeData);while(!gflag);std::cout<<"process quit!"<<std::endl;return 0;
}

在这里插入图片描述
当我们用编译器O1的优化时,main函数 里面又没有修改 gflag的值,于是编译器把内存中的值拷贝到寄存器后,就只看寄存器中的值了。
在这里插入图片描述
怎么解决这个问题呢?
我们可以在gval前 加一个volatile关键字 保证内存的可见性就行了。

sigchild信号

子进程退出的时候会给父进程发送一个sigchild信号

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <stdlib.h>
void notice(int sig)
{std::cout<<"I am fatherprocess,pid: "<<getpid()<<std::endl;std::cout<<"get sig:"<<sig<<std::endl;
}
int main()
{signal(SIGCHLD,notice);pid_t id = fork();if(id == 0){std::cout<<"I am childprocess,pid: "<<getpid()<<std::endl;sleep(3);exit(1);}sleep(100);return 0;
}

在这里插入图片描述

如果不关心 子进程的退出信息则可以把SIGCHLD 的捕捉动作改为SIG_IGN

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <stdlib.h>int main()
{signal(SIGCHLD,SIG_IGN);pid_t id = fork();if(id == 0){int cnt = 5;while(cnt--){std::cout<<"child process runing"<<std::endl;std::cout<<"cnt:"<<cnt<<std::endl;sleep(1);}exit(1);}while(true){std::cout<<"father process runing"<<std::endl;sleep(1);}return 0;
}

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

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

相关文章

视觉语言模型:融合视觉与语言的未来

1. 概述 视觉语言模型&#xff08;Vision-Language Models, VLMs&#xff09;是能够同时处理和理解视觉&#xff08;图像&#xff09;和语言&#xff08;文本&#xff09;两种模态信息的人工智能模型。这种模型结合了计算机视觉和自然语言处理的技术&#xff0c;使得它们能够在…

代码随想录-Day49

300. 最长递增子序列 给你一个整数数组 nums &#xff0c;找到其中最长严格递增子序列的长度。 子序列 是由数组派生而来的序列&#xff0c;删除&#xff08;或不删除&#xff09;数组中的元素而不改变其余元素的顺序。例如&#xff0c;[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的 …

使用握手信号实现跨时钟域数据传输

目录 描述 输入描述&#xff1a; 输出描述&#xff1a; 参考代码 描述 分别编写一个数据发送模块和一个数据接收模块&#xff0c;模块的时钟信号分别为clk_a&#xff0c;clk_b。两个时钟的频率不相同。数据发送模块循环发送0-7&#xff0c;在每个数据传输完成之后&#xf…

Cube-Studio:开源大模型全链路一站式中台

开源项目&#xff0c;欢迎star哦&#xff0c;https://github.com/data-infra/cube-studio 一款真正意义的 LLMOps 框架 LLMOps&#xff08;Large Language Model Operations&#xff09;是一个涵盖了大型语言模型&#xff08;如GPT系列&#xff09;开发、部署、维护和优化的一…

docker部署mycat,连接上面一篇的一主二从mysql

一、docker下载mycat镜像 查看安装结果 这个名称太长&#xff0c;在安装容器时不方便操作&#xff0c;设置标签为mycat docker tag longhronshens/mycat-docker mycat 二、安装容器 先安装一个&#xff0c;主要目的是获得配置文件 docker run -it -d --name mycat -p 8066:…

SpringBoot新手快速入门系列教程7:基于Redis的一个简单存取数据的例子

新手可能有这样的疑问&#xff0c;有了数据库的存取方式&#xff0c;我们为什么还要使用Redis这种缓存数据库读取方式呢&#xff1f; 原因主要有以下几点&#xff1a; 1. 性能提升 数据库查询尤其是复杂查询可能会消耗大量的时间和资源。而Redis是一个内存数据库&#xff0c…

刷题之买股票的最佳时机(leetcode)

买股票的最佳时机 动态规划入门题。 最简单的模拟式解法&#xff1a; class Solution { public:int maxProfit(vector<int>& prices) {//也可以换一种思路&#xff0c;因为只交易一次&#xff0c;那么找出股票最便宜的时候买入&#xff0c;最贵的时候卖出&#xff…

HTML5五十六个民族网站模板源码

文章目录 1.设计来源高山族1.1 登录界面演示1.2 注册界面演示1.3 首页界面演示1.4 中国民族界面演示1.5 关于高山族界面演示1.6 联系我们界面演示 2.效果和源码2.1 动态效果2.2 源代码2.3 源码目录 源码下载 作者&#xff1a;xcLeigh 文章地址&#xff1a;https://blog.csdn.ne…

基于TCP的在线词典系统(分阶段实现)

1.功能说明 一共四个功能&#xff1a; 注册 登录 查询单词 查询历史记录 单词和解释保存在文件中&#xff0c;单词和解释只占一行, 一行最多300个字节&#xff0c;单词和解释之间至少有一个空格。 2.功能演示 3、分阶段完成各个功能 3.1 完成服务器和客户端的连接 servic…

Fast R-CNN(论文阅读)

论文名&#xff1a;Fast R-CNN 论文作者&#xff1a;Ross Girshick 期刊/会议名&#xff1a;ICCV 2015 发表时间&#xff1a;2015-9 ​论文地址&#xff1a;https://arxiv.org/pdf/1504.08083 源码&#xff1a;https://github.com/rbgirshick/fast-rcnn 摘要 这篇论文提出了一…

计算机的错误计算(二十三)

摘要 计算机的错误计算&#xff08;二十二&#xff09;阐明&#xff1a;对于结果应该为 0的算式&#xff0c;即使增加计算精度&#xff0c;也得不出0. 针对 &#xff0c;本节给出一种解决方案。 计算机的错误计算&#xff08;十九&#xff09;展示了计算机对 的错误计算&…

JWT(Json Web Token)在.NET Core中的使用

登录成功时生成JWT字符串目录 JWT是什么&#xff1f; JWT的优点&#xff1a; JWT在.NET Core 中的使用 JWT是什么&#xff1f; JWT把登录信息&#xff08;也称作令牌&#xff09;保存在客户端为了防止客户端的数据造假&#xff0c;保存在客户端的令牌经过了签名处理&#xf…

JVM原理(二十):JVM虚拟机内存的三特性详解

1. 原子性、可进行、有序性 1.1. 原子性 Java内存模型围绕着在并发过程中如何处理原子性、可见性和有序性这三个特征来建立的。 Java内存模型来直接保证的原子性变量操作包括read、load、assign、use、store和write这六个。我们大致可以认为&#xff0c;基本数据类型的访问、…

初识Spark

一、简介 官网&#xff1a;Apache Spark™ - Unified Engine for large-scale data analytics Apache的顶级项目&#xff0c;用于大规模数据处理的统一分析引擎。 支持语言&#xff1a;Java、Scala、Python和R (源码为Scala) 高级工具&#xff1a; 1、SparkSQL用于SQL和结构…

云联壹云 FinOps:赋能某车企公有云成本管理与精细化运营

背景 某车企&#xff0c;世界 500 强企业&#xff0c;使用了大量的公有云资源&#xff0c;分布于多家公有云&#xff0c;月消费在千万级别。 业务线多且分散&#xff0c;相关的云消耗由一个核心团队进行管理&#xff0c;本次案例的内容将围绕这些云成本的管理展开的。 需求 …

【LabVIEW学习篇 - 3】:程序结构——顺序结构、for循环、while循环

文章目录 顺序结构案例一案例二 for循环while循环 顺序结构 LabVIEW中的顺序结构是一种常用的控制结构&#xff0c;用于按顺序执行程序的不同部分。顺序结构在程序中按照从左到右的顺序依次执行各个子结构&#xff0c;类似于传统的文本编程语言中的顺序执行。 案例一 案例一…

人工智能在病理组学虚拟染色中的应用|文献精析·24-07-07

小罗碎碎念 本期文献精析&#xff0c;分享的是一篇关于深度学习在虚拟染色技术中应用于组织学研究的综述。 角色姓名单位&#xff08;中文&#xff09;第一作者Leena Latonen东芬兰大学&#xff08;QS-552&#xff09;生物医学研究所通讯作者Pekka Ruusuvuori图尔库大学&#…

日志自动分析-Web---360星图GoaccessALBAnolog

目录 1、Web-360星图(IIS/Apache/Nginx) 2、Web-GoAccess &#xff08;任何自定义日志格式字符串&#xff09; 源码及使用手册 安装goaccess 使用 输出 3-Web-自写脚本&#xff08;任何自定义日志格式字符串&#xff09; 4、Web-机器语言analog&#xff08;任何自定义日…

FastAPI+vue3+Primeflex教学20240706,渲染阶乘案例

子绝父相 相对定位是相对于自己原本的位置定位。 绝对定位&#xff0c;如果父元素设置了相对定位&#xff0c;则相对于父元素进行绝对定位&#xff0c;否则相对于最近的设置了相对定位的元素进行绝对定位&#xff0c;或者相对于根元素进行绝对定位。 定位有四个方向&#xff0…

uniapp 在手机上导出excel

1.创建excelDev.js文件 export default {exportExcel(fileData, documentName excel) {plus.io.requestFileSystem(plus.io.PUBLIC_DOCUMENTS, function(fs) {let rootObj fs.rootlet fullPath rootObj.fullPathconsole.log("开始导出数据")// 创建文件夹rootObj…