【Linux】进程信号(上半)

作者:Lsir10110_日期:2026/2/8

当我们想要强行终止掉前台进程的时候,只需要按下Ctrl+c即可,但是Ctrl+c是如何精准杀掉前台进程的?

一、信号概念

1.如何理解信号

假设点了一份外卖,外卖员到了楼下会给你发信息或者打电话,那么这通电话或者这个信息就是信号,也就是**用来提醒你需要去做某事的一种通知手段。**那么对照Linux系统,信号就是内核向进程发送的通知,提醒进程需要去完成某个任务。

2.信号的特点

对照外卖例子,当点完外卖,我们肯定不会一直在门口等着外卖员,而是先忙手中的事情,比如打游戏,那么这个过程就叫做异步(信号由外卖员(内核)随时发出,但是对比同步的阻塞等待,我们会先解决手头的事情),等外卖员真正的到了楼下打来电话,如果我们手头事情没忙完,我们也会先等手头事情完成才会去取外卖,这就是Linux中信号的延迟性/合适时机处理,当然取外卖是默认动作,外卖也可能是买来送给女神的或者送给兄弟的,此时对于外卖就有了多种处理方式,这也是Linux中信号处理的可配置性

综上,初步地可以了解到信号具有异步性延迟性以及可配置性。

3.认识信号

聊完了信号的抽象模型(外卖),那么真正的信号长什么样呢?在bash中我们输入kill -l就可以调出全部信号

kill -l

可以看到一共有62个信号(没有32、33),前31个信号叫做普通信号,后31个信号称为实时信号,这里只谈普通信号。

1 SIGHUP(挂起信号):终端断开触发,默认终止进程,nohup可让进程忽略实现后台常驻。

2 SIGINT(中断信号):Ctrl+C手动触发,默认终止进程,支持捕获实现优雅退出。

3 SIGQUIT(退出信号):Ctrl+\触发,默认终止进程并生成core核心转储文件,用于调试。

4 SIGILL(非法指令信号):进程执行CPU无法识别指令触发,默认终止+核心转储。

5 SIGTRAP(陷阱信号):调试断点专用,gdb断点触发,默认终止+核心转储。

6 SIGABRT(异常终止信号):调用abort()函数触发,默认终止+核心转储,程序主动报致命错误。

7 SIGBUS(总线错误信号):内存访问对齐/硬件总线故障触发,默认终止+核心转储。

8 SIGFPE(浮点异常信号):浮点运算错误(除0/溢出)触发,默认终止+核心转储。

9 SIGKILL(终止信号):kill -9专用,强制终止进程,不可捕获、不可忽略。

10 SIGUSR1(用户自定义信号1):无默认触发场景,用户自定义使用,默认终止进程。

11 SIGSEGV(段错误信号):非法内存访问(越界/空指针)触发,默认终止+核心转储。

12 SIGUSR2(用户自定义信号2):无默认触发场景,用户自定义使用,默认终止进程。

13 SIGPIPE(管道破裂信号):向无读端的管道/套接字写数据触发,默认终止进程。

14 SIGALRM(闹钟信号):alarm()设置的定时到期触发,默认终止进程。

15 SIGTERM(终止信号):kill默认发送信号,默认终止进程,支持捕获实现优雅退出。

16 SIGSTKFLT(栈错误信号):协处理器栈溢出触发,默认终止进程,x86架构基本不用。

17 SIGCHLD(子进程状态变化信号):子进程退出/暂停/恢复触发,默认忽略,用于父进程回收子进程资源。

18 SIGCONT(继续信号):bg/fg/kill -CONT触发,恢复暂停进程,不可忽略。

19 SIGSTOP(暂停信号):Ctrl+Z/kill -STOP触发,强制暂停进程,不可捕获、不可忽略。

20 SIGTSTP(终端暂停信号):Ctrl+Z手动触发,默认暂停进程,支持捕获。

21 SIGTTIN(终端读信号):后台进程尝试读终端输入触发,默认暂停进程。

22 SIGTTOU(终端写信号):后台进程尝试写终端输出触发,默认暂停进程。

23 SIGURG(紧急数据信号):套接字收到紧急数据触发,默认忽略。

24 SIGXCPU(CPU超时信号):进程超出CPU使用限制触发,默认终止+核心转储。

25 SIGXFSZ(文件大小超限信号):进程写入文件超出大小限制触发,默认终止+核心转储。

26 SIGVTALRM(虚拟闹钟信号):进程虚拟运行时间定时到期触发,默认终止进程。

