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.mzph.cn/diannao/42288.shtml

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

相关文章

数据库SQL Server常用字符串函数

文章目录 字符串函数 字符串函数 CONCAT:拼接字符串 CONCAT(COLUMN1,_,COLUMN2) AS COLCONVERT&#xff1a;转换数据类型 CONVERT(data_type(length),data_to_be_converted,style)例如&#xff1a;CONVERT(VARCHAR(10),GETDATE(),110) SUBSTRING()&#xff1a;从字符串中返回…

java项目总结5

1.单列集合顶层接口Collction 集合体系结构 注意&#xff1a;因为Collection定义的方法是共性的&#xff0c;使用不能通过搜引来删除&#xff0c;只能通过元素的对象进行删除&#xff0c;返回值是boolean类型。例如我添加了"aaa"进List集合&#xff0c;删除则要对象…

STM32-01 推挽输出-点亮LED

本文以STM32中点亮LED为例&#xff0c;解读推挽输出的原理 推挽输出介绍 所谓的推挽输出&#xff0c;就是通过控制输出控制模块&#xff0c;打开或者关闭P-MOS或者N-MOS。 ─ 推挽模式下&#xff1a;输出寄存器上的’0’激活N-MOS&#xff0c;而输出寄存器上的’1’将激活P-M…

局部静态变量实现的单例存在多个对象

文章目录 背景测试代码运行测试尝试打开编译器优化进一步分析 背景 业务中出现日志打印失效&#xff0c;发现是因为管理日志对象的单例在运行过程中存在了多例的情况。下面通过还原业务场景来分析该问题。 测试代码 /* A.h */ #ifndef CALSS_A #define CALSS_A#include <…

打造属于自己的脚手架工具并发布到npm仓库

一、创建项目 使用 npm init -y 创建项目创建项目入口文件 index.js在 package.json 中添加 bin 字段使用 npm link 命令将文件映射至全局&#xff0c;使可以在本地测试 zp 命令 // "zp" 为用于全局执行脚手架的命令&#xff0c;vue-cli中使用的是vue命令 "bi…

基于java+springboot+vue实现的旅游管理系统(文末源码+lw+ppt)23-402

研究的内容 当下流行的WPS、Word等办公软件成为了人们耳熟能详的系统&#xff0c;但一些更加专业性、性能更加强大的网络信息工具被人们“埋没”在互联网的大海中。甘肃旅游管理系统是一个便于用户查看热门景点、酒店信息、推荐线路、旅游攻略、景点资讯等&#xff0c;管理员进…

【Python基础篇】你了解python中运算符吗

文章目录 1. 算数运算符1.1 //整除1.2 %取模1.3 **幂 2. 赋值运算符3. 位运算符3.1 &&#xff08;按位与&#xff09;3.2 |&#xff08;按位或&#xff09;3.3 ^&#xff08;按位异或&#xff09;3.4 ~&#xff08;按位取反&#xff09;3.5 <<&#xff08;左移&#…

HTML 【实用教程】(2024最新版)

核心思想 —— 语义化 【面试题】如何理解 HTML 语义化 ?仅通过标签便能判断内容的类型&#xff0c;特别是区分标题、段落、图片和表格 增加代码可读性&#xff0c;让人更容易读懂对SEO更加友好&#xff0c;让搜索引擎更容易读懂 html 文件的基本结构 html 文件的文件后缀为 …

【高录用、快检索、过往5届均已检索、SPIE 出版】第六届无线通信与智能电网国际会议(ICWCSG 2024)

随着科技的飞速发展和能源需求的日益增长&#xff0c;智能电网技术逐渐成为电力行业的重要发展方向。与此同时&#xff0c;无线通信技术在近年来也取得了显著的进步&#xff0c;为智能电网的发展提供了强有力的支持。为了进一步推动无线通信与智能电网的结合与发展&#xff0c;…

学IT上培训班真的有用吗?

在学习IT技术的过程中&#xff0c;你是否也被安利过各种五花八门的技术培训班&#xff1f;这些培训班都是怎样向你宣传的&#xff0c;你又对此抱有着怎样的态度呢&#xff1f;在培训班里学技术&#xff0c;真的有用吗&#xff1f; 一、引入话题 IT行业是一个快速发展和不断变化…

