Linux —— 信号(4)

news2024/11/17 0:46:57

Linux —— 信号(4)

  • 信号的处理
    • 用户态和内核态
  • 信号的捕捉
    • sigaction
  • sa_mask字段
  • volatile
  • SIGCHLD信号

我们今天接着来看信号

信号的处理

信号的处理简单一句话就是在内核态处理的。

用户态和内核态

用户态和内核态是操作系统和计组中的概念,我们这里提及一下:

用户态(User Mode)和内核态(Kernel Mode) 是操作系统的两种不同运行级别,它们在访问权限、可执行代码、以及运行环境上有所区别,以确保系统的稳定性和安全性。下面是用户态和内核态的主要区别:

  1. 访问权限
  • 内核态在内核态下,进程可以直接访问所有的系统资源,包括内存、I/O设备、系统核心数据等。这是因为内核态具有较高的权限,能够执行特权指令,比如修改内存管理单元(MMU)的设置、直接操作硬件等。
  • 用户态:相比之下,用户态下的进程权限较低,只能访问受限的资源,主要是自己的地址空间。用户态进程不能直接执行特权指令,也无法直接访问内核地址空间或硬件资源。
  1. 执行的代码
  • 内核态:当执行操作系统内核代码时,CPU处于内核态。这包括驱动程序、系统调用服务例程、中断和异常处理程序等。
  • 用户态:当执行用户程序的代码时,CPU处于用户态。大多数应用程序,如浏览器、文本编辑器等,都在用户态下运行。
  1. 切换方式
  • 用户态到内核态:切换通常发生在以下情况:系统调用(用户程序主动请求操作系统服务)、硬件中断(如键盘输入、网络数据到达)、异常(如除零错误、非法内存访问)。这些事件都会导致控制权从用户态转移到内核态,以便操作系统可以处理这些请求或事件。
  1. 安全性
  • 限制用户态程序的权限有助于保护系统稳定性,防止恶意或错误的用户程序破坏操作系统或其他用户的数据。内核态提供了必要的隔离和保护机制。
  1. 内存访问
  • 内核态可以访问整个内存空间,包括用户空间和内核空间;而用户态只能访问用户空间的内存,尝试访问内核空间的内存会触发硬件异常,进而可能导致进程被操作系统终止或产生其他错误响应。

通过这种区分,操作系统能够有效地管理资源、保护系统安全并提供稳定的服务环境。

我们不是学过进程地址空间吗?
在这里插入图片描述
我们知道,每一个进程都会有自己的进程地址空间:
在这里插入图片描述大家也看到了,我们的进程地址空间被划分成了两个部分,一个是用户空间,一个是内核空间

一般来说,我们写的东西都是在用户空间上,然后我们会有一张用户页表把我们进程地址空间上的东西映射到相应的物理内存上

在这里插入图片描述
同时,如果我们的代码要访问一些内核的东西,我们内核空间也有自己的页表来映射到相应的内存上
在这里插入图片描述

一般来说,内核的东西是不会变的,所以内核页表一般也只会有一张。

所以,如果我们要访问一些内核的东西,就要把自己切换为内核态然后通过内核页表去访问

那么对于信号来说是怎么处理的呢?

首先,我们在用户态发现了信号,会先切换为内核态:
在这里插入图片描述
如果我们自定义了信号的行为,会回到用户态:
在这里插入图片描述之后会重新回到内核态:
在这里插入图片描述

重新回到内核态会调用sigreturn
在这里插入图片描述
上面是一个大概的流程,如果搭建还不是很了解,可以看看这张图片:
在这里插入图片描述
用一张图表示的话,整个过程会有四次状态变化:
在这里插入图片描述

中间的交点可以进行信号捕捉:
在这里插入图片描述

信号的捕捉

sigaction

sigaction函数可以通过修改handle表,定义自己的handle行为:
在这里插入图片描述

