Linux信号三部曲:产生机制、处理方式与内核接口

Linux系列


文章目录

  • Linux系列
  • 前言
  • 一、背景知识铺垫
    • 1.1 信号的基本概念
    • 1.2 进程对信号的处理
  • 二、信号的产生
    • 2.1 前台进程和后台进程
    • 2.2 键盘组合键
    • 2.3 kill 命令
    • 2.4 系统调用
      • 2.4.1 signal()接口
      • 2.4.2 kill()接口
      • 2.4.3 raise()接口
      • 2.4.4 abort()接口
  • 总结


前言

Linux中,信号(Signal)是一种进程间通信(IPC)的机制,它用来通知进程发生了特定的事件。进程接受到信号后会根据信号的类型结合自己的处理方式做出相应的处理。


一、背景知识铺垫

1.1 信号的基本概念

Linux信号是一种异步通信机制,用于在进程之间传递事件或在系统于进程之间进行交互。当发生某个特定事件时,如:用户输入特定组合建(Ctrl+c等)、进程异常终止,系统就会向相关进程发送信号。

1.2 进程对信号的处理

进程在被设计时,就内置了识别信号的方法以及默认处理不同信号方式,当进程接收到信号时,并不一定会立即处理,这也就要求进程需要具有保存信号的能力,当等到合适的时候,进程会根据信号的类型结合自己的处理方式法,做出处理。
进程在处理信号的方式:

  • 默认处理方式(进程内置的)
  • 忽略信号
  • 自定义处理方式(捕捉信号后,使用用户设定的方法)

二、信号的产生

穿插了一部分拓展知识

2.1 前台进程和后台进程

首先我们先来看下面这个程序:

#include<iostream>
#include<unistd.h>
using namespace std;int main()
{while(true){cout<<"I'm a crazy process,PID:"<<getpid()<<endl;sleep(1);}return 0;
}

在这里插入图片描述
当我们执行该程序后,再输入ls、pwd,可以看到指令并不会执行,进程则一直运行,当我们使用Ctrl+c就可以将进程终止,这样的进程就是前台进程。再次执行该程序:

在这里插入图片描述
这次我们以后台进程的形式执行该进程./可执行程序 &,可以发现,当进程执行后,我们再输入指令,此时指令是可以成功执行的,当我们使用Ctrl+c时无法终止进程。这种进程为后台进程。

Linux中,一次登录,一个终端,一般配有一个bash,而每个终端只允许有一个前台进程,可有多个后台进程,当我们执行./process时,前台进程就由bash变为了./process而键盘输入是优先被前台进程获取的,所以指令无法被执行,但前台进程./peocess接收到Ctrl+c信号时就会终止。这样我们再来理解第二个现象,当我们以后台进程运行./process是,此时bash仍被视为前台进程,当用户输入指令是仍可被接收并执行,此时再输入Ctrl+c信号./process进程并没有接收,所以没有终止。

前台进程:会独占终端,直到进程执行完成或者被挂起,在这期间终端无法接受其他命令输入,用户只能与该进程进行交互。
后台进程:不会占用终端,终端可以继续接受用户输入的其他命令,用户可以在同一个终端中同时启动多个后台进程。

前台进程:其执行过程会受到用户操作的直接影响,比如用户可以通过键盘输入来中断或暂停进程。如果终端关闭,前台进程通常会被终止,除非进行了特殊的设置。
后台进程:通常是长时间运行的,不受终端关闭的影响,除非明确地对其进行停止或重启操作。它按照自身的逻辑和任务需求在后台持续运行,不会因为用户的一些常规操作而中断

Ctrl+c本质会解释为2号信号,后面我们会验证

2.2 键盘组合键

这是我们在学习Linux过程中,比较常用的一种向进程发送信号的方式,它通过一些特定的键盘组合键,来发送一些特殊的信号,如Ctrl+c终止进程。组合有很多种,都比较简单,这里我们想要介绍的是,Ctrl+c这类组合键是如何被转换为信号,又是如何被进程接收的?

