深入理解Linux中信号处理过程

news2025/1/9 16:35:01

       🔥🔥 欢迎来到小林的博客!!
      🛰️博客主页:✈️林 子
      🛰️博客专栏:✈️ Linux
      🛰️社区 :✈️ 进步学堂
      🛰️欢迎关注:👍点赞🙌收藏✍️留言

目录

  • 信号阻塞
    • 信号的常见概念
    • 在内核中的表示
    • 如何阻塞信号
  • 信号处理全过程
    • 理性认识

信号阻塞

信号的常见概念

  • 实际执行信号处理动作成为递达。
  • 信号从产生到递达之间的状态,成为未决。
  • 进程可以选择阻塞某个信号。
  • 被阻塞的信号产生时会保持在未决状态,直到进程接触对此信号的阻塞,才执行递达动作。
  • 阻塞和忽略是不同的,只要信号被阻塞,就不会被递达,而忽略递达的一种处理方式。

递达的三种处理方式

1.默认

一般默认的处理方式就是终止。

2.忽略

不对该信号做处理。

3.自定义

类似handler函数,自己指定函数处理信号。

默认和忽略是什么区别? 默认是一种默认的处理方式,和忽略的处理方式是直接不处理。

在内核中的表示

信号是由操作系统发送给进程的,而信号不一定会被立即处理,那么这就意味着进程必须有保存信号的能力!由此我们可以推断出,信号一定是以一个数据结构存储在进程控制块(PCB)这个结构体当中!而我们 1-31 个信号可以想象成对应的 1 - 31 个比特位。比特为1 则说明收到该信号,为0则说明没有收到该信号。

而在内核中的表示方式为:

在这里插入图片描述

block 是阻塞表,对应的数组下标是 1 - 31 个信号。 pending 是递达表,为1则说明被递达。 为0则说明没有被递达。handler 是一个函数指针数组,每个元素是下标对应的信号处理的函数指针。

如何阻塞信号

信号阻塞是一种什么场景? 简单来说! 被阻塞的信号不会被递达! 递达就是对信号的处理!再通俗一点,阻塞就是阻止对信号的处理,就是暂时先不处理该信号! 等解除阻塞后再处理该信号。

如何阻塞信号?

我们可以利用int sigprocmask(int how, const sigset_t *set, sigset_t *oset); 这个函数对指定信号进行阻塞, 而set是输入型数据,oset是输出型数据。

sigset_t

sigset_t 是一个位图,这个位图不能让用户直接操作,而是通过系统调用接口来修改这个位图。 这个位图 block 和pending都可以使用,每个bit位都是一个未决标志。而信号并不需要记录收到多少次,只需要记录收到或者没收到,对应0和1 。这个位图被称为信号集 , 在阻塞信号集中(block),这个标志位表示是否被阻塞。在未决信号集(pending)中表示是否处于未决。

信号集的操作函数

#include <<signal.h>>
int sigemptyset(sigset_t *set);   //该信号集的所有bit位置为0
int sigfillset(sigset_t *set);   //初始化set所指向的信号集,使其中所有信号的对应bit置位,表示 该信号集的有效信号包括系统支持的所有信号。
int sigaddset (sigset_t *set, int signo); //往位图添加信号
int sigdelset(sigset_t *set, int signo);  //往位图删除信号
int sigismember(const sigset_t *set, int signo); //信号集中是否包含某种信号

这四个函数都是成功返回0,出错返回-1。sigismember 包含则返回1,不包含则返回0,出错返回-1。

**sigprocmask 阻塞函数 **

int sigprocmask(int how, const sigset_t *set, sigset_t *oset);

调用函数sigprocmask可以读取或更改进程的信号屏蔽字(阻塞信号集)。调用成功返回0 ,调用失败返回-1

set是要修改的位图,oset是保存旧位图。

int how参数:

SIG_BLOCK : set 包含了我们希望添加到信号屏蔽的信号,相当于 mask = mask | set

SIG_UNBLOCK : set 包含了我们希望解除阻塞的信号,相当于 mask = mask & ~set