sigaction是Unix/Linux系统中用于管理信号的一个函数,它是POSIX标准的一部分,提供了比传统signal函数更强大和灵活的信号处理机制。sigaction允许程序注册对特定信号的处理动作,以及配置与信号处理相关的额外选项,如信号掩码(暂时阻塞哪些信号)和是否重新设置信号处理函数为默认行为等。

基本用法如下:

#include <signal.h>

int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

参数说明:

  • signum:要处理的信号的编号,例如SIGINT(对应Ctrl+C中断)。
  • act:指向一个struct sigaction结构体的指针,用于设置新的信号处理行为。这个结构体通常包含信号处理函数的指针(sa_handlersa_sigaction),一个信号掩码(sa_mask)表示在处理信号时应临时阻塞哪些其他信号,以及其他标志位。
  • oldact:(可选)指向另一个struct sigaction结构体的指针,用于保存之前对该信号的处理方式。如果对旧的行为不感兴趣,可以传入NULL

struct sigaction的定义大致如下:

struct sigaction {
    void (*sa_handler)(int);          // 信号处理函数指针,兼容旧式信号处理
    void (*sa_sigaction)(int, siginfo_t *, void *); // 新式信号处理函数指针,提供更多信号信息
    sigset_t sa_mask;                // 处理信号时应阻塞的信号集合
    int sa_flags;                    // 信号处理的标志,如SA_RESTART, SA_NODEFER等
    /* 其他可能的填充字段,取决于具体实现 */
};

使用sigaction而非signal的主要优势包括:

  • 更细粒度的控制,比如能够控制在处理信号期间哪些其他信号应该被阻塞。
  • 支持传递附加信息给信号处理函数,通过sa_sigactionsiginfo_t结构体。
  • 更可靠,因为它保证了信号处理函数的安装是原子操作,避免了race condition。
  • 可以设置信号处理函数是否被重新安装(SA_RESETHAND等标志)。

因此,sigaction函数常用于需要精确控制信号处理流程的高级编程中。

下面是以使用的例子:

#include <iostream>
#include <signal.h>
#include <unistd.h> // 添加头文件以使用getpid函数

// 自定义信号处理函数
void handle(int signum) {
    std::cout << "get a sign: " << signum << std::endl;
}

int main() {
    // 定义两个sigaction结构体变量,用于设置新的信号处理行为和保存原来的信号处理行为
    struct sigaction act, oact;

    // 配置act结构体,设置handle函数为信号2(SIGINT,默认为Ctrl+C)的处理函数
    act.sa_handler = handle;

    // 使用sigaction系统调用,将信号2的处理方式改为由handle函数处理
    // 同时,保存原先的信号处理方式到oact结构体中,尽管在这个示例中并未使用oact
    sigaction(SIGINT, &act, &oact);

    // 主循环,让进程持续运行并输出PID
    while (1) {
        std::cout << "process is running, PID: " << getpid() << std::endl;
        // 让进程暂停1秒,避免无休止的输出占据终端
        sleep(1);
    }
}

sa_mask字段

这里要说明一下,如果我们正在处理某个信号,中间再次发送该信号,该信号会被屏蔽

#include <iostream>
#include <signal.h>
#include <unistd.h> // 添加头文件以使用getpid函数

void PrintOpending(const sigset_t& opending);

// 打印当前待处理信号集的函数
void PrintOpending(const sigset_t& opending) {

    for(int i = 1; i <= 31; ++i) { // 遍历常见的信号编号

        if(sigismember(&opending, i)) { // 检查该信号是否在待处理集合中

            std::cout << "1"; // 是,则输出1

        } else {

            std::cout << "0"; // 否,则输出0

        }

    }

    std::cout << std::endl; // 换行

}


// 自定义信号处理函数

void handle(int signum) {

    sleep(1); // 等待一秒模拟信号处理时间

    std::cout << "catch a sign: " << signum << std::endl; // 输出接收到的信号编号


    while(true) { // 进入循环持续检查待处理信号

        sigset_t opending; // 创建一个信号集用于存放待处理的信号

        sigpending(&opending); // 获取当前进程的待处理信号集合

        PrintOpending(opending); // 打印当前待处理的信号状态

        sleep(1); // 每秒检查一次

    }

}


