【Linux】Linux 进程信号核心拆解:pending/block/handler 三张表 + signal/alarm 实战

【Linux】Linux 进程信号核心拆解:pending/block/handler 三张表 + signal/alarm 实战

前言:欢迎各位光临本博客,这里小编带你直接手撕**,文章并不复杂,愿诸君**耐其心性,忘却杂尘,道有所长!!!!

在这里插入图片描述

IF’Maxue个人主页
 🔥 个人专栏:
《C语言》
《C++深度学习》
《Linux》
《数据结构》
《数学建模》

⛺️生活是默默的坚持,毅力是永久的享受。不破不立!

文章目录

Linux进程信号详解

信号与信号量的区别

信号和信号量,就像“老婆”和“老婆饼”——没有任何关系。

信号的概念

什么是信号?

信号是一种给进程发送的异步事件通知机制。简单说,就是能“打断”进程正在做的事,让进程去处理这个通知。

比如生活中,你正在上课(进程运行),快递员敲门(信号产生),你需要暂停上课去处理快递(进程处理信号)。这里的关键是:快递员啥时候来不确定(信号异步产生),但你知道该怎么接快递(进程提前知道信号处理方式)。

image.png

基本结论

  1. 进程在信号产生前,就已经知道该怎么处理(提前“学会”了)。
  2. 信号不是立即处理,而是等进程到“合适的时机”再处理。
  3. 进程对信号的识别和处理方式,是操作系统设计时就确定好的。
  4. 信号的来源很多(键盘、系统调用、异常等)。

信号的产生方式

1. 键盘产生(仅前台进程)

键盘操作能给前台进程发信号,比如ctrl+c

ctrl+c实际发送的是2号信号(SIGINT),默认处理动作是终止进程。

image.png

kill -l可查看所有信号,1-31是普通信号(进程择时处理),34-64是实时信号(立即处理)。

image.png
信号的处理方式

进程处理信号有3种方式:

  • 默认动作(比如大部分信号默认终止进程);
  • 自定义动作(用代码修改处理逻辑);
  • 忽略(收到信号不做任何处理)。
image.png
用signal函数自定义信号处理

signal函数可以修改信号的默认处理动作,格式如下:

image.png

示例代码:

image.png

运行结果:ctrl+c不再终止进程,而是打印提示,因为我们把2号信号的处理动作改成了自定义函数。

image.png
前台与后台进程
  • 前台进程:直接运行(./xxx),能接收键盘信号(如ctrl+c)。
  • 后台进程:加&运行(./xxx &),不接收键盘信号,需用kill命令终止。
  • jobs查看后台任务,fg 1将后台任务1转为前台,ctrl+z将前台进程转后台,bg 1让后台进程继续运行。
image.png


image.png

2. 系统调用产生信号

发送信号的本质是修改进程内核数据结构(task_struct)中的信号位图(记录收到的信号)。操作系统提供了多个系统调用用于发送信号。

kill函数

给指定进程发信号,格式:

image.png

示例:给PID为1234的进程发9号信号(强制终止):kill(1234, 9)

image.png
raise函数

进程给自己发信号,示例代码:

image.png

运行结果:进程给自己发9号信号后终止(9号信号无法被自定义处理)。

image.png
abort函数

让进程自己终止,固定发送6号信号(SIGABORT),且会忽略自定义处理(强制用默认动作)。

image.png


运行结果:进程收到6号信号后终止。

image.png

3. 异常产生信号

程序运行出错时,操作系统会给进程发信号,导致进程崩溃。

除0错误

代码:

image.png

结果:触发8号信号(SIGFPE,浮点数错误),进程终止。

image.png
野指针错误

代码:

image.png

结果:触发11号信号(SIGSEGV,段错误),进程终止。

image.png
为什么操作系统会发信号?

操作系统是软硬件管理者,能检测CPU寄存器状态(如除0时的溢出标志)、内存访问错误(如野指针访问无效地址),一旦检测到,就给对应进程发信号。

4. 软件条件产生信号

