ECF机制:信号处理

news2024/11/28 12:38:08

  ​​​​​​​

📜 本章目录:

0x00 观察接收信号

0x01 阻塞和解除阻塞信号

0x02 安全的信号处理

0x03 在信号处理器中使用安全的 I/O 函数

0x04 便携式信号处理

0x05 显式信号等待


0x00 观察接收信号

假设内核从异常处理程序中返回,并准备将控制权交给进程 p

代码层级上看:假设内核从异常处理程序中返回,并准备将控制权交给进程 p,内核计算:

\textrm{pnb }= \textrm{pending}\textrm{ \&} \sim\textrm{ blocked}

(进程 p 的待处理的非阻塞信号集合)

如果 pnb == 0:

将控制权传递给进程 p 在逻辑流程中的下一条指令。

否则:

  • \textrm{pnb} 中选择最小的非零位 k ,并强制进程 p 接收信号 k
  • 信号的接收将触发 p 执行某些动作。
  • 对于 \textrm{pnb} 中的所有非零 k ,重复上述步骤。
  • 将控制权传递给 p 在逻辑流程中的下一条指令

默认操作:每种信号类型都有预定义的默认操作,包括以下几种:

  • 进程终止
  • 进程暂停,直到通过 SIGCONT 信号重新启动
  • 进程忽略该信号

安装信号处理程序

下面的函数可以修改与接收到信号signum相关联的操作(使用man命令获取详细信息):

// 设置信号 signum 的处理函数,也称为信号处理程序或信号处理函数
sighandler_t signal(int signum, sighandler_t handler)


// 设置信号 signum 的处理行为,并且可以获取先前的处理行为
int sigaction(int signum, const struct sigaction *act, 
              struct sigaction *oldact);

signal() 函数中 handler 参数的可能取值:

  • SIG_IGN:忽略类型为 signum 的信号
  • SIG_DFL:恢复类型为 signum 的信号的默认操作
  • 否则,handler 是用户级别函数的地址:当进程接收到类型为 signum 的信号时调用该函数这被称为 "安装" 信号处理程序,执行 handler 被称为 "捕获" 或 "处理" 信号,当 handler 返回时,控制权会回到被信号中断的进程指令处。

信号处理的例子:

void sigint_handler(int sig) /* SIGINT handler */
{
    printf("So you think you can stop this with ctrl-c?\n");
    sleep(2);
}

int main(int argc, char** argv)
{
    /* Install the SIGINT handler */
    if (signal(SIGINT, sigint_handler) == SIG_ERR)
    perror("signal error");
    /* Wait for the receipt of a signal */
    while(1);
    
    return 0;
}

接收信号:

信号处理器作为并发流,信号处理器是与主程序同时运行的独立逻辑流(而不是进程):

嵌套的信号处理器,处理器可以被其他处理器中断:

0x01 阻塞和解除阻塞信号

隐式阻塞机制

  • 内核会阻塞当前正在处理的类型的任何未决信号
  • 例如,SIGINT处理器不能被另一个SIGINT中断

显式阻塞和解除阻塞机制

  • 使用 sigprocmask 函数

支持的函数

  • sigemptyset - 创建一个空的信号集合
  • sigfillset - 将所有信号号码添加到集合中
  • sigaddset - 将信号号码添加到集合中
  • sigdelset - 从集合中删除信号号码

阻塞和解除阻塞的例子:

sigset_t mask, prev_mask;
sigemptyset(&mask);
sigaddset(&mask, SIGINT);

/* Block SIGINT and save previous blocked set */
sigprocmask(SIG_BLOCK, &mask, &prev_mask);
.
.     /* Code region that will not be interrupted by SIGINT */
.
/* Restore previous blocked set, unblocking SIGINT */

sigprocmask(SIG_SETMASK, &prev_mask, NULL);

0x02 安全的信号处理

处理器因为与主程序并发运行并共享相同的全局数据结构,所以比较棘手。

  • 共享的数据结构可能会被破坏

我们将在后面深入探讨并发性问题!

现在,以下是一些指导原则,以帮助您编写安全的信号处理器。

编写安全处理器的指导原则:

G0:保持您的处理器尽可能简单

  • 例如,设置一个全局标志并返回

