Linux —— 信号(4)

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结构体中,尽管在这个示例中并未使用oactsigaction(SIGINT, &act, &oact);// 主循环,让进程持续运行并输出PIDwhile (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; // 输出当前进程的PIDstruct sigaction act, oact; // 定义两个sigaction结构体变量// 配置act结构体,准备将handle函数设置为SIGINT信号的处理函数act.sa_handler = handle;// 使用sigaction系统调用,更改SIGINT信号的处理方式为handle函数// 同时,原SIGINT的处理方式被保存在oact中,但本例中并不使用这个信息sigaction(SIGINT, &act, &oact);// 主循环,让进程持续运行,但实际上由于没有具体执行内容,这里会一直占用CPUwhile (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; // 输出当前进程的PIDstruct 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);// 主循环,让进程持续运行,但实际上由于没有具体执行内容,这里会一直占用CPUwhile (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; //带上volatilevoid 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.mzph.cn/news/835807.shtml

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

相关文章

最新版Ceph( Reef版本)文件存储简单对接k8s(下集)

假如ceph集群已经创建 1.创建cephfs_pool存储池 ceph osd pool create fs_kube_data 16 162.创建cephfs_metadata存储池 ceph osd pool create fs_kube_metadata 16 163 创建cephfs ceph fs new cephfs01 fs_kube_metadata fs_kube_data4 设置最大活动数 ceph fs set cephfs01…

fatal: fetch-pack: invalid index-pack output

解决方案&#xff1a;git clone --depth1 要克隆的git地址 下载最近一次提交的代码 其他分支的内容都不下载 这样整体下载体量就变小了 执行命令&#xff1a;git clone --depth 1 https://gitlab.scm321.com/ufx/xxxx.git

Mac idea gradle解决异常: SSL peer shut down incorrectly

系统&#xff1a;mac 软件&#xff1a;idea 解决异常: SSL peer shut down incorrectly 查看有没有安装 gradle -v安装 根据项目gradle提示安装版本 brew install gradle7idea的配置 在settings搜索gradle&#xff0c;配置Local installation&#xff0c;选择自己的安装目录…

机器学习入门到放弃2:朴素贝叶斯

1. 算法介绍 1.1 算法定义 朴素贝叶斯分类&#xff08;NBC&#xff09;是以贝叶斯定理为基础并且假设特征条件之间相互独立的方法&#xff0c;先通过已给定的训练集&#xff0c;以特征词之间独立作为前提假设&#xff0c;学习从输入到输出的联合概率分布&#xff0c;再基于学习…

物联网SCI期刊,潜力新刊,审稿速度快,收稿范围广泛!

一、期刊名称 Internet of Things 二、期刊简介概况 期刊类型&#xff1a;SCI 学科领域&#xff1a;物联网 影响因子&#xff1a;5.9 中科院分区&#xff1a;3区 出版方式&#xff1a;订阅模式/开放出版 版面费&#xff1a;选择开放出版需支付$2310 三、期刊征稿范围 I…

PTP 对时协议 IEEE1588 网络对时 计算原理

前言 本文将阐述 PTP 对时协议的原理&#xff0c;slave 节点如何根据获取的时间来纠正和更新自己的时间。 协议概述 整个通讯过程中会发送 4 种类型的数据包&#xff0c;用来支撑对时。下面是 4 个包的解释 Sync message: 由 master 发送&#xff0c;发起对时事务, slave 接…

Java - Json字符串转List<LinkedHashMap<String,String>>

需求&#xff1a;在处理数据时&#xff0c;需要将一个Object类型对象集合转为有序的Map类型集合。 一、问题 1.原代码&#xff1a; 但在使用时出现报错&#xff1a; Incompatible equality constraint: LinkedHashMap<String, String> and LinkedHashMap 不兼容的相等…

副产物四氯化硅综合利用满足可持续发展需求 行业发展意义重大

副产物四氯化硅综合利用满足可持续发展需求 行业发展意义重大 副产物四氯化硅综合利用&#xff0c;是以工业&#xff08;主要是多晶硅行业&#xff09;副产物四氯化硅为原料&#xff0c;制备高价值化学品的过程&#xff0c;可保护环境不受污染&#xff0c;同时实现废物资源化再…

