Linux:深入了解进程信号(上)

目录

1. 什么是信号

1.1 引入

1.2 概念

1.3 特性

1.4 信号的三个方面

2. 信号的产生

2.1 键盘按键产生

2.2 signal捕捉信号函数

2.3 发送信号原理

2.4 硬件中断

2.5 指令和函数接口

2.5.1 kill指令

2.5.2 kill函数

2.5.3 raise与abort函数

2.6 软件条件

2.7 异常错误产生信号


 

1. 什么是信号

1.1 引入

在我们的日常生活中,会遇到许多信号,比如红绿灯、铃声和乌云。看到红灯,我们知道需要停下来等待;绿灯亮起,我们可以安全通行。教室里的铃声响起,我们明白这是下课的信号。当看到天空中布满乌云,我们就知道应该把晾晒的衣服收回家了。这些信号帮助我们按照既定的规则和自然的指示来安排我们的行动。

1.2 概念

信号是用户、操作系统、其他进程,向目标进程发送异步事件的一种方式。

什么是异步事件?异步事件(Asynchronous Event)是指在一个系统或程序中,事件的发生和它的处理不是在同一个时间序列或流程中进行的。

比如,当你正在玩游戏时,突然快递到你家楼下,你需要暂时中断游戏去取快递。这两个活动各自独立,展现了异步事件的特点。

1.3 特性

为什么我们能识别信号呢?当我们看到红绿灯,知道这是交通信号。因为有人告诉我们,所以识别信号是内置的。

  • 进程也是如此,识别信号的功能,是程序员内置的特性。

我们遇到红灯知道停下来,绿灯响才能过马路。这些都是别人规定的规则。

  • 所以信号产生后,进程知道怎么处理,是程序员设定的。

当你在玩游戏时,突然快递到楼下了。你可能处在游戏的关键时刻,不能立即处理,需要过一会。

  • 进程收到信号时,不一定要立即处理该信号,需要在合适的时候处理。

设定起床闹钟,是为了在特定时间提醒我们。当闹钟响时,有的人起床按掉闹钟,开始做计划之内的事情;有的人可能会按掉闹钟,继续睡觉;有的人可能会赖一会床,再起来。

  • 同样的,进程在接受信号后也有不同的处理方式,分别是采取默认行为,忽略信号,或执行程序员设定的自定义动作。

1.4 信号的三个方面

信号的产生与处理是信号机制中不可或缺的两个环节。然而,还有一个重要的方面尚未提及,那就是信号的保存。

当进程在收到信号时恰好忙于处理其他任务,无法立即对信号作出响应,此时信号的保存机制便发挥了作用。它确保了信号能够被临时记录在案,以便进程在合适的时机对其进行后续处理。

因此,进程信号会从信号的产生,信号的保存和信号的处理这三方面着手讲起。

2. 信号的产生

2.1 键盘按键产生

#include <iostream>
#include <unistd.h>int main() 
{    while(true){std::cout << "hello, world!" << std::endl;sleep(1);}return 0;
}

我们执行一个无限循环打印helloworld的代码。可以按键盘上Ctrl+C,来终止该进程。其中这种直接运行的进程时前台进程。

如果在可执行程序后面加上“&”符号,表示将该进程转为后台进程。

前台进程会占用了前台终端,导致shell进程无法接收输入的命令行来执行命令。所以Ctrl+C按键是发送给前台进程,作用是终止前台进程。

而后台进程允许shell进程执行命令行输入的指令。如下图,启动循环打印的程序,中间输入ll指令,显示该目录的文件内容。

如果想杀掉后台进程,可以打开一个新会话,使用kill指令配合-9选项,终止后台进程。

还可以使用nohup指令,将启动程序输出结果重定向到一个默认文件,叫做nohup.out。nohup.out文件不断记录进程输出内容。

此时,可以使用fg文件加上第一次启动进程中括号的数字1,可以将后台进程转为前台进程,再按Ctrl+C按键终止进程。

2.2 signal捕捉信号函数

其中sighandler是函数指针类型,signal函数用于捕捉信号,并使用传进来的函数指针对象对应的方法处理信号。

