进程信号(下)

上文:进程信号(上)-CSDN博客

在上篇中,我们讲了关于信号的保存,信号集的操作,那么这篇我们就来看看信号的原理。

目录

1. 键盘产生信号的原理

2. 信号是如何被处理的? 

2.1 信号处理的原理 

2.2 内核态与用户态

2.2.1 内核空间 

2.2.2 内核态与用户态的切换 

3. 捕捉信号的其他方式 sigaction

3.1 函数定义

3.2 参数说明

3.3 sigaction结构体

3.4 sa_flag标志

3.5 使用示例 

4. 可重入函数 

不可重入

可重入

5. volatile


1. 键盘产生信号的原理

在上篇中,我们讲了信号产生的几种方法, 其中键盘产生信号的原理是什么呢?

那么在看完上面这张图,有没有觉得似曾相识,他的原理和信号技术非常的相似,如果你有这样的想法,那可就有点倒反天罡了,因为信号技术源自于硬件中断技术。 

2. 信号是如何被处理的? 

上一节我们讲了信号处理的操作与过程,那么信号到底是怎么被处理的呢?

2.1 信号处理的原理 

我们上节已经谈到,对信号的写入工作是OS做的,其实啊,对信号的相关工作都是OS做的。

当进程收到信号时,进程就会进入内核态,由内核对信号进行处理,如果我们没有对信号进行捕捉,那么当信号的默认处理是忽略时,进程会重新回到用户态;当信号的默认处理是终止时,进程会直接终止。当我们对信号进行了捕捉时,进程会切换到用户态执行处理函数,信号处理函数在最后是会执行特殊的系统调用进入内核态的(注意:信号捕捉函数与main函数是不同的控制流程)。进入内核态后一切正常时进入用户态继续执行主控制流程的代码。

有人会问,为什么不直接在内核态执行处理函数呢?那可不是内核态没有权限,而是人家压根不相信你的处理函数啊。用户态的权限是很小的,万一你的代码里有违法犯罪的动作,用户态根本执行不了,但要是内核态执行,那可就完蛋了,因此自定义的信号处理函数是由用户态执行的。 

2.2 内核态与用户态

说了这么多,但还没说内核态和用户态到底是什么啊。

记得我们之前学习进程时的一张图吗?没错,这是进程的地址空间。

但此前我们只知其然而不知其所以然,我们知道进程的地址空间内有栈、堆、代码区常量区,但这可都是用户空间内的,内核空间可是没说一点儿。

2.2.1 内核空间 

那么内核空间又是什么呢?

我们知道,进程的地址空间是一个个虚拟地址,而用户空间即指向内存中进程所需资源的部分,而内核空间即指向内存中OS运行所需资源的部分。

注意: 每个进程的内核空间指向相同,即所有进程共享内核空间。

有人会说,那所有的进程都指向同一个内核空间,那大家都可以访问它,我们之前学习的那么多进程间通信方式算什么?我们所学的进程具有独立性又算什么?每个进程都有交集了,还能称作独立吗吗?

别急,所有进程指向同一个内核空间,可不代表进程都能够访问它,这就是内核态与用户态的意义了。

2.2.2 内核态与用户态的切换 

很好,知道了这些,但我们还不知道用户态与内核态是怎么进行切换的啊。

  

那么到底为什么要这么设计呢?

不仅仅是因为系统调用,更是因为进程间调度的问题,我们知道,进程的时间片一到,OS就会进行进程调度,切换进程以达到进程并发的目的。进程的调度是需要OS来做的,OS必须要拿到自己的资源才能做事,所以在每个进程里都存放一个内核空间,使得OS能够随时随地拿到自己的资源,保证自己的超然。 

3. 捕捉信号的其他方式 sigaction

sigaction 是一个在 Unix 和类 Unix 系统中用于查询或设置信号处理方式的函数。它是 POSIX 信号处理接口的一部分,提供了比标准 C 库中的 signal 函数更为灵活和强大的功能。以下是关于 sigaction 函数的详细解释:

3.1 函数定义

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

3.2 参数说明

signum:指定要查询或修改的信号编号。可以指定除 SIGKILL 和 SIGSTOP 以外的所有信号。

act:指向一个 sigaction 结构体的指针,该结构体包含了新的信号处理函数和相关标志。如果此参数为 NULL,则仅查询而不修改信号的处理方式。

oldact:如果此参数不为 NULL,则函数会将当前信号的处理方式保存到这个指向 sigaction 结构体的指针中。