27 SIGPROF(性能分析信号):进程CPU使用+系统调用时间定时到期触发,默认终止进程。

28 SIGWINCH(窗口大小变化信号):终端窗口大小调整触发,默认忽略,用于程序适配窗口尺寸。

29 SIGIO(IO就绪信号):文件/套接字IO就绪触发,默认终止进程,用于异步IO。

30 SIGPWR(电源故障信号):系统电源异常触发,默认终止进程,部分系统用于电源管理。

31 SIGSYS(非法系统调用信号):进程执行无效系统调用触发,默认终止+核心转储。

(内容参考AI大模型)

4.信号状态

对于一个进程而言,收到信号可以延迟处理或者条件满足的时候立即处理,那么此时信号就有了几种不同的状态:

未决:信号已产生并由内核记录,因进程屏蔽该信号或正处理同类型信号,暂未被进程处理的状态,信号处于未决队列中。

递达:信号从内核传递到进程,且进程成功执行该信号对应处理动作(默认 / 自定义 / 忽略)的过程,是信号处理的最终完成状态。

阻塞:进程通过信号集主动设置 “屏蔽”,让内核暂不将指定信号递达,被阻塞的信号会进入未决状态,解除阻塞后才会正常递达处理(阻塞≠忽略,信号不会丢失)。

二、信号相关接口调用

1.信号的捕获

1.1信号的处理方式

前面提到进程对于一个信号可以有多种处理方式,但是内核需要有默认处理方式保证信号的存在意义,其实对于信号处理可以大致分为三类:

默认:执行内核为该信号预设的处理动作,如终止、暂停、核心转储等,是信号的基础处理逻辑,保证无自定义处理时信号仍有实际意义。

忽略:进程收到信号后不执行任何操作,信号直接被丢弃,仅标记为处理完成,无任何附加行为。

其他:进程自定义信号处理函数,收到信号后执行开发者编写的业务逻辑,实现信号的个性化处理,如优雅退出、资源释放等。

1.2信号的捕获

已知信号有多种处理方式,但是我们如何改变信号处理方式使其按照我们的预期进行处理呢?

接口认识

这里需要引入一个新的接口:signal()

通过手册查找可以看到signal接口需要传入两个参数,第一是signum,对照前面的信号表(kill -l),这里的signum也就是信号对应的编号,可以直接传入数字也可以使用编号对应的宏定义,第二个参数则是一个返回值为void,带有一个int参数的函数指针,接口执行后会将信号的默认处理改为指定函数。若传入SIG_DFL(宏,代表默认处理)或SIG_IGN(宏,代表忽略信号),则会恢复信号的默认行为或设置为忽略。

代码实践

有了以上认识,我们就可以对进程接收到的信号进行捕获并且执行自定义函数,由于前面的概念,Ctrl+c发送的是SIGINT信号,我们对其进行尝试捕捉:

1#include<signal.h>
2#include<iostream>
3void handler(int sig){
4    std::cout<<"我是进程"<<getpid()<<",我收到了信号"<<sig<<std::endl;
5}
6int main(){
7    signal(SIGINT,handler);
8    while(true){}
9}

编译成可执行程序并且运行:

可以看到此时我们进行Ctrl+c组合键强行杀掉进程的时候进程并没有被杀掉,而是执行了我们的自定义函数,此时我们可以用Ctrl+\(SIGQUIT)进行进程关闭,但是此时也衍生了一个问题:假如所有的信号都被捕获,那么进程岂不是可以肆无忌惮的运行,一直消耗系统资源?其实操作系统对此早有预防,Linux内核规定kill -9(SIGKILL)信号无法被捕获,为系统稳定性留了一张底牌,同样可以进行代码验证:

通过手册查询,可以看到若signal()调用出错,则返回 SIG_ERR,并且留下一个错误码:

1#include<signal.h>
2#include<iostream>
3void handler(int sig){
4    std::cout<<"我是进程"<<getpid()<<",我收到了信号"<<sig<<std::endl;
5}
6int main(){
7    for(int i=1;i<=31;i++){
8        if(signal(i,handler)==SIG_ERR){
9            perror("signal failed : ");
10        }
11        else std::cout<<i<<"signal success"<<std::endl;
12    }
13}
1lsir@iZ2vc8wsdyzvsm68d9hqgbZ:~/learing/sig$ ./a.out
21signal success
32signal success
43signal success
54signal success
65signal success
76signal success
87signal success
98signal success
10signal fiiled : : Invalid argument
1110signal success
1211signal success
1312signal success
1413signal success
1514signal success
1615signal success
1716signal success
1817signal success
1918signal success
20signal fiiled : : Invalid argument
2120signal success
2221signal success
2322signal success
2423signal success
2524signal success
2625signal success
2726signal success
2827signal success
2928signal success
3029signal success
3130signal success
3231signal success
33

