Linux——进程信号2

news2024/11/16 16:21:54

阻塞信号

信号其他相关常见概念

  1. 实际执行信号的处理动作称为信号递达(Delivery)

  1. 信号从产生到递达之间的状态,称为信号未决(Pending)。

  1. 进程可以选择阻塞 (Block )某个信号。

  1. 被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作.

  1. 注意,阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作。

  1. 信号是可以被屏蔽或阻塞的,进程可以阻塞或屏蔽信号

阻塞和忽略的区别:忽略属于已经递达了,已经对信号做了忽略处理。阻塞根本不会对信号进行递达,让信号一直在排队。

在内核中示意图

pending就是位图,代表的含义是对应的信号是否被接收,若接收到了为1,否则为0,handler是函数指针数组,数组的下标就是信号的编号,方框里填的是函数地址

注意这俩个宏用0和1,0是默认,1是忽略

当获取到信号之后,handler并不是直接调用对应的函数,而是先强制类型转换判断是否为0或1,如果是0或1就执行0或1的动作,如果强转后不是0或1,当执行handle表时,若信号被忽略,pending位图会由1清0才会调用对应的函数

block表和阻塞有关

block也是位图,该位图结构和pending一模一样,位图中的内容代表的含义是对应的信号是否被阻塞,0代表未被阻塞,1代表被阻塞。

一个信号被处理,操作系统是怎样的处理过程呢?

操作系统向目标进程发送信号,其实就是在修改pending位图,接下来检测pengding位图,看哪些比特位是1,若有一个比特位为1,并不是直接调用该信号的处理方法,而是去block表看看该信号是否阻塞,如果被阻塞了,则对该信号不做任何处理,如果未被阻塞,我们才调用handler,执行handler的处理方法。

pending->block->handler

sigset_t

这是一个位图结构,是操作系统提供的数据类型,这个位图不允许用户自己进行操作,OS给我们提供了对应操作位图的方法。用户是可以直接使用该类型的,和用内置类型还有自定义类型没任何的差别。而且这个类型需要对应的系统接口,来完成对应的功能,其中系统接口需要的参数,可能就包含了sigset_t定义的变量或对象。

从上图来看,每个信号只有一个bit的未决标志,非0即1,不记录该信号产生了多少次,阻塞标志也是这样表示的。

因此,未决和阻塞标志可以用相同的数据类型sigset_t来表示,sigset_t称为信号集,这个类型可以表示每个信号的“有效”或“无效”状态,在阻塞信号集中“有效”和“无效”的含义是该信号是否被阻塞,而在未决信号集中“有效”和“无效”的含义是该信号是否处于未决状态。下一节将详细介绍信号集的各种操作。 阻塞信号集也叫做当前进程的信号屏蔽字(Signal Mask),这里的“屏蔽”应该理解为阻塞而不是忽略。

block信号集:信号屏蔽字

信号集操作函数

#include <signal.h>
int sigemptyset(sigset_t *set);//清空位图
int sigfillset(sigset_t *set);//位图全为1
int sigaddset (sigset_t *set, int signo);//添加特定信号到信号集
int sigdelset(sigset_t *set, int signo);//删除某信号
int sigismember(const sigset_t *set, int signo);//判定一个信号是否在该位图中

sigpending

参数是一个信号集,通过该函数可获取当前调用进程的pending信号集,返回值成功就是0,失败错误码被设置

sigprocmask

检查并且更改block信号集,返回值成功0,失败设置错误码

第一个参数代表用什么方式调用,第三个参数时输出型参数,返回老的信号屏蔽字。

小测试

  1. 如果我们对所有的信号都进行了自定义捕捉--我们是不是就写了一个不会被异常或用户杀掉的进程?不是

  1. 如果我们对所有的信号都进行block--我们是不是就写了一个不会被异常或用户杀掉的进程?不是

  1. 如果我们将2号信号block,并且不断的获取当前进程的pengding信号集,如果我们突然发送一个2号信号,我们就该肉眼看到pengding信号集中,有一个比特位由0变到1

我们发现9号信号不可被捕捉。a验证完毕