int main() {

    std::cout << "prcess is running PID: " << getpid() << std::endl; // 输出当前进程的PID


    struct sigaction act, oact; // 定义两个sigaction结构体变量


    // 配置act结构体,准备将handle函数设置为SIGINT信号的处理函数

    act.sa_handler = handle;


    // 使用sigaction系统调用,更改SIGINT信号的处理方式为handle函数

    // 同时,原SIGINT的处理方式被保存在oact中,但本例中并不使用这个信息

    sigaction(SIGINT, &act, &oact);


    // 主循环,让进程持续运行,但实际上由于没有具体执行内容,这里会一直占用CPU

    while (true) {

    }

}

我们运行这段代码:
在这里插入图片描述
此时我们如果再按Ctrl + C:
在这里插入图片描述我们看到第二位已经变成了1,说明2号信号处于未决,说明2号信号已经被屏蔽了。

如果我们在处理2号信号时,不想让3号信号干扰,我们就要利用sa_mask添加另外的信号:

#include <iostream>
#include <signal.h>
#include <unistd.h> // 添加头文件以使用getpid函数

void PrintOpending(const sigset_t& opending);

// 打印当前待处理信号集的函数
void PrintOpending(const sigset_t& opending) {
    for(int i = 1; i <= 31; ++i) { // 遍历常见的信号编号
        if(sigismember(&opending, i)) { // 检查该信号是否在待处理集合中
            std::cout << "1"; // 是,则输出1
        } else {
            std::cout << "0"; // 否,则输出0
        }
    }
    std::cout << std::endl; // 换行
}


// 自定义信号处理函数
void handle(int signum) {
    sleep(1); // 等待一秒模拟信号处理时间
    std::cout << "catch a sign: " << signum << std::endl; // 输出接收到的信号编号

    while(true) { // 进入循环持续检查待处理信号
        sigset_t opending; // 创建一个信号集用于存放待处理的信号
        sigpending(&opending); // 获取当前进程的待处理信号集合
        PrintOpending(opending); // 打印当前待处理的信号状态
        sleep(1); // 每秒检查一次
    }
}


int main() {
    std::cout << "prcess is running PID: " << getpid() << std::endl; // 输出当前进程的PID
    struct sigaction act, oact; // 定义两个sigaction结构体变量
    // 配置act结构体,准备将handle函数设置为SIGINT信号的处理函数
    act.sa_handler = handle;

    // 使用sigaction系统调用,更改SIGINT信号的处理方式为handle函数
    // 同时,原SIGINT的处理方式被保存在oact中,但本例中并不使用这个信息
    sigemptyset(&act.sa_mask);
    sigaddset(&act.sa_mask,3); //同时将3号信号加入
    sigaction(SIGINT, &act, &oact);

    // 主循环,让进程持续运行,但实际上由于没有具体执行内容,这里会一直占用CPU
    while (true) {

    }

}

在这里插入图片描述

此时,3号信号也被加入屏蔽集了。

不过,这种情况不是很常见,大家了解即可。

volatile

该关键字在C当中我们已经有所涉猎,今天我们站在信号的角度重新理解一下:

我们这里了解一下gcc,g++编译时候带的优化选项:
在使用g++编译C++代码时,可以通过添加优化选项来提高生成的可执行文件的执行效率。这些优化选项能够指导编译器以不同级别对代码进行优化,以减少程序的执行时间或占用的空间。以下是一些常用的g++编译时优化选项:

  1. -O1:进行基本的优化,提供了代码大小和执行速度之间的平衡。这是一个比较保守的优化级别,适合于调试和开发阶段。
  2. -O2:比-O1更进一步的优化,通常会提供更好的执行性能,可能会增加代码大小。这是推荐的优化等级,适用于大多数生产环境。
  3. -O3:这是最高的优化级别,提供了最积极的优化,可能会显著提升程序的运行速度,但也可能导致编译时间延长和代码体积增大。适合追求极致性能的应用。