使用“kill -l”指令可以查看Linux系统提供的常见信号。1号到31号中的信号,是普通信号,之后的信号统称为实时信号。我们重点关注普通信号。其中每个信号名称其实都是一个宏,表示其编号。

其中Ctrl+C按键会发送一个信号给进程,该信号被操作系统解释为2号SIGINT信号。我们自定义一个返回值类型为void,函数参数只有一个整型变量的函数,然后使用signal函数捕捉2号信号。捕捉到2号信号,进程会按照Handler方法打印一条语句。

#include <iostream>
#include <unistd.h>
#include <signal.h>void Handler(int signo)
{std::cout << "Received signal, signal number: " << signo << std::endl;
}int main() 
{    signal(SIGINT, Handler);while(true){std::cout << "hello, world!" << std::endl;sleep(1);}return 0;
}

启动该程序,按Ctrl+C按键,执行Handler函数,打印一条语句。这就证明了Ctrl+C按键,实质上是2号信号。

通过man 7 signal指令,可以查询到普通信号的默认处理动作,其中Term和Core都是终止进程。

2.3 发送信号原理

当按下键盘中Ctrl+C按键,操作系统会识别该操作,发送2号信号给进程。使用kill -l指令,会显示所有信号,你会发现信号编号是从1开始的。

因为进程PCB中有许多字段,其中使用位图上的比特位0和1两个状态,来表示该位数信号是否有接受到。还有个字段是sighandler_t类型的数组,sighandler_t是函数指针类型,也就是说该字段是函数指针数组。

当操作系统发送2号信号给进程时,其实向进程PCB位图中的第二位比特位写入1,表示该进程接受2号信号,并通过函数指针数组中下标为2的函数指针,执行该函数。如果使用signal函数,则会将该数组对应下标的内容修改为Handler函数的地址。

2.4 硬件中断

当用户按下Ctrl+C组合键时,键盘硬件检测到这一动作,并将按键编码转换为相应的信号。此时,键盘准备就绪,将数据通过中断请求线(IRQ)发送给CPU的特定引脚,以此发起一个中断请求。这个中断信号即刻通知操作系统,有来自键盘的外部事件需要被关注和处理。

操作系统接收到中断信号后,随即响应,它会暂停当前正在执行的任务,并将控制权转交给专门处理键盘输入的中断服务例程(ISR)。该例程负责将键盘缓冲区中的数据读取出来,并将其安全地拷贝到系统的内存中,以便CPU可以进一步处理这些输入。

所以,操作系统不需要对外部设备进行轮询检测,当执行完当前指令,只要等外部设备发送中断请求。操作系统如公司老板一般,指挥手底下的员工干活,自己只需要等待员工汇报工作进展,从而达到外设与操作系统并行。

2.5 指令和函数接口

2.5.1 kill指令

kill指令可以向目标进程发送指定信号。我们写一份代码,使用signal函数捕捉1到31号信号,然后打个死循环,使用kill指令发送信号。

#include <iostream>
#include <unistd.h>
#include <signal.h>void Handler(int signo)
{std::cout << "Received signal, signal number: " << signo << std::endl;
}int main() 
{    for(int signo = 1; signo <= 31; signo++){signal(signo, Handler);}while(true){sleep(1);}return 0;
}

我们使用kill指令发送1号、2号和6号信号,都被sig进程处理为打印一条语句。有人会说把所有信号捕捉,采用自己自定义方法,那进程岂不是无法终止。其实操作系统设计者也想到了这个问题,所以有些信号是无法被捕捉的,只会使用默认方式处理,如9号信号,可以终止所有你可以终止的进程。

2.5.2 kill函数

kill也是系统调用函数,向指定进程发送信号。我们可以使用这个函数写一个自己的kill指令。

#include <iostream>
#include <string>
#include <sys/types.h>
#include <signal.h>void Usage(std::string proc)
{std::cout << "Usage: " << proc << "signumber processid" << std::endl;
}// ./mykill 9 12345
int main(int argc, char* argv[]) 
{    if(argc < 3){Usage(argv[0]);exit(1);}int signumber = std::stoi(argv[1]);pid_t pid = std::stoi(argv[2]);int n = kill(pid, signumber);if(n < 0){perror("kill");exit(2);}exit(0);
}

 如下图,执行mykill进程需要输入三个参数,可以杀掉普通进程。

