进程信号 signal

news2024/9/17 2:57:08

文章目录

  • 信号基础
  • 信号的产生
    • 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系统上都可用。

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

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

相关文章

Blog搭建:pycharm+虚拟环境+django

pycharm创建项目 在pycharm新建项目&#xff0c;选择Django 项目名称&#xff1a;自定义项目位置&#xff1a;自定义创建git&#xff1a;方便上传到github选择虚拟环境方式venvpython解释器位置&#xff0c;和版本&#xff0c;如果你没有下载他会跳转到不同版本的供你下载temp…

分布式计算、并行计算、网格计算、边缘计算

分布式计算 分布式计算是一种计算方法&#xff0c;它将一个大型的计算任务分解成多个子任务&#xff0c;并将这些子任务分布在网络上的多台计算机&#xff08;节点&#xff09;上同时执行。这些节点通过通信网络协同工作&#xff0c;共同完成任务。每个节点可以独立处理自己的…

Skywalking配置traceId

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

括号生成[中等]

优质博文&#xff1a;IT-BLOG-CN 一、题目 数字n代表生成括号的对数&#xff0c;请你设计一个函数&#xff0c;用于能够生成所有可能的并且 有效的 括号组合。 示例 1&#xff1a; 输入&#xff1a;n 3 输出&#xff1a;["((()))","(()())","(())(…

MATLAB车辆动力学建模 ——《控制系统现代开发技术》

引言 在上这门课之前&#xff0c;我已经用过CasADi 去做过最优化的相关实践&#xff0c;其中每一步迭代主要就是由&#xff1a;对象系统优化求解两部分组成的。这里我们重点介绍 “对象系统”如何去描述 &#xff0c;因为它是每一步迭代中重要的一环——“优化求解”会获得控制…

2024生日快乐祝福HTML源码

源码介绍 2024生日快乐祝福HTML源码&#xff0c;源码由HTMLCSSJS组成&#xff0c;记事本打开源码文件可以进行内容文字之类的修改&#xff0c;双击html文件可以本地运行效果&#xff0c;也可以上传到服务器里面&#xff0c; 源码截图 源码下载 2024生日快乐祝福HTML源码

【C++】学习笔记——多态_1

文章目录 十二、继承8. 继承和组合 十三、多态1. 多态的概念2. 多态的定义和实现虚函数重写的两个特殊情况override 和 final 3. 多态的原理1. 虚函数表 未完待续 十二、继承 8. 继承和组合 我们已经知道了什么是继承&#xff0c;那组合又是什么&#xff1f;下面这种情况就是…

哈希表+DFS快速解决力扣129题:求根节点到叶节点数字之和

❤️❤️❤️ 欢迎来到我的博客。希望您能在这里找到既有价值又有趣的内容&#xff0c;和我一起探索、学习和成长。欢迎评论区畅所欲言、享受知识的乐趣&#xff01; 推荐&#xff1a;数据分析螺丝钉的首页 格物致知 终身学习 期待您的关注 导航&#xff1a; LeetCode解锁100…

TypeScript高级类型 在鸿蒙中的使用 Partial、Required、Readonly、Pick、Record

我的工程代码在这里&#xff0c;持续更新中 欢迎交流&#xff0c;谢谢 https://github.com/MartinLi89/WanHarmony Partial <Type> 新定义 一个类型&#xff0c;将所有属性变为可选的类. class TextTS {a: string "1"b: string "2"c: string &…

(十一)Python基础练习题二(50道选择题)#Python

本文整理了Python基础知识相关的练习题&#xff0c;共50道&#xff0c;适用于刚入门初级Python想巩固基础的同学。来源&#xff1a;如荷学数据科学题库&#xff08;技术专项-Python一&#xff09;。序号之前的题请看往期文章。 51&#xff09; 52&#xff09; 53&#xff09; …

【数据结构】时间、空间复杂度实例分析

