Linux——进程信号(3)(信号保存与信号捕捉)

进程信号3

  • 信号保存
    • 信号相关概念详解
    • 信号集(sigset_t)及操作函数详解
  • 信号捕捉
    • 信号捕捉的详细流程解析
      • 信号捕捉的核心概念
      • 信号捕捉的完整流程(以 SIGQUIT 为例)

信号保存

信号相关概念详解

1.核心概念

(1) 信号递达(Delivery)
定义:信号递达是指信号被进程实际处理的过程。

触发时机:当进程从内核态返回用户态时,内核检查并处理未决信号。

处理方式:

默认动作(SIG_DFL):系统预定义行为(如终止、暂停、忽略等)。

忽略(SIG_IGN):直接丢弃信号,不执行任何操作。

自定义处理函数:通过signal()或sigaction()注册的用户函数。

(2) 信号未决(Pending)
定义:信号从生成到递达之间的中间状态。

原因:

  • 信号被进程阻塞(Blocked)(通过sigprocmask()设置)。

  • 进程正在处理其他同类型信号(非实时信号可能合并)。

未决信号集:内核为每个进程维护一个集合,记录所有未决信号(可通过sigpending()读取)。

(3) 信号阻塞(Block)
定义:阻止信号递达(即使信号已生成),使其保持未决状态。

关键点:

  • 阻塞是未决的前提,未被阻塞的信号会立即递达(除非进程正处理其他信号)。

  • 阻塞解除后,未决信号会递达(除非被忽略)。

(4) 信号忽略(Ignore)
1.定义:信号递达后,进程选择不执行任何操作(通过SIG_IGN或空处理函数)。

与阻塞的区别:

  • 阻塞:信号未递达,停留在未决状态。

  • 忽略:信号已递达,但处理动作为“丢弃”。

2. 三者的关系

信号生成│├── 未被阻塞 ──── 立即递达(执行默认/自定义动作或忽略)│└── 被阻塞 ────── 加入未决信号集│└── 阻塞解除后 ──── 递达

3.关键区别总结

概念阶段效果系统调用
阻塞(Block)信号递达前信号保持未决,不处理sigprocmask()
忽略(Ignore)信号递达后丢弃信号,不执行任何动作signal(sig, SIG_IGN)
未决(Pending)信号生成到递达间信号等待被处理sigpending()

4. 示例代码说明

复制
#include <signal.h>
#include <stdio.h>
#include <unistd.h>void handler(int sig) {printf("递达信号 %d\n", sig);
}int main() {// 注册SIGINT处理函数signal(SIGINT, handler);// 阻塞SIGINTsigset_t block_set;sigemptyset(&block_set);sigaddset(&block_set, SIGINT);sigprocmask(SIG_BLOCK, &block_set, NULL);printf("按下 Ctrl+C,SIGINT 将被阻塞...\n");sleep(3);  // 此时发送SIGINT,信号处于未决状态// 检查未决信号sigset_t pending_set;sigpending(&pending_set);if (sigismember(&pending_set, SIGINT)) {printf("SIGINT 处于未决状态!\n");}// 解除阻塞,信号递达sigprocmask(SIG_UNBLOCK, &block_set, NULL);printf("SIGINT 已解除阻塞。\n");return 0;
}

输出结果:

在sleep(3)期间按下Ctrl+C,SIGINT被阻塞,处于未决状态。

sigpending()检测到未决的SIGINT。

解除阻塞后,handler函数执行,信号递达。

(5)注意事项
信号丢失:非实时信号(如SIGINT)在未决期间多次生成,可能仅保留一次。

实时信号(如SIGRTMIN)支持排队,不会丢失。

原子性:信号处理函数应使用可重入函数,避免竞态条件。

(6)常见问题
Q:阻塞和忽略有何本质区别?
A:阻塞阻止信号递达(未决状态),忽略是递达后的处理方式之一。

Q:如何永久阻塞一个信号?
A:通过sigprocmask()设置阻塞掩码后,除非显式解除,否则信号一直未决。

Q:sigpending()的作用?
A:获取当前进程所有未决信号,用于调试或条件处理。

信号集(sigset_t)及操作函数详解

