Linux信号机制探析--信号的处理

news2024/9/29 7:27:38
🍑个人主页:Jupiter.
🚀 所属专栏:Linux从入门到进阶
欢迎大家点赞收藏评论😊

在这里插入图片描述

在这里插入图片描述

目录

    • `🍑信号处理`
            • `信号处理常见方式概览`
      • `🍒内核如何实现信号的捕捉 `
      • `🍎内核态与用户态`
            • `操作系统是如何正常运行的(了解)`
      • `🎈sigaction`
      • `🌾可重入函数`
      • `🌽SIGCHLD信号 `


🍑信号处理

信号处理常见方式概览

可选的处理动作有以下三种:

  1. 默认处理动作。
  2. 忽略此信号。
  3. 自定义处理信号–捕捉 。提供一个信号处理函数,要求内核在处理该信号时切换到用户态执行这个处理函数。

信号是在什么时候被处理的?

  • 进程从内核态切换到用户态的时候,信号会被检测并处理。

🍒内核如何实现信号的捕捉

如果信号的处理动作是用户自定义函数,在信号递达时就调用这个函数,这称为捕捉信号。由于信号处理函数的代码是在用户空间的,处理过程比较复杂,举例如下:

  • 用户程序注册了SIGQUIT信号的处理函数sighandler。 当前正在执行main函数,这时发生中断或异常切换到内核态。 在中断处理完毕后要返回用户态的main函数之前 检查到有信号SIGQUIT递达。 内核决定返回用户态后不是恢复main函数的上下文继续执行,而是执行sighandler函数,sighandler和main函数使用不同的堆栈空间,它们之间不存在调用和被调用的关系,是两个独立的控制流程。 sighandler函数返回后自动执行特殊的系统调用sigreturn再次进入内核态。 如果没有新的信号要递达,这次再返回用户态就是恢复main函数的上下文继续执行了。
  • 信号捕捉的过程可以用一个♾️简略表示,其中交点就会信号检测,如下图所示,如果拉根横线,与♾️的四个交点,也就是在信号处理过程中(信号捕捉),有四次用户态与内核态的切换,横线的上面是用户态,下面是内核态。

🍎内核态与用户态

我们之前所说的页表,实际上是用户级页表,是进程地址空间中的用户空间与对应物理内存的映射关系,其中,还有一个内核级页表,是进程地址空间中的内核空间与对应物理内存的映射关系,系统中第一个被加载的软件是操作系统,将操作系统加载到物理内存后,为了用户将来能够以受保护的状态的访问操作系统(内核数据),必须把操作系统 映射进程地址空间的内核空间中,以后进程如果想要访问操作系统的数据,如一些系统调用,通过进程的地址空间中内核空间即可,所以,我们使用系统调用或则访问系统数据,其实还是在进程的地址空间内进行跳转的

使用系统调用或则访问系统数据,是在进程的地址空间内进行跳转的,如果不加以限制,那么用户就可以随便访问内核数据了,所以就引入了用户态与内核态。当在访问内核空间的时候,会进行运行模式检测,只有在是内核态才可以访问,否则不行。

系统中存在多个进程,但是OS只有一份,所有的进程要映射内核空间,只需要共用同一个内核级页表即可,所以进程无论怎么切换,总能找到操作系统。

在这里插入图片描述

操作系统是如何正常运行的(了解)

是通过以非常高的频率,每隔非常短的时间,就给CPU发送中断号(假设是2),CPU不断处理中断,拿着中断号,在中断向量表(其中开机的时候,OS提供的)中找到对应的处理方法并执行。这个中断称为OS的周期时钟中断。

🎈sigaction

函数原型:

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

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

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

示例代码:

void handler(int signo)
{
    std::cout << "signal : " << signo << std::endl;
    
}

int main()
{
    struct sigaction act, oact;
    act.sa_handler = handler;
    act.sa_flags = 0;
    sigemptyset(&act.sa_mask); 

    //在在调用信号处理函数的时候同时将3,4,5号信号屏蔽
    sigaddset(&act.sa_mask, 3);
    sigaddset(&act.sa_mask, 4);
    sigaddset(&act.sa_mask, 5);

    sigaction(2, &act, &oact); //自定义捕捉2号信号
 
    while(true) sleep(1);  //循环的时候给该进程发送2号信号验证

    return 0;
}

