进程信号(下)

news2024/11/24 0:16:29

上文:进程信号(上)-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.coloradmin.cn/o/1828241.html

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

相关文章

5.Sentinel入门与使用

5.Sentinel入门与使用 1.什么是 Sentinel?Sentinel 主要有以下几个功能: 2.为什么需要 Sentinel?3.Sentinel 基本概念3.1 什么是流量控制?3.1.1 常见流量控制算法3.1.2 Sentinel 流量控制流控效果介绍如下: 3.2 什么是熔断?熔断策略 3.3 Sentinel 组成&#xff08;资源和规…

Matlab信号与系统实验-实验四 连续时间系统的复频域分析

一、实验目的 1、理解拉普拉斯变换、逆变换的定义&#xff0c;掌握利用MATLAB实现解拉普拉 斯变换、逆变换的的方法&#xff1b; 2、掌握几种基本信号的拉普拉斯变换&#xff1b; 3、掌握利用MATLAB绘制连续系统零、极点的方法&#xff1b; 4、掌握系统函数H(s)的求解。 二…

CleanMyMac X软件下载附加详细安装教程

​首先要介绍的是CleanMyMac X&#xff0c;这是一款极受欢迎的苹果电脑清理软件&#xff0c;它能够全面扫描你的电脑系统&#xff0c;清理无用的文件和垃圾&#xff0c;以释放硬盘空间&#xff0c;除了清理功能之外&#xff0c;CleanMyMac X 还可协助管理应用程序、优化性能、修…

交易文本数据:情感分析 -另类数据交易- 舆情数据

交易文本数据:情感分析 这是三章中的第一章,专门介绍使用自然语言处理(NLP)和机器学习从文本数据中提取交易策略信号。 文本数据内容丰富但高度非结构化,因此需要更多预处理才能使ML算法提取相关信息。一个关键挑战是在不丢失其含义的情况下将文本转换为数值格式。我们将介绍…

FPGA IO_BANK、IO_STANDARD

描述 Xilinx 7系列FPGA和UltraScale体系结构提供了高性能&#xff08;HP&#xff09;和 高范围&#xff08;HR&#xff09;I/O组。I/O库是I/O块&#xff08;IOB&#xff09;的集合&#xff0c;具有可配置的 SelectIO驱动程序和接收器&#xff0c;支持多种标准接口 单端和差分。…

基于WPF技术的换热站智能监控系统14--搭建西门子PLC通信环境

1、安装博途软件V15 本项目需要用到西门子PLC&#xff0c;系统所需的数据来自现场PLC实时采集的数据&#xff0c;所以需要配置PLC的通信环境&#xff0c;具体请看以下博客文章。 windows10企业版安装西门子博途V15---01准备环境_博途v15.1安装需求-CSDN博客 windows10企业…

【PPT教程】一键重置幻灯片背景的方法,新建幻灯片带默认背景

目的是替换18届的研电赛ppt背景为19届 这里写目录标题 1.设计->设置背景格式2.图片或纹理填充->插入3.选择需要替换为背景的照片4.点击下方的应用到全部 1.设计->设置背景格式 2.图片或纹理填充->插入 3.选择需要替换为背景的照片 4.点击下方的应用到全部 此时全部…

flask部署mtcnn

目录 打印人脸检测信息 输出结果 保存检测结果 浏览器查看nginx&#xff08;nginx配置这里就不多介绍了&#xff09; url图片检测人脸 输出结果 Flask hello-world Flaskmtcnn python调flaskmtcnn 打印人脸检测信息 import cv2 from mtcnn.mtcnn import MTCNNimg cv2.c…

【使用 WSL子系统 在 Windows 上安装 Linux(官方教程)】

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、使用 wsl --install二、额外的命令 前言 在最新的Windows Insider Preview版本中&#xff0c;只需运行wsl.exe-install&#xff0c;就可以安装运行WSL所需…

WINUI——Trigger(触发器)使用小结

背景 WINUI不提供原生的Trigger支持&#xff0c;推荐使用VisualStateManager进行操作&#xff1b;然对于从WPF转WINUI的开发人员而言&#xff0c;经常会想用Trigger解决问题&#xff0c;鉴于此社区推出了CommunityToolkit.WinUI.Triggers以支持Trigger的使用。 使用方法 1.项…