某些软件场景会触发信号,比如管道读写错误、闹钟超时等。

alarm函数(闹钟)

给进程设置闹钟,时间到后操作系统发送14号信号(SIGALRM)。

  • 格式:unsigned int alarm(unsigned int seconds);
  • 功能:alarm(5)表示5秒后发SIGALRM;alarm(0)取消闹钟。
  • 返回值:上一个闹钟剩余的秒数(无则返回0)。
image.png

示例1:自定义处理SIGALRM
代码:

image.png

结果:5秒后收到14号信号,执行自定义打印。

image.png

示例2:重复闹钟
代码:在信号处理函数中重新设置闹钟,实现每秒触发一次。

image.png

结果:每秒打印一次提示。

image.png
pause函数

让进程暂停,直到收到一个信号才继续运行。

代码:结合alarm和pause,实现定时任务。

image.png

结果:每1秒执行一次任务。

image.png

操作系统如何管理闹钟?

操作系统用“先描述再组织”的方式管理多个闹钟:

  • 用类似“最小堆”的结构组织,堆顶是最早超时的闹钟,定期检查,超时则给进程发SIGALRM。

每个闹钟用结构体描述(包含目标进程、超时时间等)。

image.png
image.png

信号的保存

进程收到信号后不会立即处理,而是先保存起来。保存信号依赖三张“表”:pending(未决信号集)、block(阻塞信号集)、handler(处理函数集)。

image.png

三张表的作用

  • pending(未决信号集):位图,记录进程收到了哪些信号(比特位1表示收到,0表示未收到)。
  • block(阻塞信号集):位图,记录哪些信号被“屏蔽”(比特位1表示阻塞,此时即使pending有该信号,也不会处理)。
  • handler(处理函数集):函数数组,记录每个信号的处理方式(默认、忽略、自定义函数)。
image.png


image.png

信号集操作函数

Linux提供函数操作这三张表,核心是sigset_t(信号集类型,类似位图)。

image.png


image.png
常用函数
  • sigemptyset:初始化信号集为空(所有比特位0)。
  • sigfillset:初始化信号集为满(所有比特位1)。
  • sigaddset:向信号集添加某个信号(置位)。
  • sigdelset:从信号集删除某个信号(清位)。
  • sigismember:判断信号是否在信号集中。
image.png
sigprocmask:修改block表

用于设置进程的阻塞信号集(block表)。

image.png
sigpending:获取pending表

获取当前未决的信号集(pending表)。

image.png

整合示例

代码:阻塞2号信号,发送信号后查看pending表,再解除阻塞观察处理结果。

image.png


image.png

结果:

  • 阻塞期间,ctrl+c(2号信号)被记录在pending表(显示有未决信号)。
  • 解除阻塞后,信号被处理(执行自定义函数),pending表中对应位清0。
image.png

信号的处理

信号处理的时机:进程从内核态返回用户态时,会检查pending表中未被阻塞的信号,按handler表的方式处理。

处理流程:

  1. 检查pending表,找到未被block的信号。
  2. 清空该信号在pending表中的位(1→0)。
  3. 执行handler表中对应的处理动作(默认、忽略、自定义函数)。
image.png

补充知识

信号的重复产生

普通信号(1-31)多次产生时,pending表中只会记录一次(位图位只能是1);实时信号(34-64)会按顺序处理多次。

image.png

Core与Term的区别

进程终止的两种默认动作:

  • Term:直接终止进程,不保留现场。
  • Core:终止进程并生成core文件(核心转储),保存进程崩溃时的内存状态,用于调试。
image.png
开启Core dump

默认情况下,很多系统关闭了Core dump(避免占用空间),可通过ulimit临时开启:

  • ulimit -c unlimited:开启Core dump(不限制大小)。

ulimit -a:查看当前限制(core file size为0表示关闭)。

image.png
使用core文件调试

进程崩溃生成core文件后,可用gdb调试:
gdb ./程序名 core,直接定位到出错行。

image.png

进程终止总结

image.png


image.png


image.png

Read more

