35 信号处理

server/2024/12/22 23:35:03/

什么时候捕捉

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

什么时候处理

当进程从内核态返回用户态的时候,进行信号的检测和处理。内核态让进程可以访问os的代码和数据。例如调用系统调用时,操作系统会做身份切换,变成内核身份,cpu的int80中断,可以让用户态陷入内核态

在这里插入图片描述
在这里插入图片描述

整个信号处理过程类似一个8,中间的横线上面是用户,下面是内核,从用户代码开始,需要四个状态切换,中间的交点就是信号检测的时候。信号的处理函数是在用户态运行的,信号检测到后要切换到用户态处理信号,因为os不信任用户代码,如果有非法行为不能在内核执行,所以在用户态执行完后栈中的sigreturn返回到内核中,才知道主函数执行到哪了,返回用户态继续执行

就算进程中没有任何系统调用,库函数等,在时钟片到达,进程剥离cpu的时候也需要陷入内核才可以

3. 重新看地址空间

在这里插入图片描述

用户空间的地址有页表来映射,os空间也有内核的页表映射找到物理地址。用户页表有几个进程就要有几个,内核页表只需要有一份。因为每一个进程看到的3-4GB的内容都是一样的,和动态库一样。整个系统中,进程再怎么切换,3-4GB的内容是不变的。在进程视角中,调用系统方法,就是在自己的地址空间中进行,os视角,任何时刻,由进程执行os代码,可以随时执行

在这里插入图片描述

进程由os来推动运行,那os又是由谁来推动运行的
os本质是基于时钟中断的一个死循环。在硬件中,有一个时钟芯片,每隔很短的时间向计算机发送时钟中断,os收到后从执行pause停止,执行相应的中断任务,如进程的调度

计算机中的时间无论连不联网都是准确的,这是因为内部有一个一直运行的时钟芯片,关机的时候也在运行,计数器一直++,然后和上一次时间做计算得到现在的时间

如何判断内核权限

在这里插入图片描述

一个进程想访问os的内容是不被允许的,cpu中有cr3寄存器指向的进程页表,ecs寄存器中低两位的数值用来判断当前是用户态还是内核态,只有是内核态,才有资格访问os,想要修改这个状态,cpu提供了int80陷入内核的方法,可以改为内核态或用户态。除此之外,想访问内核仍有很多限制

捕捉函数

sigaction

#include <signal.h>
int sigaction(int signo, const struct sigaction *act, struct sigaction *oact);

函数可以读取和修改指定信号相关联的处理动作。调用成功则返回0,出错返回-1,signo是指定信号的编号。若act指针非空,则根据act修改该信号的处理动作。若oact指针非空,则通过oact传出该信号原来的处理动作,act和oact指向sigaction及饿哦固体
将sa_handler赋值为常数SIG_IGN传给sigaction表示忽略信号,复制为常数SIG_DFL表示执行系统默认动作,赋值为一个函数指针表示用户自定义函数捕捉信号,或者说向内核注册了一个信号处理函数,该函数返回值为void。可以带一个int参数,通过参数可以得知当前信号的编号,这样就可以用同一个函数处理多种信号。显然,这也是一个回调函数,不是被main函数调用,而是被系统所调用

当谋和信号的处理函数被调用时,内核自动将当前信号加入进程的信号屏蔽字,当信号处理函数返回时自动回复原来的信号屏蔽字,这样就保证了在处理某个信号时,如果这个信号再次产生,那么它会被阻塞到当前处理结束为止。如果在调用信号处理时,除了当前信号被自动屏蔽之外,还希望自动屏蔽另外一些信号,则用sa_mask字段说明这些需要额外屏蔽的信号,当信号处理函数返回时自动恢复原来的信号屏蔽字。so_flags字段包含一些选项,设置为0,sa_sigaction是实时信号的处理函数

sigaction结构

在这里插入图片描述