(1) 信号集(sigset_t)的基本概念
用途:表示一组信号,每个信号对应一个比特位(1有效,0无效)。

未决信号集(Pending Set):记录信号是否处于未决状态(已产生但未递达)。

阻塞信号集(Block Set / Signal Mask):记录信号是否被阻塞(屏蔽)。

特性:

仅记录信号是否有效,不记录次数(多次触发同一信号仍只显示一次)。

内部实现由系统决定,用户只能通过标准函数操作,不可直接访问比特位。

在这里插入图片描述

(2)信号集操作函数

以下函数均来自 <signal.h>,成功返回 0,失败返回 -1(sigismember 返回布尔值):

函数功能
sigemptyset(sigset_t *set)初始化信号集为空(所有比特置 0)。
sigfillset(sigset_t *set)初始化信号集包含所有信号(所有比特置 1)。
sigaddset(sigset_t *set, int signo)将指定信号(如 SIGINT)添加到信号集。
sigdelset(sigset_t *set, int signo)从信号集中删除指定信号。
sigismember(const sigset_t *set, int signo)检查信号是否在集合中(1=存在,0=不存在)。

注意:

使用 sigset_t 前必须初始化(sigemptyset 或 sigfillset),否则行为未定义。

不可直接打印 sigset_t 的内容(如 printf),需通过 sigismember 逐信号检查。

(3)信号屏蔽字操作(sigprocmask)

int sigprocmask(int how, const sigset_t *set, sigset_t *oset);

功能:读取或更改进程的阻塞信号集(Signal Mask)。

参数:

how:修改方式,可选值:

  • SIG_BLOCK:将 set 中的信号添加到当前阻塞集(mask = mask | set)。

  • SIG_UNBLOCK:从当前阻塞集中移除 set 中的信号(mask = mask & ~set)。

  • SIG_SETMASK:直接设置阻塞集为 set(mask = set)。

set:新信号集(若为 NULL,则不修改)。

oset:保存旧的阻塞集(若为 NULL,则不保存)。

返回值:成功返回 0,失败返回 -1。

关键行为:
若解除对未决信号的阻塞,sigprocmask 返回前至少会递达一个信号。

(4)获取未决信号集(sigpending)

int sigpending(sigset_t *set);

功能:读取当前进程的未决信号集,通过 set 传出。

返回值:成功返回 0,失败返回 -1。

(5) 实验代码解析
以下代码演示了信号屏蔽与未决信号的检测:

#include <iostream>
#include <signal.h>
#include <unistd.h>void PrintPending(sigset_t &pending) {std::cout << "Pending signals: ";for (int signo = 31; signo >= 1; signo--) {std::cout << (sigismember(&pending, signo) ? "1" : "0");}std::cout << std::endl;
}void handler(int signo) {std::cout << "Signal " << signo << " delivered!\n";
}int main() {signal(SIGINT, handler);  // 捕捉 SIGINT (Ctrl+C)sigset_t block_set, old_set;sigemptyset(&block_set);sigaddset(&block_set, SIGINT);  // 屏蔽 SIGINTsigprocmask(SIG_BLOCK, &block_set, &old_set);  // 设置阻塞int cnt = 5;while (cnt--) {sigset_t pending;sigpending(&pending);  // 获取未决信号PrintPending(pending);sleep(1);}// 解除屏蔽sigprocmask(SIG_SETMASK, &old_set, NULL);return 0;
}

运行结果:

按下 Ctrl+C 后,SIGINT 被阻塞,未决信号集中对应位变为 1。

解除阻塞后,SIGINT 递达,触发 handler 函数。

(6) 关键区别与注意事项
阻塞 vs 忽略:

  • 阻塞(Block):信号被暂时屏蔽,处于未决状态,解除阻塞后会递达。

  • 忽略(Ignore):信号直接被丢弃,不会递达(通过 signal(signo, SIG_IGN) 设置)。

信号集初始化:未初始化的 sigset_t 可能导致未定义行为,必须调用 sigemptyset 或 sigfillset。

多线程环境:sigprocmask 仅影响当前线程,应使用 pthread_sigmask。

(7)总结图示