2.5.3 raise与abort函数

 raise函数是发送信号给调用该函数的进程。写一个五秒后发送9号信号给自己的代码。

#include <iostream>
#include <unistd.h>
#include <signal.h>int main(int argc, char* argv[]) 
{    int cnt = 0;while(true){std::cout << "I am alive!" << std::endl;cnt++;if(cnt > 4)raise(SIGKILL);sleep(1);}return 0;
}

abort函数作用是通过发送SIGABRT信号给调用该函数的进程,以此终止该进程。这算作异常退出。

#include <iostream>
#include <string>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>void Handler(int signo)
{std::cout << "Received signal, signal number: " << signo << std::endl;
}int main(int argc, char* argv[]) 
{    signal(SIGABRT, Handler);int cnt = 0;while(true){std::cout << "I am alive!" << std::endl;cnt++;if(cnt > 4)abort();sleep(1);}
}

2.6 软件条件

软件没准备好,软件条件不具备引起信号的产生。常见的是管道的读端关闭,而某个进程向管道写入,操作系统会发送SIGPIPE信号给该进程,并终止进程。

 alarm函数用来设置发送信号的闹钟,该信号是SIGALRM信号。

#include <iostream>
#include <unistd.h>int main() 
{    alarm(1); //1s后,会收到SIGALRM信号int number = 0;while(true){printf("count: %d\n", number++);}return 0;
}

 上面的代码实现了测试服务器1s能完成多少次IO的功能,差不多稳定在七万次左右。但是对于CPU每秒几亿执行次数来说,已经很慢了。

#include <iostream>
#include <string>
#include <unistd.h>
#include <signal.h>int number = 0;void Die(int signumber)
{printf("get a signal %d, count: %d\n", signumber, number);exit(0);
}int main(int argc, char* argv[]) 
{    alarm(1);signal(SIGALRM, Die);while(true){number++;}
}

 上面的代码,没有进行调用printf进行IO。运行结果如下,结果接近6亿次,相比于上面的结果,快了将近一万倍。说明IO操作十分耗时。

2.7 异常错误产生信号

#include <iostream>
#include <unistd.h>
#include <signal.h>int main()
{   int *p = nullptr;*p = 10; while(true);
}

 上面是野指针问题,程序启动后会,操作系统会发送11号SIGSEGV信号给进程,终止该进程。

 

#include <iostream>
#include <unistd.h>
#include <signal.h>int main()
{   int a = 10;a /= 0;while(true);
}

 上面代码是除零错误,会导致栈溢出,操作系统会发送8号SIGFPE信号给进程。

当CPU执行该进程“a /= 0”代码时,实际上被解释为“a = a / 0”,CPU会用三个寄存器存储三个值。

  • eax存储一开始a变量的值,ebx存储0,再通过计算把结果存储到ecx寄存器中。CPU还有一个状态寄存器Eflags,其中有个溢出标记位,专门记录结果是否正常。
  • a变量除0后,结果会溢出,Eflags中的标记位会写入为1。
  • CPU因溢出标记位为1,得知计算结果有问题,会触发硬件中断。操作系统就会知道CPU内部出错,会向执行此代码的进程发送终止信号。
  • 即使使用signal函数捕捉SIGSEGV信号,不终止该进程。当进程被CPU执行时,该进程的上下文数据重新加载到CPU时,Eflags上的溢出标记位还是1,照样会引起硬件中断,操作系统就会不断发送信号来终止进程。


创作充满挑战,但若我的文章能为你带来一丝启发或帮助,那便是我最大的荣幸。如果你喜欢这篇文章,请不吝点赞、评论和分享,你的支持是我继续创作的最大动力!

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

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

相关文章

rustdesk远程桌面自建服务器