🌾可重入函数

  • main函数调用insert函数向一个链表head中插入节点node1,插入操作分为两步,刚做完第一步的候,因为硬件中断使进程切换到内核,再次回用户态之前检查到有信号待处理,于是切换 到sighandler数,sighandler也调用insert函数向同一个链表head中插入节点node2,插入操作的 两步都做完之后从sighandler返回内核态,再次回到用户态就从main函数调用的insert函数中继续 往下执行,先前做第一步之后被打断,现在继续做完第二步。结果是,main函数和sighandler先后 向链表中插入两个节点,而最后只有一个节点真正插入链表中了。
  • 像上例这样,insert函数被不同的控制流程调用,有可能在第一次调用还没返回时就再次进入该函数,这称为重入,insert函数访问一个全局链表,有可能因为重入而造成错乱,像这样的函数称为 不可重入函数,反之,如果一个函数只访问自己的局部变量或参数,则称为可重入(Reentrant) 函数。想一下,为什么两个不同的控制流程调用同一个函数,访问它的同一个局部变量或参数就不会造成错乱?

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

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

🌽SIGCHLD信号

进程一章讲过用wait和waitpid函数清理僵尸进程,父进程可以阻塞等待子进程结束,也可以非阻塞地查询是否有子进程结束等待清理(也就是轮询的方式)。采用第一种方式,父进程阻塞了就不能处理自己的工作了;采用第二种方式,父进程在处理自己的工作的同时还要记得时不时地轮询一 下,程序实现复杂。
其实,子进程在终止时会给父进程发SIGCHLD信号,该信号的默认处理动作是忽略,父进程可以自定义SIGCHLD信号的处理函数,这样父进程只需专心处理自己的工作,不必关心子进程了,子进程终止时会通知父进程,父进程在信号处理函数中调用wait清理子进程即可。

请编写一个程序完成以下功能:父进程fork出子进程,子进程调用exit(2)终止,父进程自定义SIGCHLD信号的处理函数,在其中调用wait获得子进程的退出状态并打印。

void CleanupChild(int signo)
{
    if (signo == SIGCHLD)
    {
        pid_t rid = waitpid(-1, nullptr, 0);
        if (rid > 0)
        {
            std::cout << "wait child success: " << rid << std::endl;
        }
    }
    std::cout << "wait sub process done" << std::endl;
}

int main()
{
    signal(SIGCHLD, CleanupChild);

    pid_t id = fork();
    if (id == 0)
    {
        // child
        std::cout << "child process died" << std::endl;
        exit(2);
    }
    // father
    while (true)
        sleep(1);
    
    return 0;
}

对于上面的代码,只有一个子进程,那如果一共有100个子进程呢,并且全部一起退出,意思就是100子进程都会同时向父进程发送SIGCHLD信号,可能父进程来不及处理,在子进程回收子进程的时候,也会回收部分,剩下的就没有被回收。
那么像上面的问题怎么解决呢?

解决方法:循环等待

void CleanupChild(int signo)
{
    if (signo == SIGCHLD)
    {
        while (true)
        {
            pid_t rid = waitpid(-1, nullptr, 0); // -1 : 回收任意一个子进程
            if (rid > 0)
            {
                std::cout << "wait child success: " << rid << std::endl;
            }
            else if(rid <= 0) break;
        }
    }
}

那么问题又来了,要是100子进程,只退出50个子进程呢?如果还是使用上面的代码的话,50个进程退出后,后面50个子进程根本没有退出,这时候循环里面调用waitpid就阻塞了,如果后面50个子进程再也不退的话,就会一直在信号捕捉的方法中不会返回了,就回不到主进程了,主进程就丢失了。解决方法:非阻塞等待即可。上面代码改为 waitpid(-1, nullptr, WNOAHG)即可