使用示例:

g++ -O2 -DNDEBUG  main.cpp -o optimized_program

这条命令编译main.cpp,使用-O2进行优化,关闭调试信息(-DNDEBUG),最终生成名为optimized_program的可执行文件。

我们这里有这么一段代码:

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

int flag = 0;

void handler(int sig)
{
    printf("chage flag 0 to 1\n");
    flag = 1;
}

int main()
{
    signal(2, handler);
    while(!flag);
    printf("process quit normal\n");
    return 0;
}

如果我们正常编译,是没啥问题的:
在这里插入图片描述
但是如果带上-O2:
在这里插入图片描述
程序直接退出,因为编译器对flag做了优化,处理了死循环,如果我们不想让它优化,我们得使用volatile关键字:

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

volatile int flag = 0; //带上volatile

void handler(int sig)
{
    printf("chage flag 0 to 1\n");
    flag = 1;
}

int main()
{
    signal(2, handler);
    while(!flag);
    printf("process quit normal\n");
    return 0;
}

在这里插入图片描述
其实volatile是保证内存可见性

SIGCHLD信号

如果我们查看信号,会有这么一个信号:
在这里插入图片描述

SIGCHLD信号是在类Unix操作系统中,当一个子进程终止或者停止时,操作系统发送给其父进程的一种信号。这个信号的主要用途是通知父进程有关子进程状态的变化,以便父进程可以采取相应的行动,比如收集子进程的退出状态、资源清理等。以下是关于SIGCHLD信号的一些关键点:

  1. 默认行为:如果不特别设置,SIGCHLD信号的默认处理动作是忽略。这意味着父进程不会自动执行任何操作来响应子进程的终止,这可能导致子进程成为僵尸进程(zombie process)。
  2. 避免僵尸进程:父进程可以通过注册一个SIGCHLD信号处理函数,并在该函数中调用wait()waitpid()系统调用来回收子进程的状态信息,从而防止子进程变为僵尸进程。这样做可以让操作系统释放与子进程相关的资源。
  3. 自动重aping(Auto-reaping):如果父进程将SIGCHLD信号的处理设置为SIG_IGN(忽略),子进程在终止时会被内核自动清理,而不会生成僵尸进程。这种做法适用于那些不需要关注子进程具体退出状态的场景,例如某些高性能服务器。
  4. 非叠加性:SIGCHLD信号是不可累积的,也就是说,如果有多个子进程相继终止,父进程只会接收到一个SIGCHLD信号,而不是每个子进程一个。因此,在信号处理函数中可能需要使用循环调用wait()waitpid()来处理所有已终止的子进程。
  5. 信号处理策略:在编写多进程应用程序时,合理处理SIGCHLD信号非常重要,既可以避免资源泄露,又可以确保程序的健壮性。开发者可以根据应用的需求选择合适的处理方式,比如主动等待子进程结束、忽略信号或结合其他机制。
  6. 并发服务器中的应用:在并发服务器设计中,由于频繁创建和销毁子进程,正确处理SIGCHLD信号尤为重要,以防止系统中积累大量僵尸进程,影响系统性能。
#include<iostream>
#include<unistd.h>
#include<sys/wait.h>

void handler(int signum)
{
    std::cout << "catch a sign: "<< signum << std::endl;
}

int main()
{
   signal(17,handler);

   pid_t id = fork();

   if(id == 0)
   {
      std::cout << "child is running Pid:" << getpid() << std::endl;
      sleep(10);
      exit(0);
   }

   int cnt = 5;
   while(cnt--)
   {
      sleep(1);
   }

   wait(nullptr);
}

在这里插入图片描述

综上所述,SIGCHLD信号是管理子进程生命周期的关键机制,理解并正确处理它对于编写高效、稳定的多进程程序至关重要。

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

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