首先&#xff0c;我这里用到的是阿里云服务器 centos7版本&#xff0c;win版客户端。 准备工作 centos7 服务器端文件&#xff1a; https://github.com/rustdesk/rustdesk-server/releases/download/1.1.11-1/rustdesk-server-linux-amd64.zip win版客户端安装包&#xff1…

PostgreSQL有undo表空间吗?

PostgreSQL有undo表空间吗 PostgreSQL 没有单独的 Undo 表空间&#xff0c;其事务回滚和多版本并发控制&#xff08;MVCC&#xff09;机制与 Oracle 等数据库有显著差异。 一 PostgreSQL 的 MVCC 实现 PostgreSQL 通过 多版本并发控制&#xff08;MVCC&#xff09; 管理事务…

Java项目《苍穹外卖》BUG修复记录

一、订单详情地址显示为null 原因&#xff1a;查看订单详情接口中&#xff0c;未设置收货地址信息&#xff0c;故地址返回为null。 解决方案&#xff1a; 1、OrderServiceImpl中创建一个私有方法专门获取订单收货地址 /*** 获取订单收获地址* param addressBookId* return*/…

NO.18十六届蓝桥杯备战|循环嵌套|乘法表|斐波那契|质数|水仙花数|(C++)

循环嵌套 循环嵌套的使⽤ while &#xff0c; do while &#xff0c; for &#xff0c;这三种循环往往会嵌套在⼀起才能更好的解决问题&#xff0c;就是我们所说的&#xff1a;循环嵌套。这三种循环都可以任意嵌套使⽤ ⽐如&#xff1a; 写⼀个代码&#xff0c;打印⼀个乘法⼝…

【第6章:强化学习基础与深度强化学习—6.4 强化学习在游戏、自动驾驶等领域的应用案例】

你是否想过,为什么《王者荣耀》的AI总能预判你的走位?特斯拉的Autopilot如何实现复杂路况的决策?这背后都藏着一个改变人工智能格局的技术——强化学习。今天我们将深入这个让机器学会"思考"的黑科技,揭开它从基础理论到工业应用的全貌。 一、强化学习的"…

【Linux内核】进程管理(上)

一、进程简介 关于进程相关内容直接看我的操作系统专栏&#xff0c;在这里不再赘述。我们直接快进到Linux中的进程管理部分 二、Linux中的进程描述符 晋城市操作系统中调度的实体&#xff0c;因此需要对进程的信息、所持有的资源进行描述&#xff0c;这种抽象描述称之为进程…

类和对象(5)——抽象类和接口

目录 1. 抽象类 1.1 抽象类的概念 1.2 抽象类语法&#xff1a;abstract关键字 1.3 抽象类的特性 1.4 抽象类的作用 2. 接口 2.1 接口的概念 2.2 接口语法&#xff1a;interface关键字 2.3 接口的实现&#xff1a;implements关键字 2.4 接口的特性 2.5 实现多个接口 …

利用租用的GPU进行训练

对于大模型的微调以及推理&#xff0c;对显卡的要求较高&#xff0c;我们就可以通过租一台来进行训练&#xff0c;这里我租用的是&#xff1a;AutoDL算力云 | 弹性、好用、省钱。租GPU就上AutoDL 推荐博客&#xff1a;新手小白如何租用GPU云服务器跑深度学习_gpu租用-CSDN博客…

[操作系统] 基础IO:系统文件I/O

在 Linux 操作系统中&#xff0c;文件 I/O&#xff08;输入/输出&#xff09;是程序与文件系统交互的基础。理解文件 I/O 的工作原理对于编写高效、可靠的程序至关重要。本文将深入探讨系统文件 I/O 的机制。 一种传递标志位的方法 在 Linux 中&#xff0c;文件的打开操作通常…

Qt MainWindow

文章目录 0. 概述1. 菜单栏 QMenuBar1.1 例子1&#xff0c;使用图形化界面1.2 例子2&#xff0c;使用代码创建1.3 例子3&#xff0c;添加快捷键1.4 例子4&#xff0c;添加子菜单1.5 例子5&#xff0c;添加分割线和图标1.6 内存泄漏问题 2. 工具栏 QToolBar2.1 例子1&#xff0c…