#include <iostream>
#include <signal.h>
#include <unistd.h>
#include <cassert>
static void showPending(sigset_t& pending)
{
    for (int sig = 1; sig <= 31; sig++)
    {
        if (sigismember(&pending, sig))//sig信号是否在pending这个集合当中
            std::cout << "1";
        else
            std::cout << "0";
    }
    std::cout << std::endl;
}
int main()
{
 // 1. 定义信号集对象
    sigset_t bset, obset;
    sigset_t pending;
    // 2. 初始化
    sigemptyset(&bset);
    sigemptyset(&obset);
    sigemptyset(&pending);
    // 3. 添加要进行屏蔽的信号
    sigaddset(&bset, 2 /*SIGINT*/);
    // 4. 设置set到内核中对应的进程内部[默认情况进程不会对任何信号进行block]
    int n = sigprocmask(SIG_BLOCK, &bset, &obset);
    assert(n == 0);
    (void)n;

    std::cout << "block 2 号信号成功...., pid: " << getpid() << std::endl;
    // 5. 重复打印当前进程的pending信号集
    int count = 0;
    while (true)
    {
        // 5.1 获取当前进程的pending信号集
        sigpending(&pending);
        // 5.2 显示pending信号集中的没有被递达的信号
        showPending(pending);
        sleep(1);
    }
    return 0;
}

此时我们可以看到2号信号已经被屏蔽

屏蔽2号信号,仅仅是修改了当前的pengding位图,将对应2号信号的比特位由0变为1,并不代表当前收到了2号信号,我们当前没2号信号,pengding位图中2号信号对应的比特位依旧是0,我们现在可以确定的是如果发送了2号信号,2号信号一定不会被递达,这个2号信号就永远保存在pending表当中

我们发送二号信号后,该二号信号一直在pengding表中,我们此时正在打印的正是penging表,此时频幕上会出现1

c验证完毕

在c的基础上,我们对2号信号进行恢复,再观察现象

我们看到pengding中2号信号对应的比特位没有从1变成0,而是直接终止了,这是因为默认情况下恢复对于2号信号block的时候确实会进行递达,但是2号信号的默认处理动作是终止进程,因此我们需要对2号信号进行捕捉。

此时我们可以观察到pending位图由0变成了1

我们可以看出先打印捕捉,然后打印解除,我们的预期是先打印解除,然后再打印捕捉。这是因为我们在写代码的时候把捕捉放到了前面,若想先解除再捕捉,我们把解除这句话放在捕捉前面即可。

我们发现貌似没有一个接口可以用来设置pending位图,但是我们是可以获取的sigpending,因为所有的信号发送方式,都是修改pending位图的过程,所以我们不需要特定的接口来修改pending位图

当捕捉方法执行完后,pending位图由1置0,便于下一次捕捉。

 i=1; id=$(pidof mysignal); while [ $i -le 31 ]; do echo "i: $i, id: $id"; let i++;sleep 1; done
i=1; id=$(pidof mysignal); while [ $i -le 31 ]; do kill -$i $id; let i++;sleep 1; done

当我们发到9号信号的时候,进程被终止,说明9号信号不能被进行屏蔽或阻塞,9号信号不可被捕捉和屏蔽b验证完毕

当发到19号信号时,进程也会被终止,我们可以看到20号信号也没被block,但我们的进程并未终止,20号信号可能被忽略了。

信号处理

信号相关的数据字段都是在进程PCB内部,PCB内部属于内核范畴,要检测信号或检测当前信号是否被屏蔽一定在内核状态,当执行代码的状态叫用户态。

处理信号在内核态中,从内核态返回用户态的时候进行信号的检测和处理。

用户态是一个受管控的状态,如受访问权限的约束。

内核态是一个操作系统执行自己代码的一个状态,具备非常高的优先级,基本不受任何资源的约束和权限的管控。

为什么会进入内核态?

一般进行系统调用会进入内核态,当有缺陷陷阱异常等也会进入内核态。汇编语句中有int 80这条语句内置在了系统调用函数中,当执行这条语句后,我们便会进入内核态