3.3 sigaction结构体

struct sigaction {  
    void     (*sa_handler)(int);       // 信号处理函数,类似于 signal 函数的 handler  
    void     (*sa_sigaction)(int, siginfo_t *, void *); // 另一个信号处理函数,提供额外信息  
    sigset_t   sa_mask;                // 在处理信号时,要阻塞的信号集  
    int        sa_flags;               // 信号处理选项标志  
    void     (*sa_restorer)(void);     // 废弃的字段,不再使用  
};

3.4 sa_flag标志

SA_RESETHAND:当调用信号处理函数时,将信号的处理函数重置为缺省值 SIG_DFL。
SA_NODEFER:一般情况下,当信号处理函数运行时,内核会阻塞该信号。但如果设置了此标志,则不会阻塞。
SA_RESTART:如果信号中断了进程的某个系统调用,则系统自动启动该系统调用。
SA_SIGINFO:如果设置了此标志,则使用 sa_sigaction 字段作为信号处理函数,并且可以向处理函数发送附加信息。

3.5 使用示例 

#include <unistd.h>
#include <iostream>
#include <signal.h>

void print(sigset_t pending)//打印当前pending位图
{
    std::cout<<"pending:  ";
    for (int i = 31; i > 0; i--)
    {
        if (sigismember(&pending, i))
        {
            std::cout << "1";
        }
        else
        {
            std::cout << "0";
        }
    }
    std::cout<<std::endl;
}

void handler(int signo)//二号信号的自定义捕捉函数
{
    std::cout<<"signo:"<<signo<<std::endl;
    sigset_t pending;
    sigemptyset(&pending);
    while (true)
    {
        sleep(1);
        sigpending(&pending);
        print(pending);
    }
}

int main()
{
    std::cout<<getpid()<<std::endl;
    struct sigaction act;
    act.sa_handler = handler;
    act.sa_flags = 0;
    sigaddset(&act.sa_mask, 11);
    sigaddset(&act.sa_mask, 15);
    sigaddset(&act.sa_mask, 5);
    sigaddset(&act.sa_mask, 3);
    sigaddset(&act.sa_mask, 4);//向阻塞信号集内添加信号

    sigaction(2, &act, nullptr);//修改二号信号的捕捉函数,不需要返回旧的处理方式
    while (1)
    {
        sleep(1);
    }
    return 0;
}

在进程运行时我们发现,在进程未接受到二号信号时,其他信号不会被阻塞,只有在进程处理二号信号的过程中,其他信号才会被阻塞。

这是因为sigaction与signal一样,只是告诉OS当进程收到该信号时这样处理,因此只有进程收到该信号时,才会执行sigaction函数。

4. 可重入函数 

 

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

如果一个函数符合以下条件之一则是不可重入的:
调用了malloc或free,因为malloc也是用全局链表来管理堆的。
调用了标准I/O库函数。标准I/O库的很多实现都以不可重入的方式使用全局数据结构。

举个例子:

不可重入

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <string>

void Print(std::string str)
{
    std::cout << str << std::endl;
}

int Add(int a, int b)
{
    return a + b;
}

int main()
{
    pid_t pid = fork();
    if (pid == 0)
    {
        int cnt = 5;
        while (cnt--)
        {
            sleep(1);
            std::string str = "我是子进程";
            Print(str);
        }
        exit(0);
    }
    int cnt = 5;
    while (cnt--)
    {
        sleep(1);
        std::string str = "我是父进程";
        Print(str);
    }
    wait(0);
    return 0;
}

可重入

  pid_t pid = fork();
    if (pid == 0)
    {
        int cnt = 5;
        while (cnt--)
        {
            int ret=Add(1,2);
        }
        exit(0);
    }
    int cnt = 5;
    while (cnt--)
    {
        int ret=Add(3,4);
    }
    wait(0);

5. volatile

在有的平台下运行下面这段代码,程序收到二号信号后不会终结,这是为什么呢? 

int g_val=0;
void handler(int signo)
{
    std::cout<<"g_val: 0-> 1"<<std::endl;
    g_val=1;//修改g_val
}

int main()
{
    signal(2,handler);//捕捉SIGINT
    while(!g_val);//当g_val=1,退出循环
    std::cout<<"g_val:"<<g_val<<std::endl;
    return 0;
}