SIG_SETMASK : 设置当前屏蔽字为set指向的值,mask = set

sigpending 读取未决信号集

int sigpending(sigset_t *set);

读取当前进程的未决信号集,调用成功返回0,调用失败返回-1。

了解了以上操作函数之后,我们接下来可以写个代码做个小实验。

#include<stdio.h>
#include<signal.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>

void show(sigset_t* set)
{
  int i = 0;
  for(i  =1;  i <= 31 ; i++)
  {
    if(sigismember(set,i))
      printf("1");
    else printf("0");
  }
  printf("\n");
}

int main()
{

  sigset_t set,p; //定义信号集

  sigemptyset(&set); //初始化信号集
  sigemptyset(&p); 

  sigaddset(&set,2); //往set信号集添加一个2号信号
  sigprocmask(SIG_SETMASK,&set,NULL) ; //设置屏蔽信号集为set
  while(1)
  {
    sigpending(&p); //获取信号集
    show(&p); //打印信号集
    sleep(1);
  }
  return 0;
}

然后我们执行程序后 按 ctrl + c 发送2号信号,看看会发生什么。

在这里插入图片描述

我们会发现两个现象。

1.第二个比特位由 0 置 1 ,证明该信号被保存进PCB里的位图结构

2. 发送2号信号后,程序并没有终止。

所以,我们可以知道,2号信号被屏蔽了。 只有解除屏蔽时才会处理2号信号,否则信号将一直处于未决状态。

而操作系统给进程发送信号,本质就是往进程控制块(PCB)内部的位图结构的对应位置由0置1 , 阻塞信号也是相同道理, 而handler 表 存放的是处理信号的函数的地址。

信号处理全过程

要知道信号处理的全过程,我们要清楚2个概念。用户态和内核态。

用户态就是用户代码和数据被访问或执行的时候,此时所处的状态就是用户态。

OS的代码和数据被执行的时候,计算机所处的状态就叫做内核态。

理性认识

当我们的进程在执行系统调用时,会转换为内核态,因为用户态不能执行OS的代码和数据!因为用户没有权限,因为操作系统不相信任何人!

而实际上当一个进程执行的时间片到了之后,操作系统会进入内核态。把该进程下掉,然后把新执行的进程放上来,再转换为用户态执行该进程。 而CPU为了分清楚当前是内核态还是用户态,会有一个CR寄存器来保存当前的用户状态。

而我们还要知道,用户态使用的是用户级页表,每个用户级页表都是独立的,因为进程具有独立性!

而操作系统也有一份系统级页表,系统级页表被所有进程所共享!!这就是为什么在不同的进程在使用系统调用时,都能找到同一份操作系统提供的代码和数据!本质就是因为所有进程共享系统级页表!!

有了以上前置知识之后,我们就可以来剖析信号处理的全过程。

首先,进程在CPU调度时会从用户态陷入内核态 -> 陷入内核态先不着急返回,先去看是否收到信号,如果收到信号,再看该信号是否被阻塞,如果没被阻塞,那么执行对应的handler操作,如果是默认,那么直接释放这个进程,如果是忽视,那么直接返回到用户态,如果是自定义处理,那么从内核态返回到用户态,执行进程中的handler处理信号函数 -> 再陷入内核态,执行sys_sigreturn()函数 -> 返回用户态。

在这里插入图片描述

我们可以用一个无穷大的符号来总结。

在这里插入图片描述

为什么不在内核态处理信号,反而回到用户态处理信号? 原因很简单! 因为操作系统不相信任何人! 如果你在handler函数进行一些非法操作,例如 rm -r * 。那么这样的破坏是非常大的,所以要转换到用户态来处理信号。如果处理方式是忽视,那么直接返回用户态。如果是默认,那么释放掉进程。

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

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

相关文章

联想笔记本怎么关闭/开启自带键盘