可以看到,31个信号,绝大多数都成功修改处理,但是其中9(SIGKILL)和19(SIGSTOP)无法进行修改,显示Invalid argument(无效参数),说明传入的9和19参数不支持修改,其中信号19是进程暂停的信号,同信号9属于内核强制管控的信号,防止程序恶意利用。

补充

当我们使用键盘触发的信号(**如 Ctrl+C、Ctrl+\、Ctrl+Z)**只能发送给前台进程,当我们在使用一个终端的时候,只能存在一个前台进程,而我们通过键盘触发的信号也只会发送给这个前台,结合信号知识就解释了开头的问题:为什么Ctrl+c能精准杀掉进程?因为当我们进行Ctrl+c操作的时候,**内核会向唯一的前台进程发送SIGINT信号,待进程收到信号进行默认处理的时候,进程就会自动退出。**而对于终端中的前后台进程,我们可以通过 fg、bg 命令灵活实现进程的前后台状态切换,配合 jobs 命令可查看当前终端的所有进程作业状态。

1# 1. 启动前台进程,按Ctrl+Z暂停(触发SIGTSTP,状态变为Stopped)
2sleep 100
3^Z  # 按下Ctrl+Z,输出如下
4[1]+  Stopped                 sleep 100
5
6# 2. 用jobs查看作业(确认作业号和状态)
7jobs -l
8[1]+  12345 Stopped                 sleep 100  # [1]是作业号,12345是PID
9
10# 3. bg:将暂停的作业恢复为后台运行
11bg %1  # 或简写bg 1、bg(无参数,默认处理+标记的作业)
12[1]+  sleep 100 &  # 输出表示作业已在后台运行
13
14# 4. fg:将后台作业调回前台
15fg %1  # 或简写fg 1、fg
16sleep 100  # 此时进程回到前台,占用终端,按Ctrl+C可终止

2.信号发送

kill接口

kill在Bash和C语言层面都有对应封装,但是用法大同小异

Bash命令行解释器

在Bash我们可以直接进行kill命令向指定进程发送指定信号,格式:

kill [信号] <PID/作业号>,例如kill -9 12345就代表向pid为12345的进程发送9(SIGKILL)信号,使其强行终止。

C语言

同样通过手册查询可以看到kill接口需要进行两个参数传入:pid和sig,与Bash中的kill用法大同小异kill -9 12345->kill(12345,9)。虽然和Bash中的kill是不同层面的封装,但是其系统调用接口一致,因此可以尝试用C语言封装的kill接口自制Bash中的kill:

1#include <iostream>
2#include <unistd.h>
3#include <sys/types.h>
4#include <signal.h>
5// mykill -signumber pid
6int main(int argc, char *argv[])
7{
8if(argc != 3)
9{
10std::cerr << "Usage: " << argv[0] << " -signumber pid" << std::endl;
11return 1;
12}
13int number = std::stoi(argv[1]+1); 
14pid_t pid = std::stoi(argv[2]);
15int n = kill(pid, number);
16return n;
17}

raise接口

raise是C语言封装的一个向caller(当前进程)发送信号的一个接口,参数传入需要发送的信号编号即可,代码示例:

1#include <iostream>
2#include <unistd.h>
3#include <signal.h>
4
5void handler(int signumber)
6{
7std::cout << "获取了一个信号: " << signumber << std::endl;
8}
9// mykill -signumber pid
10int main()
11{
12signal(2, handler); // 先对2号信号进⾏捕捉
13// 每隔1S,⾃⼰给⾃⼰发送2号信号
14while(true)
15{
16sleep(1);
17raise(2);
18}
19}
1$ g++ raise.cc -o raise
2$ ./raise
3获取了⼀个信号: 2
4获取了⼀个信号: 2
5获取了⼀个信号: 2

abort接口

abort() 是 C 标准库函数(头文件 <stdlib.h>),核心作用是让进程立即异常终止,底层是向当前进程发送信号6(SIGABRT),并且该终止行为无法被捕获(即便执行前使用signal进行捕捉设置,会先执行你注册的 SIGABRT 自定义处理函数,执行完后再强制终止进程)。