在很多编译器里,会对代码进行优化。而在上面这段代码里是具有两个执行流的,编译器不会对捕捉函数进行扫描,编译器在主控制流程里并没有检测到对g_val的修改,就会将g_val存放在cpu的寄存器里以方便取用,而我们修改的g_val是内存里的,但程序拿g_val是从寄存器拿的,因此程序不会终结。

这个时候volatite就起到了至关重要的作用,在变量前加volatite,可以强制变量不被放入寄存器,而是从内存读取,这样上面的内存忽略问题就不存在了。 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

深度神经网络——深度学习中的 RNN 和 LSTM 是什么?

引言 自然语言处理和人工智能聊天机器人领域许多最令人印象深刻的进步都是由 递归神经网络&#xff08;RNN&#xff09; 和长短期记忆&#xff08;LSTM&#xff09;网络。 RNN 和 LSTM 是特殊的神经网络架构&#xff0c;能够处理顺序数据&#xff0c;即按时间顺序排列的数据。…

实用软件下载:会声会影2023最新安装包及详细安装教程

会声会影2023的智能工具&#xff0c;使用AI面部识别对效果最好的照片和视频片段进行分析&#xff0c;提取&#xff0c;并编译到可以项目中&#xff0c;将我们的精彩时刻、美好回忆和媒体内容转换为影片。 全新的AR贴纸让视频更具感染力和趣味性&#xff0c;AR贴纸功能可以识别并…

python 实现各种数据分析方法

1、相关性分析 1.1、https://zhuanlan.zhihu.com/p/669355778https://zhuanlan.zhihu.com/p/669355778

Jacob环境探索(兼容性、管理员、DLL位置、VS环境,COM权限)

概述&#xff1a; 最近在生产开发实践出现了很多问题&#xff0c;经过了一系列排查&#xff0c;特做如下总结 探索成果&#xff1a; 1. jacob.dll的建议位置 首先jacob的官网&#xff0c;以及官方GitHub&#xff0c;你可以从这里找到DLL文件&#xff0c;以及相关资料然后DLL文…

WordPress——Argon主题美化

文章目录 Argon主题美化插件类类别标签页面更新管理器文章头图URL查询监视器WordPress提供Markdown语法评论区头像设置发信设置隐藏登陆备份设置缓存插件 主题文件编辑器页脚显示在线人数备案信息(包含备案信息网站运行时间)banner下方小箭头滚动效果站点功能概览下方Links功能…

GitHub Copilot 登录账号激活,已经在IntellJ IDEA使用

GitHub Copilot 想必大家都是熟悉的&#xff0c;一款AI代码辅助神器&#xff0c;相信对编程界的诸位并不陌生。 今日特此分享一项便捷的工具&#xff0c;助您轻松激活GitHub Copilot&#xff0c;尽享智能编码之便利&#xff01; GitHub Copilot 是由 GitHub 和 OpenAI 共同开…

38、基于卷积神经网络(CNN)的车牌自动识别系统(matlab)

1、原理及流程 1&#xff09;原理 CNN&#xff08;卷积神经网络&#xff09;是一种深度学习模型&#xff0c;可以用于图像识别和分类任务。车牌自动识别系统的原理基本上就是使用CNN模型对车牌图像进行处理和识别。 首先&#xff1a;系统需要收集大量的含有车牌的图像数据作…

windows系统,家庭自用NAS。本地局域网 Docker安装nextcloud

windows系统&#xff0c;家庭自用NAS。本地局域网 Docker安装nextcloud 1、docker安装 太简单了&#xff0c;直接去搜一搜。 docker-compose 相关命令 docker-compose down docker compose up -d2、还是使用老的 在你需要挂载的目录下&#xff0c;新建一个文件&#xff0c;…

航顺MCU概览

前言: 截止2023年底,全国有3451家芯片设计公司,已经IPO的就有168家,尚未IPO的3283家中超过一半的年营收在1000万以下,迅猛发展的几年的确有些国产芯片开始站上赛道,这也是国际大背景下的一种必然选择,毕竟突然间出现的大市场需要国产顶上,但资本市场是周期性的,国产替…

港科夜闻 | 香港科大与香港科大(广州)合推红鸟跨校园学习计划,共享教学资源,促进港穗学生交流学习...

关注并星标 每周阅读港科夜闻 建立新视野 开启新思维 1、香港科大与香港科大(广州)合推“红鸟跨校园学习计划”&#xff0c;共享教学资源&#xff0c;促进港穗学生交流学习。香港科大与香港科大(广州)6月14日共同宣布推出“红鸟跨校园学习计划”&#xff0c;以进一步加强两校学…