主要关注第一个和第三个参数,第一个是自定义的函数。第三个参数当这个信号被屏蔽的时候还希望同时屏蔽其他信号,可以设置

测试这个函数的基本捕捉功能

#include <signal.h>
#include <stdio.h>
#include <cstring>
#include <unistd.h>
#include <iostream>using namespace std;
void handler(int signo)
{printf("catch a signo:%d\n", signo);
}int main()
{struct sigaction act;memset(&act, 0, sizeof(act));act.sa_handler = handler;sigaction(2, &act, nullptr);while (true){printf("%d\n", getpid());sleep(1);}return 0;
}

在这里插入图片描述

当一个信号正在被处理时,这个信号会被阻塞,防止信号处理的嵌套调用。只有在处理完毕后才会返回继续检测。信号的pend位图是在什么时候修改的,关于这两个问题验证一下:

#include <signal.h>
#include <stdio.h>
#include <cstring>
#include <unistd.h>
#include <iostream>using namespace std;
void printblock()
{sigset_t set, ost;sigprocmask(SIG_BLOCK, nullptr, &ost);printf("block: ");for (int i = 31; i >= 1; i--){if (sigismember(&ost, i)){printf("1");}else{printf("0");}}printf("\n");
}void printpend()
{sigset_t set;sigpending(&set);printf("pend: ");for (int i = 31; i >= 1; i--){if (sigismember(&set, i)){printf("1");}else{printf("0");}}printf("\n");
}void handler(int signo)
{printpend();  //打印pendprintblock(); //打印blockint n = 5;while (n > 0){printf("catch a signo:%d\n", signo);sleep(1);n--;}
}int main()
{struct sigaction act;memset(&act, 0, sizeof(act));act.sa_handler = handler;sigaction(2, &act, nullptr);while (true){printf("%d\n", getpid());printblock();sleep(1);}return 0;
}

在这里插入图片描述在这里插入图片描述

结论
图1:当收到2号信号到达处理函数时,pend表已经将信号修改为无。block表修改为阻塞状态,当执行完后解除阻塞
图2:信号递达时,由于信号被os设置为阻塞,再次发送信号,pend表位1,但不会再递达,处于未决状态,递达完毕后才会再次递达,递达过程中只会保存一次信号

信号递达时可以让屏蔽多个信号,返回时自动解除

	struct sigaction act;memset(&act, 0, sizeof(act));act.sa_handler = handler;sigemptyset(&act.sa_mask);sigaddset(&act.sa_mask, 4);sigaddset(&act.sa_mask, 5);

可重入函数

在这里插入图片描述

上面是一个链表的操作,main函数在链表里插入了node1,在插入的过程中收到了信号,信号的处理动作是在相同位置插入node2节点,当插入完成后回到insert函数,又改变了头节点的指向,指到node1节点,完成了node1的插入。此时node2节点没有节点指向它,就变了内存泄露的一个节点

main函数调用insert函数向一个链表head中插入节点node1,插入操作分为两步,刚做完第一步的时候,因为硬件中断使进程切换到内核,再次回到用户态之前检查到有信号待处理,于是切换到sighandler函数,sighandler函数也调用insert函数向同一个链表head中插入节点node2,插入操作的两步都做完后返回内核态,再次回到用户态从main函数调用的insert函数中继续往下执行,闲情做第一步之后杯打断,现在继续做完第二步。结果是,main函数和sighanler先后,向链表中插入两个节点,而最后只有一个节点真正插入链表中

像上例这样,insert函数被不同的控制流程调用,有可能在第一次调用还没返回时就再次进入该函数,这称为重入,insert函数访问一个全局链表,有可能因为重入而造成错乱,像这样的函数称为不可重入函数。反之,如果一个函数只访问自己的局部变量或参数,则称为可重入函数。为什么两个及控制流程调用同一个函数,访问它的同一个局部变量或参数不会造成错乱?
因为sighandler的函数和main调用的是两个栈空间,局部变量不会造成冲突

