Linux操作系统5-进程信号2(信号的4种产生方式,signal系统调用)

上篇文章:Linux操作系统5-进程信号1(信号基础)-CSDN博客

本篇Gitee仓库:myLerningCode/l25 · 橘子真甜/Linux操作系统与网络编程学习 - 码云 - 开源中国 (gitee.com)

本篇重点:信号的4种产生

目录

一. signal系统调用

二. 产生信号的4种方式

2.1 终端按键产生信号

 2.2 系统调用/命令产生信号

a kill调用向其他进程发送信号

b raise向自己发送信号

2.3 硬件异常产生信号

a 除 0 异常

b 空指针解引用 

2.4 软件产生信号

a pipe 读端退出,写端立马退出

b alarm定时器产生信号 


一. signal系统调用

        signal系统调用可以帮助我们自定义信号的行为

//所需头文件
#include <signal.h>//函数原型    当进程收到signum这个信号之后,执行handler中的代码
typedef void(* sighandler_t)(int)    //函数指针
sighandler_t signal(int signum, sighandler_t handler);//参数说明
signum    需要自定义行为的信号编号
handler   自定义行为的函数//当某一个进程使用了signal系统调用之后,捕捉signum编号的信号就会执行下面的自定义行为
void handler(int signum)
{//由程序员自定义
}

举例代码:

我们自定义了2号信号的行为(ctrl c 发送的信号就是2号信号

#include <iostream>#include <unistd.h>
#include <signal.h>void handler(int signum)
{while(true){printf("进程[%d]收到信号[%d]\n",getpid(),signum);sleep(1);}
}int main()
{signal(2, handler);while (true){std::cout << "进程pid:" << getpid() << std::endl;sleep(1);}return 0;
}

        我们定义一个死循环的进程,并且自定义2号信号的行为。如果该进程收到2号信号那么他就会执行handler 中的死循环代码

测试结果如下:

我们输入ctrl c 来测试一下。

可以看到,输入ctrl c之后该进程收到2号信号。并且再次ctrl c 之后仍执行自定义行为的代码

注意:在我们调用signal之后并不会执行handler中的方法,而是在收到2号信号后再调用

二. 产生信号的4种方式

2.1 终端按键产生信号

常见的比如 ctrl c 向当前的前台进程发送2号信号,ctrl \ 向当前前台进程发送3号信号。

代码测试:

我们自定义2号和3号信号的行为来测试 ctrl c 和 ctrl \:

#include <iostream>#include <unistd.h>
#include <signal.h>void handler(int signum)
{while(true){printf("进程[%d]收到信号[%d]\n",getpid(),signum);sleep(1);}
}int main()
{//同时自定义2号信号和3号信号的行为signal(2, handler);signal(3, handler);while (true){std::cout << "进程pid:" << getpid() << std::endl;sleep(1);}return 0;
}

 2.2 系统调用/命令产生信号

        命令产生信号我们经常使用,就是 kill 信号 pid 即可向对应的进程发送对应的信号

a kill调用向其他进程发送信号

        kill不仅仅在命令中可以发送信号,也能在代码中使用

//头文件
#include <sys/types.h>
#include <signal.h>//函数原型
int kill(pid_t pid, int signum);//参数
向 pid 这个进程编号的进程发送 signum 这个编号的信号//返回值
成功返回0,失败返回-1,并且设置错误码

测试代码:

mykill.cpp

#include <iostream>
#include <sys/types.h>
#include <signal.h>void Usage(const std::string &proc)
{std::cout << "Usage\n"<< proc << "pid signum\n ";
}int main(int argc, char *argv[])
{if (argc != 3)Usage(argv[0]);pid_t pid = atoi(argv[1]);int signo = atoi(argv[2]);int n = kill(pid, signo);if(n < 0){std::cout << "kill error"<<std::endl;}return 0;
}

该代码通过命令行参数获取键盘输入的信息,解析后执行kill

test.cpp 

#include <iostream>#include <unistd.h>
#include <signal.h>void handler(int signum)
{while (true){printf("进程[%d]收到信号[%d]\n", getpid(), signum);sleep(1);}
}int main()
{// 同时自定义2号信号和9号信号的行为signal(2, handler);signal(3, handler);while (true){std::cout << "进程pid:" << getpid() << std::endl;sleep(1);}return 0;
}

可以看到,我们可以通过kill系统调用向其他进程发送信号

b raise向自己发送信号

#include <signal>int rasie(int sig);//给自己发送sig这个信号

测试代码:

通过raise向自己发送3号信号 

#include <iostream>#include <unistd.h>
#include <signal.h>void handler(int signum)
{while (true){printf("进程[%d]收到信号[%d]\n", getpid(), signum);sleep(1);}
}int main()
{// 同时自定义2号信号和9号信号的行为signal(3, handler);int cnt = 0;while (true){std::cout << "进程pid:" << getpid() << "[" << cnt++ << "]" << std::endl;if (cnt == 5)raise(3);sleep(1);}return 0;
}

测试结果:

可以看到,第5次的时候,收到3号信号执行自定义行为。

2.3 硬件异常产生信号

        信号不一定由用户发出,也有可能由OS发出。比如我们的 /0操作,越界访问操作。

a 除 0 异常

#include <iostream>
#include <string>#include <sys/types.h>
#include <signal.h>
#include <unistd.h>int main()
{// 3.硬件异常产生信号// 信号产生,不一定由用户显示发送。有可能由操作系统自动产生while (true){std::cout << "我正在运行..." << std::endl;sleep(1);int a = 10;a /= 0;}return 0;
}

运行结果如下:

可以看到进程收到了 Floating point exception。这个其实就是8号信号

可以自定义8号信号的行为来证明:

#include <iostream>
#include <string>#include <sys/types.h>
#include <signal.h>
#include <unistd.h>void catchSig(int signo)
{std::cout << "获取一个信号编号,编号是:" << signo << std::endl;
}int main()
{// 3.硬件异常产生信号// 信号产生,不一定由用户显示发送。有可能由操作系统自动产生signal(SIGFPE, catchSig);while (true){std::cout << "我正在运行..." << std::endl;sleep(1);int a = 10;a /= 0; // 为什么除0 会终止进程? 当前进程会收到来自OS的信号}return 0;
}

运行结果如下:

可以看到,该进程收到了8号信号。

可是为什么一直打印这条信息呢?我们没有写死循环

分析如下:

1 OS怎么知道该进程  \0 了?

        因为在cpu中有一个状态寄存器,这个寄存器中有一个状态标志位。如果我们有 \0 运算,就会导致结果溢出,此时这个寄存器就会将标志位由 0 设置为 1。说明该进程发生了运算异常。

        当OS发现某一个进程的状态标志位是1,就会向其发送8号信号终止它!

2 为什么会一直打印信息?

        一个进程不会一直占用CPU。当发送进程调度的时候,这个进程可能会被调走。此时进程会将自己的上下文信息保存到PCB中。当进程切换切换回来的时候,这个进程的状态标志位还是1,OS仍会向其发送8号信号,继续打印这条信息!

b 空指针解引用 

#include <iostream>
#include <string>#include <sys/types.h>
#include <signal.h>
#include <unistd.h>void catchSig(int signo)
{std::cout << "获取一个信号编号,编号是:" << signo << std::endl;
}int main()
{ signal(SIGFPE, catchSig);while (true){std::cout << "我正在执行代码" << std::endl;sleep(1);int *p = nullptr;*p = 1; //野指针}return 0;
}

运行结果如下:

 可以看到,显示段错误。收到11号信号(非法访问内存)

原因分析:

        我们的指针都是在虚拟内存上的,虚拟内存通过页表和MMU的映射到物理内存上(MMU是集成在CPU上的)。当我们发送非法访问的时候,MMU就会发送硬件异常,OS向进程发送11号信号进行终止。

        不断打印的原因如上面。

2.4 软件产生信号

a pipe 读端退出,写端立马退出

        在这篇文章中,我们看到。管道的读端退出,写端会收到13号信号退出

Linux操作系统4-进程间通信1(通信与管道实现通信)-CSDN博客

这就是一种软件异常产生的信号

b alarm定时器产生信号 

//头文件
#include <unistd.h>//函数原型
unsigned int alarm(unsigned int seconds);//使用alarm可以设定闹钟,在输入的参数 seconds 秒之后
//OS会向当前进程发送 SIGALRM 信号,该信号的默认行为是终止该进程

测试代码: 

#include <iostream>
#include <string>#include <sys/types.h>
#include <signal.h>
#include <unistd.h>void catchSig(int signo)
{std::cout << "获取一个信号编号,编号是:" << signo << std::endl;exit(1);
}int main()
{alarm(10);int count = 0;while (1){std::cout << "hello world! " << count++ << std::endl;sleep(1);}return 0;
}

运行结果: 

可以看到10秒后,进程收到14号信号退出 

        通过alarm定义闹钟我们可以写出很多有用的代码。

        任意一个进程都能通过alarm向OS中设置闹钟,OS会周期性检测这些闹钟,当闹钟到了之后OS就会向设置闹钟的进程发送信号。

        这种超时的行为,全部是由软件构成的。所以称为 软件条件产生信号

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

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

相关文章

如何在docker中的mysql容器内执行命令与执行SQL文件

通过 docker ps -a 查询当前运行的容器&#xff0c;找到想执行命令的容器名称。 docker ps -a若想执行sql文件&#xff0c;则将sql文件放入当前文件夹下后将项目内的 SQL 文件拷贝到 mysql 容器内部的 root下。 sudo docker cp /root/enterprise.sql mysql:/root/然后进入 my…

STM32 RTC实时时钟详解与HAL库实战教程

摘要&#xff1a;本文深入讲解STM32的RTC&#xff08;Real-Time Clock&#xff09;模块&#xff0c;涵盖原理分析、CubeMX配置、HAL库编程实现&#xff0c;并提供完整的闹钟设置与时间校准例程代码。通过本文&#xff0c;您将掌握RTC在低功耗场景下的核心应用技巧。 1. RTC模块…

Spring Boot拦截器(Interceptor)与过滤器(Filter)详细教程

Spring Boot拦截器&#xff08;Interceptor&#xff09;与过滤器&#xff08;Filter&#xff09;详细教程 目录 概述 什么是拦截器&#xff08;Interceptor&#xff09;&#xff1f;什么是过滤器&#xff08;Filter&#xff09;&#xff1f;两者的核心区别 使用场景 拦截器的典…

Tauri跨平台开发问题及解决方案深度解析(React版)

Tauri跨平台开发问题及解决方案深度解析&#xff08;React版&#xff09; 一、环境配置与项目初始化难题&#xff08;React适配&#xff09; 1.1 React项目初始化 推荐模板&#xff1a; # 使用ReactTypeScript模板 npm create tauri-applatest -- --template react-ts# 项目…

AIGC和搜索引擎的异同

AIGC&#xff08;生成式人工智能&#xff09;与搜索引擎的核心差异体现在信息处理方式和输出形态上&#xff0c;我们可以从以下维度对比&#xff1a; 一、工作原理的本质差异 信息检索机制 搜索引擎&#xff1a;基于关键词匹配&#xff08;如"中暑怎么办"→返回相关…

SFT与RLHF的关系

在大模型训练中&#xff0c;SFT&#xff08;监督微调&#xff09;和RLHF&#xff08;基于人类反馈的强化学习&#xff09;是相互关联但目标不同的两个阶段&#xff0c;通常需要结合使用以优化模型性能&#xff0c;而非互相替代。以下是关键要点&#xff1a; 1. 核心关系 SFT&…

C# 类型转换

C# 类型转换 引言 在C#编程语言中&#xff0c;类型转换是一种将一个数据类型的变量转换成另一个数据类型的操作。类型转换是编程中常见的操作&#xff0c;特别是在处理不同数据类型的变量时。本文将详细探讨C#中的类型转换&#xff0c;包括隐式转换和显式转换&#xff0c;以及…

提升系统效能:从流量控制到并发处理的全面解析

在当今快速发展的数字时代&#xff0c;无论是构建高效的网络服务、管理海量数据&#xff0c;还是优化系统的并发处理能力&#xff0c;都是技术开发者和架构师们面临的重大挑战。本文集旨在深入探讨几个关键技术领域&#xff0c;包括用于网络通信中的漏桶算法与令牌桶算法的原理…

Git GitHub基础

git是什么&#xff1f; Git是一个分布式版本控制系统&#xff0c;用于管理源代码的变更。它允许多个开发者在同一个项目上协作&#xff0c;同时跟踪每个修改的历史记录。 关键词&#xff1a; 分布式版本控制软件 软件 安装到我们电脑上的一个工具 版本控制 例如论文&…

派可数据BI接入DeepSeek,开启智能数据分析新纪元

派可数据BI产品完成接入DeepSeek&#xff0c;此次接入标志着派可数据BI在智能数据分析领域迈出了重要一步&#xff0c;将为用户带来更智能、更高效、更便捷的数据分析体验。 派可数据BI作为国内领先的商业智能解决方案提供商&#xff0c;一直致力于为用户提供高效、稳定易扩展…

Linux-ftrace-双nop机制的实现

Linux 内核调试工具ftrace 之&#xff08;NOP动态插桩的实现原理&#xff09; ftrace 是 Linux 内核中的一种跟踪工具&#xff0c;主要用于性能分析、调试和内核代码的执行跟踪。它通过在内核代码的关键点插入探针&#xff08;probe&#xff09;来记录函数调用和执行信息。这对…

Qt互斥锁(QMutex)的使用、QMutexLocker的使用

Qt互斥锁【QMutex】的使用、QMutexLocker的使用 基于读写锁(QReadWriteLock)的线程同步Chapter1 Qt互斥锁(QMutex)的使用、QMutexLocker的使用一、QMutexLocker和QMutex实现示例图二、QMutex和QMutexLocker的关系&#xff08;个人理解&#xff09;三、QMutex使用和QMutexLocker…

【无标题】Ubuntu22.04编译视觉十四讲slambook2 ch4时fmt库的报错

Ubuntu22.04编译视觉十四讲slambook2 ch4时fmt库的报错 cmake ..顺利&#xff0c;make后出现如下报错&#xff1a; in function std::make_unsigned<int>::type fmt::v8::detail::to_unsigned<int>(int): trajectoryError.cpp:(.text._ZN3fmt2v86detail11to_unsi…

SpringBoot ——简单开发流程实战

本文使用SpringBoot进行电商系统商品数据增删改查的简单开发流程。 本文目录 一、创建Spring Boot项目二、配置数据库连接三、创建实体类四、创建Repository接口五、创建Service层六、创建Controller层七、测试 一、创建Spring Boot项目 可以通过https://start.spring.io/或者…

fastadmin 后台商品sku(vue)

先上个效果图 首先先引入vue define([backend], function (Backend) {require.config({paths: {vue: /assets/jeekshopskugoods/libs/vue.min,skuimg: /assets/jeekshopskugoods/js/skuimg,skugoods: /assets/jeekshopskugoods/js/skugoods,layui: /assets/LayuiSpzj/layui/la…

LeetCode 718 - 最长重复子数组

LeetCode 718 - 最长重复子数组 是一个典型的数组和字符串问题&#xff0c;适合考察动态规划、滑动窗口和二分查找等多种编程能力。掌握其多种解法及变体能够有效提高处理字符串和数组算法的能力。 题目描述 输入: 两个整数数组 nums1 和 nums2。输出: 两个数组中存在的最长的…

LeetCode 0132.分割回文串 II:动态规划

【LetMeFly】132.分割回文串 II&#xff1a;动态规划 力扣题目链接&#xff1a;https://leetcode.cn/problems/palindrome-partitioning-ii/ 给你一个字符串 s&#xff0c;请你将 s 分割成一些子串&#xff0c;使每个子串都是回文串。 返回符合要求的 最少分割次数 。 示例 …

iOS 实现UIButton自动化点击埋点

思路&#xff1a;我们HOOK UIControl的 addtarget:action:forControlEvents方法&#xff0c;交换UIControl的 addtarget:action:forControlEvents 方法的实现&#xff0c; 在交换的方法中添加原来响应的同时&#xff0c;再添加一个埋点响应&#xff0c;该响应方法实现了点击埋点…

C++蓝桥杯基础篇(六)

片头 嗨~小伙伴们&#xff0c;大家好&#xff01;今天我们来一起学习蓝桥杯基础篇&#xff08;六&#xff09;&#xff0c;练习相关的数组习题&#xff0c;准备好了吗&#xff1f;咱们开始咯&#xff01; 第1题 数组的左方区域 这道题&#xff0c;实质上是找规律&#xff0c;…

git -学习笔记

目录 基本操作语法 设置用户和邮箱 版本回退 工作区和暂存区 撤销修改 删除与恢复 一工作区删除了&#xff0c;但是暂存区没删除 二工作区误删了&#xff0c;暂存区还有 github-Git 连接 报错解决-push远程仓库被拒绝 远程库 分支 分支冲突 储藏分支 回到当前分…