G1:在处理器中只调用异步信号安全的函数

  • 如果函数是可重入的(例如,所有变量都存储在栈帧上,参见CS:APP 12.7.2),或者不会被信号中断,则该函数是异步信号安全的。
  • 常用的异步信号安全的函数有:_exit、write、wait、waitpid、sleep、kill
  • 不安全的函数包括:exit、printf、sprintf、malloc

G2:在进入和退出时保存和恢复 errno 值

  • 这样,您的处理器不会影响主程序中观察到的 errno 值

G3:通过临时阻塞所有信号来保护对共享数据结构的访问

防止可能的数据结构损坏,如何实现?

  • 在处理器中调用 sigprocmask 函数(可能不稳定)
  • 当使用sigaction函数时,可以指定在处理器执行时额外阻塞哪些信号

G4:将全局变量声明为 volatile

  • 防止编译器将其存储在寄存器中
  • 如果变量存储在寄存器中,对该变量的更新可能对读取者不可见

0x03 在信号处理器中使用安全的 I/O 函数

在信号处理器中考虑使用可重入的 SIO (Safe I/O) 库,例如 sio.c 中提供的库函数。

ssize_t sio_puts(char s[])   /* Put string */
ssize_t sio_putl(long v)     /* Put long */
void sio_error(char s[])     /* Put msg & exit */

SIO库是一个安全的I/O库,专门为信号处理器设计,用于处理信号处理器中的并发问题。它提供了一组可重入的I/O函数,用于在信号处理器中进行安全的I/O操作,避免了潜在的并发问题和数据损坏。

代码例子:

ssize_t sio_puts(char s[])
{
    return write(STDOUT_FILENO, s, sio_strlen(s));
}

void sio_error(char s[])
{
    sio_puts(s);
    _exit(1);
}

0x04 便携式信号处理

在不同的UNIX操作系统中,信号处理的实现可能会有所不同。

  • 一些旧系统在捕捉信号后会将动作恢复为默认值
  • 一些系统不会阻塞正在处理的类型的信号
  • 一些被中断的系统调用可能会返回errno == EINTR

解决方案:使用 sigaction 函数

handler_t *Signal(int signum, handler_t *handler)
{
    struct sigaction action, old_action;
    action.sa_handler = handler;
    sigemptyset(&action.sa_mask);  /* Which signal will be additionally blocked */
    action.sa_flags = SA_RESTART;  /* Restart syscalls if possible */
    if (sigaction(signum, &action, &old_action) < 0)
    unix_error("Signal error");

    return (old_action.sa_handler);
}

常见错误:

volatile int ccount = 0;
void child_handler(int sig) {
	int olderrno = errno;
	pid_t pid;
	if ((pid = wait(NULL)) < 0)
		sio_error("wait error");
	ccount--;
	sio_puts("Handler reaped child ");
	sio_putl((long)pid);
	sio_puts("\n");
	sleep(1);
	errno = olderrno;
}
int main(void) {
	pid_t pid[N];
	int i;
	ccount = N;
	signal(SIGCHLD, child_handler);
	for (i = 0; i < N; i++) {
		if ((pid[i] = fork()) == 0) {
			sleep(1);
			exit(0); /* Child exits */
		}
	}
	while (ccount > 0); /* Parent spins */
}

未决信号未排队:对于每种信号类型,一位指示信号是否处于未决状态,因此任何特定类型的至多一个未决信号。所以不能使用信号来计算事件,例如 child 的终止。

修复错误:必须等待所有终止的子进程,在循环中加入 wait 以获取所有终止的子进程。

void child_handler(int sig)
{
	int olderrno = errno;
	pid_t pid;
	while ((pid = wait(NULL)) > 0) {
		ccount--;
		sio_puts("Handler reaped child ");
		sio_putl((long)pid);
		sio_puts("\n");
	}
	if (errno != ECHILD)
		sio_error("wait error");
	errno = olderrno;
}

另一个细微的错误:带有细微同步错误的简易 shell

int main(void) {
	int pid;
	sigset_t mask_all, prev_all;
	Sigfillset(&mask_all);
	Signal(SIGCHLD, handler);
	initjobs(); /* Initialize the job list */
	while (1) {
		if ((pid = Fork()) == 0) { /* Child */
			Execve("/bin/date", argv, NULL);
		}
		/* Parent */
		Sigprocmask(SIG_BLOCK, &mask_all, &prev_all);
		addjob(pid); /* Add the child to the job list */
		Sigprocmask(SIG_SETMASK, &prev_all, NULL);
	}
	exit(0);
}

