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.xdnf.cn/news/1473649.html

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

相关文章

3-3 超参数

3-3 超参数 什么是超参数 超参数也是一种参数&#xff0c;它具有参数的特性&#xff0c;比如未知&#xff0c;也就是它不是一个已知常量。是一种手工可配置的设置&#xff0c;需要为它根据已有或现有的经验&#xff0c;指定“正确”的值&#xff0c;也就是人为为它设定一个值&…

Linux系统的服务——以Centos7为例

一、Linux系统的服务简介 服务是向外部提供对应功能的进程&#xff0c;其运行在系统后台&#xff0c;能够7*24小时持续不断的提供外界随时发来的服务请求&#xff0c;且服务进程常驻在内存中&#xff0c;具有固定的端口号&#xff0c;通过端口号就能找到服务内容。 提供服务的一…

2000-2022年地级市数字经济指数(含控制变量)

2000-2022年地级市数字经济指数&#xff08;含控制变量&#xff09; 目录 数字经济对区域经济发展的影响实证研究 一、引言 二、文献综述 三、数据来源与变量说明 四、实证模型 五、程序代码与运行结果 数字经济对区域经济发展的影响实证研究 摘要&#xff1a; 本文旨在…

Ubuntu防火墙相关内容

Ubuntu防火墙相关的命令&#xff0c;主要用于日常使用过程中&#xff0c;忘记命令时查找方便&#xff0c;不用再去各种地方搜索了。以下命令均已root用户执行&#xff0c;如果是非root用户&#xff0c;需要添加sudo 查看防火墙的启用状态 ufw status 说明是启用状态。 启用防…

Fish Speech: 开源文本转语音技术(TTS)的新里程碑

简介 Fish Speech 是一个全新的文本转语音(TTS)解决方案&#xff0c;该项目由fishaudio开发。当前模型使用约十五万小时三语数据训练&#xff0c;对中文支持非常的完美。 能够熟练处理和生成中文、日语和英语的语音&#xff0c;语言处理能力接近人类水平&#xff0c;并且声音…

代码随想录算法训练营第4天|LeetCode24,19,02,07,142

24.交换链表结点 题目链接&#xff1a;24. 两两交换链表中的节点 - 力扣&#xff08;LeetCode&#xff09; 文章链接&#xff1a;代码随想录 (programmercarl.com) 视频链接&#xff1a;代码随想录算法公开课 | 最强算法公开课 | 代码随想录 第一想法 正常模拟&#xff0c;先画…

算法金 | 欧氏距离算法、余弦相似度、汉明、曼哈顿、切比雪夫、闵可夫斯基、雅卡尔指数、半正矢、Sørensen-Dice

大侠幸会&#xff0c;在下全网同名「算法金」 0 基础转 AI 上岸&#xff0c;多个算法赛 Top 「日更万日&#xff0c;让更多人享受智能乐趣」 抱个拳&#xff0c;送个礼 在算法模型构建中&#xff0c;我们经常需要计算样本之间的相似度&#xff0c;通常的做法是计算样本之间的距…

性价比蓝牙耳机排行榜前十名有哪些?十大性价比蓝牙耳机榜单盘点

作为使用真无线蓝牙耳机长达5-6年的资深爱好者&#xff0c;我始终对音频技术和产品的创新保持着浓厚的兴趣&#xff0c;最近&#xff0c;我投入了一笔不小的资金&#xff0c;超过大几千元&#xff0c;用于深入测试和评估市面上多款来自各大品牌的真无线蓝牙耳机&#xff08;包括…

EtherCAT转Profinet网关配置说明第一讲:配置软件安装及介绍

网关XD-ECPNS20为EtherCAT转Profinet协议网关&#xff0c;使EtherCAT协议和Profinet协议两种工业实时以太网网络之间双向传输 IO 数据。适用于具有EtherCAT协议网络与Profinet协议网络跨越网络界限进行数据交换的解决方案。 本网关通过上位机来进行配置。 首先安装上位机软件 一…

用递归解决冒泡排序问题

