进程信号 signal

文章目录

  • 信号基础
  • 信号的产生
    • OS中的时间
  • 信号的保存
    • sigset_t
    • sigprocmask
    • sigpending
  • 信号的捕捉
    • 用户态和内核态
    • sigaction
    • volatile
  • SIGCHLD

信号基础

生活中的信号
你在网上买了很多件商品,再等待不同商品快递的到来。但即便快递没有到来,你也知道快递来临时,你该怎么处理快递。也就是你能“识别快递”当快递员到了你楼下,你也收到快递到来的通知,但是你正在打游戏,需5min之后才能去取快递。那么在在这5min之内,你并没有下去去取快递,但是你是知道有快递到来了。也就是取快递的行为并不是一定要立即执行,可以理解成“在合适的时候去取”。在收到通知,再到你拿到快递期间,是有一个时间窗口的,在这段时间,你并没有拿到快递,但是你知道有一个快递已经来了。本质上是你“记住了有一个快递要去取”当你时间合适,顺利拿到快递之后,就要开始处理快递了。而处理快递一般方式有三种:1. 执行默认动作(幸福的打开快递,使用商品)2. 执行自定义动作(快递是零食,你要送给你你的女朋友)3. 忽略快递(快递拿上来之后,扔掉床头,继续开一把游戏)快递到来的整个过程,对你来讲是异步的,你不能准确断定快递员什么时候给你打电话。

总而言之

  1. 信号没有产生的时候我们已经知道怎么处理这个信号了
  2. 信号的到来,我们并不清楚具体是什么时候,信号对于我现在正在左的工作是异步产生的。
  3. 信号产生了我们不一定要立即处理它,而是在合适的时候去处理
  4. 因为我们不一定会要立即处理它,所以我们要有对信号的保存能力

信号:信号是一种向目标进程发送通知消息的一种机制。

所以进程在收到信号之前已经知道了有哪些信号并且知道对应信号的处理方法。

在Linux中可以通过kill -l 查看所有的信号。
在这里插入图片描述
并且在进程能够通过自己的PCB找到一张函数指针数组,数组的下标对应的就是各个信号的编号,数组的内容就是对应信号的处理方法。这么多的信号中1 - 34 号信号为普通信号,剩下的为实时信号,我们只说普通信号。

一个信号的处理方法分为三种:

  1. 默认行为
  2. 忽略
  3. 自定义

我们是可以通过signal修改对于信号的执行方法。 其中9号信号为管理员信号,默认方法不能被修改。
在这里插入图片描述
第二个参数设置为SIG_DFL就是默认行为,设置为SIGIGN就是忽略。
假设我们现在修改二号信号的默认行为

#include <iostream>
#include <unistd.h>
#include <signal.h>
void sigcb(int signal)
{std::cout << "get a singal :" << signal << std::endl;exit(0);
}
int main()
{signal(2,sigcb);while(true){std::cout << "run.." << std::endl;sleep(1);}return 0;
}

信号的产生

在命令行shell中,前台命令(./xxx)只能有一个,后台命令(./xxx &)可以有多个,前台进程是不能被暂停(ctrl + z),如果被暂停,该前台进程要立即被放到后台。OS会自动的把shell自动的提到前台或者后台。ctrl + c一般情况下可以终止一个前台进程。判断是不是前台进程可以看有没有接受用户输入的能力,有就是前台进程。

LInux中可以通过jobs命令查看后台进程,fg + num 可以把一个后台进程提到前台,bg + num 可以启动一个被暂停的后台任务。

OS是怎么知道键盘有数据准备就绪了呢?
CPU其实和外设也是相连的,CPU上有很多针脚,硬件中有一个8269,作为针脚和硬件的中间设备,因为外设很多,CPU的针脚有限,所以可以通过这个设备把多的外设和CPU连接起来,然后当键盘有数据了,会通过针脚产生硬件中断,OS中会有一张中断向量表(函数指针数组),然后每个硬件都有自己的编号,CPU有一个寄存器专门存储硬件的中断号,数组的下标就是对于硬件的编号,数组的内容就是硬件的读取方法,所以CPU接收到了硬件中断,然后直接通过数组下标找到对于的方法,然后把内容加载到内存。