相关文章

MySQL系列之索引

&#x1f339;作者主页&#xff1a;青花锁 &#x1f339;简介&#xff1a;Java领域优质创作者&#x1f3c6;、Java微服务架构公号作者&#x1f604; &#x1f339;简历模板、学习资料、面试题库、技术互助 &#x1f339;文末获取联系方式 &#x1f4dd; 往期热门专栏回顾 专栏…

【声呐仿真】学习记录2-运行ROV(带camera、sonar、dvl等传感器)例程

【声呐仿真】学习记录2-运行ROV&#xff08;带camera、sonar、dvl等传感器&#xff09;例程 前言第一阶段-学习Gazebo第二阶段-学习URDF、xacro第三阶段-寻找例程跑一个rexrov示例程序1.uuvsimulator quick_start2.能键盘控制的示例程序&#xff08;失败&#xff09;3.能键盘控…

[初阶数据结构】单链表

前言 &#x1f4da;作者简介&#xff1a;爱编程的小马&#xff0c;正在学习C/C&#xff0c;Linux及MySQL。 &#x1f4da;本文收录于初阶数据结构系列&#xff0c;本专栏主要是针对时间、空间复杂度&#xff0c;顺序表和链表、栈和队列、二叉树以及各类排序算法&#xff0c;持…

【Python系列】字节串与字典字节串

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

【高校科研前沿】北师大陈晋教授团队在遥感顶刊发表最新成果:ClearSCD模型:在高空间分辨率遥感影像中综合利用语义和变化关系进行语义变化检测

01文章简介 论文名称&#xff1a;The ClearSCD model: Comprehensively leveraging semantics and change relationships for semantic change detection in high spatial resolution remote sensing imagery&#xff08;ClearSCD模型&#xff1a;在高空间分辨率遥感影像中综合…

Nextcloud私有云盘-重新定义云存储体验

Nextcloud私有云盘-重新定义云存储体验 1. 什么是Nextcloud ​ Nextcloud是一个开源的云存储和协作平台&#xff0c;旨在为个人用户、企业和团队提供安全、隐私保护的数据存储和共享解决方案。它允许您在不同设备之间同步、共享文件&#xff0c;提供了强大的协作工具和应用生…

VSCode:设置顶部文件标签页滚动条的宽度

使用VSCode打开多个文件后&#xff0c;顶部的文件标签可以通过滚动条进行滚动&#xff0c;但是缺点是该滚动条太窄了&#xff0c;不好选择。 可以通过如下方法修改改滚动条的宽度&#xff1a; 1.点击设置 2.选择工作台->编辑管理->Title Scrollbar Sizing->Large 3.可…

MSP430环境搭建

1.下载ccs编译器 注意&#xff1a;安装路径和工作路径不能出现中文&#xff01; 没有说明的步骤就点next即可&#xff01; 1.1下载适合自己电脑的压缩包。 下载好压缩包后解压&#xff0c;点击有图标进行安装。 1.2创建一个文件夹用于安装编译器位置 选择安装地址&#xff0…

FFmpeg常用API与示例(四)——过滤器实战

1.filter 在多媒体处理中&#xff0c;filter 的意思是被编码到输出文件之前用来修改输入文件内容的一个软件工具。如&#xff1a;视频翻转&#xff0c;旋转&#xff0c;缩放等。 语法&#xff1a;[input_link_label1]… filter_nameparameters [output_link_label1]… 1、视…

Mybatis操作数据库的两种方式:原生API

mybatis操作数据的两种方式&#xff1a;原生api和mapper代理对象 1.mybatis的api提供的方法 insert() 增加 delete() 删除 update() 更新 selectOne() 返回一个数据 selectList() 返回多个数据&#xff0c;结果类型为List selectMap() 返回多个数据&…

YOLOv5-7.0改进(四)添加EMA注意力机制

前言 关于网络中注意力机制的改进有很多种&#xff0c;本篇内容从EMA注意力机制开始&#xff01; 往期回顾 YOLOv5-7.0改进&#xff08;一&#xff09;MobileNetv3替换主干网络 YOLOv5-7.0改进&#xff08;二&#xff09;BiFPN替换Neck网络 YOLOv5-7.0改进&#xff08;三&…

net 7部署到Linux并开启https

1.修改代码设置后台服务运行 安装nuget包 Install-Package Microsoft.Extensions.Hosting.WindowsServices Install-Package Microsoft.Extensions.Hosting.Systemd在Program代码中设置服务后台运行 var builder WebApplication.CreateBuilder(args);if (System.OperatingS…

x6.js bug记录-流程图json数据导入进来之后拖拽节点,节点直接飞走了

添加josn数据进来之后虽然能正常渲染&#xff0c;但是只要一拖拽&#xff0c;则节点就直接飞走了&#xff0c;看不到了。 找了一下午的问题&#xff0c;最后发现。保存的json坐标位置是字符串类型&#xff0c;而这边的位置必须是数字类型。如下&#xff1a; {position: { x: &…

词令蚂蚁新村今日答案:微信小程序怎么查看蚂蚁新村今天问题的正确答案?

微信小程序怎么查看蚂蚁新村今天问题的正确答案&#xff1f; 1、打开微信&#xff0c;点击搜索框&#xff1b; 2、打开搜索页面&#xff0c;选择小程序搜索&#xff1b; 3、在搜索框&#xff0c;输入词令搜索点击进入词令微信小程序&#xff1b; 4、打开词令微信小程序关键词口…

基于Matplotlib的模型性能可视化工作

一、项目简介 本项目是科技考古墓葬识别工作的中间过程&#xff0c;因为需要大量复用所以另起一章好了。 主要涉及到数据读取、数据可视化和少量的数据处理过程。 二、相关知识 PandasMatplotlib 三、实验过程 1. 数据探索性分析 1.1 准备工作–导入模块 import pandas…

考研入门55问---基础知识篇

考研入门55问---基础知识篇 01 &#xff1e;什么是研究生入学考试&#xff1f; 研究生是指大专和本科之后的深造课程。以研究生为最高学历, 研究生毕业后&#xff0c;也可称研究生&#xff0c;含义为研究生学历的人。在中国大陆地区&#xff0c;普通民众一般也将硕士毕业生称…

JavaSE基础小知识Ⅱ(很容易错!!!)

1. 变量被final修饰后不能再指向其他对象&#xff0c;但可以重写 如果是引用变量被final修饰&#xff0c;那么的确如此&#xff1b; 基本变量不能重写 2. 下列代码的输出结果是&#xff1f; public class Test {static {int x 5; }static int x,y; public static void ma…

conan2 基础入门(01)-介绍

conan2 基础入门(01)-介绍 文章目录 conan2 基础入门(01)-介绍⭐什么是conan官网Why use Conan? ⭐使用现状版本情况个人知名开源企业 ⭐ConanCenter包中心github ⭐说明文档END ⭐什么是conan 官网 官网&#xff1a;Conan 2.0: C and C Open Source Package Manager 一句话来…

【Linux:lesson1】的基本指令

&#x1f381;个人主页&#xff1a;我们的五年 &#x1f50d;系列专栏&#xff1a;Linux课程学习 &#x1f337;追光的人&#xff0c;终会万丈光芒 &#x1f389;欢迎大家点赞&#x1f44d;评论&#x1f4dd;收藏⭐文章 目录 &#x1f697;打开Xshell&#xff0c;登陆root…

个人网站快速搭建手册:低成本,高效率,轻松发布

​&#x1f308;个人主页&#xff1a;前端青山 &#x1f525;系列专栏&#xff1a;React篇 &#x1f516;人终将被年少不可得之物困其一生 依旧青山,本期给大家带来-快速构建个人站|博客|系统,低成本发布上线 目录 前言 博主主页搭建案例 虚拟主机|服务器|域名 使用免费二级…