事实上,由于UNIX 的历史原因,要想不产生僵尸进程还有另外一种办法:父进程调用sigaction将SIGCHLD的处理动作置为SIG_IGN,这样fork出来的子进程在终止时会自动清理掉,不会产生僵尸进程,也不会通知父进程。系统默认的忽略动作用户用sigaction函数自定义的忽略`通常是没有区别的,但这是一个特例。此方法对于Linux可用,但不保证在其它UNIX系统上都可用。请编写程序验证这样做不会产生僵尸进程。

int main()
{
    signal(SIGCHLD, SIG_IGN);

    for (int i = 0; i < 100; i++)
    {
        pid_t id = fork();
        if (id == 0)
        {
            std::cout << "child process died" << std::endl;
            exit(0);
        }
    }
    while (true) sleep(1);
    
    return 0;
}

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

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

相关文章

下载cmake操作步骤

cmake官网链接 cmake-3.30.2.tar.gz源代码官网下载链接

中国的人形机器人都有哪些出色的产品?

8月21日&#xff0c;2024世界机器人大会在北京亦庄正式开幕。本次大会共有169家企业集中展出了600余件机器人创新产品&#xff0c;人形机器人占比最大&#xff0c;大会还开设人形机器人专区&#xff0c;共亮相27款整机。 展会中多数人形机器人产品都偏向服务型&#xff0c;主要…

乾坤大挪移!将脚趾移到手指上,江山邦尔骨科医院成功完成一例断指再植手术

2024年6月中旬&#xff0c;家住江山贺村的何阿姨经历一次不小的意外。 那天天气晴朗&#xff0c;何阿姨准备把院子修缮修缮。操作切割工具时&#xff0c;何阿姨没有握稳&#xff0c;让工具一下子飞了出去——飞出去的瞬间&#xff0c;工具切掉了她的左手拇指&#xff0c;血流不…

网络安全大考,攻防演练驱动企业常态化安全运营升级!

当前&#xff0c;网络安全形势日益严峻&#xff0c;恶意软件、勒索软件肆虐&#xff0c;钓鱼攻击手段层出不穷&#xff0c;不断威胁企业数据安全与业务连续性。随着云计算、大数据、物联网等新兴技术的广泛应用&#xff0c;网络边界模糊化&#xff0c;攻击面急剧扩大&#xff0…

Qt (10)【Qt窗口 —— 如何在窗口中创建浮动窗口和状态栏】

阅读导航 引言一、如何在窗口中创建浮动窗口1. 浮动窗口的创建2. 设置停靠的位置 二、如何在窗口中创建状态栏1. 状态栏的创建2. 在状态栏中显示实时消息3. 在状态栏中显示永久消息4. 调整显示消息的位置&#xff0c;并加上进度条 引言 在上一篇文章中&#xff0c;我们一同探索…

数据结构(6_3_1)——图的广度优先遍历

树和图的广度优先遍历区别 树的广度优先遍历&#xff1a; 图的广度优先遍历&#xff1a; 代码&#xff1a; 注:以下代码只适合连通图 #include <stdio.h> #include <stdbool.h>#define MAX_VERTEX_NUM 100typedef struct ArcNode {int adjvex; // 该边所指向的顶…

慧灵夹爪:工业智能的创新先锋

慧灵作为一个知名老品牌&#xff0c;其机器人产品在众多场景中广为人知。随着智能化、自动化技术的不断提升&#xff0c;智能工业飞速发展&#xff0c;慧灵夹爪在其中发挥的作用也越来越多。 在工业自动化生产中&#xff0c;精准与灵活是衡量设备性能的重要标尺。慧灵夹爪以其卓…

Criteria 是干什么用的?

我 | 在这里 ⭐ 全栈开发攻城狮、全网10W粉丝、2022博客之星后端领域Top1、专家博主。 &#x1f393;擅长 指导毕设 | 论文指导 | 系统开发 | 毕业答辩 | 系统讲解等。已指导60位同学顺利毕业 ✈️个人公众号&#xff1a;热爱技术的小郑。回复 Java全套视频教程 或 前端全套视频…

简易电压表设计验证

前言 电压表是测量电压的一种仪器。由永磁体、线圈等构成。电压表是个相当大的电阻器&#xff0c;理想的认为是断路。初中阶段实验室常用的电压表量程为0~3V和0~15V。 传统的指针式电压表包括一个灵敏电流计&#xff0c;在灵敏电流计里面有一个永磁体&#xff0c;在电流计的两个…

GenAI 的产品:快速行动,但失败

2022 年秋季&#xff0c;我正在做一个很酷的项目。是的&#xff0c;你猜对了——使用公司特定的数据对预先训练的 LLM&#xff08;Bert&#xff09;进行微调。 然而&#xff0c;很快 ChatGPT 就发布了&#xff0c;并席卷了全世界。既然已经有一门非常强大的 LLM 了&#xff0c…

支持AI智能搜索的知识库管理系统有哪些?分享4个软件

引言 在数字化时代&#xff0c;知识的获取、管理和利用已成为企业竞争力的重要组成部分。随着信息量的爆炸性增长&#xff0c;如何快速、准确地从海量数据中检索出有价值的知识&#xff0c;成为企业面临的一大挑战。支持AI智能搜索的知识库管理系统能够快速准确地检索信息&…

【前端】vue监视属性和计算属性对比

首先分开讲解各个属性的作用。 1.计算属性 作用&#xff1a;用来计算出来一个值&#xff0c;这个值调用的时候不需要加括号&#xff0c;会根据依赖进行缓存&#xff0c;依赖不变&#xff0c;computed的值不会重新计算。 const vm new Vue({el:#root,data:{lastName:张,firstNa…

严重腰椎滑脱、无法走路,江山邦尔骨科医院机器人辅助手术为患者完美复位

8月8日上午&#xff0c;53岁的李清&#xff08;化名&#xff09;扶着腰、跛脚走进江山邦尔骨科医院。接诊他的&#xff0c;是江山邦尔骨科医院脊柱科的林科院长。 李清和林院长说&#xff0c;自己已有长达两年的腰痛史&#xff0c;最近还伴随右腿麻木及跛行的症状&#xff0c;严…

深度解析上海我店 三年突破一百亿销售额!

在当今数字化时代的大潮中&#xff0c;消费模式正经历着翻天覆地的变革。上海我店网络科技有限公司&#xff08;简称“我店”&#xff09;&#xff0c;凭借其创新的“互联网实体终端”融合商业模式与独特的绿色积分体系&#xff0c;在消费市场中异军突起&#xff0c;成为引领行…

ClkLog常见问题-埋点集成篇Sec. 1

本篇主要解答ClkLog使用过程中【埋点集成】阶段的常见问题。 1.【指标项数据统计】 问&#xff1a;数据概览无法看到数据。 答&#xff1a;如果数据概览所有指标项都没有数据&#xff0c;则需要先检查埋点数据是否接收成功&#xff1b;如果只是会话相关数据&#xff08;访问次数…

缺陷检测之Anomalib

缺陷检测的现状 工业缺陷数据有一个比较显著的特征&#xff1a;样本不平衡。绝大部分采集得到的工业数据都是没有缺陷的&#xff0c;这样一来&#xff0c;正样本的数据在模型训练中根本没有起到作用&#xff0c;负样本又太少&#xff0c;很难训练得到有效的模型。使用有监督学…

六西格玛与5S管理体系并行落地,实现生产事件精益管理

在现代制造业中&#xff0c;六西格玛管理和5S管理体系是提升生产效率和质量控制的重要工具。六西格玛以其严格的数据分析方法帮助企业减少过程中的变异和缺陷&#xff0c;而5S管理则通过优化工作环境和流程&#xff0c;确保生产线的整洁、有序和高效。如何将这两大管理体系成功…

wpf livechart 绘制笛卡尔曲线

先上图&#xff1a; 代码部分&#xff1a; <GroupBox Header"各生产线生产量趋势"><Grid><Grid.RowDefinitions><RowDefinition Height"45"/><RowDefinition Height"auto"/><RowDefinition/></Grid.RowD…

第R1周: RNN-心脏病预测

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 一、什么是RNN RNN&#xff08;Recurrent Neural Network&#xff09;是一种特殊的神经网络&#xff0c;它能够处理序列数据&#xff0c;如时间序列、文本序列…

MedGraphRAG:医学版 GraphRAG

MedGraphRAG&#xff1a;医学版 GraphRAG 提出我的解法思路 MedGraphRAG 大纲解法大纲 解法拆解U-retrieve 双向检索 分析性关联图创意视角MedGraphRAG 对比 传统知识图谱大模型现在医疗知识图谱的问题MedGraphRAG的三层层级图结构&#xff0c;能不能让普通的医疗知识图谱&…