+-------------------+      sigprocmask()       +-------------------+
|   Block Set       | <---------------------- | 用户设置的信号集   |
|  (Signal Mask)    |                         | (sigset_t)        |
+-------------------+                         +-------------------+|                                             ^| 信号产生但被阻塞                            | sigpending()v                                             |
+-------------------+                         +-------------------+
|   Pending Set     | ----------------------> | 未决信号集        |
|  (待处理信号)      |                         | (sigset_t)        |
+-------------------+                         +-------------------+|| 解除阻塞后v
+-------------------+
|   信号递达         |
|   (执行处理函数)    |
+-------------------+

信号捕捉

信号捕捉的详细流程解析

信号捕捉的核心概念

当信号的处理动作用户自定义函数(而非 SIG_IGN 或 SIG_DFL),内核会在信号递达时调用该函数,这一过程称为 信号捕捉。

由于信号处理函数运行在用户态,其执行流程涉及 用户态-内核态切换。

信号捕捉的完整流程(以 SIGQUIT 为例)

1.注册信号处理函数
用户程序通过 signal() 或 sigaction() 注册信号处理函数(如 sighandler):

signal(SIGQUIT, sighandler);  // 自定义SIGQUIT处理函数

2.主程序执行时发生中断/异常

假设 main() 函数正在执行,此时发生 硬件中断(如时钟中断)或 异常(如缺页异常),CPU 切换到 内核态 处理中断。

3.内核检查未决信号

中断处理完成后,内核在 返回用户态前 检查进程是否有未决信号(如 SIGQUIT)。

若发现未决信号且其动作为“捕捉”,内核准备切换到用户态执行 sighandler。

4.切换到信号处理函数

内核 不恢复 main() 的上下文,而是:

为用户态信号处理函数 sighandler 创建独立的 临时栈帧(与 main() 栈隔离)。

修改用户态栈和寄存器,使 sighandler 成为下一个执行入口。

切换回用户态后,CPU 开始执行 sighandler。

5.信号处理函数执行完毕

sighandler 执行完成后,会 隐式调用 sigreturn() 系统调用,再次进入内核态。

内核此时:

销毁临时栈帧。

恢复 main() 的原始上下文(寄存器、栈指针等)。

若无其他未决信号,返回用户态继续执行 main()。
在这里插入图片描述
在这里插入图片描述

(3) 关键点与注意事项

独立性:
sighandler 和 main() 是 两个独立的控制流,它们:

使用不同栈空间(内核为 sighandler 分配临时栈)。

无直接调用关系(非函数调用,而是通过内核调度切换)。

可重入性问题:
信号处理函数中应避免调用 非异步信号安全函数(如 printf、malloc),否则可能导致死锁或数据损坏。

内核态-用户态切换:
一次完整的信号捕捉涉及 两次状态切换:

用户态 → 内核态(中断/异常处理)。

内核态 → 用户态(执行 sighandler)。

用户态 → 内核态(sigreturn)。

内核态 → 用户态(恢复 main())。
在这里插入图片描述

信号屏蔽与竞态条件:
内核会在执行 sighandler 时 自动阻塞同一信号(除非显式设置 SA_NODEFER),防止递归触发。

(4)代码示例与验证
以下代码演示信号捕捉流程,并打印栈地址以验证独立性:

#include <stdio.h>
#include <signal.h>
#include <unistd.h>void sighandler(int signo) {printf("Signal %d caught!\n", signo);printf("sighandler stack frame: %p\n", (void*)&signo);  // 打印处理函数栈地址sleep(1);  // 模拟处理耗时
}int main() {printf("Main stack frame: %p\n", (void*)&main);  // 打印main函数栈地址signal(SIGQUIT, sighandler);  // 注册SIGQUIT处理函数while (1) {printf("Main running...\n");sleep(1);}return 0;
}

输出分析:

main 和 sighandler 的栈地址差异显著,说明二者栈空间独立。

按下 Ctrl+\(触发 SIGQUIT)后,sighandler 打断 main 的执行流。

(5) 信号捕捉的底层机制

内核数据结构:

进程的 task_struct 中维护:

  • pending:未决信号集。

  • blocked:阻塞信号集。

  • sighand:指向信号处理函数表。

sigreturn 的作用:
恢复原始上下文的关键系统调用,由 glibc 在信号处理函数返回前自动触发。

(6)总结图示