用户级页表每个进程都有,而且不一样,因为进程具有独立性,每个进程都有3-4G的地址空间给内核用。

内核级页表可以被所有进程看到。内核级页表可以把内核地址空间中的数据映射到物理内存

软件层面:如我们要调用OPEN接口,OPEN相关的代码在内核地址空间当中,调用OPEN的时候跳转到内核地址空间,然后经过页表查找到对应的物理内存。

当执行进程切换的代码时:当进程时间片到了,操作系统底层硬件发送时钟中断,由于当前进程还在CPU上,操作系统在CPU中找到当前正在执行的进程,然后通过该进程的地址空间找到对应的切换进程的函数,然后在进程的上下文中进行切换,因此能直接访问该进程在CPU中的临时数据,然后所有的临时数据被压倒PCB当中,进而把进程放下去,然后选择一个进程再上来,操作系统继续使用下一个进程3-4G的地址空间和内核级页表,然后恢复上下文代码和数据,然后把上一个进程恢复上来。

内核是在所有进程的地址空间上下文数据跑的。

我们执行OS的代码依靠的是处于内核态还是用户态。

硬件层面:CPU寄存器有俩套,其中一套可见,另一套不可见(CPU自用),其中CR3寄存器中用比特位表示当前CPU的执行权限,如1表示内核态,3表示用户态。当执行int 80后,CPU内寄存器状态由内核态改为用户态

信号捕捉

  1. 当正在执行用户代码时,可能会遇到一些情况,而导致陷入内核,列入:进程的系统调用或进程时间片到了,此时会发生进程调度,会在当前进程的上下文执行调度函数。在执行调度函数之前会陷入内核。即时间片到了->陷入内核->执行调度函数,当陷入内核之后OS去查找原因,找为什么会陷入内核,当OS调度进程的时候,在调度期间进程可能会收到信号,即被调度。

  1. 当从用户态进入内核态,我们一定是在完成某种行为如打开文件或读写网络等等,当我们把所对应的工作做完了,OS就准备给我们从内核态返回到用户态,返回到曾经被中断的地方继续向后执行。

  1. 当返回的时候,OS会顺手处理信号,先检测当前进程所对应的pending位图,如果全为0,直接返回,若有1,再检测block位图,之后再根据block确定是否执行handle。当执行handle表时,若信号被忽略,pending位图会由1清0,然后直接返回继续执行剩下的代码。若是默认,默认大多数是终止,即不调度进程,把进程的PCB页表全部释放掉。我们之前的_exit就是进程终止,但这种终止方式不会返回用户态。系统中有一些系统调用会在进程终止之前,帮我们返回用户态,之后执行特定的方法。在进程终止的时候,帮我们执行设定好的用户返回调用。

有一些信号不会退出,如暂停,当某个进程收到暂停信号后,OS会把该进程放到等待队列里,然后重新选择进程去调度,所以该进程进入了暂停状态。

当我们检测到信号要被捕捉,要执行对应的信号捕捉方法时,我们身份是内核即操作系统,而且这个状态能执行用户设置好的handler方法。

OS能做到帮用户执行对应的handler方法,但是它不愿意,也不想。若以操作系统内核态的身份去执行handler,handler中若有非法操作,则会引起大麻烦。OS不相信任何人,因此不能用内核态执行用户的代码。因此执行handler方法时,需要从内核态变成用户态。

  1. 当我们把信号处理完毕,此时需将pending中的比特位由1置0,此时需要进入内核态修改,而且同时要返回用户陷入内核的地方,将曾经的代码继续向后执行(这里需要在内核态中将函数的栈帧结构等恢复,然后跳回用户态继续向后执行后续代码),这里从用户态到内核态的系统接口是sys_sigreturn。

横线上面是用户态,下面是内核态,蓝色圆圈代表状态的切换,箭头代表从某个状态跳转到另一个状态。

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

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

相关文章

一文4000字用Jmeter +Maven+jenkins实现接口性能全自动化测试