alarm接口

调⽤ alarm 函数可以设定⼀个闹钟,也就是告诉内核在 seconds 秒之后给当前进程发

SIGALRM 信号,该信号的默认处理动作是终⽌当前进程。

这个函数的返回值是0或者是以前设定的闹钟时间还余下的秒数。打个⽐⽅,某⼈要⼩睡⼀觉,设定闹

钟为30分钟之后响,20分钟后被⼈吵醒了,还想多睡⼀会⼉,于是重新设定闹钟为15分钟之后响,“以

前设定的闹钟时间还余下的时间”就是10分钟。如果seconds值为0,表⽰取消以前设定的闹钟,函数

的返回值仍然是以前设定的闹钟时间还余下的秒数。

利用该接口可以简单感受一下IO操作与单纯CPU计算之间的速度差距:

1//IO多
2#include <iostream>
3#include <unistd.h>
4#include <signal.h>
5int main()
6{
7int count = 0;
8alarm(1);
9while(true)
10{
11std::cout << "count : "
12<< count << std::endl;
13count++;
14}
15return 0;
16}
17//纯计算,IO少
18#include <iostream>
19#include <unistd.h>
20#include <signal.h>
21int count = 0;
22void handler(int signumber)
23{
24std::cout << "count : " <<
25count << std::endl;
26exit(0);
27}
28int main()
29{
30signal(SIGALRM, handler);
31alarm(1);
32while (true)
33{
34count++;
35}
36return 0;
37}
1//多IO
2... ...
3count : 107148
4count : 107149
5Alarm clock
6//纯计算,少IO
7$ g++ alarm.cc -o alarm
8$ ./alarm
9count : 492333713

CPU 运算属于计算机内部核心硬件的指令执行与数据处理,全程在主机箱内的处理器 / 内存总线上完成;而 IO(输入 / 输出)是计算机核心计算部件与外部存储设备的数据交换,属于主机与外设的通信行为。通过这个简单的实验可以清楚感知到IO操作相对于内部硬件在操作效率上清晰的差距。


【Linux】进程信号(上半)》 是转载文章,点击查看原文


相关推荐


JSyncQueue——一个开箱即用的鸿蒙异步任务同步队列
江澎涌2026/1/31

零、JSyncQueue JSyncQueue 是一个开箱即用的鸿蒙异步任务同步队列。 项目地址:github.com/zincPower/J… 一、JSyncQueue 有什么作用 在鸿蒙应用开发中,有时需要让多个异步任务按顺序执行,例如状态的转换处理,如果不加控制,会因为执行顺序混乱而产生一些莫名其妙的问题。 所以 JSyncQueue 提供了一个简洁的解决方案: 保证顺序执行:所有任务严格按照入队顺序执行,即使任务内部有异步操作也能保证顺序 两种执行模式:支持 "立即执行" 和 "延时执


Python 线程局部存储:threading.local() 完全指南
哈里谢顿2026/1/21

一句话总结: threading.local() 是 Python 标准库提供的「线程局部存储(Thread Local Storage, TLS)」方案,让同一段代码在不同线程里拥有各自独立的变量空间,从而避免加锁,也避免了层层传参的狼狈。 1. 为什么需要线程局部存储? 在多线程环境下,如果多个线程共享同一个全局变量,就必须: 加锁 → 代码变复杂、性能下降; 或者层层传参 → 代码臃肿、可维护性差。 有些场景只想让线程各自持有一份副本,互不干扰: Web 服务:每个请求线程绑定自


绘制K线第二章:背景网格绘制
佛系打工仔2026/1/13

