35 信号处理

news2025/1/2 5:46:49

什么时候捕捉

如果信号的处理动作是用户自定义函数,在信号递达时就调用这个函数,称为捕捉信号,由于信号处理函数的代码是在用户空间的,处理过程比较复杂,举例如下:用户程序注册了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();  //打印pend
    printblock(); //打印block
    int 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.coloradmin.cn/o/1649571.html

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

相关文章

【C++】C++11--- 类的新功能

目录 类的新功能 默认成员函数 示例 类成员变量初始化 强制生成默认函数的关键字default 禁止生成默认函数的关键字delete 类的新功能 默认成员函数 构造函数析构函数拷贝构造函数拷贝赋值重载取地址重载const取地址重载 C11在原先的6个默认成员函数的基础上&#xff0c…

基于Flask的岗位就业可视化系统(一)

&#x1f31f;欢迎来到 我的博客 —— 探索技术的无限可能&#xff01; &#x1f31f;博客的简介&#xff08;文章目录&#xff09; 前言 本项目综合了基本数据分析的流程&#xff0c;包括数据采集&#xff08;爬虫&#xff09;、数据清洗、数据存储、数据前后端可视化等 推荐…

Redis(Redis配置和订阅发布)

文章目录 1.Redis配置1.网络配置1.配置文件位置 /etc/redis.conf2.bind&#xff08;注销支持远程访问&#xff09;1.默认情况bind 127.0.0.1 只能接受本机的访问2.首先编辑配置文件3.进入命令模式输入/bind定位&#xff0c;输入n查找下一个&#xff0c;shift n查找上一个&…

STM32F407VET6 学习笔记2:定时器、串口、自定义串口打印函数

今日继续学习使用嘉立创购买的 立创梁山派天空星&#xff0c;芯片是 STM32F407VET6 因为已经有学习基础了&#xff0c;所以学习进度十分快&#xff0c;这次也是直接一块学习配置定时器与串口了&#xff0c;文章也愈来愈对基础的解释越来越少了...... 文章提供测试代码讲解、完…

springboot项目组合定时器schedule注解实现定时任务

springboot项目组合定时器schedule注解实现定时任务&#xff01; 创建好springboot项目后&#xff0c;需要在启动类上增加注解开启定时器任务 下图所示&#xff1a; 增加这个注解&#xff0c;启动项目&#xff0c; package com.example.scheduledemo.util;import org.springf…

Baidu Comate——您的智能编码伙伴

文章目录 1.Baidu Comate智能编码助手简介2.Baidu Comate安装使用3.查看Comate插件功能4.Baidu Comate基础功能介绍✨注释生成代码✨实时续写✨函数注释✨行间注释✨代码解释✨单元测试生成✨代码优化✨技术问答 5.使用体验结语 1.Baidu Comate智能编码助手简介 ✨Baidu Comat…

ASP.NET MVC企业级程序设计 (入住退房,删除)