背景&#xff1a; 首先用jmeter录制或者书写性能测试的脚本&#xff0c;用maven添加相关依赖&#xff0c;把性能测试的代码提交到github&#xff0c;在jenkins配置git下载性能测试的代码&#xff0c;配置运行脚本和测试报告&#xff0c;配置运行失败自动发邮件通知&#xff0c…

分布式id解决方法--雪花算法

uuid&#xff0c;jdk自带&#xff0c;但是数据库性能差&#xff0c;32位呀。 mysql数据库主键越短越好&#xff0c;Btree产生节点分裂&#xff0c;大大降低数据库性能&#xff0c;所以uuid不建议。 redis的自增&#xff0c;但是要配置维护redis集群&#xff0c;就为了一个id&a…

【前后端分离博客】学习笔记01 --- 登录模块Sa-Token

前言 用于记录自己学习博客项目的流程 基于Springboot Vue3 开发的前后端分离博客 项目源码&#xff1a;Blog: 基于SpringBoot Vue3 TypeScript Vite的个人博客&#xff0c;MySQL数据库&#xff0c;Redis缓存&#xff0c;ElasticSearch全文搜索&#xff0c;支持QQ、Gite…

20230509MTCNN2

卷积切分图片 怎么切分图片? 使用opencv,PIL切分图片有什么问题? 慢 使用 卷积来切分图片 卷积的运算过程 类似于切图 卷积 对输入图片的尺寸 有没有 的要求? 就是 输入的图片尺寸 必须大于 卷积核的大小 test1.py import torch from torch import nn""&quo…

springboot + vue3实现视频播放Demo

文章目录 学习链接前言ffmpeg安装ffmpeg配置环境变量分割视频文件 后台配置WebConfig 前端代码video.js示例安装依赖视频播放组件效果 Vue3-video-play示例安装依赖main.js中使用视频播放组件效果 学习链接 ffmpeg官网 长时长视频java存储及vue播放解决方法 【 攻城略地 】vue…

BitKeep逆势崛起:千万用户的信任,终点还未到来

在全球范围内&#xff0c;BitKeep钱包如今已拥有超过千万忠实用户。 当我得知这一令人震撼的数字时&#xff0c;既感到惊讶&#xff0c;同时也觉得这是意料之中的事情。几年来关注BitKeep的发展历程&#xff0c;我深切地感受到了这家公司的蓬勃壮大。回顾2018年他们发布的第一个…

linux0.12-8-9-fork.c

[362页] 1、 verify_area函数给其他文件使用的&#xff0c;跳转开始位置&#xff1b; 2、 copy_mem函数复制内存页表&#xff1b; 3、 copy_process函数是fork.c主要函数&#xff1b; 4、find_empty_process函数就2个作用&#xff1a;在一个范围内找last_pid和找空槽&#xff1…

如何利用互联网优势进行茶叶销售?

茶叶是中国传统文化的重要组成部分&#xff0c;具有丰富的文化内涵和高度的营养价值。如今&#xff0c;随着互联网的普及&#xff0c;越来越多的茶叶销售商&#xff08;文章编辑ycy6221&#xff09;开始利用互联网的优势来开拓市场。本文将介绍如何利用互联网优势进行茶叶销售。…

SecureCRT的下载安装

亲测成功了&#xff0c;按照下面的步骤完成即可&#xff01; 下载安工具包包地址连接&#xff1a;网盘地址点击即可 提取码&#xff1a;0lp7 1、下载SecureCRT 2、从百度网盘下载SecureCRT&#xff0c;页面如下 3、安装SecureCRT 4、激活SecureCRT 第一步&#xff1a;打开安装…

自学Java怎么找工作?好程序员学员大厂面试经验分享!

简历要详细&#xff1a; 简历中的项目用到的技术点和个人负责的模块尽量写详细一些。其次&#xff0c;根据自己项目中用到的熟悉的技术点&#xff0c;在个人技能介绍模块去突出&#xff0c;面试官基本会根据你简历上写的去提问的&#xff0c;这样我们回答起来就会更加得心应手。…

【多线程初阶四】单例模式阻塞队列

