当前位置: 首页 > news >正文

Linux 进程信号补充知识点总结(按重要程度排序)

一、核心必掌握知识点(高频考点 + 实操重点)

1. 信号递达与阻塞的深度理解(含内核态存储逻辑)

关键概念区分
概念定义核心差异
信号未决(Pending)信号产生后到递达前的状态,进程已感知但未处理未决是 “待处理” 状态,阻塞是 “禁止处理” 开关
信号阻塞(Block)进程主动设置 “屏蔽”,被阻塞信号即使产生也不会递达阻塞不影响信号产生(仍会标记未决),仅阻止递达
信号递达(Delivery)实际执行信号处理动作(默认 / 忽略 / 自定义)递达是最终处理环节,需先解除阻塞(若被阻塞)
内核中存储逻辑(必须理解)

进程 PCB(task_struct)中通过 3 个核心结构管理信号:

  • blocked(信号屏蔽字)sigset_t类型,位图结构,某 bit 为 1 表示对应信号被阻塞。
  • pending(未决信号集)sigset_t类型,位图结构,某 bit 为 1 表示对应信号已产生但未递达。
  • sighand(信号处理动作):存储每个信号的处理方式(SIG_DFL/SIG_IGN/ 自定义函数指针)。

示例场景:若进程阻塞SIGINT(2 号信号),此时按下Ctrl+C

  1. 内核标记pendingSIGINT的 bit 为 1(未决);
  2. blockedSIGINT的 bit 为 1(阻塞),信号不递达;
  3. 当进程解除SIGINT阻塞后,内核检测到未决信号,立即执行递达(如默认终止)。
实操练习:验证阻塞与未决
#include <iostream>
#include <unistd.h>
#include <signal.h>
using namespace std;// 打印未决信号集
void printPending() {sigset_t pending;sigpending(&pending); // 获取当前进程未决信号集cout << "Pending signals (31~1): ";for (int i = 31; i >= 1; i--) {if (sigismember(&pending, i)) cout << "1";else cout << "0";}cout << endl;
}int main() {sigset_t blockSet, oldSet;sigemptyset(&blockSet);       // 初始化空信号集sigaddset(&blockSet, SIGINT); // 将SIGINT(2号)加入阻塞集// 设置阻塞信号集,备份原阻塞集到oldSetsigprocmask(SIG_BLOCK, &blockSet, &oldSet);cout << "Block SIGINT, press Ctrl+C (5s later unblock)..." << endl;for (int i = 5; i > 0; i--) {printPending();sleep(1);}// 解除阻塞(恢复原阻塞集)sigprocmask(SIG_SETMASK, &oldSet, nullptr);cout << "Unblock SIGINT, now Ctrl+C will terminate process" << endl;while (1) sleep(1);return 0;
}

运行结果:按下Ctrl+C后,未决信号集第 2 位(SIGINT)会显示 “1”;5 秒后解除阻塞,进程立即被SIGINT终止。

2. 信号捕捉的完整流程(含内核态 / 用户态切换)

核心流程(必须能复述)
  1. 用户态执行主流程:进程在用户态运行main函数,此时发生中断 / 异常 / 系统调用,切换到内核态。
  2. 内核态检测信号:内核处理完中断 / 异常后,准备返回用户态前,检查进程的pendingblocked
    • 若存在未被阻塞的信号,且处理方式为 “自定义函数”,则不返回主流程,而是切换到用户态执行自定义处理函数。
  3. 用户态执行处理函数:处理函数执行完毕后,自动调用sigreturn系统调用,再次切换到内核态。
  4. 内核态恢复主流程:内核确认无新信号需递达,恢复主流程的上下文,切换回用户态继续执行main函数。
关键函数:sigaction(比signal更稳定,推荐使用)

signal函数存在兼容性问题,sigaction是 POSIX 标准接口,支持更精细的信号控制:

#include <signal.h>
// 函数原型
int sigaction(int signo, const struct sigaction *act, struct sigaction *oact);
  • struct sigaction核心字段
    • sa_handler:处理函数指针(SIG_DFL/SIG_IGN/ 自定义函数)。
    • sa_mask:执行处理函数期间,额外阻塞的信号集(防止处理函数被其他信号打断)。
    • sa_flags:控制信号行为(如SA_RESTART使被信号打断的系统调用自动重启)。
