【Linux】进程间通信之信号机制

news2024/11/25 4:31:04

文章目录

  • 信号的概念
  • 信号的产生
    • 硬件产生(按键盘中的按键)
    • 软件产生
      • kill函数
      • kill命令
      • abort函数
      • raise函数
  • 捕捉信号后的处理方式
    • 默认处理方式SIG_DFL
    • 忽略处理方式SIG_IGN
    • 自定义信号处理方式
      • signal函数
      • sigaciotn函数
  • 信号的注册
    • 进程中的未决信号集(位图)
    • 非实时信号(非可靠信号)的注册
    • 实时信号(可靠信号)的注册
  • 信号的注销
    • 非可靠信号的注销
    • 可靠信号的注销
  • 信号的捕捉流程
  • 信号阻塞
    • 信号阻塞的理解
    • 设置阻塞位图函数

信号的概念

信号是一个程序中断,且是一个软中断,收到一个信号后,具体怎么处理该信号,什么时候处理是由进程决定的,所以是软中断。

信号的种类:使用kill -l 命令可以查看有多少个信号

在这里插入图片描述

  • 1~31是非可靠信号
  • 34~64是可靠信号
  • 非可靠信号:当前信号有可能丢失,丢失就无法执行该信号
  • 可靠信号:当前信号不可能会丢失的

可以通过man 7 signal 查看信号的具体含义命令

信号的产生

硬件产生(按键盘中的按键)

  • 终止进程组合键 ctrl + c

    在程序运行过程中我们按下 ctrl + c 就可以中断进程运行

    组合键ctrl + c 本质上是SIGINT 信号(2号信号),是一个终止信号,终止正在进行的这个前台进程,该组合键对后台进程没有任何作用

  • 暂停进程组合键 ctrl + z

    在程序运行过程中我们按下 ctrl + z 就可以暂停进程运行,此时该进程的进程状态是T,意为暂停状态

    组合键 ctrl + z 本质上是SIGTSTP信号(20号信号),是一个暂停信号,让正在运行的前台程序暂停运行

  • 产生核心转储文件组合键 ctrl + |

    组合键 ctrl + | 本质上是SIGQUIT信号(3号信号),是一个结束进程并产生核心转储文件的信号

    核心转储文件:核心转储文件中存储的是程序异常终止进程产生的一个文件,进程终止瞬间将程序的内存映像,包括程序代码、变量、堆栈、寄存器状态等,以及导致程序崩溃的错误信息写到这个磁盘文件,其中信息常用于调试寻找程序错误原因

    核心转储:是指在计算机程序发生严重错误或崩溃时,操作系统将程序在崩溃瞬间的内存状态和相关信息保存到一个文件中的过程。这个文件被称为核心转储文件(Core Dump File)。核心转储通常在以下情况产生:程序崩溃:当程序遇到无法处理的错误,如访问无效内存地址、除以零等,操作系统会生成核心转储文件;异常信号:当程序收到操作系统发送的某些异常信号,如段错误(Segmentation Fault)或浮点异常(Floating Point Exception),也可能触发生成核心转储文件。

    当进程异常退出或收到信号退出时,却没有产生核心转储文件,此时可以通过ulimit -a命令查看core file size设置情况,如果它被设置为0,我们需要命令ulimit -c unlimited修改设置值为unlimited后,进程异常退出后才能产生核心转储文件

    我们可以用gdb来加载核心转储文件并检查程序的状态和堆栈,找到错误原因,修复bug

    gdb调试核心转储文件寻找错误地方:gdb [可执行文件名] [核心转储文件]

一些非法行为对应的信号量:

非法行为信号量信号名
解引用空指针11号信号并产生核心转储文件SIGSEGV
访问越界11号信号并产生核心转储文件SIGSEGV
动态分配空间free两次6号信号并产生核心转储文件SIGABRT

软件产生

kill函数

int kill(pid_t pid, int sig)

功能:kill 函数在操作系统中用于发送信号给进程,以控制和影响其行为。这些信号可以用于各种目的,包括终止进程、重新加载配置、重新启动等。需要注意的是,kill 函数的名称可能会导致误解,因为它实际上并不是用来强制终止进程的专门函数。

一些常见信号编号包括

  • 1:SIGHUP(终端挂起)
  • 2:SIGINT(中断信号,通常由Ctrl+C发送)
  • 9:SIGKILL(强制终止)
  • 15:SIGTERM(正常终止)
  • 20:SIGTSTP(挂起进程)

头文件:sys/types.h、signal.h

参数:

  • pid : 进程标识符,给哪一个进程发送信号
  • sig : 信号值,具体发送哪一个信号