用户态 内核态 硬件 main()正常执行 发生中断/异常(如Ctrl+\) 处理硬件中断 检查未决信号(发现SIGQUIT需捕捉) 构建sighandler执行环境(临时栈+寄存器) sighandler()执行 sighandler返回(自动调用sigreturn) 恢复main()上下文 返回main()继续执行 用户态 内核态 硬件

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

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

相关文章

批量在 txt 记事本文件的指定位置插入行,如在最末尾位置插入行

我们在网上下载的小说经常可以看到内容中每隔多少行都会现一些网站名称和联系方式等广告信息&#xff0c;这些都是固定或者随机插入在小说正文中的。那是怎么样实现在文本文件中指定位置插入这些行的呢&#xff1f;今天就给大家介绍一下如何在 txt 记事本文件或者其它类型的文本…

python的内存管理机制

目录 内存分配方式自动内存管理内存分配策略 垃圾回收机制引用计数垃圾回收对象创建和引用关系引用计数的状态删除变量 标记 - 清除垃圾回收 内存分配的区域划分栈内存堆内存 内存管理的优化内存池技术对象共享 Python 的内存管理机制是其运行效率和安全性的重要保障&#xff0…

火山引擎coze用户市场

火山引擎 **Coze**&#xff08;扣子&#xff09;的用户市场主要集中在 **需要快速构建和部署智能对话应用的企业及开发者群体**&#xff0c;覆盖多个行业与场景。以下是具体分析&#xff1a; --- ### **一、核心用户群体** 1. **企业用户** - **互联网/科技公司**&#…

Java 责任链模式 详解

责任链模式详解 一、责任链模式概述 责任链模式(Chain of Responsibility Pattern)是一种行为型设计模式&#xff0c;它允许你将请求沿着处理链传递&#xff0c;直到有一个处理者能够处理它为止。这种模式解耦了请求的发送者和接收者&#xff0c;使多个对象都有机会处理请求。…

【C++初阶】--- string类

1.STL简介 STL(standard template libaray-标准模板库)&#xff1a;是C标准库的重要组成部分&#xff0c;不仅是一个可复用的组件库&#xff0c;而且是一个包罗数据结构与算法的软件框架。 2.string类 2.1什么是string类 std::string 类是 C 标准库中用于处理和操作字符串…

Ubuntu 20.04 中 Git 的安装、配置和基本操作指南

本文为经验 所谈 使用版本为ubuntu20.04 1 ubuntu的git初始化 1.安装git sudo apt-get install git 可通过git --version 命令查看&#xff0c;正常输出git版本号即安装成功。 2.配置用户名和邮箱名 git config --global user.name "Your Name" git config --globa…

conda 激活环境vscode的Bash窗口

多份conda环境注意事项&#xff0c;当时安装了两个conda环境&#xff0c;miniconda和conda&#xff0c;导致环境总是冲突矛盾。初始化时需要更加注意。 $ C:/Users/a_hal/miniconda3/Scripts/conda.exe init bash能够显示用哪里的conda环境命令执行。 然后直接conda activate…

Mac下小智AI本地环境部署

可以进行聊天、编写程序、播放歌曲等等的小智语音聊天小助手&#xff0c;在Mac环境下修改源代码&#xff0c;值得拥有。本篇内容主要讲解Mac下环境的搭建&#xff0c;WebSocket的修改。注&#xff1a;环境python3.12.0、ESP-IDF5.4.0、开发板ESP32S3。 目录 1.Git安装2.Python…

Linux安装Cmake (Centos 7.9)

cmake安装 这个虽然已经更新到了4.0.0版本了&#xff0c;但是我们要用3.5版本的&#xff0c;因为这个比较稳定 官方地址&#xff1a;https://github.com/Kitware/CMake/releases/tag/v3.5.0&#xff0c;选择那个cmake-3.5.0-Linux-x86_64.tar.gz下载&#xff0c; 首先解压文…

基于 SpringBoot 的旧物置换网站

收藏关注不迷路&#xff01;&#xff01; &#x1f31f;文末获取源码数据库&#x1f31f; 感兴趣的可以先收藏起来&#xff0c;还有大家在毕设选题&#xff08;免费咨询指导选题&#xff09;&#xff0c;项目以及论文编写等相关问题都可以给我留言咨询&#xff0c;希望帮助更多…