实操练习:用sigaction捕捉SIGINT
#include <iostream>
#include <unistd.h>
#include <signal.h>
using namespace std;void sigintHandler(int signo) {cout << "Catch SIGINT (signo=" << signo << "), wait 2s..." << endl;sleep(2); // 执行处理期间,若再次按Ctrl+C,因sa_mask会阻塞SIGINT,需等待处理完毕
}int main() {struct sigaction act, oldAct;// 初始化actact.sa_handler = sigintHandler;  // 设置自定义处理函数sigemptyset(&act.sa_mask);       // 初始化额外阻塞集sigaddset(&act.sa_mask, SIGINT); // 执行处理函数时,额外阻塞SIGINTact.sa_flags = 0;                // 默认行为// 设置SIGINT的处理动作,备份原动作到oldActsigaction(SIGINT, &act, &oldAct);cout << "Wait for SIGINT (press Ctrl+C), enter 'q' to quit" << endl;while (cin.get() != 'q');// 恢复SIGINT原处理动作sigaction(SIGINT, &oldAct, nullptr);return 0;
}

运行结果:第一次按Ctrl+C会执行处理函数(2 秒内再次按Ctrl+C无反应),处理完毕后恢复响应;输入q退出程序。

3. 可重入函数与竞态条件(避坑重点)

可重入函数定义
  • 可重入:多个控制流程(如主流程 + 信号处理函数)同时调用该函数,不会导致数据错乱(仅访问局部变量 / 参数,无全局 / 静态变量操作)。
  • 不可重入:调用后可能导致数据错乱,常见场景:
    1. 操作全局 / 静态变量(如全局链表插入);
    2. 调用malloc/free(堆管理用全局链表);
    3. 调用标准 I/O 函数(如printf,内部用全局缓冲区)。
竞态条件示例(不可重入函数问题)
#include <iostream>
#include <unistd.h>
#include <signal.h>
using namespace std;struct Node { int val; Node* next; } node1, node2, *head = nullptr;// 不可重入函数:操作全局链表head
void insert(Node* p) {p->next = head; // 步骤1:保存原head// 若此时发生信号,进入处理函数调用insert,会修改headsleep(1);       // 模拟信号打断head = p;       // 步骤2:更新head为p
}void sigHandler(int signo) {insert(&node2); // 信号处理函数调用insertcout << "Signal handler insert node2" << endl;
}int main() {node1.val = 1; node2.val = 2;signal(SIGINT, sigHandler); // 注册SIGINT处理函数insert(&node1); // 主流程调用insertcout << "Main insert node1" << endl;// 遍历链表(预期node1->node2,实际可能只有node2)Node* cur = head;while (cur) {cout << cur->val << " ";cur = cur->next;}return 0;
}

运行结果:主流程执行insert(&node1)sleep(1)时,按下Ctrl+C,信号处理函数插入node2并修改head;主流程恢复后继续执行head = &node1,最终链表仅node1(数据错乱)。

避坑方案
  1. 优先使用可重入函数(如自己实现无全局变量的工具函数);
  2. 若必须操作全局数据,在信号处理函数执行期间,通过sigprocmask阻塞相关信号(避免打断)。

4. SIGCHLD信号与僵尸进程处理(实战必备)

核心作用

子进程终止时,会向父进程发送SIGCHLD信号(默认处理动作是忽略),父进程可通过捕捉该信号,调用waitpid清理僵尸进程(避免轮询)。

关键特性
  • 子进程终止后若父进程未处理,会变为僵尸进程(Z状态);
  • 父进程若用sigactionSIGCHLD设为SIG_IGN,子进程终止后会自动清理(不产生僵尸进程,Linux 特有)。
实操练习:捕捉SIGCHLD清理僵尸进程
#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/types.h>
using namespace std;// SIGCHLD处理函数:清理所有终止的子进程
void sigchldHandler(int signo) {pid_t id;// WNOHANG:非阻塞,有子进程终止则返回其PID,否则返回0while ((id = waitpid(-1, nullptr, WNOHANG)) > 0) {cout << "Clean zombie child, PID=" << id << endl;}
}int main() {// 注册SIGCHLD处理函数struct sigaction act;act.sa_handler = sigchldHandler;sigemptyset(&act.sa_mask);act.sa_flags = 0;sigaction(SIGCHLD, &act, nullptr);// 创建3个子进程for (int i = 0; i < 3; i++) {pid_t cpid = fork();if (cpid == 0) {cout << "Child PID=" << getpid() << ", exit after 2s" << endl;sleep(2);exit(0); // 子进程终止,发送SIGCHLD}}// 父进程持续运行,等待子进程终止while (1) sleep(1);return 0;
}

运行结果:3 个子进程 2 秒后陆续终止,父进程通过SIGCHLD处理函数自动清理,无僵尸进程残留(可通过ps aux | grep 程序名验证)。

二、重要理解类知识点(原理性内容)

1. 内核态与用户态的切换(信号处理的底层支撑)

核心区别
维度用户态(Ring 3)内核态(Ring 0)
权限仅能访问用户空间(0~3GB),无硬件操作权限可访问内核空间(3~4GB),有所有硬件操作权限
执行代码应用程序代码(如main函数)内核代码(如系统调用、中断处理)
切换触发系统调用(如fork)、异常(如除零)、中断(如键盘)处理完毕后自动切换回用户态
与信号处理的关联