返回值:

  • 成功:返回信号值
  • 失败:返回 -1

kill命令

kill命令可以指定给具体进程发送具体信号量

kill -signal pid
  • signal:信号量,可以是具体数值,也可以是信号名字
  • pid : 进程标识符

abort函数

void abort(void);

功能:可以向进程发送SIGABRT信号(6号信号),使进程异常终止,并关闭刷新进程打开的流。哪一个进程调用该函数,便向该进程传送SIGABRT信号(6号信号),其实abort内部封装了kill函数

头文件:stdlib.h

raise函数

#include <signal.h>
int raise(int sig);

功能:sig 是要发送的信号编号。调用 raise 函数会向当前进程发送指定的信号。这个函数实际上是一个库函数的封装,其底层实现会调用系统调用来发送信号。

捕捉信号后的处理方式

#define SIG_DFL	((__sighandler_t)0)	/* default signal handling */
#define SIG_IGN	((__sighandler_t)1)	/* ignore signal */
#define SIG_ERR	((__sighandler_t)-1)	/* error return from signal */

默认处理方式SIG_DFL

#define SIG_DFL	((__sighandler_t)0)	/* default signal handling */
//SIG_DFL就是 __sighandler_t结构体类型 的0

默认处理方式:SIG_ DFL:操作系统当中已经定义号信号的处理方式了,例如2号信号用于终止进程;11号信号用户终止进程,并且产生核心转储文件

忽略处理方式SIG_IGN

还记得我们之前说僵尸进程子进程先于父进程退出,子进程在退出的时候,会告知父进程,其实就是子进程向父进程发送了一个SIGCHLD信号,但是父进程接收到信息之后是忽略处理,父进程并没有回收子进程的退出状态信息,就是说父进程对SIGCHLD信号的处理方式是忽略处理,从而导致子进程变成僵尸进程。

#define SIG_IGN	((__sighandler_t)1)	/* ignore signal */
//SIG_IGN就是 __sighandler_t结构体类型 的1

自定义信号处理方式

自定义处理方式,就是让程序猿自己定义某一个信号的处理方式

signal函数

typedef void (*sighandler_t)(int);		// void handler(int)
sighandler_t signal(int signum, sighandler_t handler);

功能:当进程接收到了signum信号时,调用handler函数,执行handler函数中的代码,该信号以前需要执行的任务不再执行

头文件:signal.h

参数:

  • signum : 信号量值,要处理的信号
  • handler : 信号处理句柄,就是一个函数指针(回调函数),当进程接收到了signum这个信号时,进程需要调用handler函数去做一些事先规定好的事情

注意:因为9号信号是强杀信号,不能被自定义处理

sigaciotn函数

int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

功能:自定义信号处理方式函数:程序员定义一个函数去处理接收到的信号

头文件:signal.h

参数:

  • signum : 信号量值
  • act : 输入型参数,保存 对signum信号 所采取的措施信息
  • oldact : 输出型参数,保存 以前对signum信号 所采取的措施信息

sigaction结构体详解:

struct sigaction {
    void     (*sa_handler)(int);
    		//保存了内核对信号的处理方式的函数指针:默认处理方式和忽略处理方式
    void     (*sa_sigaction)(int, siginfo_t *, void *); 
    		//函数指针,保存自定义处理函数,但是没有使用
    		//当要使用的时候,配合sa_flags一起使用。
    		//当sa_flags的值为SA_SIGINFO的时候,信号按照sa_sigaction保存的函数地址进行处理
    sigset_t   sa_mask;		
    		//信号集位图,保存收到的信号。
   			//当进程在处理信号的时候,如果还在收到信号,
   			//则放到该信号位图当中,后续再放到进行的信号位图当中
    int        sa_flags;	//填入宏
    void     (*sa_restorer)(void);	//保留字段
};
含义
SA_SIGINFO操作系统在处理信号的时候,调用的就是sa_sigaction函数指针当中保存的函数
0操作系统在处理信号的时候,调用的就是sa_handler函数指针当中保存的函数

内核源码中的sigaction结构体源码:

struct sigaction {
	union {
	  __sighandler_t	_sa_handler;
	  void (*_sa_sigaction)(int, struct siginfo *, void *);
	} _u;
	sigset_t	sa_mask;
	int		sa_flags;
};
#define sa_handler	_u._sa_handler
#define sa_sigaction	_u._sa_sigaction

typedef char* __user __sighandler_t;
typedef struct {
	unsigned long sig[_NSIG_WORDS];
} sigset_t;