void handler(int sig)
{
	int olderrno = errno;
	sigset_t mask_all, prev_all;
	pid_t pid;
	Sigfillset(&mask_all);
	while ((pid = waitpid(-1, NULL, 0)) > 0) {
		/* Reap child */
		Sigprocmask(SIG_BLOCK, &mask_all, &prev_all);
		deletejob(pid); /* Delete child from the job list */
		Sigprocmask(SIG_SETMASK, &prev_all, NULL);
	}
	if (errno != ECHILD) /* ECHILD: child does not exist */
		sio_error("waitpid error");
	errno = olderrno;
}

修复该错误:

int main(void) {
	int pid;
	sigset_t mask_all, mask_one, prev_one;
	Sigfillset(&mask_all);
	Sigemptyset(&mask_one);
	Sigaddset(&mask_one, SIGCHLD);
	Signal(SIGCHLD, handler);
	initjobs(); /* Initialize the job list */
	while (1) {
		Sigprocmask(SIG_BLOCK, &mask_one, &prev_one); /* Block SIGCHLD */
		if ((pid = Fork()) == 0) { /* Child process */
			Sigprocmask(SIG_SETMASK, &prev_one, NULL); /* Unblock SIGCHLD */
			Execve("/bin/date", argv, NULL);
		}
		Sigprocmask(SIG_BLOCK, &mask_all, NULL); /* Parent process */
		addjob(pid); /* Add the child to the job list */
		Sigprocmask(SIG_SETMASK, &prev_one, NULL); /* Unblock SIGCHLD */
	}
	exit(0);
}

0x05 显式信号等待

显式地等待 SIGCHLD 到来的程序的处理程序。

int main(void) {
	sigset_t mask, prev;
	Signal(SIGCHLD, sigchld_handler);
	Signal(SIGINT, sigint_handler);
	Sigemptyset(&mask);
	Sigaddset(&mask, SIGCHLD);
	while (1) {
		Sigprocmask(SIG_BLOCK, &mask, &prev); /* Block SIGCHLD */
		if (Fork() == 0) /* Child */
			exit(0);
		/* Parent */
		pid = 0;
		Sigprocmask(SIG_SETMASK, &prev, NULL); /* Unblock */
		/* Wait for SIGCHLD to be received (wasteful!) */
		while (!pid);
		/* Do some work after receiving SIGCHLD */
		printf(".");
	}
	exit(0);
}

如果程序是正确的,那么将会导致浪费,程序会进入忙等待循环:

while (!pid);

可能的竞争条件,可能在检查 pid 和启动暂停信号之间接收信号:

while (!pid) /* Race! */
    pause();

安全,但速度慢:最多需要一秒钟才能做出响应

while (!pid) /* Too slow! */
    sleep(1);

解决方案:sigsuspend

int sigsuspend(const sigset_t *mask)

不间断版本:

sigprocmask(SIG_SETMASK, &mask, &prev);
pause();
sigprocmask(SIG_SETMASK, &prev, NULL);

💬 代码演示:使用 sigsuspend 等待信号

int main(int argc, char** argv) {
	sigset_t mask, prev;
	Signal(SIGCHLD, sigchld_handler);
	Signal(SIGINT, sigint_handler);
	Sigemptyset(&mask);
	Sigaddset(&mask, SIGCHLD);
	while (1) {
		Sigprocmask(SIG_BLOCK, &mask, &prev); /* Block SIGCHLD */
		if (Fork() == 0) /* Child */
			exit(0);
		/* Wait for SIGCHLD to be received */
		pid = 0;
		while (!pid)
			Sigsuspend(&prev);
		/* Optionally unblock SIGCHLD */
		Sigprocmask(SIG_SETMASK, &prev, NULL);
		/* Do some work after receiving SIGCHLD */
		printf(".");
	}
	exit(0);
}

📌 [ 笔者 ]   王亦优
📃 [ 更新 ]   2022.3.
❌ [ 勘误 ]   /* 暂无 */
📜 [ 声明 ]   由于作者水平有限,本文有错误和不准确之处在所难免,
              本人也很想知道这些错误,恳望读者批评指正!

📜 参考资料 

C++reference[EB/OL]. []. http://www.cplusplus.com/reference/.

Microsoft. MSDN(Microsoft Developer Network)[EB/OL]. []. .

百度百科[EB/OL]. []. https://baike.baidu.com/.