如果一个函数符合以下条件之一则是不可重入的:

  • 调用了malloc或free,因为malloc也是用全局链表来管理堆的
  • 调用了标准I/O库函数,标准I/O库的很多实现都是不可重入的方式使用全局数据结构

volatile

下面的一个代码,main函数中对flag变量没有做改变,编译器识别后可能会将flag全局变量优化到寄存器中

#include <signal.h>
#include <stdio.h>
#include <cstring>
#include <unistd.h>
#include <iostream>using namespace std;
void printblock()
{sigset_t set, ost;sigprocmask(SIG_BLOCK, nullptr, &ost);printf("block: ");for (int i = 31; i >= 1; i--){if (sigismember(&ost, i)){printf("1");}else{printf("0");}}printf("\n");
}void printpend()
{sigset_t set;sigpending(&set);printf("pend: ");for (int i = 31; i >= 1; i--){if (sigismember(&set, i)){printf("1");}else{printf("0");}}printf("\n");
}
int flag = 0;
void handler(int signo)
{printf("catch a signo:%d\n", signo);flag = 1;
}int main()
{signal(2, handler);//flag可能会优化到寄存器变量while (!flag);printf("process quit\n");return 0;
}

在这里插入图片描述
没有优化时,发送2号新号会退出
在这里插入图片描述

当我们编译时加入O1优化,进程就不能退出了
这时因为默认编译不做优化,O1优化后,os将这个变量优化到寄存器中。一般情况下,访问变量都要从内存中读取,寄存器变量后,发送信号,内存中的值修改了,但cpu只访问寄存器中的值,导致内存不可见了。register关键字就是建议优化为寄存器变量,只是建议,最终结果还是看具体情况

volatile关键字的作用:
保存内存的可见性,告知编译器,被修饰的关键字的变量,不允许被优化,对该变量的任何操作,都必须在真是的内存中进行

SIGCHLD信号

子进程在退出后,会向父进程发送SIGCHLD(17)信号

#include <unistd.h>
#include <signal.h>
#include <stdio.h>void handler(int signo)
{printf("catch a signo: %d", signo);
}int main()
{signal(17, handler);pid_t id = fork();if (id == 0){sleep(5);}while (true){sleep(1);}
}

在这里插入图片描述

等待的好处
信号的方式还是要调用wait/waitpid这样的接口
1.获取子进程的退出状态,释放子进程的僵尸
2.虽然不知道父子谁先运行,但一定是父进程最后退出

如果有多个进程需要回收,当回收第一个进程的时候,会把这个信号屏蔽掉,这时如果好几个进程都发了信号,就会得不到回收,还是僵尸进程,这种情况可以通过判断wait返回值,只要有需要回收的就一直回收

void handler(int signo)
{pid_t rid;while (rid = waitpid(-1, nullptr, 0) > 0){printf("catch a signo: %d", signo);    }}

阻塞方式如果回收一半,就会一直卡在判断里,所以采取非阻塞方式等待可以回收多个进程

当父进程不关心子进程的结果,可以让子进程自动清理,不需要通知父进程,可以将信号处理方式设置为忽略。只对linux有用

signal(17, SIG_IGN);

17号的默认处理动作是忽略,我们又设置了忽略,为什么这样就可以了。17号的默认是它对信号的处理是默认方式,默认方式忽略执行。而设置信号的处理方式为忽略,是忽略子进程的处理方式

进程第一章用wait和waitpid函数清理僵尸进程,父进程可以阻塞等待子进程结束,也可以非阻塞的查询是否有子进程结束等待清理(也就是轮询的方式)。采用第一种方式,父进程阻塞了就不能处理自己的工作了,采用第二种方式,父进程在处理自己的工作的同时还要记得是不是的轮询一下,程序实现复杂

其实,子进程在终止时会给父进程发sigchld信号,该信号的默认处理动作是忽略,父进程可以自定义SIGHLD信号的处理函数,这样父进程只需专心处理自己的工作,不必关心子进程了,子进程终止时会通知父进程,父进程在信号处理函数中调用wait,清理子进程即可