目录 &#x1f31f;一、单例模式 &#x1f308;1、饿汉模式 &#x1f308;2、懒汉模式&#xff08;重点&#xff01;&#xff09; &#x1f31f;二、工厂模式 &#x1f31f;三、阻塞式队列 &#x1f308;1、阻塞队列是什么&#xff1f; &#x1f308;2、…

如何注册appuploader账号​

如何注册appuploader账号​ 我们上一篇讲到appuploader的下载安装&#xff0c;要想使用此软件呢&#xff0c;需要注册账号才能使用&#xff0c;今​ 天我们来讲下如何注册appuploader账号来使用软件。​ 1.Apple官网注册Apple ID​ 首先我们点击首页左侧菜单栏中的“常见网…

为什么企业选择局域网即时通讯软件?局域网即时通讯软件哪家好?

在当今互联网普及的时代&#xff0c;企业内部的沟通对企业管理有着非常重要的意义&#xff0c;即时通讯软件已成为企业工作中广泛采用的沟通工具。 然而&#xff0c;随着企业内部敏感信息通过互联网泄露的频繁发生&#xff0c;例如在工作期间&#xff0c;企业员工自发地频繁使…

盘点四款免费在线采购管理系统

今天来盘点五款免费在线采购管理系统。中小型企业在选择采购管理系统时成本是需要考虑的重要因素之一&#xff0c;因此免费在线的采购管理系统是最合适的第一步选择&#xff0c;本文将为您盘点免费在线采购管理系统&#xff1a;1.简道云&#xff1b;2.甄云&#xff1b;3.携客云…

【正点原子STM32连载】 第九章 STM32启动过程分析 摘自【正点原子】STM32F103 战舰开发指南V1.2

1&#xff09;实验平台&#xff1a;正点原子stm32f103战舰开发板V4 2&#xff09;平台购买地址&#xff1a;https://detail.tmall.com/item.htm?id609294757420 3&#xff09;全套实验源码手册视频下载地址&#xff1a; http://www.openedv.com/thread-340252-1-1.html 第九章…

Redis可持久化详解1

目录 Redis可持久化 以下是RDB持久化的代码示例&#xff1a; 面试常问 1什么是Redis的持久化机制&#xff1f; 2Redis支持哪些持久化机制&#xff1f;它们有什么区别&#xff1f; 3Redis的RDB持久化机制的原理是什么&#xff1f; 4Redis的AOF持久化机制的原理是什么&…

《三》包管理工具 npm

包管理工具 npm&#xff1a; npm&#xff1a;Node Package Manager&#xff0c;Node 包管理器&#xff0c;目前已经不仅仅作为 Node 的包管理工具&#xff0c;也作为前端的包管理工具来管理包。 npm 管理的包是存放在一个名为 registry 的仓库中的&#xff0c;发布一个包时是…

分享推荐32位MCU低成本替换8/16位升级完美之选——MM32G0001

灵动微在嵌入式闪存技术上做了优化&#xff0c;采用更稳定和经大规模量产验证的12寸晶圆工艺平台&#xff0c;对产品的功能、性能和成本进行了全方位的打磨&#xff0c;在保持MM32品质目标的前提下&#xff0c;推出了这款极具性价比、低成本的MM32G0001系列MCU产品。 不同于市…

Nuxt学习笔记

创建项目 npx create-nuxt-app projectName SSR 渲染流程 客户端发送 URL 请求到服务端&#xff0c;服务端读取对应的 URL 的模板信息&#xff0c;在服务端做出 HTML 和数据的渲染&#xff0c;渲染完成之后返回整个 HTML 结构给客户端。所以用户在浏览首屏的时候速度会比较快…

Scala学习(四)

文章目录 1.闭包2.函数式编程递归和尾递归2.1递归2.2 尾递归 3.控制抽象3.1 值调用3.2 名调用 4.惰性函数 1.闭包 如果一个函数&#xff0c;访问到了它的外部(局部)变量的值&#xff0c;那么这个函数和它所处的环境称之为闭包 //闭包练习def sumX(x:Int){def sumY(y:Int):Int{…