信号产生的方式:

  1. 可以通过键盘产生
    ctrl + c (发送2号信号终止进程)
    ctrl + z (暂停进程,发送19号信号)
    ctrl + \ (终止进程,发送3号信号)

  2. 通过系统调用
    kill命令是调用kill函数实现的。kill函数可以给一个指定的进程发送指定的信号。raise函数可以给当前进程发送指定的信号(自己给自己发信号)。
    在这里插入图片描述
    abort函数使当前进程接收到信号而异常终止。 并且abort就算被signal重定义,就算最后我们没有终止进程,它自己最后也会终止进程。
    在这里插入图片描述

  3. 异常
    硬件异常被硬件以某种方式被硬件检测到并通知内核,然后内核向当前进程发送适当的信号。例如当前进程执行了除以0的指令,CPU的运算单元会产生异常,内核将这个异常解释为SIGFPE(8)信号发送给进程。再比如当前进程访问了非法内存地址,MMU会产生异常,内核将这个异常解释为SIGSEGV(11)信号发送给进程。

#include <iostream>
#include <signal.h>void handler(int signo)
{std::cout << "run.." << std::endl;
}int main()
{signal(8,handler);int a = 6;a /= 0;return 0;
}

这段代码会出现死循环的情况 ,原因就是因为出现除0错误,然后CPU硬件报错,然后处理方法就是让OS给目标进程发信号并且把该进程剥离CPU,但是我们对8号信号进行自定义,没有退出进程,然后当CPU再一次调度这个进程时,接着出错,重复之前的动作。

  1. 软件条件
    管道的一种特性当读端退出,写端无意义,OS就会写端发送SIGPIPE信号,SIGPIPE是一种由软件条件产生的信号。除了这个以外还有alarm函数 和SIGALRM信号。
    在这里插入图片描述
    这个函数的返回值是0或者是以前设定的闹钟时间还余下的秒数。如果seconds值为0,表示取消以前设定的闹钟,函数的返回值仍然是以前设定的闹钟时间还余下的秒数。

总而言之信号产生的方式多种多样,但是信号发送都是由OS来发送的。

OS中的时间

  1. 所有的用户行为都是以进程的形式在OS中表现的。
  2. OS只要把进程管理号就能完成所有的用户任务。
  3. CMOS会周期性高频的像CPU发送时钟中断。

我们知道我们自己写的代码是由OS来调度执行的,但是OS的代码是谁来调度的呢?
CMOS向CPU发送时钟中断就是让CPU来执行OS的代码的,他会给CPU一个操作数,然后OS通过这个操作数在中断向量表中索引下标,数组的内容就是OS的调度方法,所以OS的执行是基于硬件中断的。。

所以对OS朴素的理解就是OS在电脑开机时完成各种的初始化工作后,开始进入死循环执行自己的调度方法。

信号的保存

信号的其他概念

  1. 实际执行信号的处理动作称为信号递达(Delivery)
  2. 信号从产生到递达之间的状态,称为信号未决(Pending)。
  3. 进程可以选择阻塞 (Block )某个信号。一旦被阻塞,就不能递达,直到对该信号解除屏蔽。
  4. 被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作.注意,阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作。

信号在内核中其实是很好表示的,因为我们只需要表示是否收到了某某信号,所以用位图这个数据结构就刚刚好。被阻塞也可以这样表示,都是用位图就可以表示。

在这里插入图片描述
每个信号都有两个标志位分别表示阻塞(block)和未决(pending),还有一个函数指针表示处理动作。信号产生时,内核在进程控制块中设置该信号的未决标志,直到信号递达才清除该标志。

所以当收到一个信号是,先看block是否被阻塞,如果没有阻塞就会递达,如果阻塞了,就需要等解除阻塞之后再递达。

sigset_t

OS为了我们对信号集进行操作,设置了sigset_t的数据类型,它本质就是一个位图,这个类型可以表示每个信号的“有效”或“无效”状态,在阻塞信号集中“有效”和“无效”的含义是该信号是否被阻塞,而在未决信号集中“有效”和“无效”的含义是该信号是否处于未决状态。

为了对信号集更好的操作,OS也为我们提供了对信号集的操作函数。
在这里插入图片描述

  1. 函数sigemptyset初始化set所指向的信号集,使其中所有信号的对应bit清零,表示该信号集不包含任何有效信号。
  2. 函数sigfillset初始化set所指向的信号集,使其中所有信号的对应bit置位,表示该信号集的有效信号包括系统支持的所有信号。

注意,在使用sigset_ t类型的变量之前,一定要调 用sigemptyset或sigfillset做初始化,使信号集处于确定的状态。初始化sigset_t变量之后就可以在调用sigaddset和sigdelset在该信号集中添加或删除某种有效信号。这几个函数都是成功返回0,出错返回-1。sigismember是一个布尔函数,用于判断一个信号集的有效信号中是否包含某种 信号,若包含则返回1,不包含则返回0,出错返回-1。