Properties与xml知识点总结

文章目录 一、Properties1.1 构造方法1.2 从Properties文件中获取1.3 向Properties文件中存储 二、xml2.1 XML2.2 特点2.3 规则2.3 抬头声明2.4 特殊字符2.5 **CDATA区段**2.4 作用和应用场景 三、区别 一、Properties 定义&#xff1a;properties是一个双列集合集合&#xff…

双链表——AcWing.827双链表

双链表 定义 双链表是链表的一种&#xff0c;它的每个节点有两个指针&#xff0c;一个指向前一个节点&#xff0c;一个指向后一个节点。这样使得链表可以双向遍历。 运用情况 频繁进行前后双向遍历操作时非常有用&#xff0c;比如在一些需要来回移动处理数据的场景。可以方…

【Linux系列】深入理解 CURL 命令及其在网络请求中的应用

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

用宝塔部署vue+springboot上线公网详细步骤

首先自己在腾讯云中按照教程安装好宝塔。这是宝塔面板&#xff0c;获取登录宝塔的网址和账号密码。 1.在navicat新建数据库 如果出现权限问题&#xff0c;可以在宝塔数据库面板phpMyAdmin中进行权限设置 navicat可以修改用户权限 2.在宝塔面板新建数据库 3.将前端打包的dist文件…

k8s部署短视频网站(后台+web前端+web管理)

一、系统环境 系统centos7k8sv1.24containerdv1.7.16etcdv3.5.0 二、镜像生成工具准备 nerdctlv1.7.6buildkitv0.13.2 1 nerdctl安装 下载&#xff1a; wget -c https://github.com/containerd/nerdctl/releases/download/v1.7.6/nerdctl-full-1.7.6-linux-amd64.tar.gz …

【电路笔记】-共基极放大器

共基极放大器 文章目录 共基极放大器1、概述2、等效电路3、电流增益4、输入阻抗5、输出阻抗6、电压增益7、示例:电压、电流和功率增益8、总结1、概述 在本文中,我们将介绍双极晶体管放大器的最后一种拓扑,称为共基极放大器 (CBA)。 下面的图 1 显示了 CBA 的电气图,此处没…

RabbitMQ揭秘:轻量级消息队列的优缺点全解析

我是小米,一个喜欢分享技术的29岁程序员。如果你喜欢我的文章,欢迎关注我的微信公众号“软件求生”,获取更多技术干货! 亲爱的读者朋友们,大家好!我是小米,一个热爱技术、喜欢分享的大哥哥。今天我们来聊聊一个在消息队列领域非常重要的工具——RabbitMQ。作为一个在通信…

python简单练习案例-石头剪刀布小游戏

&#x1f308;所属专栏&#xff1a;【python】 ✨作者主页&#xff1a; Mr.Zwq ✔️个人简介&#xff1a;一个正在努力学技术的Python领域创作者&#xff0c;擅长爬虫&#xff0c;逆向&#xff0c;全栈方向&#xff0c;专注基础和实战分享&#xff0c;欢迎咨询&#xff01;…

L53--- 144. 二叉树的后序遍历(深搜)---Java版

1.题目描述 2.思路 &#xff08;1&#xff09;比如 1/ \2 3从根节点开始&#xff1a; 初始时&#xff0c;currentSum 为 0。 根节点的值为 1。 更新后的 currentSum 0 * 10 1 1。 处理左子节点&#xff1a; 当前 currentSum 为 1&#xff08;即路径 “1”&#xff09;。…

AI预测体彩排3采取888=3策略+和值012路或胆码测试6月16日升级新模型预测第1弹

根据前面的预测效果&#xff0c;我对模型进行了重新优化&#xff0c;因为前面的模型效果不是很好。熟悉我的彩友比较清楚&#xff0c;我之前的主要精力是对福彩3D进行各种模型的开发和预测&#xff0c;排三的预测也就是最近1个月才开始搞的。3D的预测&#xff0c;经过对模型的多…