signal函数与sigaction函数的关系:

  • signal函数只是修改了sigaction结构体中的_sa_sigaction
  • sigaction函数修改了整个sigaction结构体
  • signal函数内部调用了sigaction函数

在这里插入图片描述

信号的注册

一个进程收到一个信号,这个过程称之为注册,信号的注册和注销并不是一个过程,是两个独立的过程

内核中信号注册位图以及sigqueue队列的的了解:都是task_ struct结构体内部的内容;每一个进程都有自己独有的注册位图和sigqueue队列

进程中的未决信号集(位图)

位图:进程的task_struct中位图的初始定义:

struct task_struct {
    ...
      struct sigpending pending;  
    ...
}

sigpending:内核源码的include\linux\signal.h中sigpending:

struct sigpending {
	struct list_head list;//双向链表
	sigset_t signal;
};

sigset_t:内核源码的 include\asm-generic\signal.h中定义了sigset_t:

typedef struct {
	unsigned long sig[_NSIG_WORDS];
} sigset_t;

_NSIG_WORDS:内核源码的 include\asm-generic\signal.h中定义了_NSIG_WORDS:

#define _NSIG		64
#define _NSIG_BPW	__BITS_PER_LONG
#define _NSIG_WORDS	(_NSIG / _NSIG_BPW)

__BITS_PER_LONG:内核源码的 arch\alpha\include\asm\bitsperlong.h中定义了__BITS_PER_LONG:

#define __BITS_PER_LONG 64

最终我们发现:位图就是一个unsigned long sig[1],Linux操作系统中long占8个字节,即64位
,每一个信号,在该位图中存在一个与之对应的比特位,当信号对应的比特位为1时,表示当前进程接收到该信号

在这里插入图片描述

非实时信号(非可靠信号)的注册

第一次注册:修改sig位图(0->1),修改sigqueue队列

第二次注册:相同信号值的信号,在前一个信号未被处理的前提下:修改sig位图(1->1),并不会添加sigqueue节点,也就是说再次添加,不会添加sigqueue节点

非实时信号容易信号丢失的原因就是再次注册的时候不会添加sigqueue节点

实时信号(可靠信号)的注册

第一次注册:修改sig位图(0->1),修改sigqueue队列

第二次注册:相同信号值的信号:修改sig位图(1->1),添加sigqueue节点到sigqueue队列中
,也就是说,再次添加,会再次添加siquque节点

sigqueue源码:

struct sigqueue {
	struct list_head list;
	int flags;
	siginfo_t info;
	struct user_struct *user;
};

信号的注销

非可靠信号的注销

如果信号已经处理完,则将处理完的信号对应位图中的比特位从1变成0,并将该信号的sigqueue节点从sigqueue队列中出队

可靠信号的注销

如果信号已经处理完了,则将该信号的sigqueue节点从sigqueue队列中出队,同时需要判断sigqueue队列中是否还有与出队的该信号相同的sigqueue节点:如果还有相同的sigqueue节点:则不修改位图中的对应比特位;如果没有:将该信号对应位图中的比特位从1变成0

信号的捕捉流程

在这里插入图片描述

如果没有收到信号,执行顺序为1->2->3->4
如果收到了信号,执行顺序为1->2->5->6->7->3->4

main函数调用了一个系统调用函数或者调用库函数(库函数底层也是封装的系统调用函数)后,cpu从用户态切换到内核态,内核态调用系统调用函数后想回到用户态需要调用do_signal函数,do_signal函数的功能是检查进程是否接受到信号:如果接受到信号,调用sigcb函数去处理信号(默认处理方式则不需要切换到用户态,直接在内核态进行信号处理;自定义处理方式需要切换到用户态进行信号处理),信号处理完毕后调用sig_return函数表明信号处理完,再调用do_signal函数检查是否接受到新的信号;如果未接收到信号,直接调用sys_return函数,让cpu从内核态切换到用户态

信号阻塞

信号阻塞的理解

信号的注册是信号注册, 信号阻塞是信号阻塞。信号的阻塞并不会干扰信号的注册,而是说进程收到这个信号之后,由于阻塞, 暂时不处理该信号 。

task_struct源码中定义了信号阻塞位图和信号注册位图

struct task_struct{
	...
	sigset_t blocked, real_blocked;	// 信号阻塞位图
	struct sigpending pending;	// 信号注册位图 位于这个结构体内部
	...
}

当信号阻塞位图block中对应信号的位为1,表示当前进程阻塞该信号

当进程进入内核状态,准备返回到用户态时,调用do_signal函数时,接收到了一个信号,如果该信号的阻塞位图中的对应位置为1,则不会立即去处理该信号

