当前位置: 首页 > news >正文

Linux:进程信号---信号的保存与处理

文章目录

      • 1. 信号的保存
        • 1.1 信号的状态管理
      • 2. 信号的处理
        • 2.1 用户态与内核态
        • 2.2 信号处理和捕捉的内核原理
        • 2.3 sigaction函数
      • 3. 可重入函数
      • 4. Volatile
      • 5. SIGCHLD信号

  • 序:在上一章中,我们对信号的概念及其识别的底层原理有了一定认识,也知道了信号产生的五种方式,以及core dump是什么,而本章将着重对信号的保存与处理进行讲解,去深入了解信号处理的底层逻辑,去了解什么是用户态和内核态,以及用户态和内核态转换的时机,本章还会浅谈可重入函数以及Volatile关键字

1. 信号的保存

1.1 信号的状态管理

对于普通信号而言,对于进程(是给进程的PCB发)而言,要识别自己有没有收到信号,以及收到了哪一个信号。

task_struct{
int signal;// 0000 ...... 0000普通信号,位图管理信号
}

1. 比特位的内容是0还是1,表明是否收到
2. 比特位的位置(第几个),表明信号的编号
3. 所谓的 “发信号” ,本质就是操作系统去修改task_struct的信号位图对应的比特位(写信号!!!)

问题一:信号为什么要保存?

进程收到信号之后,可能不会立即处理这个信号。此时信号不会被处理,就要有一个时间窗口。信号的范围[1,31],每一种信号都要有自己的一种处理方法.

信号的几种状态:

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

信号在内核中的表示示意图:
在这里插入图片描述

每个信号都有两个标志位分别表示阻塞(block)和未决(pending),还有一个函数指针表示处理动作。信号产生时,内核在进程控制块中设置该信号的未决标志,直到信号递达才清除该标志。在上图的例子中,SIGHUP信号未阻塞也未产生过,当它递达时执行默认处理动作。

SIGINT信号产生过,但正在被阻塞,所以暂时不能递达。虽然它的处理动作是忽略,但在没有解除阻塞之前不能忽略这个信号,因为进程仍有机会改变处理动作之后再解除阻塞。

SIGQUIT信号未产生过,一旦产生SIGQUIT信号将被阻塞,它的处理动作是用户自定义函数sighandler。如果在进程解除对某信号的阻塞之前这种信号产生过多次,将如何处理?POSIX.1允许系统递送该信号一次或多次。Linux是这样实现的:常规信号在递达之前产生多次只计一次,而实时信号在递达之前产生多次可以依次放在一个队列里。本章不讨论实时信号。

sigpending函数:
在这里插入图片描述

其中的set参数是一个输出型参数,他会将当前的pending表传出。

sigprocmask函数:
在这里插入图片描述

第一个参数how:

参数含义
SIG_BLOCKset包含了我们希望添加到当前信号屏蔽字的信号
SIG_UNBLOCKset包含了我们希望从当前信号屏蔽字中解除阻塞的信号
SIG_SETMASK设置当前信号屏蔽字为set所指向的值

第二个参数:表示要传入的block阻塞表
第三个参数:表示之前的block旧表

问题二:我要是将所以信号都进行屏蔽,信号不就不会被处理了吗?

然而操作系统不会给你这个机会的,比如9号信号和19号信号,用户就屏蔽不了

2. 信号的处理

2.1 用户态与内核态

问题一:信号是什么时候被处理的?

当我们的进程从内核态返回到用户态的时候,进行信号好的检测和处理

当我们在调用系统调用时,操作系统会将我们的用户身份转化为内核身份,然后由操作系统帮我把函数执行完,返回时,再把我的内核身份换回用户身份 ------ 操作系统是自动会做“身份”切换的,用户身份变成内核身份,或者反着来!

问题二:什么是用户态和内核态?

内核态:允许你访问操作系统的代码和数据
用户态:只能访问用户自己的代码和数据

2.2 信号处理和捕捉的内核原理

问题三:信号是如何被处理的?

在这里插入图片描述

操作系统不信任用户,不仅仅体现在不让用户访问自己,也体现在操作系统不会访问用户自己写的代码!!!所以当在内核态处理信号时,会先将该信号的pending置0,然后去执行,要是执行到了自定义行为,那么进程就要先由内核状态转化为用户态,去执行这个自定义行为,然后再变为内核态继续在操作系统中执行系统操作,然后再回到用户态,返回值。(基于用户捕捉代码)

问题四:内核如何实现信号的捕捉

在这里插入图片描述

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

在这里插入图片描述

在这个过程中,一共发生了四次状态的转换,所谓的信号的识别,其实就是在进程进入内核态时,顺手完成的任务。CPU内部的信号int 80(是一条汇编语句) 从用户态陷入内核态,这样就有权利去访问操作系统的数据了。

2.3 sigaction函数

sigaction函数:
在这里插入图片描述

sigaction结构体:

struct sigaction {void     (*sa_handler)(int);void     (*sa_sigaction)(int, siginfo_t *, void *);sigset_t   sa_mask;int        sa_flags;void     (*sa_restorer)(void);};

由于我们目前只研究普通信号,所以,该结构体当中的第二、第四和第五个参数我们不做讨论,感兴趣的小伙伴可以自行去搜索。
该函数的第一个参数是要处理的信号数字,第二个参数表示要传入的sigaction结构体,第三个参数是输出型参数,他会保存上一次的sigaction结构体的数据。

直观的代码和运行结果能让我们直接看到这个函数的作用:

#include<iostream>
#include<cstring>
#include<signal.h>
#include<unistd.h>using namespace std;void PrintPending()
{sigset_t myset;sigpending(&myset);for(int signo=31;signo>=1;signo--){if(sigismember(&myset,signo)){cout<<"1";}else{cout<<"0";}}cout<<endl;
}void handler(int signo)
{while(true){PrintPending();cout<<"signal : "<<signo<<" acted"<<endl;sleep(1);}
}int main()
{signal(SIGINT,handler);struct sigaction myset,oset;memset(&myset,0,sizeof(myset));memset(&myset,0,sizeof(oset));//sigemptyset(&myset.sa_mask);//sigaddset(&myset.sa_mask,SIGINT);myset.sa_handler=handler;sigaction(SIGINT,&myset,&oset);while(true){cout<<"i am process: "<<getpid()<<endl;sleep(1);}return 0;
}

运行结果如下:
在这里插入图片描述

由上图可知,在处理某个信号之前,该信号的pending表对应的值会先置0,然后才会执行对应的处理行动。

总结:如果某个信号正在进行处理,那么,在这个期间,这个信号的信号屏蔽字将会变成1,此时,无论外界再发送多少个该信号,都不会执行,只会将该信号对应的pending表中的值置1,但是不是执行,因为此时该信号已经阻塞了,这就防止了当该信号处理时,被重复执行,之后当这个信号处理完毕,才会取消阻塞,然后继续执行之前发的信号,并在信号执行期间继续阻塞该信号!!!

3. 可重入函数

在这里插入图片描述

当我们执行insert函数时,进行到一半,执行完p->next=head;语句后,触发了信号,执行此时信号中也有insert函数,然后执行信号中的insert,执行信号处理后,再继续执行main函数中的语句head=p;此时,由于函数重入,引发节点丢失,导致内存泄漏!!!

如果一个函数,被重复进入的情况下,出错了,或者可能出错,就叫做不可重入函数。否则就叫做可重入函数。

4. Volatile

在这里插入图片描述

在g++中是有优化选项的,编译器在编译的时候,有不同的优化级别

源代码:

#include<iostream>
#include<cstring>
#include<signal.h>
#include<unistd.h>using namespace std;int flag=0;void handler(int signo)
{cout<<" change flag 0->1 "<<endl;flag=1;
}int main()
{signal(SIGINT,handler);while(!flag);cout<<"process quit"<<endl;return 0;
}

1. O0的优化:

sigproc:sigproc.cppg++ -o $@ $^ -O1 -g -std=c++11

结果如图:
在这里插入图片描述

2. O1的优化:

sigproc:sigproc.cppg++ -o $@ $^ -O1 -g -std=c++11

结果如图:
在这里插入图片描述

3. O3的优化:

sigproc:sigproc.cppg++ -o $@ $^ -O3 -g -std=c++11

结果如图:
在这里插入图片描述

问题一:为什么优化过后,程序退不出去?

在这里插入图片描述
此时volatile int flag=0;使用volatile关键字修饰flag就能防止编译器过度优化,保持内存的可见性!!!

5. SIGCHLD信号

当子进程退出时,不是静悄悄的退出,子进程在退出的时候,会主动向父进程发送SIGCHLD(17号)信号。所以在进行进程等待的时候,我们可以采用基于信号的方式进行等待

问题一:等待的好处是什么?

1. 获取子进程的退出状态,释放子进程的僵尸。
2. 虽然不知道父子进程谁先运行(由调度器决定),但是我们清楚,一定是父进程先退出!!!
所以,还是要调用wait/waitpid这样的接口,且父进程必须保证自己是一直在运行的!

问题二:我们必须等待吗?必须调用wait吗?

Signal(17,SIG_IGN);表示忽略17号信号,子进程会自动退出!!!

总结:

本章带大家理解了信号的保存与处理,知道了信号的不同的保存状态,以及处理信号时的内核态和用户态的转化原理,还扩展了可重入函数和Volatile关键字,以及SIGCHLD信号,最后,希望这篇文章对大家有一定帮助。

http://www.xdnf.cn/news/561169.html

相关文章:

  • docker使用
  • SRS流媒体服务器,配置国标协议对接和HTTPS视频流输出功能
  • 孤岛检测应用背景及实现原理
  • 解决Query Error: [S1000][15233] 无法添加属性。‘dbo.xxx.area_ids‘ 已存在属性‘MS_Description‘。
  • PaddleOCR的Pytorch推理模块
  • 每日算法-250521
  • RISC-V IDE MRS2 开发笔记一:volatile关键字的使用
  • ArcGIS Pro 3.4 二次开发 - Arcade
  • react中运行 npm run dev 报错,提示vite.config.js出现错误 @esbuild/win32-x64
  • vue项目启动报错(node版本与Webpack)
  • 创建Workforce
  • Apollo10.0学习——cyber常用指令
  • windows7安装node18
  • DeepSeek源码解构:从MoE架构到MLA的工程化实现
  • 基于 Node.js 的 HTML 转 PDF 服务
  • 在C#中对List<T>实现多属性排序
  • PostgreSQL日常维护
  • FastAPI 支持文件下载和上传
  • Axure项目实战:智慧运输平台后台管理端-订单管理1(多级交互)
  • PDF 文档结构化工具对比:Marker 与 MinerU
  • 整除的进一步性质与最小公倍数
  • 【深度剖析】三一重工的数字化转型(上1)
  • 11-码蹄集600题基础python篇
  • 【Linux高级全栈开发】2.2.1 Linux服务器百万并发实现2.2.2 Posix API与网络协议栈
  • 智能指针RAII
  • springboot3+vue3融合项目实战-大事件文章管理系统-文章分类也表查询(条件分页)
  • 年会招标抽奖活动软件———仙盟创梦IDE
  • 【后端】【UV】【Django】 `uv` 管理的项目中搭建一个 Django 项目
  • Mysql索引实战1
  • 【人工智能发展史】从黎明到曙光01