sigprocmask

调用函数sigprocmask可以读取或更改进程的信号屏蔽字(阻塞信号集)。
在这里插入图片描述
如果oset是非空指针,则读取进程的当前信号屏蔽字通过oset参数传出。如果set是非空指针,则 更改进程的信号屏蔽字,参数how指示如何更改。如果oset和set都是非空指针,则先将原来的信号 屏蔽字备份到oset里,然后根据set和how参数更改信号屏蔽字。假设当前的信号屏蔽字为mask,下表说明了how参数的可选值。

在这里插入图片描述

sigpending

在这里插入图片描述
可以通过这个函数获取当前进程的pending表。通过set参数传出。

信号的捕捉

发送信号后,信号不会立即递达,而是在合适的时候递达,那什么才算合适的时候呢?
进程从内核态返回用户态时,进行信号的检测和处理。

用户态和内核态

在这里插入图片描述
用户态:只能访问自己的0 - 3GB,是一种受控的状态,能访问的资源是有限的。
内核态:可以让用户以OS的身份访问3 - 4GB,是一种OS的工作状态,可以访问大部分资源。

我们之前说的所有的地址空间都是用户空间,里面都是对我们用户自己的代码,对应的还有一张用户级页表,而内核的进程地址空间都是OS的代码数据和数据结结构,其中对应的还有一张内核级页表,因为所有的进程都有自己的进程机地址空间,虽然用户空间的使用情况可能千奇百怪,但是OS只有一个,所以他们所有的内核空间中的数据都是一样的,并且在内存中也只会存在一张内存级页表,所有的进程的内核空间的内容一样,所以都指向同一张内核级页表就可以了,我们平时调用函数实在自己的进程地址空间调用,系统调用也是代码,是OS的代码,他映射在内核级页表中,所以我们普通用户需要进行系统调用一定要发生身份的切换,因为普通用户是不允许访问内核级空间的,CPU中有一个CS寄存器,可以标识当前进程是用户态还是内核态。所以不管是系统调用还是库函数还是自己写的函数都可以在自己的进程地址空间进行跳转和返回,并且无论进程怎么切换,CPU都可以直接找到OS的代码。

在这里插入图片描述
在调用自己的方法时,进程是要切换回用户态的,因为如果不切换的用户就可以在自定义方法中利用内核身份做不好的事情了。

如果信号的处理动作是用户自定义函数,在信号递达时就调用这个函数,这称为捕捉信号。由于信号处理函数的代码是在用户空间的,处理过程比较复杂,举例如下: 用户程序注册了SIGQUIT信号的处理函数sighandler。 当前正在执行main函数,这时发生中断或异常切换到内核态。 在中断处理完毕后要返回用户态的main函数之前检查到有信号SIGQUIT递达。 内核决定返回用户态后不是恢复main函数的上下文继续执行,而是执行sighandler函 数,sighandler和main函数使用不同的堆栈空间,它们之间不存在调用和被调用的关系,是 两个独立的控制流程。 sighandler函数返回后自动执行特殊的系统调用sigreturn再次进入内核态。 如果没有新的信号要递达,这次再返回用户态就是恢复main函数的上下文继续执行了。信号的捕捉过程中,是要进行4次的身份切换的。

sigaction

在这里插入图片描述
sigaction函数可以读取和修改与指定信号相关联的处理动作。调用成功则返回0,出错则返回- 1。signum是指定信号的编号。若act指针非空,则根据act修改该信号的处理动作。若oact指针非空,则通过oact传出该信号原来的处理动作。act和oact指向sigaction结构体.

在这里插入图片描述
将sa_handler赋值为常数SIG_IGN传给sigaction表示忽略信号,赋值为常数SIG_DFL表示执行系统默认动作,赋值为一个函数指针表示用自定义函数捕捉信号,或者说向内核注册了一个信号处理函数,该函数返回值为void,可以带一个int参数,通过参数可以得知当前信号的编号,这样就可以用同一个函数处理多种信号。显然,这也是一个回调函数,不是被main函数调用,而是被系统所调用。