JavaScript APIs

控制网页元素交互等各种网页交互效果。 一、Web API基本认知 声明数组和变量优先使用const 使用let声明变量的情况&#xff1a; 1、如果基本数据类型的值或者引用类型的地址发生变化的时候&#xff0c;需要用let 2、比如 一个变量进行加减运算&#xff0c;比如 for循环中的…

Linux日常管理和服务器配置(二)

一、在系统中配置FTP服务器&#xff1a; 准备工作&#xff1a; a.下载ftp命令 sudo apt install vsftpd 可以先用命令更新一下库 sudo apt-get update 接着输入 systemctl status vsftpd 检查ftp运行状态 然后进入vsftpd.conf文件中修改write为 vim /etc/vsftpdf.conf …

深入理解指针(1)

在之前我们学习了许多c语言的基础知识&#xff0c;让我们初步了解了c语言&#xff0c;接下来将来到c语言中一个重点的知识章节--指针&#xff0c;学习完指针后将会让我们对c语言有更深入的理解&#xff0c;接下来就开始指针的讲解 1.内存与地址 1.指针 在了解内存与地址前&am…

vue开发网站—①调用$notify弹窗、②$notify弹窗层级问题、③js判断两个数组是否相同等。

一、vue中如何使用vant的 $notify&#xff08;展示通知&#xff09; 在Vue中使用Vant组件库的$notify方法来展示通知&#xff0c;首先确保正确安装了Vant并在项目中引入了Notify组件。 1.安装vant npm install vant --save# 或者使用yarn yarn add vant2.引入&#xff1a;在ma…

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

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

Python专题:九、元组

append&#xff08;&#xff09;函数添加列表元素 remove&#xff08;&#xff09;函数移除列表元素 数据存储知识 变量保存的就是数据在内存中的地址 id()函数查看变量存储地址 动态分配 内存地址是动态分配的&#xff0c;每次的数值不一致 copy&#xff08;&#xff09;函…

redis的双写一致性

双写一致性问题 1.先删除缓存或者先修改数据库都可能出现脏数据。 2.删除两次缓存&#xff0c;可以在一定程度上降低脏数据的出现。 3.延时是因为数据库一般采用主从分离&#xff0c;读写分离。延迟一会是让主节点把数据同步到从节点。 1.读写锁保证数据的强一致性 因为一般放…

音视频入门基础:像素格式专题(2)——不通过第三方库将RGB24格式视频转换为BMP格式图片

音视频入门基础&#xff1a;像素格式专题系列文章&#xff1a; 音视频入门基础&#xff1a;像素格式专题&#xff08;1&#xff09;——RGB简介 音视频入门基础&#xff1a;像素格式专题&#xff08;2&#xff09;——不通过第三方库将RGB24格式视频转换为BMP格式图片 一、引…

AI领域最伟大的论文检索网站

&#x1f4d1; 苏剑林&#xff08;Jianlin Su&#xff09;开发的“Cool Papers”网站旨在通过沉浸式体验提升科研工作者浏览论文的效率和乐趣。这个平台的核心优势在于利用Kimi的智能回答功能&#xff0c;帮助用户快速了解论文的常见问题&#xff08;FAQ&#xff09;&#xff0…

基于Qt的Model-View显示树形数据

目标 用qt的模型-视图框架实现树型层次节点的显示&#xff0c;从QAbstractItemModel派生自己的模型类MyTreeItemModel&#xff0c;用boost::property_tree::ptree操作树型数据结构&#xff0c;为了演示&#xff0c;此处只实现了个只读的模型 MyTreeItemModel的定义 #pragma o…

数据结构(一)绪论

2024年5月11日 一稿 数据元素+数据项 逻辑结构 集合 线性结构 树形结构 </

【驱动】SPI

1、简介 SPI(Serial Peripheral interface)串行外设接口。 特点: 高速:最大几十M,比如,AD9361的SPI总线速度可以达到40MHz以上全双工:主机在MOSI线上发送一位数据,从机读取它,而从机在MISO线上发送一位数据,主机读取它一主多从:主机产生时钟信号,通过片选引脚选择…