进程信号 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)
    { // child
        printf("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系统上都可用。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

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

第五次作业【回溯算法】 文章目录 第五次作业【回溯算法】<1> 算法分析题5-3 回溯法重写0-1背包<2> 算法分析题5-5 旅行商问题&#xff08;剪枝&#xff09;<3> 算法实现题5-2 最小长度电路板排列问题<4> 算法实现题5-7 n色方柱问题<5> 算法实现…

[论文阅读]FINE-TUNE THE PRETRAINED ATST MODEL FOR SOUND EVENT DETECTION

摘要 本研究提出了一种微调预训练模型ATST&#xff08;音频师生转换模型&#xff09;的方法&#xff0c;用于声音事件检测&#xff08;SED&#xff09;。通过引入ATST-Frame模型&#xff0c;该方法在DCASE挑战任务4数据集上取得了新的SOTA结果&#xff0c;有效解决了预训练模型…

Leetcode - 130双周赛

目录 一&#xff0c;3142. 判断矩阵是否满足条件 二&#xff0c;3143. 正方形中的最多点数 三&#xff0c;3144. 分割字符频率相等的最少子字符串 四&#xff0c;3145. 大数组元素的乘积 一&#xff0c;3142. 判断矩阵是否满足条件 本题题意&#xff0c;满足每一列的数全部…

LLama3大模型本地部署 仅需6步完成对话模型本地安装部署。附送可视化ui安装、自定义模型目录,修改模型保存地址,第三方微调模型、中文模型下载地址

本篇分为三部分 一&#xff1a;6步完成llama3大模型本地部署 二&#xff1a;8步完成llama3可视化对话界面安装 三&#xff1a;重设模型文件路径 四&#xff1a;微调模型、中文模型下载资源分享 一、LLama3 大模型本地部署安装 首先去mata官网下载ollama客户端 Ollama 选择合适…

Linux操作系统最著名的两大系列Red Hat和Debian

Linux操作系统可以根据其背后的项目或社区分为不同的系列&#xff0c;其中最著名的两大系列是Red Hat系列和Debian系列。 1.著名的两大系列是Red Hat和Debian Red Hat系列&#xff1a; Red Hat Enterprise Linux (RHEL)&#xff1a;这是Red Hat公司推出的企业级操作系统&#…

计算机网络-路由策略与路由控制一

到目前为止我们学习了路由与交换基础&#xff0c;路由协议有静态、RIP、OSPF、IS-IS等&#xff0c;但是根据实际组网需求&#xff0c;往往需要实施一些路由策略对路由信息进行过滤、属性设置等操作&#xff0c;通过对路由的控制&#xff0c;可以影响数据流量转发。 因此我们开始…

Vitis HLS 学习笔记--资源绑定-使用URAM(1)

目录 1. 简介 2. 代码分析 2.1 存储器代码 2.2 Implementation报告 2.3 存储器类型指定 2.4 存储器初始化 3. 总结 1. 简介 在博文《Vitis HLS 学习笔记--资源绑定-使用URAM-CSDN博客》中&#xff0c;介绍了如何在Vitis HLS环境下设计一个简易的存储器模型。 通过以下…

Skywalking配置traceId

1.引言 1.1 SkyWalking概述 SkyWalking是一个开源的分布式系统观测平台&#xff0c;旨在解决微服务和云原生架构中常见的性能监控和故障排除问题。自2015年由Apache基金会孵化以来&#xff0c;SkyWalking已经成为全球范围内广泛使用的APM&#xff08;应用性能管理&#xff09…

Selenium 自动化 —— 高级交互(click、sendKeys、submit、clear、select)

更多关于Selenium的知识请访问CSND论坛“兰亭序咖啡”的专栏&#xff1a;专栏《Selenium 从入门到精通》 ​​ 1. 前言 这是我的《Selenium从入门到精通》专栏的第11篇文章&#xff0c;前面花了很多时间在元素的定位上。不管是爬虫和自动化&#xff0c;找到元素后&#xff0c…

jvisualvm安装Visual GC插件

给jdk自带的jvisualvm安装Visual GC插件&#xff0c;遇到We’re sorry the java.net site has closed&#xff08;我们很抱歉java.net网站已经关闭&#xff09; 1、找到新的更新地址 visualvm新访问地址&#xff1a;https://visualvm.github.io/index.html 进入“Plugins”&am…

【介绍下Python多线程,什么是Python多线程】

&#x1f308;个人主页: 程序员不想敲代码啊 &#x1f3c6;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f44d;点赞⭐评论⭐收藏 &#x1f91d;希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff0c;让我们共…

一个可自动生成行排号的excel VBA小工具

如下图&#xff0c;点击“生成行排号”按钮即可生成想要的行排号 基本用法如下&#xff1a; 1、设置顺序排列的行排号&#xff08;每排的行号一致&#xff0c;行的方向排序方向也一致&#xff09; 2、设置顺序排列的行排号&#xff08;行号从小到大排列&#xff0c;而不受排的…

UEC++学习(十五)创建、查找、加入会话

创建会话 基于上篇配置steam在线子系统之后&#xff0c;在Character.h中声明一个会话创建完成时的委托以及回调函数。 #include "Interfaces/OnlineSessionInterface.h"public://指向在线会话界面的指针,将会话接口存储在里面TSharedPtr<class IOnlineSession, ES…

电脑缺失api-ms-win-crt-runtime-l1-1-0.dll文件的几种修复方法

当您在使用电脑过程中遇到程序启动失败&#xff0c;提示缺少“api-ms-win-crt-runtime-l1-1-0.dll”文件时&#xff0c;不必过于焦虑&#xff0c;此问题通常与Windows系统的Visual C Redistributable组件未正确安装或损坏有关。小编将介绍5种修复电脑缺失api-ms-win-crt-runtim…

STM32-09-IWDG

文章目录 STM32 IWDG1. IWDG2. IWDG框图3. IWDG寄存器4. IWDG寄存器操作步骤5. IWDG溢出时间计算6. IWDG配置步骤7. 代码实现 STM32 IWDG 1. IWDG IWDG Independent watchdog&#xff0c;即独立看门狗&#xff0c;本质上是一个定时器&#xff0c;这个定时器有一个输出端&#…

elementui 那些遇到的问题呀

1、在父组件调用子组件方法的&#xff0c;现在想关闭el-dialog 弹框&#xff0c;清除编辑器里面的值&#xff0c;结果哦方法走了但是没清空&#xff0c;原代码是这样的 父组件&#xff1a;<el-dialog closed"formulaclosed" v-model"detailsFormVisible&quo…

颜色的表示和还原(一)

这篇文章主要提炼于ICCV 2019 Tutorial: Understanding Color and the In-Camera Image Processing Pipeline for Computer Vision。里面深入浅出地讲解了很多ISP中的基础知识&#xff0c;这里主要对颜色相关的部分做一点总结。 假设不成立了 相机经常被简单地看作是衡量光线…

2022 年高教社杯全国大学生数学建模竞赛-C 题 古代玻璃制品的成分分析与鉴别详解+聚类模型Python代码源码

前言 简单介绍一下我自己&#xff1a;博主专注建模四年&#xff0c;参与过大大小小数十来次数学建模&#xff0c;理解各类模型原理以及每种模型的建模流程和各类题目分析方法。参与过十余次数学建模大赛&#xff0c;三次美赛获得过二次M奖一次H奖&#xff0c;国赛二等奖。**提…

设计模式:外观模式(Facade)

设计模式&#xff1a;外观模式&#xff08;Facade&#xff09; 设计模式&#xff1a;外观模式&#xff08;Facade&#xff09;模式动机模式定义模式结构时序图模式实现在单线程环境下的测试在多线程环境下的测试模式分析优缺点适用场景应用场景模式扩展参考 设计模式&#xff1…

21【Aseprite 作图】画白菜

1 对着参考图画轮廓 2 缩小尺寸 变成这样 3 本来是红色的描边&#xff0c;可以通过油漆桶工具&#xff08;取消 “连续”&#xff09;&#xff0c;就把红色的轮廓线&#xff0c;变成黑色的 同时用吸管工具&#xff0c;吸取绿色和白色&#xff0c;用油漆桶填充颜色 4 加上阴影…