我们可以确定的意见事是,CPU不能直接从键盘读取数据(冯诺依曼体系结构),那么这个工作只能交由操作系统来完成,操作系统又是如何得知键盘有数据了呢?我们根据下图来回答:
在这里插入图片描述

CPU上有很多针脚,每个针脚对应一个硬件设备(键盘、网卡),当用户按下Ctrl+c组合键时,键盘发生硬件中断,产生中断号,通过对于针脚发送(充放电)给CPU,通知CPU进行相关处理,操作系统从CPU读取到中断号,通过中断号在自己的中断向量表中索引到对应方法地址,执行该方法(读取键盘),操作系统识别如果是数据直接读取,如果是组合键就将他解释成对应信号,如ctrl+c解释为2号信号,并将它读入键盘缓冲区(一切皆文件),再拷贝至用户缓冲区,被进程接收,进程执行对应处理方法。我们的信号处理方式(异步通知、事件驱动等)就是模拟的硬件中断,因此信号又被叫做软件中断。

2.3 kill 命令

对于上面的后台进程,我们可以通过kill指令的形式,给它发送信号,终止它,可以通过kill -l查看信号种类:

在这里插入图片描述
0~31为普通信号,34~64为实时信号(我们不研究),这里有多种信号来都能达到终止,后台进程的要求,如:2号终止进程,9号杀掉进程。
使用格式:kill -信号编号 进程PID.

常用信号

信号编号信号名称触发方式作用
2SIGINTCtrl+C终止前台进程
3SIGQUITCtrl+\终止进程并生成core文件
9SIGKILLkill -9 pid强制终止进程,不可被捕获或忽略
15SIGTERMkill -15 pid正常终止进程,进程可捕获并进行清理
1SIGHUP终端连接断开等让进程重新初始化或终止
18SIGCONTkill -18 pid继续执行暂停的进程
19SIGSTOPkill -19 pid暂停进程,不可被捕获或忽略
20SIGTSTPCtrl+Z暂停前台进程,将其放入后台
10SIGUSR1kill -10 pid用户自定义信号,用于特定程序逻辑
12SIGUSR2kill -12 pid用户自定义信号,用于特定程序逻辑

2.4 系统调用

2.4.1 signal()接口

#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);

功能:捕捉指定信号,执行自定义功能

参数

  1. signum: 要捕捉信号编号
  2. handler:函数指针,用互自定义的方法

下面的程序我们使用signal函数捕捉二号信号,执行我们自定义的方法。

#include<iostream>
#include<unistd.h>
#include<signal.h>
using namespace std;void handler(int num)
{cout<<"I captured Signal No."<<num<<endl;return ;
}
int main()
{sighandler_t _handler=signal(2,handler);while(true){cout<<"I'm a crazy process,PID:"<<getpid()<<endl;sleep(1);}return 0;
}

在这里插入图片描述
可以看到此时我们再使用Ctrl+c,信号程序就不会终止,而是执行我们的自定义方法。上面的场景也完美的呈现了,信号的异步性(程序先调用singnal,但是此时进程没有收到信号,所以这个函数不会执行,当进程接收到信号后,就会执行对于方法)。
需要注意的是,并不是所有信号,都可以被捕捉的,我们可以通过下面的方式来验证:

#include<iostream>
#include<unistd.h>
#include<signal.h>
using namespace std;void handler(int num)
{cout<<"I captured Signal No."<<num<<endl;return ;
}
int main()
{for(int i=0;i<=64;i++)sighandler_t _handler=signal(i,handler);while(true){cout<<"I'm a crazy process,PID:"<<getpid()<<endl;sleep(1);}return 0;
}

执行这个进程时,通过kill指令向进程发信号,这里不方便演示,大家可以自己尝试(9号和19号好像不能被捕捉)。

2.4.2 kill()接口

#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);

功能:向指定进程发送信号

参数

  1. pid:接收信号的进程PID
  2. sig:信号编号