当某个信号的处理函数被调用时,内核自动将当前信号加入进程的信号屏蔽字,当信号处理函数返回时自动恢复原来的信号屏蔽字,这样就保证了在处理某个信号时,如果这种信号再次产生,那么 它会被阻塞到当前处理结束为止。 如果在调用信号处理函数时,除了当前信号被自动屏蔽之外,还希望自动屏蔽另外一些信号,则用sa_mask字段说明这些需要额外屏蔽的信号,当信号处理函数返回时自动恢复原来的信号屏蔽字。 sa_flags字段包含一些选项,把sa_flags设为0就行,sa_sigaction是实时信号的处理函数。

volatile

保持内存的可见性,告知编译器,被该关键字修饰的变量,不允许被优化,对该变量的任何操作,都必须在真实的内存中进行操作

SIGCHLD

现在我们已经会创建子进程了,子进程在退出的时候什么都没说吗?
答案肯定是不是的,子进程在退出是是会给父进程发送SIGCHLD信号的。

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

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>
void handler(int sig)
{pid_t id;while ((id = waitpid(-1, NULL, WNOHANG)) > 0){printf("wait child success: %d\n", id);}printf("child is quit! %d\n", getpid());
}
int main()
{signal(SIGCHLD, handler);pid_t cid;if ((cid = fork()) == 0){ // childprintf("child : %d\n", getpid());sleep(3);exit(1);}while (1){printf("doing some thing!\n");sleep(1);}return 0;
}

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

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

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

相关文章

elememt-plus的表格的增删改查#Vue3无需json数据,无需后端接口

elememt-plus的表格的增删改查#Vue3无需json数据&#xff0c;无需后端接口 实现效果&#xff1a; <template><!-- 演示地址 --><div class"dem-add"><!-- Search start --><div class"dem-title"><p>演示地址</…

【Git教程】(十八)拆分大项目 — 概述及使用要求,执行过程及其实现,替代解决方案 ~

Git教程 拆分大项目 1️⃣ 概述2️⃣ 使用要求3️⃣ 执行过程及其实现3.1 拆分模块版本库3.2 将拆分出的模块作为外部版本库集成 4️⃣ 替代解决方案 通常软件项目都是由单体小型系统开始的&#xff0c;在开发过程中项目规模和团队人员不断扩大&#xff0c; 将项目模块化会显得…

C#实现各种Hash计算

C#实现各种Hash计算 文章目录 C#实现各种Hash计算涉及框架及库目前支持可计算的类型核心代码完整可运行代码 BCrypt总结 涉及框架及库 自己在NuGet管理器里面安装即可 BouncyCastle.Cryptography&#xff1a;是加密算法和协议的.NET实现。 目前支持可计算的类型 BLAKE2B_16…

如何在Idea离线情况下安装vue.js插件

亲践有效&#xff0c;步骤如下: 1. 互联网环境登陆vue.js官网(Vue.js - IntelliJ IDEs Plugin | Marketplace)。 2. 然后先确定你的IDEA的版本&#xff1a;在你IDEA的安装文件中找到product-info.json&#xff0c;里面的buildNumber记录着你IDEA的精确版本号&#xff0c;根据…

roscore启动报错的解决方法【将环境变量配置于最后】

今天在启动rviz时发生一个很奇怪的报错&#xff1a; rviz: error while loading shared libraries: librviz.so: cannot open shared object file: No such file or directory 我感觉很纳闷&#xff01;再试着启动一下roscore&#xff0c;发现如下报错&#xff1a; [rosout-1…

Python爬虫从入门到精通:一篇涵盖所有细节的高质量教程

目录 第一部分&#xff1a;Python爬虫基础 1.1 爬虫原理 1.2 Python爬虫常用库 1.3 爬虫实战案例 1.4 注意事项 第二部分&#xff1a;爬虫进阶技巧 2.1 处理动态加载的内容 2.2 登录认证 2.3 分布式爬取 2.4 反爬虫策略 第三部分&#xff1a;爬虫实战项目 3.1 豆瓣…

【C语言】指针(二)

目录 一、传值调用和传址调用 二、数组名的理解 三、通过指针访问数组 四、一维数组传参的本质 五、指针数组 六、指针数组模拟实现二维数组 一、传值调用和传址调用 指针可以用在哪里呢&#xff1f;我们看下面一段代码&#xff1a; #include <stdio.h>void Swap(i…

基于Spring封装一个websocket工具类使用事件发布进行解耦和管理

最近工作中&#xff0c;需要将原先的Http请求换成WebSocket&#xff0c;故此需要使用到WebSocket与前端交互。故此这边需要研究一下WebSocket到底有何优点和不可替代性&#xff1a; WebSocket优点&#xff1a; WebSocket 协议提供了一种在客户端和服务器之间进行全双工通信的…

异地组网群晖不能访问怎么办?

在日常使用群晖网络储存设备时&#xff0c;我们常常会遇到无法访问的情况&#xff0c;特别是在异地组网时。这个问题很常见&#xff0c;但也很让人困扰。本文将针对异地组网群晖无法访问的问题进行详细解答和分析。 异地组网的问题 在异地组网中&#xff0c;群晖设备无法访问的…

Unity | Spine动画动态加载

一、准备工作 Spine插件及基本知识可查看这篇文章&#xff1a;Unity | Spine动画记录-CSDN博客 二、Spine资源动态加载 1.官方说明 官方文档指出不建议这种操作。但spine-unity API允许在运行时从SkeletonDataAsset或甚至直接从三个导出的资产实例化SkeletonAnimation和Skel…

HNU-算法设计与分析-作业3

第三次作业【动态规划】 文章目录 第三次作业【动态规划】<1>算法实现题 3-1 独立任务最优解问题<2>算法实现题 3-4 数字三角形问题<3>算法实现题 3-8 最小m段和问题<4>算法实现题 3-25 m处理器问题 <1>算法实现题 3-1 独立任务最优解问题 ▲问…

Linux(七) 动静态库

目录 一、动静态库的概念 二、静态库的打包与使用 2.1 静态库的打包 2.2 静态库的使用 三、动态库的打包与使用 3.1 动态库的打包 3.2 动态库的使用 3.3 运行动态库的四种方法 四、总makefile 一、动静态库的概念 静态库&#xff1a; Linux下&#xff0c;以.a为后缀的…

Python专题:十五、JSON数据格式

Python的数据处理&#xff1a;JOSN 计算机的主要工作&#xff1a;处理数据 最容易处理的数据就是结构化数据 非结构化数据&#xff1a;视频&#xff0c;文件等 近些年的大数据、数据挖掘就是对互联网中的各种非结构化的数据的分析和处理 半结构化数据 明确的结构属性&…

陪诊服务运用预约小程序的效果是什么

在中高型城市里&#xff0c;陪诊师近些年也很有热度&#xff0c;已经衍生成为一个新的小众行业&#xff0c;不同医院/不同科目等其它情况针对不同群体往往很难完善&#xff0c;比如部分老年人腿脚不便、不认识字、外地语言难以沟通等&#xff0c;陪诊师的作用就尤为凸显. 对相…

[Bootloader][uboot]code总结

文章目录 1、U_BOOT_DRIVER2、DM框架dm_scan_platdatadm_extended_scan_fdt 1、U_BOOT_DRIVER 使用这个宏可以定义一个驱动实例&#xff0c;宏定义是 其中使用的struct driver结构体 使用的ll_entry_declare宏定义是 归结为 2、DM框架 1、 DM框架 DM模型抽象出了以下四个…

16.投影矩阵,最小二乘

文章目录 1. 投影矩阵1.1 投影矩阵P1.2 投影向量 1. 投影矩阵 1.1 投影矩阵P 根据上节知识&#xff0c;我们知道当我们在解 A X b AXb AXb的时候&#xff0c;发现当向量b不在矩阵A的列空间的时候&#xff0c;我们希望的是通过投影&#xff0c;将向量b投影到矩阵A的列空间中&…

ModuleNotFoundError: No module named ‘sklearn‘

ModuleNotFoundError: No module named sklearn 解决办法&#xff1a; pip install scikit-learn

7B2 PRO主题5.4.2免授权直接安装

B2 PRO 5.4.2 最新免授权版不再需要改hosts&#xff0c;直接在wordpress上传安装即可

Vue的学习 —— <网络请求库Axios>

目录 前言 正文 一、Axios基本概念 二、安装Axios 三、Axios使用方法 四、向服务器发送请求 前言 在之前的开发案例中&#xff0c;我们通常直接在组件中定义数据。但在实际的项目开发中&#xff0c;我们需要从服务器获取数据。当其他用户希望访问我们自己编写的网页时&a…

定档 11.2-3,COSCon'24 第九届中国开源年会暨开源社十周年嘉年华正式启动!

中国开源年会 COSCon 是业界最具影响力的开源盛会之一&#xff0c;由开源社在2015年首次发起&#xff0c;今年将举办第九届。 以其独特定位及日益增加的影响力&#xff0c;COSCon 吸引了越来越多的国内外企业、高校、开源组织/社区的大力支持。与一般企业、IT 媒体、行业协会举…