信号处理函数在用户态执行,但信号的检测、未决 / 阻塞标记的维护在内核态完成:

  • 当内核检测到可递达信号时,会 “跳转” 到用户态执行处理函数;
  • 处理函数返回后,通过sigreturn系统调用切回内核态,再恢复主流程。

2. volatile关键字(解决编译器优化导致的 bug)

作用

告知编译器:被修饰的变量不允许优化,每次访问必须从内存读取(而非寄存器缓存),保证内存可见性。

典型场景

信号处理函数修改全局变量,主流程循环判断该变量(无volatile会导致优化 bug):

#include <iostream>
#include <unistd.h>
#include <signal.h>
using namespace std;// 必须加volatile,否则编译器可能将flag缓存到寄存器
volatile int flag = 0;void sigHandler(int signo) {flag = 1;cout << "Signal set flag=1" << endl;
}int main() {signal(SIGINT, sigHandler);cout << "Wait for SIGINT (press Ctrl+C)" << endl;// 若flag无volatile,编译器可能优化为while(1)(认为flag不会变)while (!flag);cout << "Main exit (flag=1)" << endl;return 0;
}

注意:编译时加-O2(优化选项),若不加volatile,程序按下Ctrl+C后仍会卡在while循环;加volatile则正常退出。

三、了解类知识点(扩展认知)

1. 实时信号与常规信号(编号差异)

  • 常规信号:编号 1~31,不支持排队(同一信号多次产生,递达时仅处理 1 次);
  • 实时信号:编号 34~64,支持排队(多次产生会依次递达),用于需要精确处理的场景(如工业控制),本章暂不涉及。

2. Core Dump(核心转储)

  • 作用:进程异常终止时(如SIGSEGV段错误),将用户空间内存数据保存到core文件,用于事后调试(gdb ./程序名 core.进程号)。
  • 开启方法:默认关闭,通过ulimit -c 1024(允许最大 1024KB 的 core 文件)开启,测试代码:
    #include <stdio.h>
    int main() {int* p = nullptr;*p = 100; // 非法内存访问,产生SIGSEGV,触发Core Dumpreturn 0;
    }

    编译运行后会生成core.xxx文件,用gdb ./a.out core.xxx可定位到错误行。

3. 操作系统的中断驱动模型

  • 操作系统通过 “中断”(硬件中断如键盘、软中断如系统调用)驱动运行,核心是 “中断向量表(IDT)”:
    • 每个中断对应一个处理例程(如0x20是时钟中断,0x80是系统调用);
    • 时钟中断定期触发,推动进程调度(如时间片轮转),是操作系统的 “心脏”。

http://www.dtcms.com/a/363555.html

相关文章:

  • 立足稳联技术的Ethernet IP转ModbusTCP网关与触摸屏连接案例的专业研判
  • Web3 出海香港 101 |BuildSpace AMA 第一期活动高亮观点回顾
  • C++全局变量未初始的和已初始化的位置放在哪里?
  • Web3兴起:重新定义互联网格局
  • 强化学习PPO/DDPG算法学习记录
  • 图像编码之摄像机的H264 分块编码的含义是什么,以分块编码(tile)192X192为例子说明,好处与缺点分别是什么
  • Day19(前端:JavaScript基础阶段)
  • Linux笔记14——shell编程基础-8
  • 解决戴尔笔记本电脑键盘按键部分失灵
  • 未来工厂雏形:基于Three.js的自主演进式数字孪生系统设计
  • Qwen3-Reranker-0.6B 模型结构
  • Coze平台指南(2):开发环境的搭建与配置
  • Cisco FMC利用sftp Server拷贝文件方法
  • Ubuntu中配置JMmeter工具
  • 从零开始:用代码解析区块链的核心工作原理
  • Ubuntu 24.04 服务器配置MySQL 8.0.42 三节点集群(一主两从架构)安装部署配置教程
  • 软件设计师——软件工程学习笔记
  • 矩阵scaling预处理介绍
  • AI代码生成神器终极对决:CodeLlama vs StarCoder vs Codex,谁才是开发者的「最佳拍档」?
  • STM32CUBEMX配置LAN8720a实现UDP通信
  • 【C++游记】红黑树
  • 嵌入式C语言之链表冒泡排序
  • Java基础第9天总结(可变参数、Collections、斗地主)
  • 深入浅出数据库事务:从原理到实践,解决 Spring 事务与外部进程冲突问题
  • github下载的文件内容类似文件哈希和存储路径原因
  • Kafka 分层存储(Tiered Storage)从 0 到 1 的配置、调优与避坑
  • Vue3 实现自定义指令点击空白区域关闭下拉框
  • 【51单片机】【protues仿真】 基于51单片机智能电子秤系统
  • 工业界实战之数据存储格式与精度
  • 嵌入式解谜日志-网络编程