返回值
执行成功返回零,失败饭回-1并设置errno

示例:

#include<iostream>
#include<signal.h>
#include<sys/types.h>
using namespace std;
int main(int argc,char*argv[])
{if(argc!=3){cout << "Usage:\n\t" << argv[0] << " signum pid\n\n";exit(1);}int signum=stoi(argv[1]);int pid=stoi(argv[2]);int n=kill(pid,signum);if(n==-1){perror("kill");exit(1);}return 0;
}

现在我们就可以使用上面这个进程,来对其他进程发送信号了。
在这里插入图片描述
当然你可以让你的进程直接使用kill自己给自己发信号。

2.4.3 raise()接口

#include <signal.h>
int raise(int sig);

功能:让当前进程给自己发送信号

参数

  • sig:信号编号
#include<iostream>
#include<unistd.h>
#include<signal.h>
using namespace std;void handler(int num)
{cout<<"I captured Signal No."<<num<<endl;return ;
}
int main()
{sighandler_t _handler=signal(2,handler);int cnt=5;while(true){cout<<"I'm a crazy process,PID:"<<getpid()<<endl;cnt--;if(cnt==0)break;//跳出循环sleep(1);}raise(9);cout<<"111111"<<endl;return 0;

在这里插入图片描述
循环执行五次后跳出,执行raise()完成“自杀”,程序终止,我们可以感受到raise()其实就是分装的kill()

2.4.4 abort()接口

#include <stdlib.h>
void abort(void);

功能:向当前进程发送SIGABRT(6号)信号,默认情况下,进程收到该信号后会立即终止,即使被用户捕获,在执行过用户提供的方法后依然终止进程。

#include<iostream>
#include<unistd.h>
#include<signal.h>
using namespace std;void handler(int num)
{cout<<"I captured Signal No."<<num<<endl;return ;
}
int main()
{sighandler_t _handler=signal(6,handler);while(true){cout<<"I'm a crazy process,PID:"<<getpid()<<endl;abort();}return 0;
}

在这里插入图片描述

如果不调用该函数,使用指令发送六号信号,当信号被捕获后,进程不会终止,abrot函数内部做了特殊处理,才会使进程终止,你可以测试看看。

总结

特点:
异步传递(随时可能中断进程)

  • SIGINT(2):由键盘输入Ctrl+C产生,用于中断正在运行的进程。
  • SIGKILL(9):强制终止进程,不能被捕获和忽略,用于紧急情况下终止进程。
  • SIGSTOP(19):暂停进程,不能被捕获和忽略,可使用SIGCONT信号恢复进程运行

不可捕获的信号

SIGKILL(9)和SIGSTOP(19)无法被捕获、阻塞或忽略,用于强制控制进程。
信号发送函数

  • kill函数:可以向指定进程发送指定信号,例如 kill(pid, SIGINT) 向进程号为 pid 的进程发送 SIGINT 信号。
  • raise函数:用于向当前进程发送信号,如 raise(SIGABRT) 向当前进程发送 SIGABRT 信号。

处理方式:

  • 默认处理:系统为每个信号定义了默认的处理行为,如终止进程、产生核心转储、忽略信号等。
  • 捕获信号:进程可以通过 signal 函数当接收到指定信号时,执行自定义的处理逻辑。
  • 忽略信号:进程可以使用 signal 函数将信号设置为忽略,使进程不响应该信号,但有些信号(如 SIGKILL 和 SIGSTOP )不能被忽略(这点我在下篇介绍)。

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

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

相关文章

win7/win10/macos如何切换DNS,提升网络稳定性

本篇教程教您如何在Windows10、Windows8.1、Windows7、MacOS操作系统切换DNS&#xff0c;以提升系统的稳定性&#xff0c;获得更好的操作体验。 Windows10及Windows8.1 1、右键单击“此计算机”&#xff0c;然后选择“属性”。进入Windows系统界面后&#xff0c;选择左侧的“…

移动硬盘突然打不开紧急救援指南:从排查到完整恢复‌

突发状况的典型特征‌ 当移动硬盘突然打不开时&#xff0c;用户常会遇到多种异常表现&#xff1a;接入电脑后硬盘指示灯虽亮但无法识别、系统反复提示“设备未连接成功”或弹出“磁盘结构损坏”的警告。部分情况下&#xff0c;资源管理器中的盘符虽可见&#xff0c;但双击后显示…

华为OD机试真题——统计匹配的二元组个数(2025A卷:100分)Java/python/JavaScript/C++/C语言/GO六种最佳实现

2025 A卷 100分 题型 本文涵盖详细的问题分析、解题思路、代码实现、代码详解、测试用例以及综合分析&#xff1b; 并提供Java、python、JavaScript、C、C语言、GO六种语言的最佳实现方式&#xff01; 2025华为OD真题目录全流程解析/备考攻略/经验分享 华为OD机试真题《统计匹配…

半导体制造如何数字化转型

半导体制造的数字化转型正通过技术融合与流程重构&#xff0c;推动着这个精密产业的全面革新。全球芯片短缺与工艺复杂度指数级增长的双重压力下&#xff0c;头部企业已构建起四大转型支柱&#xff1a; 1. 数据中枢重构产线生态 台积电的「智慧工厂4.0」部署着30万物联网传感器…

[Spark]深入解密Spark SQL源码:Catalyst框架如何优雅地解析你的SQL

本文内容组织形式 总结具体例子执行语句解析层优化层物理计划层执行层 猜你喜欢PS 总结 先写个总结&#xff0c;接下来会分别产出各个部分的源码解析&#xff0c;Spark SQL主要分为以下五个执行部分。 具体例子 接下来举个具体的例子来说明 执行语句 SELECT name, age FR…

【数据结构】4.单链表实现通讯录

在上一篇文章我们学会了用单链表来实现各种方法&#xff0c;在这一篇文章我们将在单链表的基础上实现通讯录。 0、准备工作 实现通讯录之前&#xff0c;我们还需要在单链表的基础上添加2个文件&#xff0c;头文件Contact.h和源文件Contact.c。Contact.c来实现通讯录方法的声明…

【bash】.bashrc

查看当前路径文件数量 alias file_num"ls -l | grep ^- | wc -l"查看文件大小 alias file_size"du -sh"alias ll alias ll"ls -ltrh"cd的同时执行ll alias cdcdls; function cdls() {builtin cd "$1" && ll }自定义prompt…

微信小程序实战案例 - 餐馆点餐系统 阶段 2 – 购物车

阶段 2 – 购物车&#xff08;超详细版&#xff09; 目标 把“加入购物车”做成 全局状态&#xff0c;任何页面都能读写在本地 持久化&#xff08;关闭小程序后购物车仍在&#xff09;新建 购物车页&#xff1a;数量增减、总价实时计算、去结算入口打 Git Tag v2.0‑cart 1. …

从红黑树到哈希表:原理对比与典型场景应用解析(分布式以及布隆过滤器)

在数据结构的世界里&#xff0c;红黑树一直以「自平衡二叉查找树」的身份备受赞誉。凭借红黑节点的精妙设计&#xff0c;它能将插入、删除、查找的时间复杂度稳定控制在 ( log ⁡ n ) (\log n) (logn)&#xff0c;成为处理有序数据的经典方案。然而&#xff0c;当业务场景对「…

游戏报错?MFC140.dll怎么安装才能解决问题?提供多种MFC140.dll丢失修复方案

MFC140.dll 是 Microsoft Visual C 2015 运行库的重要组成部分&#xff0c;许多软件和游戏依赖它才能正常运行。如果你的电脑提示 "MFC140.dll 丢失" 或 "MFC140.dll 未找到"&#xff0c;说明系统缺少该文件&#xff0c;导致程序无法启动。本文将详细介绍 …

《电子类专业:通往科技未来的钥匙》

一、电子类专业全景概览 在当今科技飞速发展的时代,电子类专业无疑占据着现代科技体系中基础与核心的重要地位。从我们日常生活中不可或缺的智能手机、电脑,到推动社会进步的人工智能、大数据技术,再到探索宇宙奥秘的航天航空设备,电子类专业的身影无处不在。它就像一把万…

Java--批量删除

前端部分 前端代码主要负责收集用户选择的学生记录的 id&#xff0c;并将这些 id 发送给后端的 DeleteMoreServlet 进行处理。 批量删除按钮绑定点击事件 $(".deleteMore").on("click",function(){// ... }); 当用户点击 “批量删除” 按钮时&#xff…

2025年4月份生活有感

今天在5000B培训的下午&#xff0c;一起入所来的小伙伴&#xff0c;有个申请了深圳大学的博士&#xff0c;已录取。哎&#xff0c;想起了当年申博时候信心和决心不足&#xff0c;导致后面匆匆的拿了offer去工作。看到同事的选择还是非常羡慕&#xff0c;想到自己5月份的婚礼&am…

数学建模学习资料免费分享:历年赛题与优秀论文、算法课程、数学软件等

本文介绍并分享自己当初准备数学建模比赛时&#xff0c;收集的所有资料&#xff0c;包括历年赛题与论文、排版模板、算法讲解课程与书籍、评分标准、数学建模软件等各类资料。 最近&#xff0c;准备将自己在学习过程中&#xff0c;到处收集到的各类资料都整理一下&#xff0c;并…

关于 微服务负载均衡 的详细说明,涵盖主流框架/解决方案的对比、核心功能、配置示例及总结表格

以下是关于 微服务负载均衡 的详细说明&#xff0c;涵盖主流框架/解决方案的对比、核心功能、配置示例及总结表格&#xff1a; 1. 负载均衡的核心概念 负载均衡在微服务中用于将请求分发到多个服务实例&#xff0c;以实现&#xff1a; 高可用性&#xff1a;避免单点故障。性…

个人博客搭建过程

尝试博客搭建,前面注册部分很简单&#xff0c;就不写了&#xff0c;以我看到的一个博客为基础&#xff0c;加上我自己的测试结束 1.官网 https://dash.infinityfree.com 前半部分参考&#xff1a; 使用Infinityfree免费虚拟主机搭建一个wordpress网站 2.创建账户或登录您的…

Proxmox VE 网络配置命令大全

如果对 Proxmox VE 全栈管理感兴趣&#xff0c;可以关注“Proxmox VE 全栈管理”专栏&#xff0c;后续文章将围绕该体系&#xff0c;从多个维度深入展开。 概要&#xff1a;Proxmox VE 网络配置灵活&#xff0c;满足虚拟化组网需求。基础靠桥接实现虚拟机与物理网络互联&#x…

【QT入门到晋级】QT打动态库包及引入动态库包

前言 本篇为持续更新状态&#xff0c;内容包含window、Linux下打动态库包&#xff0c;以及引入动态库包的方式。 一、window 1、动态库打包 以百度的OCR接口调用打dll库为例&#xff0c;以下为qtcreator创建动态库过程&#xff1a; 1.1Qtcreator创建lib项目 创建成功后&…

Uniapp: 大纲

目录 一、基础巩固1.1、Uniapp:下拉选择框ba-tree-picker1.2、Uniapp&#xff1a;确认框1.3、Uniapp&#xff1a;消息提示框1.4、Uniapp&#xff1a;列表提示框1.5、Uniapp&#xff1a;获取当前定位坐标 二、项目配置2.1、Uniapp&#xff1a;修改端口号2.2、Uniapp&#xff1a;…

WPF 从Main()方法启动

1.去掉App.xaml StartupUri“MainWindow.xaml” 只会让App.g.cs 不生成这行代码&#xff0c;但是还是会生成的App.g.cs文件中生成Main方法 this.StartupUri new System.Uri("MainWindow.xaml", System.UriKind.Relative);默认的App.xaml的生成操作是 应用程序定义…