等到该信号的阻塞位图上的该信号对应位变成1之后才会去处理该信号,可靠信号发送了几次处理几次,非可靠信号发送大于等于1次都是处理1次

设置阻塞位图函数

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

功能:设置阻塞位图

头文件:signal.h

参数:

  • how:想让sigprocmask函数做什么事情

    • SIG_ BLOCK:设置某个信号为阻塞状态
    • SIG_ UNBLOCK:设置某个信号为非阻寒状态
    • SIG_ SETMASK:替换阻塞位图,用第二个参数“set”,替换原来的阻寨位图
  • set:新替换入的阻塞位图,可以为NULL

  • alodset:原阻塞位图,可以为NULL

SIG_BLOCK设置阻塞原理:按位或,将新的要阻塞的信号加入了阻塞位图中 (原阻塞位图 | 新阻塞位图)
SIG_UNBLOCK解除阻塞原理:按位与,将要解除阻塞的信号的比特位变成0(原阻塞位图 & 新阻塞位图)

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

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

相关文章

在python中通过调用dll来提高运行速度

1、前言 最近项目中需要录制键鼠命令&#xff0c;然后再通过注入的方式回放录制过程。一般来说&#xff0c;普通的一些点击命令完全可以通过python的一些包来完成键鼠模拟操作&#xff0c;比如通过pyautogui包。但是&#xff0c;鼠标移动过程中&#xff0c;如果采用频率很高的…

C++对象模型实验(clang虚函数表结构)

摘要&#xff1a;本科期间有对比过msvc&#xff0c;gcc&#xff0c;clang的内存布局&#xff0c;距今已经6-7年了&#xff0c;当时还是使用的c11。时间过得比较久了&#xff0c;这部分内容特别是内存对齐似乎C17发生了一些变化&#xff0c;因此再实践下C类模型。本文描述了C不同…

DuckDB Executor:物理计划构建Pipeline并执行(PipelineExecutor)

2023-03-20 duckdb-Push-Based Execution Model 如下SQL的物理计划执行 select 100(select 3), id from user where id (select id from score where id 1) Pipeline与MetaPipeline Pipeline是一串Op. MetaPipeline是pipeline组成的树 Executor构建MetaPipeline MetaPip…

【汇编语言】关于“段”的总结