跌倒了&#xff0c;就重新站起来&#xff0c;继续向前走&#xff1b;傻坐在地上是没用的。&#x1f493;&#x1f493;&#x1f493; 目录 •✨说在前面 &#x1f34b;知识点一&#xff1a;算法的效率 • &#x1f330;1.斐波那契数列的第n项 • &#x1f330;2.算法的复杂度…

ChatGPT 4o 使用案例之一

2024年GPT迎来重大更新&#xff0c;OpenAI发布GPT-4o GPT-4o&#xff08;“o”代表“全能”&#xff09; 它可以接受任意组合的文本、音频和图像作为输入&#xff0c;并生成任意组合的文本、音频和图像输出。它可以在 232 毫秒内响应音频输入&#xff0c;平均为 320 毫秒&…

数学建模——建立数学模型(1)

前言 这个也是对《数学模型》&#xff08;姜启源第四版&#xff09;书内容的摘抄 建立数学模型 数学模型这个词汇现在越来越多地出现在现代入的 生产、工作和社会活动中&#xff0e;广大的科学技 术人员和应用数学工作者来说&#xff0c;建立数学模型是沟通摆在面前的实际问…

无人售货奶柜:掘金新零售蓝海,

无人售货奶柜&#xff1a;掘金新零售蓝海&#xff0c; 在日新月异的商业浪潮中&#xff0c;无人奶柜犹如一股清新的创业飓风&#xff0c;正以不可阻挡之势吸引着众多创业者的目光。这股新兴力量以其独到之处和庞大的市场蓝海&#xff0c;预示着一场关于健康、便捷消费方式的深…

荣耀MagicBook X 14 Pro锐龙版 2023 集显(FRI-H76)笔记本电脑原装出厂Windows11系统工厂模式安装包下载,带F10智能还原

恢复开箱状态预装OEM系统&#xff0c;适用型号&#xff1a;HONOR荣耀FRI-H76、FRI-H56 链接&#xff1a;https://pan.baidu.com/s/1Lcg45byotu5kDDSBs3FStA?pwdl30r 提取码&#xff1a;l30r 华为荣耀原装WIN11系统工厂安装包&#xff0c;含F10一键恢复功能、系统自带所有驱…

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…

航向数据之海:Spring的JPA与Hibernate秘籍

Hi&#xff0c;俺又来给大家上课啦~ 在数字化的海域中&#xff0c;掌握数据访问与集成的航海术至关重要。本文将带你揭秘Spring框架如何借助JPA与Hibernate这两大法宝&#xff0c;引领开发者在数据库的汪洋中乘风破浪&#xff01; 航向数据之海&#xff1a;Spring的JPA与Hibern…

JavaScript-BOM编程

BOM对象 1 什么是BOM2 window对象的常见属性3 window对象的常见方法4 通过BOM编程控制浏览器行为演示5 通过BOM编程实现会话级和持久级数据存储 1 什么是BOM BOM是Browser Object Model的简写&#xff0c;即浏览器对象模型。 BOM由一系列对象组成&#xff0c;是访问、控制、修…

Vue和Django前后端实现跨域

1.为什么要解决跨域&#xff1a; 前端与后端分处不同的域名&#xff0c;因为客户端访问不同源的服务端时会遭到浏览器的同源策略的拦截&#xff0c;所以我们需要配置CORS&#xff0c;处理的方式有很多&#xff0c;先来说下自己学习到的。 2.前端处理跨域&#xff1a; 前端项目是…

基于HTML5和CSS3搭建一个Web网页(一)

倘若代码中有任何问题或疑问&#xff0c;欢迎留言交流~ 网页描述 创建一个包含导航栏、主内容区域和页脚的响应式网页。 需求: 导航栏: 在页面顶部创建一个导航栏&#xff0c;包含首页、关于我们、服务和联系我们等链接。 设置导航栏样式&#xff0c;包括字体、颜色和背景颜…