【Linux/C++多进程篇(二) 】万字解析从“传纸条”到“建仓库”:一文读懂linux系统编程之进程间通信 (IPC)

【Linux/C++多进程篇(二) 】万字解析从“传纸条”到“建仓库”:一文读懂linux系统编程之进程间通信 (IPC)

⭐️在这个怀疑的年代,我们依然需要信仰。 个人主页:YYYing. ⭐️Linux/C++进阶系列专栏:【从零开始的linux/c++进阶编程】 系列上期内容:【Linux/C++多进程篇(一) 】C/C++ 程序中神奇的“分身术” 系列下期内容:【Linux/C++多线程篇(一) 】多线程编程入门 目录 前言: 进程间通信(IPC) 一、进程间通信的基础概念 二、内核提供的通信方式 2.1、无名管道  📖 无名管道的API  📖 代码案例 2.2、有名管道  📖 有名管道的API  📖 代码案例 2.3、管道特点 2.4、信号  📖 信号相关概念

By Ne0inhk
【C++ 内存申请】从 C++ new 到内核:虚拟内存、VMA 与内存泄漏的全链路解析

【C++ 内存申请】从 C++ new 到内核:虚拟内存、VMA 与内存泄漏的全链路解析

目录标题 * 1. 从 C++ `new` 到物理内存:堆、虚拟内存和 VMA 究竟发生了什么 * 1.1 C++ 视角:`new` / `malloc` 并不等于系统调用 * 1.2 OS 视角:VMA、页表和按需分配(demand paging) * 1.3 硬件视角:第一次访问堆区、page fault 和 MMU 流程 * 1.4 难点对比:VMA / 页表 / 虚拟地址 / 物理页 * 2. 销毁与并发:`free` / `munmap`、线程和页表更新 * 2.1 C++ 语义:

By Ne0inhk
【零基础学java】(等待唤醒机制,线程池补充)

【零基础学java】(等待唤醒机制,线程池补充)

等待唤醒机制 生产者和消费者(常见方法) void wait()当前线程等待,直到被其他线程唤醒 void notify()随机唤醒单个线程 void notifyAll()唤醒所有线程 等待唤醒机制的阻塞队列方式实现 put数据时:放不进去会等着,叫做阻塞 take数据时:取出第一个,取不到的等着 线程的六种状态 线程池 线程池的作用  1减少线程创建和销毁的开销 * 问题:每次需要任务时都创建新线程,完成后立即销毁,会消耗大量CPU和内存资源。 * 解决:线程池复用已创建的线程,避免频繁创建/销毁。 2. 控制并发.数量,防止系统过载 * 问题:无限制创建线程可能导致: * 内存耗尽 * CPU过度切换(上下文切换开销大) * 系统不稳定 * 解决:线程池设置最大线程数,控制同时运行的线程数量。 3. 提高响应速度 * 任务到达时,通常已有空闲线程可以立即执行,无需等待线程创建。

By Ne0inhk
《C/C+++ Boost 轻量级搜索引擎实战:架构流程、技术栈与工程落地指南——构造正/倒排索引(中篇)》

《C/C+++ Boost 轻量级搜索引擎实战:架构流程、技术栈与工程落地指南——构造正/倒排索引(中篇)》

前引:这是一个聚焦基础搜索引擎核心工作流的实操项目,基于 C/C++ 技术生态落地:从全网爬虫抓取网页资源,到服务器端完成 “去标签 - 数据清洗 - 索引构建” 的预处理,再通过 HTTP 服务接收客户端请求、检索索引并拼接结果页返回 —— 完整覆盖了轻量级搜索引擎的端到端逻辑。项目采用 C++11、STL、Boost 等核心技术栈,搭配 CentOS 7 云服务器 + GCC 编译环境(或 VS 系列开发工具)部署,既适配后端工程的性能需求,也能通过可选的前端技术(HTML5/JS 等)优化用户交互,是理解搜索引擎底层原理与 C++ 工程实践的典型案例 目录 【一】Jieba分词工具 【二】正/倒排索引结构设计

By Ne0inhk