比特科技. Linux[EB/OL]. 2021[2021.8.31 xi

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

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

相关文章

多通道 JESD204B接口FMC子卡原理图: 8 通道 125MSPS16 位 AD 采集

板卡概述 FMC129 是一款 8 通道 125MHz 采样率 16 位 AD 采集 FMC子卡&#xff0c;符合 VITA57.1 规范&#xff0c;可以作为一个理想的 IO 模块耦合至 FPGA 前端&#xff0c;8 通道 AD 通过高带宽的 FMC 连接器&#xff08;HPC&#xff09;连接至 FPGA 从 而大大降低了系统信…

Mysql group by 查询报错 1055 this is incompatible with sql_mode=only_full_group_by

文章目录 一、问题二、原因三、解决办法 一、问题 mysql8 使用 group by 查询时报错&#xff1a; 1055 - Expression #1 of SELECT list is not in GROUP BY clause and contains nonaggregated column ‘dkia_quality_db.b.id’ which is not functionally dependent on colu…

OpenCV CascadeClassifier级联分类器 人脸检测

一、CascadeClassifier的简介 CascadeClassifier是opencv下objdetect模块中用来做目标检测的级联分类器的一个类&#xff1b;简而言之是滑动窗口机制级联分类器的方式&#xff1b;早期opencv版本仅支持haar特征的目标检测&#xff0c;分别在opencv2.2和2.4之后开始支持LBP和HO…

你知道如何使用C语言实现递归吗?

本篇博客会讲解如何使用C语言实现递归&#xff0c;以及2个注意事项。 递归是什么 递归&#xff0c;简单来说&#xff0c;就是自己调用自己。C语言中&#xff0c;可以使用函数来实现递归&#xff0c;也就是让一个函数自己调用自己。举一个简单的例子&#xff1a; 请你求斐波…

微服务---分布式事务Seata(XA,AT,TCC,SAGA模式基本使用)

分布式事务 1.分布式事务问题 1.1.本地事务 本地事务&#xff0c;也就是传统的单机事务。在传统数据库事务中&#xff0c;必须要满足四个原则&#xff1a; 1.2.分布式事务 分布式事务&#xff0c;就是指不是在单个服务或单个数据库架构下&#xff0c;产生的事务&#xff0c…

开发中不可轻视的接口文档

接口文档是描述如何与软件系统中的特定接口进行交互的文档&#xff0c;通常包含接口的名称、描述、请求和响应的格式、参数、返回值、错误码、调用示例等信息。它是开发人员在设计和开发软件系统时必不可少的参考资料。 日常工作中&#xff0c;运用接口文档最多的是前后端的同…

提高效率:使用这些工具,让你开发和学习更简单

&#x1f34e;道阻且长&#xff0c;行则将至。&#x1f353; 目录 零、ChatGPT一、代码1.代码备忘清单2.菜鸟教程3.代码转图片4.代码在线运行5.LaTeX 公式编辑器6.GitCode、GitHub 等代码仓库平台 二、绘图1.Canva 可画2.Echarts Js画图3.算法可视化4.函数绘图5.遇到 Alt 截不…

236:vue+openlayers输入经纬度坐标,校验并在地图上标记点,enter提交

第236个 点击查看专栏目录 本示例的目的是介绍演示如何在vue+openlayers中输入坐标点,在地图上显示点图形。这里面校验了输入的经纬度坐标,同时使用了@keyup.enter.native来做提交处理。 直接复制下面的 vue+openlayers源代码,操作2分钟即可运行实现效果 文章目录 示例效果…

第七章SpingMVC

1.MVC模式 视图&#xff08;View&#xff09;-对应组件&#xff1a;JSP或者HTML文件 控制器&#xff08;Controller&#xff09;-对应组件&#xff1a;Servlet 模型&#xff08;Model&#xff09; -对应组件&#xff1a;JavaBean 2.MVC模式具体说明 JSP&#xff1a;负责生成动态…

python resnet实例,残差网络医学分类,基于resnet医学图像分类任务;医学图像处理实战

一&#xff0c;数据集介绍&#xff1a; 数据预处理&#xff1a; 把数据处理成相同大小&#xff1a; 数据集&#xff1a; PathMNIST:结直肠癌组织学切片&#xff1b;ChestMNIST&#xff1a;胸部CT数据集&#xff0c;来源于NIH-ChestXray14 dataset&#xff1b;DermaMNIST&#…

【刷题之路Ⅱ】LeetCode 138. 复制带随机指针的链表

【刷题之路Ⅱ】LeetCode 138. 复制带随机指针的链表 一、题目描述二、解题难点分析方法——插入拷贝节点2、将拷贝节点插入到原节点的后面3、复制原节点的random到拷贝节点中4、将拷贝节点尾插到新链表中并恢复原链表的结构 一、题目描述 原题连接&#xff1a; 138. 复制带随机…

考研拓展:汇编基础

一.说明 本篇博客是基于考研之计算机组成原理中的程序机器级代码表示进行学习的&#xff0c;并不是从汇编语言这一门单独的课程来学习的&#xff0c;涉及的汇编语言知识多是帮助你学习考研之计算机组成原理中对应的考点。 二.相关寄存器 1.相关寄存器 X86处理器中有8个32位…

【三十天精通Vue 3】第二十天 Vue 3的性能优化详解

✅创作者&#xff1a;陈书予 &#x1f389;个人主页&#xff1a;陈书予的个人主页 &#x1f341;陈书予的个人社区&#xff0c;欢迎你的加入: 陈书予的社区 &#x1f31f;专栏地址: 三十天精通 Vue 3 文章目录 引言一、Vue3 性能优化的概念1.1 为什么需要性能优化1.2 性能优化…

基于dsp+fpga+AD+ENDAC的半导体运动台高速数据采集电路仿真设计(四)

整个调试验证与仿真分析分三个步骤&#xff1a;第一步是进行 PCB 检查及电气特性测试&#xff0c;主 要用来验证硬件设计是否正常工作&#xff1b;第二步进行各子模块功能测试&#xff0c;包括高速光纤串行 通信的稳定性与可靠性测试&#xff0c; A/D 及 D/A 转换特性测…

26从零开始学Java之如何对数组进行排序与二分查找?

作者&#xff1a;孙玉昌&#xff0c;昵称【一一哥】&#xff0c;另外【壹壹哥】也是我哦 千锋教育高级教研员、CSDN博客专家、万粉博主、阿里云专家博主、掘金优质作者 前言 在上一篇文章中&#xff0c;壹哥给大家讲解了数组的扩容、缩容及拷贝方式。接下来在今天的文章中&…

深眸科技|深度学习、3D视觉融入机器视觉系统,实现生产数智化

随着“中国制造2025”战略加速落实&#xff0c;制造业生产线正在加紧向智能化、自动化和数字化转型之路迈进。而人工智能技术的兴起以及边缘算力持续提升的同时&#xff0c;机器视觉及其相关技术也在飞速发展&#xff0c;并不断渗透进工业领域&#xff0c;拓展应用场景的同时&a…

Apache Druid中Kafka配置远程代码执行漏洞(MPS-2023-6623)

漏洞描述 Apache Druid 是一个高性能的数据分析引擎。 Kafka Connect模块曾出现JNDI注入漏洞(CVE-2023-25194)&#xff0c;近期安全研究人员发现Apache Druid由于支持从 Kafka 加载数据的实现满足其利用条件&#xff0c;攻击者可通过修改 Kafka 连接配置属性进行 JNDI 注入攻…

软件架构中间件技术

中间件的定义 其实中间件是属于构件的一种。是一种独立的系统软件或服务程序&#xff0c;可以帮助分布式应用软件在不同技术之间共享资源。 我们把它定性为一类系统软件&#xff0c;比如我们常说的消息中间件&#xff0c;数据库中间件等等都是中间件的一种体现。一般情况都是…

减少 try catch ,可以这样干

软件开发过程中&#xff0c;不可避免的是需要处理各种异常&#xff0c;就我自己来说&#xff0c;至少有一半以上的时间都是在处理各种异常情况&#xff0c;所以代码中就会出现大量的try {...} catch {...} finally {...}代码块&#xff0c;不仅有大量的冗余代码&#xff0c;而且…

d3.js学习笔记①创建html文档

本人之前从未学过HTML、CSS、JavaScript&#xff0c;然而我导是做前端的&#xff0c;要求我必须在三周内掌握d3.js&#xff0c;我只能从0学起并以此记录自己的学习过程。 首先对这三种语言有一个初步的认识&#xff1a;HTML是用于搭建网页框架&#xff0c;CSS是美化网页的&…