阅读论文“用于车联网安全车载通信的机器学习技术“的学习笔记

前言 论文全称为Machine Learning Technologies for Secure Vehicular Communication in Internet of Vehicles: Recent Advancesc and Applications 智能交通系统&#xff08;ITS&#xff09;和计算系统的快速发展为智能交通安全提供了新的科学研究&#xff0c;并提供了舒适和…

[java] 集合-Collection、ArrayList、LinkedList源码篇

目录 Collection集合 集合类体系结构 常用方法 遍历方式 迭代器遍历 增强for lambda表达式 List集合 特有方法 五种遍历方式 细节点注意 List集合的实现类 List集合子类的特点 LinkedList集合的特有功能 源码分析 ArrayList源码分析 LinkedList源码分析 迭代…

DeepSeek自动化写作软件

DeepSeek写作软件的三大核心功能 对于内容创作者来说&#xff0c;写作不仅是表达思想的过程&#xff0c;更是一项需要投入大量时间和精力的任务。面对日益增长的内容需求&#xff0c;写作效率低下、内容质量不高等问题&#xff0c;常常让创作者感到焦虑。而 DeepSeek 写作软件…

前端里的this指向问题

目录 1.代码输出结果 2.代码输出结果 3.代码输出结果 4.代码输出结果 5.代码输出结果 6.代码输出结果 7.代码输出结果 8.代码输出结果 9.代码输出结果 10.代码输出结果 11.代码输出结果 12.代码输出结果 13.代码输出结果 14.代码输出结果 总结 1.代码输出结果 f…

苹果CMS新版站群管理更新_新增批量生成插件优势何在

引言 随着互联网的发展&#xff0c;站群管理成为了网站运营者提升流量和SEO效果的重要策略。苹果CMS新版站群管理系统通过引入批量生成插件&#xff0c;为用户提供了更高效、更智能的解决方案。本文将详细介绍这一更新的功能特点及其优势。 站群管理功能特点 多域名独立配置…

时序约束进阶八:时钟抖动Jitter与不确定性Uncertainty

目录 一、前言 二、时钟抖动 2.1 时钟抖动类型 2.2 set_input_jitter 2.3 set_system_jitter 2.4 set_clock_uncertainty 2.5 设计代码 2.6 约束解析 2.7 Input_jitter报告 2.8 System Jitter报告 2.9 Clock Uncertainty报告 2.9.1 Uncertainty的计算 2.9.2 Uncer…

小米 R3G 路由器(Pandavan)实现网络打印机功能

小米 R3G 路由器&#xff08;Pandavan&#xff09;实现网络打印机功能 一、前言 家中有多台 PC 设备需要打印服务&#xff0c;但苦于家中的 Epson L380 打印机没有网络打印功能&#xff0c;并且配置 Windows 共享打印机实在是过于繁琐且需要共享机保持唤醒状态过于费电。想到…

Leetcode Hot100 第30题 416.分割等和子集

class Solution { public:bool canPartition(vector<int>& nums) {int sum0;for(int num:nums){sumnum;}if(sum%21) return false;int bag_size sum/2;// return dfs(nums,nums.size()-1,bag_size);//递归做法vector<vector<bool>> dp(nums.size()1,vec…

技术晋升读书笔记—阿里管理三板斧(二)

一、引子 美团王兴问马云&#xff1a;“你最强的地方是什么&#xff1f;” 马云反问王兴&#xff1a;“你觉得呢&#xff1f;” 王兴回答&#xff1a;“战略和忽悠。” 马云哈哈大笑&#xff0c;笑完&#xff0c;他一本正经地说&#xff1a;“我最强的地方是管理。” &quo…

引入了 Disruptor 后,系统性能大幅提升!

Disruptor 是一个很受欢迎的内存消息队列&#xff0c;它源于 LMAX 对并发、性能和非阻塞算法的研究。今天一起来学习一下这个消息队列。 简介 对于主流的分布式消息队列来说&#xff0c;一般会包含 Producer、Broker、Consumer、注册中心等模块。比如 RocketMQ 架构如下&…