Linux(十二)信号
今天我们就要来一起学习信号啦!!!还记得小编在之前的文章中说过的ctrl+c吗?之前小编没有详细介绍过,现在我们就要来学习啦!!!
一、信号的基本介绍
首先,小编带领大家先一起学习一下什么是信号吧。
信号是系统响应某个条件而产生的事件,进程接收到信号会执行相应的操作;
大家要注意,我们在使用信号时,是需要添加头文件的。与信号有关的系统调用在<signal.h>头文件中。
1、信号的存储位置
旧版
vim /usr/include/x86_64-linux-gnu/bits/signum.h
新版(23版)
vim /usr/include/x86_64-linux-gnu/bits/signum-arch.h
vim /usr/include/x86_64-linux-gnu/bits/signum-generic.h
2、常见信号对应的功能
SIGBORT *进程异常终止
SIGALRM 超时警告
SIGFPE *浮点运算异常
SIGHUP 连接挂断
SIGILL *非法指令
SIGINT 终端中断
SIGKILL 终止进程(此信号不能被捕获或忽略)
SIGPIPE 向无读进程的管道写数据
SIGQUIT 终端退出
SIGSEGV *无效内存段访问
SIGTERM 终止
SIGUSR1 用户定义信号1
SIGUSR2 用户定义信号2
(在这里,重点的信号用了加粗提醒大家一定要记住,在这篇文章里,小编还不会向大家介绍SIGPIPE,在后面小编介绍管道时,会结合前边的内容和新的内容全面介绍管道)
3、信号的值
信号名称 信号代号
#define SIGHUP 1
#define SIGINT 2 //键盘按下 Ctrl+c 时,会产生终端中断信号
#define SIGQUIT 3 //键盘按下 Ctrl+\时,会产生终端退出信号
#define SIGILL 4
#define SIGTRAP 5
#define SIGABRT 6
#define SIGIOT 6
#define SIGBUS 7
#define SIGFPE 8
#define SIGKILL 9 //该信号的响应方式不允许改变
#define SIGUSR1 10
#define SIGSEGV 11
#define SIGUSR2 12
#define SIGPIPE 13 //读端关闭的描述符,写端写入时产生,该信号会终止程序(向无读进程的管道写数据)
#define SIGALRM 14
#define SIGTERM 15 //系统 kill 命令默认发送的信号
#define SIGSTKFLT 16
#define SIGCHLD 17 //子进程结束后,内核会默认给父进程发送该信号
#define SIGCONT 18
#define SIGSTOP 19
#define SIGSTP 20
#define SIGTTIN 21
#define SIGTTOU 22
#define SIGURG 23
(通过这个,小编是想告诉大家,这些信号其实对应的就是数字)
二、信号的响应方式
信号有三种响应方式:默认、忽略、自定义;
1、信号处理函数
在Linux系统中,我们想要了解一个新的知识必不可少的就是帮助手册啦!!!大家还记得怎么使用吗?
答案就是:man signal




2、 三种响应方式
(1)默认
如果signal函数的参数为 SIG_DFL,则系统将使用默认的信号处理动作。
大家可以输入命令“man 7 signal”查看默认处理方式,当然啦,小编也会为大家展示出来。


在上图,小编只截取了刚刚加粗的几个信号,想看完整的小伙伴可以自己输入命令“man 7 signal”,往下翻就能看到啦。
(2)忽略
如果signal函数的参数为 SIG_IGN,则系统将忽略该信号。
(3)自定义
信号自定义处理,其实是对信号进行捕捉,然后让信号执行自定义的方法。
下面,小编向大家演示一下默认的处理方式(也就是收到信号后,进程按照信号默认的方式去处理)
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<assert.h>
#include<signal.h>
int main()
{while(1){printf("main run\n");sleep(1);} exit(0);
}
在上述代码中,小编写了一个while(1)的循环,会一直执行, 当我们键盘按下ctrl+c时,其实就是因为该进程收到了一个信号:SIGNT——终端中断的信号(2号信号);就是说,在键盘上按下ctrl+c时,会给当前终端前台执行的进程发送SIGINT信号;

3、改变型号的响应方式
(1)将默认改为自定义
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<assert.h>
#include<signal.h>
#include<string.h>
void sig_fun(int sig)
{printf("sig=%d\n",sig);
}
int main()
{signal(SIGINT,sig_fun);//这里不是调用,这里是作约定while(1){printf("main run\n");sleep(1);} exit(0);
}