ELK 通过 logstash 收集单个/多个日志文件

一、收集单个日志文件 注意事项&#xff1a; logstah 服务默认启动用户和组是 logstash被收集的日志文件有读的权限并对写入的文件有写权限而 logstash 是普通用户 1.1 编辑 logstash 配置文件 vim /etc/logstash/conf.d/test.conf input {file {path > "/var/log/…

分享一个Drools规则引擎微服务Docker部署

通常我们都是把Drools作为嵌入式使用&#xff0c;但在微服务泛滥时代&#xff0c;还在老套的嵌入式显然不符合微服务架构要求&#xff0c;本文分享一个把Drools作为微服务独立部署的方案。 本方案基于Drools引擎微服务&#xff0c;提供REST接口。 1、可以动态部署Drools规则2…

Mac 本地化部署 dify

Macbook 本地化部署 dify 目录 Macbook 本地化部署 dify安装dockerdocker下载地址 安装dify下载dify到本地github可能遇到的问题: github打开超时在本地解压dify.zip文件本地化部署docker部署可能遇到的问题: 部署超时登录体验 dify 安装docker docker下载地址 根据电脑芯片选…

串口中断接收与环形缓冲实例(apollo3 blue plus)

#define DEV_UART1 1 #define GPS_POWER_PIN 13 #define GPS_LOG_ENABLE 1 #define MAX_UART1_SIZE 1024 #define AM_BSP_GPIO_COM_UART1_TX 8 #define AM_BSP_GPIO_COM_UART1_RX 9 // 定义环形缓冲区结构 typed…

操作系统高频(五)linux命令

操作系统高频&#xff08;五&#xff09;linux命令 1.Linux中查看进程运行状态的指令、tar解压文件的参数。⭐⭐⭐ 在Linux中&#xff0c;可以使用以下指令查看进程的运行状态&#xff1a; top&#xff1a; 用于实时监视系统的进程活动和系统资源使用情况。在终端中运行top…

Spring Boot 快速入手

前言&#xff1a;为什么选择 Spring Boot&#xff1f; &#x1f680; 在现代 Java 开发中&#xff0c;Spring Boot 已成为最流行的后端框架之一。无论是小型 Web 应用、企业级系统&#xff0c;还是微服务架构&#xff0c;Spring Boot 都能提供快速开发、自动配置、轻量级部署的…

oracle-blob导出,在ob导入失败

导出&#xff1a; [oraclelncs dmp]$ /home/oracle/sqluldr2 gistar/res#pwd192.168.205.58:1521/lndb query"select * from an_odn_picture where length(PIC_CONTENT)<25000" filean_odn_picture.csv Charsetutf8 textCSV 0 rows exported at 2025-…

RK3568 pinctrl内容讲解

文章目录 一、pinctrl的概念`pinctrl` 的作用设备树中的 `pinctrl` 节点典型的 `pinctrl` 节点结构例子`pinctrl` 的重要性总结二、RK3568的pinctrl讲解1. `pinctrl` 节点2. `gpio0` 至 `gpio4` 子节点每个 `gpioX` 子节点的结构和作用3. `gpio1` 到 `gpio4` 子节点总结1. `aco…

北京南文观点:后糖酒会营销,以战略传播重构品牌信心坐标

第112届全国糖酒会落下帷幕&#xff0c;参展品牌面临一个关键命题。如何在流量洪流中沉淀品牌价值&#xff1f;北京南文&#xff08;全称&#xff1a;南文乐园科技文化&#xff08;北京&#xff09;有限公司&#xff09;认为&#xff0c;糖酒会的结束恰是算法时代品牌认知战的真…

html5时钟升级!支持切换深浅模式 Canvas实现现代化动态时钟

HTML5 Canvas实现现代化动态时钟 这里写目录标题 HTML5 Canvas实现现代化动态时钟项目介绍技术实现1. 项目架构2. Canvas绘图实现2.1 表盘绘制2.2 刻度绘制2.3 指针绘制 3. 动画效果4. 主题切换 项目亮点技术要点总结项目收获改进方向结语 项目介绍 本项目使用HTML5 Canvas技术…