transformer和Non-local

两者本质上是一个东西&#xff0c;都是用来求自注意力的&#xff0c;但具体而言还是有一些差别&#xff1b; 1&#xff1a;首先说Non-local&#xff0c;它是像素级别的self-attention,算的是图片中各个像素点对指定像素点的影响&#xff1b; 2&#xff1a;transformer我们拿s…

算法人生(22):从“生成对抗网络”看“逆商提升”

​ 在图像生成与编辑、音频合成、视频生成领域里&#xff0c;有一个非常重要的深度学习方法——生成对抗网络&#xff08;简称GANs&#xff09;&#xff0c;它是由两个神经网络组成的模型&#xff0c;分别为生成器&#xff08;Generator&#xff09;和判别器&#xff08;Discr…

移动硬盘数据恢复方法哪个好?六个硬盘恢复,新手也能用!

移动硬盘数据恢复方法哪个好&#xff1f;移动硬盘&#xff0c;作为我们存储重要数据的常用设备&#xff0c;一旦里面的视频、文档、音频等资料突然消失&#xff0c;确实会令人烦恼和担忧。然而&#xff0c;因为数据丢失的原因可能多种多样&#xff0c;因此恢复方法也会有所不同…

【嵌入式DIY实例】-Nokia 5110显示DS3231 RTC数据

Nokia 5110显示DS3231 RTC数据 文章目录 Nokia 5110显示DS3231 RTC数据1、硬件准备与接线2、代码实现本文将介绍如何使用 ESP8266 NodeMCU 板和 DS3231 RTC 模块制作一个简单的数字实时时钟,其中可以使用连接到 NodeMCU 的两个按钮设置时间和日期,并将它们打印在诺基亚 5110 …

Ubuntu server 24 (Linux) 新增磁盘 lvm 动态扩容磁盘空间

1 新增一块硬盘 #查看 sudo fdisk -l #重新分区&#xff0c;转换成lvm类型 sudo fdisk /dev/sdb 2 查看磁盘 df -h3 lvm 配置 #查看lvm逻辑卷 sudo lvdisplay #创建物理卷 sudo pvcreate /dev/sdb1 #扩展卷组 sudo vgextend ubuntu-vg /dev/sdb1 #扩展逻辑卷 sudo lvexte…

【Linux】pycharmgit相关操作

目录 1. git安装配置2. 相关内容3. pycharm连接远程仓库3.1 配置3.2 clone远程仓库3.3 本地仓库上传远程 4. 分支管理4.1 更新代码4.2 新建分支4.3 分支合并4.4 代码比对 5. 版本管理6. 命令行操作6.1 配置git6.2 基础操作6.3 分支操作 1. git安装配置 下载链接&#xff1a;官…

从数据库到数据仓库:数据仓库导论

导言 本文为数据仓库导论&#xff0c;旨在介绍数据仓库的基本理念和应用场景&#xff0c;帮助读者理解数据仓库的重要性及其在企业中的实际应用。 数据仓库作为重要的数据管理和分析工具&#xff0c;已经发展了30多年&#xff0c;其过程中生态和技术都发生了巨大的变化。尽管…

1832javaERP管理系统之能力物料管理Myeclipse开发mysql数据库servlet结构java编程计算机网页项目

一、源码特点 java erp管理系统之能力物料管理是一套完善的web设计系统&#xff0c;对理解JSP java编程开发语言有帮助采用了serlvet设计&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统采用web模式&#xff0c;系统主要采用 B/S模式开发。开发环境为TOMCAT7.0,My…

深入理解指针(四)

目录 1. 回调函数是什么? ​2. qsort使用举例 2.1冒泡排序 2.2使用qsort函数排序整型数据 ​2.3 使用qsort排序结构数据(名字) 2.4 使用qsort排序结构数据(年龄) 3. qsort函数的模拟实现 1. 回调函数是什么? 回调函数就是⼀个通过函数指针调⽤的函数。 如果你把函数…

CSS概述

CSS是一种样式表语言&#xff0c;用于为HTML文档控制外观&#xff0c;定义布局。例如&#xff0c; CSS涉及字体、颜色、边距、高度、宽度、背景图像、高级定位等方面 。 ● 可将页面的内容与表现形式分离&#xff0c;页面内容存放在HTML文档中&#xff0c;而用 于定义表现形式…