搜索&#xff0c;命令提示符&#xff0c;以管理员身份运行在弹出的窗口中将下面这段代码输入进去&#xff0c;并且回车。 sc config i8042prt startdisabled&#xff0c;提示成功即可然后重启&#xff0c; 笔记本自带键盘就会关闭。如果想恢复&#xff0c; 只要以同样方法输入下…

开发板TFTP调试

问题描述 开发板和host(此处指虚拟机linux)可以平通&#xff0c;但是通过uboot tftp下载请求时一直显示T T T, 即超时 使用wireshark抓包也显示超时 措施 关闭windows和linux的防火墙 重新进行下载成功

智慧公厕,公共厕所数字化促进智慧城市管理的成效

随着科技的不断进步和城市化的快速发展&#xff0c;城市管理也面临着新的挑战和机遇。而智慧公厕作为基层配套设施&#xff0c;通过数字化提升城市管理的效能&#xff0c;成为了现代智慧城市建设的重要一环。本文以智慧公厕领先厂家广州中期科技有限公司&#xff0c;大量项目案…

MySQL学习笔记11

MySQL日期类型&#xff1a; ###㈠ DATE类型&#xff08;年-月-日&#xff09; The DATE type is used for values with a date part but no time part. MySQL retrieves and displays DATE values inYYYY-MM-DD format. The supported range is 1000-01-01 to 9999-12-31. ##…

RASP hook插桩原理解析

javaagent技术&#xff0c;实现提前加载类字节码实现hook&#xff0c;插桩技术 javassist技术ASM字节码技术 像加载jar&#xff0c;有两种方式 premain启动前加载&#xff1a;每次变动jar包内容&#xff0c;都需要进行重启服务器利用java的动态attch加载原理&#xff0c;采用pr…

查询统计当前日期往前推近七天每天的记录数

1、查询统计当前日期往前推近七天每天的记录数。 并且如果某一天没有数据&#xff0c;则该天不会显示在结果集中&#xff0c;也不会用零值补充 SELECT date_format(create_time, %Y-%m-%d), count(*) FROM your_table WHERE create_time > date_sub(curdate(), interval 6…

恒合仓库 - 采购单管理模块

采购单管理模块 文章目录 采购单管理模块一、添加采购单(核心)1.1 采购流程1.2 采购单实体类1.3 添加采购单1.3.1 Mapper1.3.2 Service1.3.3 Controller1.3.4 效果图 二、采购单管理模块2.1 仓库数据回显2.1.1 Mapper2.1.2 Service2.1.3 Controller2.1.4 效果图 2.2 采购单列表…

Docker - Docker启动的MySql修改密码

基于上篇文章《Docker - Docker安装MySql并启动》&#xff0c;在Docker中启动了mysql服务&#xff0c;但是密码设置成了123456&#xff0c;想起来学生时代数据库被盗走&#xff0c;然后邮箱收到被勒索BTC的场景还历历在目&#x1f62d;&#xff0c;密码不能再设置这么简单了啊&…

【prometheus+grafana】快速入门搭建-服务监控各插件及企业微信告警

目录 1. 安装qywechat_webhook插件通知企业微信 1.1. 新建目录/opt/prometheus/qywechathook/conf 1.2. 新建编辑wx.js文件 1.3. 运行启动容器 1.4. 查看容器启动情况 1.5 企业微信通知地址为&#xff1a; 2. 安装altermanager 2.1. 下载altermanager 2.2. 解压alterm…

Linux 远程登录(Xshell7)

为什么需要远程登录Linux&#xff1f;因为通常在公司做开发的时候&#xff0c;Linux 一般作为服务器使用&#xff0c;而服务器一般放在机房&#xff0c;linux服务器是开发小组共享&#xff0c;且正式上线的项目是运行在公网&#xff0c;因此需要远程登录到Liux进行项日管理或者…

LeetCode算法二叉树—二叉树的中序遍历

目录 94. 二叉树的中序遍历 - 力扣&#xff08;LeetCode&#xff09; 代码&#xff1a; 运行结果&#xff1a; 给定一个二叉树的根节点 root &#xff0c;返回 它的 中序 遍历 。 示例 1&#xff1a; 输入&#xff1a;root [1,null,2,3] 输出&#xff1a;[1,3,2]示例 2&am…