C++初学者指南-4.诊断---未定义行为检测器

C初学者指南-4.诊断—未定义行为检测器 未定义行为检测器(UBSAN) 适用编译器&#xff1a;clang,g在运行时检测许多类型的未定义行为 解引用空指针从未对齐的指针读取整数溢出被0除 … 在代码中加入额外的指令:在调试构建中增加运行时约25% 示例&#xff1a;有符号整形溢出 …

Git在多人开发中的常见用例

前言 作为从一个 svn 转过来的 git 前端开发&#xff0c;在经历过git的各种便捷好处后&#xff0c;想起当时懵懂使用git的胆颤心惊&#xff1a;总是害怕用错指令&#xff0c;又或者遇到报错就慌的场景&#xff0c;想起当时查资料一看git指令这么多&#xff0c;看的头晕眼花&am…

深度学习原理与Pytorch实战

深度学习原理与Pytorch实战 第2版 强化学习人工智能神经网络书籍 python动手学深度学习框架书 TransformerBERT图神经网络&#xff1a; 技术讲解 编辑推荐 1.基于PyTorch新版本&#xff0c;涵盖深度学习基础知识和前沿技术&#xff0c;由浅入深&#xff0c;通俗易懂&#xf…

家里老人能操作的电视直播软件,目前能用的免费看直播的电视软件app,适合电视和手机使用!

2024年许多能看电视直播的软件都不能用了&#xff0c;家里的老人也不会手机投屏&#xff0c;平时什么娱乐都没有了&#xff0c;这真的太不方便了。 很多老人并不喜欢去买一个广电的机顶盒&#xff0c;或者花钱拉有线电视。 现在的电视大多数都是智能电视&#xff0c;所以许多电…

Redis基本命令源码解析-字符串命令

1. set 用于将kv设置到数据库中 2. mset 批量设置kv mset (msetnx) key1 value1 key2 value2 ... mset:msetCommand msetnx:msetnxCommand msetCommand和msetnxCommand都调用msetGenericCommand 2.1 msetGenericCommand 如果参数个数为偶数,则响应参数错误并返回 如果…

【项目日记(一)】梦幻笔耕-数据层实现

❣博主主页: 33的博客❣ ▶️文章专栏分类:项目日记◀️ &#x1f69a;我的代码仓库: 33的代码仓库&#x1f69a; &#x1faf5;&#x1faf5;&#x1faf5;关注我带你了解更多项目内容 目录 1.前言2.后端模块3数据库设计4.mapper实现4.1UserInfoMapper4.2BlogMapper 5.总结 1.…

硬件开发笔记(二十四):贴片电容的类别、封装介绍,AD21导入贴片电容、原理图和封装库3D模型

若该文为原创文章&#xff0c;转载请注明原文出处 本文章博客地址&#xff1a;https://hpzwl.blog.csdn.net/article/details/140241817 长沙红胖子Qt&#xff08;长沙创微智科&#xff09;博文大全&#xff1a;开发技术集合&#xff08;包含Qt实用技术、树莓派、三维、OpenCV…

存储结构与管理磁盘

前言&#xff1a;本博客仅作记录学习使用&#xff0c;部分图片出自网络&#xff0c;如有侵犯您的权益&#xff0c;请联系删除 目录 一、一切从“/”开始 二、物理设备的命名规则 三、文件系统与数据资料 四、挂载硬件设备 五、添加硬盘设备 六、添加交换分区 七、磁盘容…

如何在 PostgreSQL 中实现数据的增量备份和恢复?

文章目录 一、增量备份的原理二、准备工作&#xff08;一&#xff09;环境配置&#xff08;二&#xff09;创建测试数据库和表&#xff08;三&#xff09;插入初始数据 三、全量备份四、基于时间点的增量备份&#xff08;一&#xff09;开启 WAL 归档&#xff08;二&#xff09…

政策公告与提醒

自 2024 年 4 月 3 日起,您将至少有 30 天的时间来更新应用,使其符合下方所述的政策变更。 我们将推出“儿童安全标准”政策,规定社交应用和约会交友应用必须遵循特定标准,并在 Play 管理中心内以自行认证的形式证明合规后才能发布。 为了提高健康相关应用在 Google Play…