文章目录 各种段三种段具体案例截图数据段、栈段、代码段同时使用不同段地址数据段、栈段、代码段同时使用一个段地址![在这里插入图片描述](https://img-blog.csdnimg.cn/45c299950ad949e3a90b7ed012b3a9ee.png) 各种段 1、基础 物理地址 段地址 x 16 偏移地址 2、做法 编…

Redis——主从复制+集群搭建(非哨兵)

主从复制 概念 主从复制&#xff0c;是指将一台Redis服务器的数据&#xff0c;复制到其他的Redis服务器。前者称为主节点(master/leader)&#xff0c;后者称为从节点(slave/follower); 数据的复制是单向的&#xff0c;只能由主节点到从节点。Master以写为主&#xff0c;Slave…

CentOS6.8图形界面安装Oracle11.2.0.1.0

Oracle11下载地址 https://edelivery.oracle.com/osdc/faces/SoftwareDelivery 一、环境 CentOS release 6.8 (Final)&#xff0c;测试环境&#xff1a;内存2G&#xff0c;硬盘20G&#xff0c;SWAP空间4G Oracle版本&#xff1a;Release 11.2.0.1.0 安装包&#xff1a;V175…

【C++奇遇记】构造函数 | 初始化列表

&#x1f3ac; 博客主页&#xff1a;博主链接 &#x1f3a5; 本文由 M malloc 原创&#xff0c;首发于 CSDN&#x1f649; &#x1f384; 学习专栏推荐&#xff1a;LeetCode刷题集 数据库专栏 初阶数据结构 &#x1f3c5; 欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如…

LeetCode:Hot100python版本之回溯

回溯算法其实是纯暴力搜索。for循环嵌套是写不出的 组合&#xff1a;没有顺序 排列&#xff1a;有顺序 回溯法可以抽象为树形结构。只有在回溯算法中递归才会有返回值。 46. 全排列 78. 子集 17. 电话号码的字母组合 39. 组合总和 22. 括号生成 79. 单词搜索 ​​​​​​13…

剪枝基础与实战(3): 模型剪枝和稀疏化训练流程

Model Pruning 相关论文:Learning Efficient Convolutional Networks through Network Slimming (ICCV 2017) 考虑一个问题,深度学习模型里面的卷积层出来之后的特征有非常多,这里面会不会存在一些没有价值的特征及其相关的连接?又如何去判断一个特征及其连接是否有价值? …

Android学习之路(7) Frament

Fragment 表示应用界面中可重复使用的一部分。fragment 定义和管理自己的布局&#xff0c;具有自己的生命周期&#xff0c;并且可以处理自己的输入事件。fragment 不能独立存在。它们必须由 activity 或其他 fragment 托管。fragment 的视图层次结构会成为宿主的视图层次结构的…

2023国赛数学建模C题思路模型代码 高教社杯

本次比赛我们将会全程更新思路模型及代码&#xff0c;大家查看文末名片获取 之前国赛相关的资料和助攻可以查看 2022数学建模国赛C题思路分析_2022国赛c题matlab_UST数模社_的博客-CSDN博客 2022国赛数学建模A题B题C题D题资料思路汇总 高教社杯_2022国赛c题matlab_UST数模社…

【Keil软件 ST-Link下载问题】:提示ST-Link需要升级,但是升级之后会报错:Upgrade Error, Please try again.

项目场景&#xff1a; 报错&#xff1a; 平常一直使用J-link&#xff0c;一直没有问题&#xff0c;今天使用了一下ST-Link发现了问题&#xff0c;下载不进去。 并且提示我需要升级ST-Link&#xff0c;我按照规定的升级&#xff0c;但是依然报错。 下面是我的心路历程&#xff…

JDK中的Timer总结

目录 一、背景介绍二、思路&方案三、过程1.Timer关键类图2.Timer的基本用法3.结合面向对象的角度进行分析总结 四、总结五、升华 一、背景介绍 最近业务中使用了jdk中的Timer&#xff0c;通过对Timer源码的研究&#xff0c;结合对面向对象的认识&#xff0c;对Timer进行针…

简单认识Docker数据管理

文章目录 为何需要docker数据管理数据管理类型 一、数据卷二、数据卷容器三、容器互联 为何需要docker数据管理 因为数据写入后如果停止了容器&#xff0c;再开启数据就会消失&#xff0c;使用数据管理的数据卷挂载&#xff0c;实现了数据的持久化&#xff0c;重启数据还会存在…

合宙Air724UG LuatOS-Air LVGL API--运行

硬件准备 可以使用 724A13 开发板 ST7735S 屏幕 或者 iCool 开发板 软件准备 底层固件 底层固件需要支持 LVGL&#xff0c;通常选用的固件是 LuatOS-Air_VXXXX_RDA8910_BT_FLOAT.pac。 当然&#xff0c;您也可以通过 更新说明 选择支持 LVGL 的其他底层版本。 脚本 首先需要…

垂类模型大有前景,但AGI却给自己“挖了个坑”

巨量模型是个“坑”&#xff0c;但垂直模型不是。 数科星球原创 作者丨苑晶 编辑丨大兔 2023年4月&#xff0c;GPT-5的相关消息引起了一阵轰动。彼时&#xff0c;人们对巨量大模型既有期待、也有恐惧。更有甚者&#xff0c;认为人类历史或许将因此而画上终止符。 但很快&#…

Java并发编程之线程池详解

目录 &#x1f433;今日良言:不悲伤 不彷徨 有风听风 有雨看雨 &#x1f407;一、简介 &#x1f407;二、相关代码 &#x1f43c;1.线程池代码 &#x1f43c;2.自定义实现线程池 &#x1f407;三、ThreadPoolExecutor类 &#x1f433;今日良言:不悲伤 不彷徨 有风听风 有…

基于 BEM 规范实现简单的全局 scss

该文章是在学习 小满vue3 课程的随堂记录示例均采用 <script setup>&#xff0c;且包含 typescript 的基础用法 前言 BEM 是 css 常用的命名规范BEM &#xff1a;block(块)、 element(元素)、 modify(修饰符)以 namespace-block__element、namespace-block---modify 格式…

【es6】中的Generator

Generator 一、Generator 是什么&#xff1f;1.1 与普通函数写法不一样&#xff0c;有两个不同 二、Generator 使用2.1 书写方法 三、yield语句3.1 yield和return3.2 注意事项3.3 yield*语句3.4 yield*应用 四、next方法4.1参数 总结 一、Generator 是什么&#xff1f; Genera…

[Go版]算法通关村第十三关青铜——数字数学问题之统计问题、溢出问题、进制问题

这里写自定义目录标题 数字统计专题题目&#xff1a;数组元素积的符号思路分析&#xff1a;无需真计算&#xff0c;只需判断负数个数是奇是偶复杂度&#xff1a;时间复杂度 O ( n ) O(n) O(n)、空间复杂度 O ( 1 ) O(1) O(1)Go代码 题目&#xff1a;阶乘尾数0的个数思路分析&am…