目录 效果图 实现过程 控制器代码 DAL BLL Index 效果图 实现过程 控制器代码 using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc;namespace MvcApplication1.Controllers {public class HomeController …

python环境下labelImg图片标注工具的使用

labelimg GitHub地址 python环境下labelImg图片标注工具的使用 1. 写在开头2. 如何使用2.1安装2.2 启动2.2.1 先启动后设置标注的目录2.2.2 指定标注的目录和预设置的标签 2.3 设置自动保存和显示类别。2.4 保存文件类型2.5 [快捷键](https://github.com/HumanSignal/labelImg…

矩阵快速幂

要想知道矩阵快速幂&#xff0c;我们先了解一下什么叫快速幂和矩阵乘法 一、快速幂 快速幂算法是用来快速计算指数表达式的值的&#xff0c;例如 210000000,普通的计算方法 2*2*2*2…10000000次&#xff0c;如果一个数字的计算都要计算那么多次的话&#xff0c;那么这个程序一…

c++多线程2小时速成

简介 c多线程基础需要掌握这三个标准库的使用&#xff1a;std::thread,std::mutex, andstd::async。 1. Hello, world #include <iostream> #include <thread>void hello() { std::cout << "Hello Concurrent World!\n"; }int main() {std::th…

5.合并两个有序数组

文章目录 题目简介题目解答解法一 &#xff1a;合并后排序解法二&#xff1a;双指针排序 题目链接 大家好&#xff0c;我是晓星航。今天为大家带来的是 合并两个有序数组 相关的讲解&#xff01;&#x1f600; 题目简介 题目解答 解法一 &#xff1a;合并后排序 假设我们要合…

【C++】从零开始认识多态

送给大家一句话&#xff1a; 一个犹豫不决的灵魂&#xff0c;奋起抗击无穷的忧患&#xff0c;而内心又矛盾重重&#xff0c;真实生活就是如此。 ​​​​ – 詹姆斯・乔伊斯 《尤利西斯》 _φ(*&#xffe3;ω&#xffe3;)&#xff89;_φ(*&#xffe3;ω&#xffe3;)&…

期权买方要保证金吗?期权交易保证金怎么计算?

今天期权懂带你了解期权买方要保证金吗&#xff1f;期权交易保证金怎么计算&#xff1f;期权保证金其实就是你在购买期权合约时&#xff0c;作为卖方要付出的那一小笔钱。简单说&#xff0c;就是为了防止你违约&#xff0c;给交易双方一个保障的“小押金”。 期权买方要保证金吗…

软考中级-软件设计师(八)算法设计与分析 考点最精简

一、算法设计与分析的基本概念 1.1算法 算法&#xff08;Algorithm&#xff09;是对特定问题求解步骤的一种描述&#xff0c;有5个重要特性&#xff1a; 有穷性&#xff1a;一个算法必须总是在执行又穷步骤后结束&#xff0c;且每一步都可在又穷时间内完成 确定性算法中每一…

如何做好一个活动策划?

活动策划的关键要素是什么&#xff1f; 首先&#xff0c;要明确一个概念:做活动就是走钢丝&#xff0c;没有保险的高空走钢丝!因为&#xff0c;活动没有“彩排”&#xff0c;只有现场"直播”! 无论什么类型的活动&#xff0c;人数是50人还是2000人&#xff0c;也不论预算…

Parts2Whole革新:多参照图定制人像,创新自定义肖像生成框架!

DeepVisionary 每日深度学习前沿科技推送&顶会论文分享&#xff0c;与你一起了解前沿深度学习信息&#xff01; Parts2Whole革新&#xff1a;多参照图定制人像&#xff0c;创新自定义肖像生成框架&#xff01; 引言&#xff1a;探索多条件人像生成的新篇章 在数字内容创作…

用户管理中心——数据库设计用户注册逻辑设计

用户管理中心——数据库设计&用户注册逻辑设计 规整项目目录1. 数据库自动生成器的使用实现基本的数据库操作&#xff08;操作user表&#xff09; 2. 注册逻辑的设计(1) 写注册逻辑(2) 实现(3) 测试代码 3. 遇到的问题 规整项目目录 utils–存放工具类&#xff0c;比如加密…

贪心算法应用例题

最优装载问题 #include <stdio.h> #include <algorithm>//排序int main() {int data[] { 8,20,5,80,3,420,14,330,70 };//物体重量int max 500;//船容最大总重量int count sizeof(data) / sizeof(data[0]);//物体数量std::sort(data, data count);//排序,排完数…

OpenHarmony 实战开发—— refreshlayout 组件开发学习指南~

1. RefreshLayout_harmonyos 功能介绍 1.1. 组件介绍&#xff1a; RefreshLayout_harmonyos 是一款下拉刷新组件 1.2. 手机模拟器上运行效果&#xff1a; 2. RefreshLayout_harmonyos 使用方法 2.1 在目录 build.gradle 下 implementation project(":refreshlayout_ha…

【YoloDeployCsharp】基于.NET Framework的YOLO深度学习模型部署测试平台

YoloDeployCsharp|基于.NET Framework的YOLO深度学习模型部署测试平台 1. 项目介绍2. 支持模型3. 时间测试4. 总结 1. 项目介绍 基于.NET Framework 4.8 开发的深度学习模型部署测试平台&#xff0c;提供了YOLO框架的主流系列模型&#xff0c;包括YOLOv8~v9&#xff0c;以及其系…