那我们该如何结束进程呢?
方法一(图上方法):我们可以通过ctrl+\这个是终端退出的信号
方法二:打开另外一个终端,通过ps -eflgrep test[test是程序名,可替换]这个命令找到该进程的pid,然后kill掉它。(这也是我们在前面学习kill时掌握的方法)

(2)将默认改为忽略
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<assert.h>
#include<signal.h>
#include<string.h>
int main()
{signal(SIGINT,SIG_IGN);//这里不是调用,这里是作约定while(1){ printf("main run\n");sleep(1);} exit(0);
}

4、SIGCHLD信号
(1)子进程结束,父进程会收到内核发送的SIGCHLD信号(注意:内核发送)
大家还记得我们在学习fork复制进程中的父子进程时用到的代码吗?
小编把代码放到这里帮助大家回顾昂
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>int main(){char *s=NULL;int n=0;pid_t id=fork();if(pid==-1){printf("fork err\n");exit(1);}if(id == 0){s="child";n=3;}//子进程else{s="parent";n=7;}//父进程int i=0;for(;i<n;i++){printf("s=%s\n",s);sleep(1);}exit(0);
}
在学习僵死进程时,小编说过父进程没有获取退出码是会产生僵死进程的;
在上面这段代码里,其实子进程结束了,已经给父进程发送了一个信号.只不过父进程忽略了;那么,我们修改一下代码,让父进程收到子进程的代码,打印一下收到的信号代号,不要忽略掉;
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<signal.h>void sig_fun(int sig)
{printf("sig = %d\n",sig);printf("child over!\n");
}
int main()
{int n=0;char *s = NULL;pid_t pid=fork();if(pid == -1) { printf("fork err\n");exit(1);} if(pid==0){ n=3;s="child";} else{ signal(SIGCHLD,sig_fun);//子进程结束,内核会默认给父进程发送信号n=7;s="parent";}for(int i =0;i<n;i++){printf("s=%s\n",s);sleep(1);}exit(0);
}

由执行结果可以看出,子进程结束,确实是会给父进程发送17号信号SIGCHLD;只不过遇到默认情况,父进程不会理会而已;所以,这个17号信号的默认方式就是忽略;
再次强调一下,这个不是子进程发送的信号,是内核发送的信号;
大家还记得处理僵死进程的两种方法嘛
(1)父进程先结束(2)父进程调用wait()方法获取子进程的退出码
两个方法的本质是一样的,但是方法二会阻塞,就是父进程在等子进程结束,才会获取退出
码。结合信号,如何处理,让它不再阻塞呢?
父进程调用wait是配合信号使用的。让我们通过下面的代码观察一下吧!
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<signal.h>
#include<wait.h>//注意,wait的头文件不要忘记void sig_fun(int sig)
{printf("sig = %d\n",sig);printf("child over!\n");int val;wait(&val);//我们也可以简单写,就是不获取退出码,只要不变成僵死进程就可以//wait(NULL);
}
int main()
{int n=0;char *s = NULL;pid_t pid=fork();if(pid == -1) { printf("fork err\n");exit(1);} if(pid==0){ n=3;s="child";}else{signal(SIGCHLD,sig_fun);//子进程结束,内核会默认给父进程发送信号n=7;s="parent";}for(int i =0;i<n;i++){printf("s=%s\n",s);sleep(1);}exit(0);
}
三、信号实例练习
1、收到SIGINT这个信号,第一次打印信号的代号,第二次按照默认形式把进程结束
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<assert.h>
#include<string.h>
#include<signal.h>
void sig_fun(int sig)
{printf("sig = %d\n",sig);signal(sig,SIG_DFL);
}
int main(){signal(SIGINT,sig_fun);//这里不是调用,这里是作约定while(1){ printf("main run\n");sleep(1);} exit(0);
}
2、自己实现kill命令
(1)系统调用kill与kill命令
kill也是一个命令,它底层就封装了我们的系统调用kill;
所以,man kill是1命令,man 2 kill才是系统调用;
man 2 kill得到原型:
int kill(pid_t pid,int sig);
就是向PID为pid的发送sig信号;
返回值为-1说明失败,0表示成功.
(2)回顾kill命令
执行kill PID命令,这个就是系统调用,默认发送了15号信号。比如我们sleep 500,然后
打开另外一个终端kill掉它,这个kill就是默认发送了15号信号。
(3)实现kill命令
自己实现kill命令,需要PID,需要信号代号。就是我们也要写一个类似kill-9 PID 的命令;为什么需要信号代号呢?
9号信号是一个特殊的信号,它是不允许改变响应方式的。
比如暂停进程(ctrl+Z),那么kill不掉,就需要9号信号强制结束。
写一个类似kill-9 PID的命令;(./mykill PID SIG)
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<assert.h>
#include<signal.h>//./mykill pid 信号代号
int main(int argc,char *argv[])
{if(argc!=3){printf("argc error!\n");return -1; } int pid = 0;int sig = 0;sscanf(argv[1],"%d",&pid);sscanf(argv[2],"%d",&sig);if(kill(pid,sig)==-1){ perror("kill error!\n");//perror是打印出错信息,输出错误原因} exit(0);
}



(4)15号信号和9号信号
运行sleep 500这个进程,发现使用自己的mykill命令发送15号信号显示的是“已终止(Terminated)",发现使用自己的mykill命令发送9号信号是“已杀死(killed)",和系统的kill命令是一样的。
那可能有小伙伴就会说kill命令没有传递信号代号,其实是一样的,也就是mykill传递两个参数即可,把信号代号也就是argv[2]定义成15,或者9即可。
在这里小编想补充一下,其实19号信号也不能被忽略,它是暂停进程。
【小编有话说】
本次内容就要结束啦,截止到这篇文章,小编其实已经带领大家自己写了两个命令了,分别是mycp和mykill,还有小伙伴记得嘛,mycp是在读写操作那里实现的。小编现在正在筹备一个关于LINUX项目的文章,大概可能会再过两篇Linux文章就会发布啦,到时后希望小伙伴们能够多多捧场呀!!!
最后还是老三样,点赞收藏和关注~
喜欢小编的文章就不要忘记这三样,点赞收藏加关注,找到小编不迷路~
相关文章:
Linux(十二)信号
今天我们就要来一起学习信号啦!!!还记得小编在之前的文章中说过的ctrlc吗?之前小编没有详细介绍过,现在我们就要来学习啦!!! 一、信号的基本介绍 首先,小编带领大家先一…...
LeetCode算法题(Go语言实现)_30
题目 给定单链表的头节点 head ,将所有索引为奇数的节点和索引为偶数的节点分别组合在一起,然后返回重新排序的列表。 第一个节点的索引被认为是 奇数 , 第二个节点的索引为 偶数 ,以此类推。 请注意,偶数组和奇数组内…...
无线通信技术(三):5G NR通信频带划分与应用场景
目录 一.5G NR频带划分概述 二.全球运营商5G频带分配对比 三.5G频带的应用场景 5G网络的发展离不开频谱资源的合理分配。不同的频段决定了5G的覆盖范围、传输速率和应用场景。本文将系统介绍5G NR频带划分,并结合实际应用场景,理解不同频段的特性及其适用环境。 …...
【读书笔记·VLSI电路设计方法解密】问题61:扫描插入的目的是什么
如问题60所述,要构建可测试电路,必须确保电路中每个节点都具有可控性和可观测性。但对于包含时序元件(如触发器、锁存器等存储元件)的电路,若不采取特殊设计则难以实现这两项特性。这是因为时序元件关联节点的逻辑状态不仅取决于当前输入,还受其先前存储状态影响——它们…...
VirtualBox安装FnOS
1.下载FnOS镜像 下载网址: https://www.fnnas.com/2.创建虚拟机 虚拟机配置如图所示(注意操作系统类型和网卡配置) (注意启动顺序) 3.启动虚拟机 网卡类型选择桥接的Virtual Adapter 如果没有IP地址或者IP地址无法…...
Pycharm(十二)列表练习题
一、门和钥匙 小X在一片大陆上探险,有一天他发现了一个洞穴,洞穴里面有n道门, 打开每道门都需要对应的钥匙,编号为i的钥匙能用于打开第i道门, 而且只有在打开了第i(i>1)道门之后,才能打开第i1道门&#…...
集合与容器:List、HashMap(II)
一、ArrayList 是集合框架中最核心的动态数组实现,高频使用的容器之一。 1. 核心数据结构 基于数组实现,维护elementData数组存储元素: transient修饰的elementData不会被默认序列化(通过自定义序列化逻辑优化存储)…...
Eclipse 视图(View)
Eclipse 视图(View) Eclipse 视图(View)是 Eclipse 界面的重要组成部分,它提供了用户交互的平台,使得用户可以通过图形界面来编辑、调试、分析代码等。在本文中,我们将深入探讨 Eclipse 视图的功能、使用方法以及它们在软件开发中的作用。 1. 视图的功能 Eclipse 视图具…...
《AI大模型应知应会100篇》第3篇:大模型的能力边界:它能做什么,不能做什么
第3篇:大模型的能力边界:它能做什么,不能做什么 摘要 在人工智能飞速发展的今天,大语言模型(LLM)已经成为许多领域的核心技术。然而,尽管它们展现出了惊人的能力,但也有明显的局限性…...
Springboot----@Role注解的作用
Role(BeanDefinition.ROLE_INFRASTRUCTURE) 是 Spring 框架中的一个注解,用于显式标记 Bean 的角色,表明该 Bean 是 Spring 容器内部的基础设施组件(如后置处理器、工具类等),而非用户直接使用的业务 Bean。其核心作用…...
小程序API —— 58 自定义组件 - 创建 - 注册 - 使用组件
目录 1. 基本介绍2. 全局组件3. 页面组件 1. 基本介绍 小程序目前已经支持组件化开发,可以将页面中的功能模块抽取成自定义组件,以便在不同的页面中重复使用;也可以将复杂的页面拆分成多个低耦合的模块,有助于代码维护࿱…...
前端页面鼠标移动监控(鼠标运动、鼠标监控)鼠标节流处理、throttle、限制触发频率(setTimeout、clearInterval)
文章目录 使用lodashjs库手动实现节流(通过判断之前设定的定时器setTimeout是否存在) 使用lodashjs库 <!DOCTYPE html> <html lang"zh-CN"><head><meta charset"UTF-8"><meta http-equiv"X-UA-Com…...
在 Android Studio 中运行安卓应用到 MuMu 模拟器
一、准备工作 1、确保 MuMu 模拟器已正确安装并启动 从官网下载安装最新版 MuMu 模拟器。启动后,建议在设置中调整性能参数(如 CPU 核心数和内存分配),以保证流畅运行。 2、配置 Android Studio 环境(按…...
从文本到多模态:如何将RAG扩展为支持图像+文本检索的增强生成系统?
目录 从文本到多模态:如何将RAG扩展为支持图像文本检索的增强生成系统? 一、为什么需要扩展到多模态? 二、多模态 RAG 系统的基本架构 三、关键技术点详解 (一)多模态嵌入(Embedding)技术 …...
【JavaEE】网络原理详解
1.❤️❤️前言~🥳🎉🎉🎉 Hello, Hello~ 亲爱的朋友们👋👋,这里是E绵绵呀✍️✍️。 如果你喜欢这篇文章,请别吝啬你的点赞❤️❤️和收藏📖📖。如果你对我的…...
Python项目-基于Flask的个人博客系统设计与实现(2)
源代码 续 {% extends base.html %}{% block title %}评论管理{% endblock %}{% block content %} <div class"container py-4"><div class"row"><div class"col-md-3"><div class"list-group mb-4"><a h…...
洛谷题单3-P1720 月落乌啼算钱(斐波那契数列)-python-流程图重构
题目描述 给定一个整数 N N N,请将该数各个位上数字反转得到一个新数。新数也应满足整数的常见形式,即除非给定的原数为零,否则反转后得到的新数的最高位数字不应为零(参见样例 2)。 输入格式 一个整数 N N N。 …...
NOIP2013提高组.华容道
题目 509. 华容道 算法标签: 搜索, b f s bfs bfs, s p f a spfa spfa 思路 不难发现, 在人移动的过程中, 箱子是不动的, 从当前位置到下一个箱子旁边的位置不会移动箱子, 可以预处理出人在每个位置到其他位置的距离预处理, 从某一个状态出发, 走到另一个状态的最短路使…...
政安晨【超级AI工作流】—— 基于COZE探索有趣的主题互动问答工作流(同宇宙儿童提问机)
政安晨的个人主页:政安晨 欢迎 👍点赞✍评论⭐收藏 希望政安晨的博客能够对您有所裨益,如有不足之处,欢迎在评论区提出指正! 本例,我们将从零展示如何创建一款专门针对儿童对某项主题进行问答的对话流智能体…...
Derivatives and Differentiation (导数和微分)
Derivatives and Differentiation {导数和微分} 1. Derivatives and Differentiation (导数和微分)1.1. Visualization Utilities 2. Chain Rule (链式法则)3. DiscussionReferences For a long time, how to calculate the area of a circle remained a mystery. Then, in Anc…...
P17_ResNeXt-50
🍨 本文为🔗365天深度学习训练营 中的学习记录博客🍖 原作者:K同学啊 一、模型结构 ResNeXt-50由多个残差块(Residual Block)组成,每个残差块包含三个卷积层。以下是模型的主要结构࿱…...
Ubuntu上离线安装ELK(Elasticsearch、Logstash、Kibana)
在 Ubuntu 上离线安装 ELK(Elasticsearch、Logstash、Kibana)的完整步骤如下: 一.安装验证 二.安装步骤 1. 在联网机器上准备离线包 (1) 安装依赖工具 #联网机器 sudo apt update sudo apt install apt-rdepends wget(2) 下载 ELK 的 .deb 安装包 #创建目录将安装包下载…...
PyCharm 下载与安装教程:从零开始搭建你的 Python 开发环境
PyCharm 是一款专为 Python 开发设计的集成开发环境(IDE),它提供了强大的代码编辑、调试、版本控制等功能,是 Python 开发者的必备工具之一。如果你是初学者,或者正在寻找一款高效的开发工具,这篇文章将帮助…...
TSMaster在新能源汽车研发测试中的硬核应用指南
——从仿真到标定,全面赋能智能汽车开发 引言:新能源汽车测试的挑战与TSMaster的破局之道 新能源汽车的快速发展对研发测试提出了更高要求:复杂的电控系统、高实时性通信需求、多域融合的验证场景,以及快速迭代的开发周期。传统测…...
C/C++的条件编译
一、什么是条件编译? 条件编译是指在编译阶段根据某些条件来决定是否编译某段代码。这通常通过预处理指令来实现,比如 #if、#ifdef、#ifndef、#else、#elif 和 #endif。 二、为什么使用条件编译? 跨平台开发:不同的操作…...
使用 requests 和 BeautifulSoup 解析淘宝商品
以下将详细解释如何通过这两个库来实现按关键字搜索并解析淘宝商品信息。 一、准备工作 1. 安装必要的库 在开始之前,确保已经安装了 requests 和 BeautifulSoup 库。如果尚未安装,可以通过以下命令进行安装: bash pip install requests…...
安装 TabbyAPI+Exllamav2 和 vLLM 的详细步骤
在 5090 显卡上成功安装 TabbyAPIExllamav2 和 vLLM 并非易事,经过一番摸索,我总结了以下详细步骤,希望能帮助大家少走弯路。 重要提示: 用户提供的 PyTorch 安装使用了 cu128,这并非标准 CUDA 版本。请根据你的系统实…...
小动物多导生理记录仪产品需求定义
小动物多导生理记录仪的产品需求定义如下: 功能需求 信号采集功能:能采集多种生理信号,如心电、脑电、肌电、眼电、胃肠电、诱发电位、神经电位、细胞电位、有创血压、无创血压、dP/dt、体温、肌张力、呼吸波、呼吸流速、组织血流速度、血管…...
深入理解C++引用:从基础到现代编程实践
一、引用的本质与基本特性 1.1 引用定义 引用是为现有变量创建的别名,通过&符号声明。其核心特点: 必须初始化且不能重新绑定 与被引用变量共享内存地址 无独立存储空间(编译器实现) 类型必须严格匹配 int value 42; in…...
黑白彩色相机成像原理
文章目录 黑白相机成像原理彩色相机成像原理 黑白相机成像原理 参考:B站优致谱视觉 光线聚焦:相机镜头将外界景物反射的光线聚焦到相机内部的成像平面上。光电转换:成像平面上通常是图像传感器,黑白相机常用的是CCD(…...