事实上,由于UNIX的历史原因,要想不产生僵尸进程还有另外一种方法,父进程调用sigaction将sigchold的处理动作设置为SIG_IGN没这样fork出来的紫禁城在终止时会自动清理掉,不会产生僵尸进程,也不会通知父进程,系统默认的忽略动作和用户用sigaction函数自定义的忽略,通产是没有区别的,但这是一个特例。此方法对于linux可用,不保证在其他UNIX系统上都可用


http://www.ppmy.cn/server/27112.html

相关文章

基于3D机器视觉的注塑缺陷检测解决方案

注塑检测是对注塑生产过程中的产品缺陷进行识别和检测的过程。这些缺陷可能包括色差、料流痕、黑点&#xff08;包括杂质&#xff09;等&#xff0c;它们可能是由多种因素引起&#xff0c;如原料未搅拌均匀、烘料时间过长、工业温度局部偏高、模具等问题造成的。不仅影响产品的…

格雷希尔E10系列大电流测试连接器,在新能源汽车大电流接插件的电气测试方案

在新能源汽车的电驱动、电池包等设备的电测试处理中&#xff0c;格雷希尔E10系列电测试连接器具有显著的优势。E10系列的核心设计——插孔/插针&#xff0c;可以达到实验室10万次的插拔寿命&#xff0c;相比传统公母电接头500次左右的连接寿命&#xff0c;E10系列无疑大大减少测…

闪存存储和制造技术概述

闪存存储技术 引言 性能由高到低排序&#xff1a;SLC -> MLC -> TLC -> QLC 根据这个排序读写速度也越来越低&#xff0c;价格越来越便宜 1. SLC SLC&#xff08;Single-Level Cell&#xff0c;单层单元&#xff09;&#xff1a; SLC 闪存具有最高的性能、耐用性和可…

【Qt】Visual Studio中打开Qt工程中的.ui文件闪退

1. 问题 Visual Studio中双击打开Qt工程中的.ui文件闪退。 2. 解决方法 .ui文件鼠标右键打开方式添加msvc中designer.ext路径&#xff08;安装路径\Qt6\5.15.2\msvc2015_64\bin&#xff09;将新设置的打开方式设置为默认打开方式。

Centos卸载mysql

停止MySQL服务 sudo systemctl stop mysqld 查找已安装的MySQL软件包 rpm -qa | grep -i mysql 这将列出所有已安装的MySQL相关软件包。 卸载MySQL软件包 sudo rpm -e --nodeps 软件包名称 将"软件包名称"替换为上一步列出的软件包名称。如果有多个软件包,逐个…

FFmpeg开发笔记(二十三)使用OBS Studio开启RTMP直播推流

OBS是一个开源的直播录制软件&#xff0c;英文全称叫做Open Broadcaster Software&#xff0c;广泛用于视频录制、实时直播等领域。OBS不但开源&#xff0c;而且跨平台&#xff0c;兼容Windows、Mac OS、Linux等操作系统。 OBS的官网是https://obsproject.com/&#xff0c;录制…

android 启动优化方向跟踪

先简单带过framwork以上的流程&#xff0c;主要看framwrok里面的步骤 一 前期启动流程速览 1 kernel内核空间启动 负责启动 native层的init进程 具体可以参考linux内核&#xff08; Bootloader启动Kernel的swapper进程(pid0)&#xff0c;它是内核首个进程&#xff0c;用于初始…

【网络】gateway 可以提供的一些功能之二 “ 提供Restful服务器路由转发 ”

一、提供web静态资源服务 Web静态资源服务是指通过HTTP协议提供静态文件&#xff08;如HTML、CSS、JavaScript、图片、字体等&#xff09;的服务。这些静态资源文件不经过服务器端处理&#xff0c;直接由客户端&#xff08;如浏览器&#xff09;请求并加载。提供Web静态资源服…