绘制K线第二章:背景网格绘制 在第一章的基础上,我们简单修饰一下,补充一个背景九宫格的绘制功能。这个功能可以让K线图更加清晰易读,帮助用户快速定位价格和时间。 二、网格配置 确定网格的行数和列数 在绘制网格之前,我们需要确定: 几行:将高度分成几等份(对应价格轴) 几列:将宽度分成几等份(对应时间轴) 例如:4列5行,表示宽度分成4等份,高度分成5等份。 在Config中配置 为了灵活配置网格,我们在 KLineConfig 中添加了两个字段: data class KLineConfig(


Linux系统安全及应用(账号权限管理、登录控制、弱口令、端口扫描)
晚风吹人醒.2026/1/5

目录 1. 账号管理与权限控制         1.1 基本安全措施:                 1.1.1 账号管理和文件权限                 1.1.2 密码安全控制                 1.1.3历史命令和自动注销         1.2 用户切换与提权: 2. 系统引导与登录控制         2.1 开关机安全控制:                 2.1.1 GRUB                 2.1.2 限制更改GRUB


算法竞赛中的数据结构:图
喜欢吃燃面2025/12/27

目录 一.图的基本概念1.图的定义2.图、树、线性表的联系与区别2.1 核心联系2.2 核心区别 二.图的分类1.按边的方向分类2.按边的权重分类3 .按顶点和边的数量分类4 .按连通性分类(针对无向图)5 .按强连通性分类(针对有向图)6 .其他特殊类型7.顶点的度(补充)8.路径及相关长度概念(补充)8.1 路径8.2 路径长度(无权图)8.3 带权路径长度(带权图)8.4 核心区别对比 三.邻接矩阵1.邻接矩阵【注意】 四.邻接表五.链式前向星


ZooKeeper+Kafka
吉良吉影1232025/12/18

目录 一、Zookeeper 1.1 Zookeeper 概述 1.2 Zookeeper 工作机制 1.3 ZooKeeper 特点 1.4 Zookeeper 数据结构 1.5 ZooKeeper 应用场景 1.6 Zookeeper 选举机制 1.6.1 第一次启动选举机制 1.6.2 非第一次启动选举机制 Leader 的作用 1. 处理所有写请求(核心职责) 2. 主导 Leader 选举 3. 管理集群数据同步 4. 维护集群状态 Follower


编程界 语言神 : 赶紧起来学 Rust 了!
Pomelo_刘金2025/12/10

大家对 Rust 的印象 没接触过的: 编程界语言神 整天重构这重构那 还要 要干掉 c++ ?! 稍微了解过的: 学习曲线: 但实际上是: 第一个高峰是 借用检查器,第二个是异步,第三个是unsafe,第四个是宏怎么玩? 开始接触之后 编译器不让我写代码,怎么写都报错 写 rust 代码像是在跟 rust 编译器谈对象 , 我只是传个参数,你跟我讲所有权、借用、生命周期?” 写的代码上线之后,还不错哦 “别的语言项目上线流程” 内容: 编译 ✔ 测试(偶尔挂一两条)✔ 上线后:半


单片机手搓掌上游戏机(十六)—pico运行fc模拟器之程序修改烧录
Bona Sun2025/11/30

我们来山寨picosystem,毕竟79刀,有些地方还是要简化修改的。 到: https://github.com/fhoedemakers/PicoSystem_InfoNes 下载zip或者git clone都可以。 解压缩,用vscode 打开文件夹   修改的地方:  首先是那个VSYNC,也就是8引脚的一个输入信号,我能买到的st7789上都没有这个引脚,看了一下代码 就是等待它的下降沿,也就知道该刷下一屏了。  其实没多大作用,我孤陋寡闻,还没见过屏幕撕裂,


React 性能优化:图片懒加载
NEXT062026/2/17

引言 在现代 Web 应用开发中,首屏加载速度(FCP)和最大内容绘制(LCP)是衡量用户体验的核心指标。随着富媒体内容的普及,图片资源往往占据了页面带宽的大部分。如果一次性加载页面上的所有图片,不仅会阻塞关键渲染路径,导致页面长时间处于“白屏”或不可交互状态,还会浪费用户的流量带宽。 图片懒加载(Lazy Loading)作为一种经典的性能优化策略,其核心思想是“按需加载”:即只有当图片出现在浏览器可视区域(Viewport)或即将进入可视区域时,才触发网络请求进行加载。这一策略能显著减少首屏


Gemini 3.1 Pro 正式发布:一次低调更新,还是谷歌的关键反击?
IvanCodes2026/2/25

今天凌晨,谷歌发布了新一代模型——Gemini 3.1 Pro 没有大型发布会,没有提前预热,甚至连宣传节奏都显得克制。 很多人会把它看作 Gemini 3 的小版本升级,但从目前披露的测试数据和演示能力来看,这更像是一次结构性强化,而不是简单的参数迭代。 如果说 Gemini 3 是谷歌重新回到核心竞争区间的标志,那么 Gemini 3.1 Pro,则明显带着更强的实战优化意味。 它在几个关键方向上给出了非常明确的信号:谷歌不只是追赶者。 性能升级:从可用到强势竞争 这次升

首页编辑器站点地图

本站内容在 CC BY-SA 4.0 协议下发布

Copyright © 2026 XYZ博客