Linux--线程 创建、等待、退出

Linux上线程开发API概要 多线程开发的最基本概念主要包含&#xff1a;线程&#xff0c;互斥锁&#xff0c;条件。   线程 3 种操作&#xff1a;线程的创建&#xff0c;退出&#xff0c;等待。   互斥锁 4 种操作&#xff1a;创建&#xff0c;销毁&#xff0c;加锁和解锁。…

iterm2 配置自动登录跳板机,无需输入密码和google验证码

1、准备&#xff1a;编写Python脚本计算生成google身份验证码&#xff0c;参考python3 实现 google authenticator 认证/校验_我要买GTR45的博客-CSDN博客 脚本拿来就可以用&#xff0c;只需要替换脚本中的secret字段的值为自己的密钥即可 2、在~/.ssh/目录下编写expect脚本 …

多台群晖实现按计划WOL网络自动唤醒数据冷备份

几年前买了2盘位的DS218&#xff0c;但是随着照片的增加已经不够用。年中购入了4盘位的群晖DS923、2块16T西数数企业级硬盘、1块2T intel企业级 SSD 1.什么是冷备份 冷备是离线备份&#xff0c;备份好的数据可以单独存取&#xff0c;定期冷备可以保证数据安全&#xff0c;适合…

怎样快速打开github.com

1访问这个网站很慢是因为有DNS污染&#xff0c;被一些别有用心的人搞了鬼了&#xff0c; 2还有一个重要原因是不同的DNS服务器解析的速度不一样。 1 建议设置dns地址为114.114.114.114.我觉得假设一个县城如果有一个DNS服务器的话&#xff0c;这个服务器很可能不会存储…

求组合数(递归版)(杨辉三角形)

description 请编写递归函数&#xff0c;求组合数。 函数原型 double Cmb(int x, int y); 说明&#xff1a;x 和 y 为非负整数&#xff0c;且 x≥y&#xff0c;函数值为组合数 C x y ​ 。 裁判程序 #include <stdio.h> double Cmb(int x, int y); int main() { int m…

基于华为云云耀云服务器L实例下的场景体验 | Docker可视化工具Portainer

基于华为云云耀云服务器L实例下的场景体验 | Docker可视化工具Portainer 1. 简介2. 准备工作3. 工具配置3.1. 配置安全组3.2. 初始化配置Portainer 4. 使用Portainer部署MySQL容器4.1. 创建MySQL容器4.2. 连接MySQL容器 1. 简介 随着云计算时代的进一步深入&#xff0c;越来越多…

【计算机网络笔记三】传输层

端口 在网络中如何标记一个进程&#xff1f; TCP/IP 体系的传输层使用【端口号】来标记区分应用层的不同应用进程。这里说的端口是一个逻辑的概念&#xff0c;并不是实实在在的物理端口。 端口号使用 16 比特表示&#xff0c;取值范围是 0 ~ 65535&#xff0c;端口号分为以…

「大数据-2.0」安装Hadoop和部署HDFS集群

目录 一、下载Hadoop安装包 二、安装Hadoop 0. 安装Hadoop前的必要准备 1. 以root用户登录主节点虚拟机 2. 上传Hadoop安装包到主节点 3. 解压缩安装包到/export/server/目录中 4. 构建软链接 三、部署HDFS集群 0. 集群部署规划 1. 进入hadoop安装包内 2 进入etc目录下的hadoop…

分享40个Python源代码总有一个是你想要的

分享40个Python源代码总有一个是你想要的 源码下载链接&#xff1a;https://pan.baidu.com/s/1PNR3_RqVWLPzSBUVAo2rnA?pwd8888 提取码&#xff1a;8888 下面是文件的名字。 dailyfresh-天天生鲜 Django-Quick-Start freenom-自动续期域名的脚本 Full Stack Python简体中…