冒泡排序是种很简单的排序方式. 如果用循环方式, 通常就是两层循环. 由于两层循环都是与元素个数 N 线性相关, 所以可以简单估算出它的时间复杂度是 O(N2), 通常而言, 这是较糟糕的复杂度. 当然, 这也几乎是所有简单方式的宿命, 想简单就别想效率高! 前面篇章说到递归也是一种循…

基于Java技术的人事管理系统

你好&#xff0c;我是专注于计算机科学领域的小野。如果你对人事管理系统感兴趣或有相关需求&#xff0c;欢迎私信交流。 开发语言&#xff1a; Java 数据库&#xff1a; MySQL 技术&#xff1a; B/S模式、Java技术、SpringBoot 工具&#xff1a; Eclipse、MySQL、浏览…

【C++:类的基础认识和this指针】

C的类与C语言的struct结构体有啥区别&#xff1f; 默认的访问限定符不同 类的简要 关键字&#xff1a;class{}里面是类的主体&#xff0c;特别注意&#xff1a;{}后面的&#xff1b;不可以省略类中的变量叫做成员变量&#xff0c;类中的函数叫做成员函数类中访问有三种访问权限…

HTML5使用<blockquote>标签:段落缩进

使用<blockquote>标签可以实现页面文字的段落缩进。这一标签也是每使用一次&#xff0c;段落就缩进一次&#xff0c;并且可以嵌套使用&#xff0c;以达到不同的缩进效果。语法如下&#xff1a; <blockquote>文字</blockquote> 【实例】使用<blockquote&…

谷粒商城----通过缓存和分布式锁获取数据。

高并发下缓存失效的问题 高并发下缓存失效的问题--缓存穿透 指查询一个一定不存在的数据&#xff0c;由于缓存是不命中&#xff0c;将去查询数据库&#xff0c;但是数据库也无此记录&#xff0c;我们没有将这次查询的不写入缓存&#xff0c;这将导致这个不存在的数据每次请求…

工厂模式之简单工厂模式

文章目录 工厂模式工厂模式分为工厂模式的角色简单工厂模式案例代码定义一个父类&#xff0c;三个子类定义简单工厂客户端使用输出结果 工厂模式 工厂模式属于创造型的模式&#xff0c;用于创建对象。 工厂模式分为 简单工厂模式&#xff1a;定义一个简单工厂类&#xff0c;根…

VuePress 的更多配置

现在&#xff0c;读者应该对 VuePress、主题和插件等有了基本的认识&#xff0c;除了插件&#xff0c;VuePress 自身也有很多有用的配置&#xff0c;这里简单说明下。 ‍ ‍ VuePress 的介绍 在介绍了 VuePress 的基本使用、主题和插件的概念之后&#xff0c;我们再来看看官…

【MySQL】1.初识MySQL

初识MySQL 一.MySQL 安装1.卸载已有的 MySQL2.获取官方 yum 源3.安装 MySQL4.登录 MySQL5.配置 my.cnf 二.MySQL 数据库基础1.MySQL 是什么&#xff1f;2.服务器&#xff0c;数据库和表3.mysqld 的层状结构4.SQL 语句分类 一.MySQL 安装 1.卸载已有的 MySQL //查询是否有相关…

vue事件参数

事件参数 事件参数可以获取event对象和通过事件传递数据 获取event对象 <template> <buttonclick"addCount">点击</button><p>count is: {{ count }}</p><p>{{ coutent_e }}</p> </template> <script>expor…

昇腾910B部署Qwen2-7B-Instruct进行流式输出【pytorch框架】NPU推理

目录 前情提要torch_npu框架mindsport框架mindnlp框架 下载模型国外国内 环境设置代码适配&#xff08;非流式&#xff09;MainBranch结果展示 代码适配&#xff08;流式&#xff09; 前情提要 torch_npu框架 官方未适配 mindsport框架 官方未适配 mindnlp框架 官方适配…

25.【C语言】循环结构之for 上

1.基本使用 类比while 在while循环中&#xff0c;有三个不可或缺的部分&#xff1a;初始化&#xff0c;判断部分&#xff0c;调整部分 int i 0;//初始化 while (i < 10)//判断部分 {……i;//调整部分 }三个部分太分散&#xff0c;用for循环可集为一体&#xff0c;简洁 …