【进程通信】了解信号以及信号的产生

news2024/11/29 12:51:43

文章目录

  • 0.前言
  • 1.信号的基本概念
    • 1.1中断
      • 1.1.1 软中断
      • 1.1.2硬中断
    • 1.2异步
      • 1.2.1异步和同步的比较
  • 2.信号的主要用途
  • 3.信号的特点
  • 4.查看信号
    • 4.1Core和Term的区别
    • 4.2生成Core文件
  • 5.初识捕捉信号
    • 5.1signal函数
  • 6.产生信号的方式
    • 6.1.通过终端按键产生信号
    • 6.2.调用系统函数向进程发送信号
      • kill函数
      • raise函数
      • abort函数
    • 6.3.由软件条件产生信号
      • 6.3.1alarm函数
    • 6.4硬件异常产生信号
  • 7总结

0.前言

信号是进程通信的一种方式。如同我们按下ctrl+c就能终止一个进程,实际上就
是bash进程向子进程发送了一个终止信号。和手机信号,wifi信号等类似,进程之间也可以通过信号来传递某种信息。不同的是,进程之间的信号本身就是一种共享资源信号是如何产生的?被谁产生的?又是怎么获取信号的? 本篇文章将从这几个问题展开叙述,详细讲解信号产生的原理。

1.信号的基本概念

在前言中我们只是知道了信号是实现进程通信的一种机制,那么于其它通信方式来说,信号又有着什么样的特性呢?

在Linux操作系统中,信号是一种软件中断机制,用于通知进程某些事件的发生。信号通常用于处理异步事件。信号可以由操作系统、其它进程、或者同一进程内的其它线程发送。

根据上面的概述给出以下知识扩展:

1.1中断

中断是指处理器响应硬件或软件事件的机制(其本身就是一个信号)。中断分为软件中断和硬件中断,这两者在来源处理方式上有所不同。

1.1.1 软中断

软件中断又叫软中断,是操作系统生成的中断,用于处理进程间通信或者操作系统内部的事件,比如调度、资源管理等。软中断可以通过执行特定的指令来认为生成(如kill指令),或者由操作系统内部事件触发不依赖于硬件

软中断的特性

  • 处理延迟性:软中断不需要立即被响应,可以按照系统的调度策略进行处理
  • 上下文:软中断通常在操作系统系统的上下文中执行,不会直接与硬件交互
  • 灵活性:软中断可以由操作系统根据需求灵活生成

1.1.2硬中断

硬中断是由硬件设备产生的中断,用于通知处理器一些外部事件的发生。比如键盘在输入消息时会向cpu发送一个硬件中断,告诉cpu键盘要输入了。此外鼠标、网卡等都可以通过中断请求线(IRQ)发送到cpu。硬中断依赖硬件。

硬中断特性:

  • 即时性:硬件中断通常需要cpu立即响应
  • 优先级:不同硬件中断有不同的优先级,处理器根据优先级来决定响应哪一个中断,通常硬盘中断优先级是最高的
  • 中断处理程序:每个硬中断都有一个专门的中断处理程序。

1.2异步

异步是指一个过程不需要等待其它任务完成的情况下继续进行的能力。不同于依赖同步机制的管道通信,必须要有数据写入,读端进程才会去读。发送信号的一端并不关心接收端此刻有没有做好接收的准备,也不关心信号发送之后会不会被处理。异步处理是提高程序效率和响应性的一种常用方法

1.2.1异步和同步的比较

特性异步同步
执行流非阻塞,允许并发执行阻塞,按照顺序执行
资源使用高效,可以等待时执行其他任务效率较低,cpu可能在等待时闲置
复杂度,需要处理竞态条件、死锁等问题,因为操作按顺序执行,易于理解和实现

根据以上信息得出信号的以下几种用途

2.信号的主要用途

  1. 异常处理:如当程序执行除0操作时,系统会发送一个SIGFPE浮点异常信号给进程
  2. 外部中断:比如键盘发送的中断信号例如ctrl+c,进程会收到一个SIGINT信号。
  3. 进程控制:比如有些信号可以暂停进程,有些信号用来终止进程。

同样根据信号的定义,我们能得到以下关于信号的特点:

3.信号的特点

  • 异步性:信号可以在进程的任何石刻发送或者接收。
  • 通知:信号只起到通知作用,接收信号之后怎么处理跟信号本身无关。
  • 自定义处理方式:对于一个进程来说,对待一个信号有三种处理方式:
    1.忽略信号不做处理 2.默认处理方式,即让操作系统自己去处理 3.自已定义信号的处理方式(自定义函数)

4.查看信号

在linux中我们可以使用kill -l指令来查看系统定义的信号列表
在这里插入图片描述
对于以上信号列表:

  • 每个信号都有一个编号和一个宏定义名称,这些宏定义可以在signal.h里面找到,例如其中有定义 #define SIGINT 2
  • 常见的信号是1-31,其中34-64都是实时信号,实时信号不在本文讨论范围内。每个信号都有自己默认的处理动作,在signal(7)中都有详细说明,指令man 7 signal可以查看:
    在这里插入图片描述

在谈信号的产生之前,我们可以先学习如何捕捉信号,因为捕捉信号能让我们更好的理解信号的产生。

4.1Core和Term的区别

查看信号的默认响应行为我们可以发现发现,大多信号都是Core或者Term,且这两种信号都表示终止进程。那这两种终止进程的方式有什么区别呢?
其中Term就是普通的终止进程,之后没有其他动作。而Core不仅会终止进程,还会生成一个核心转储文件

核心转储(Core Dump)文件包含了进程终止时的内存映像,和关于进程状态的详细信息,也就意味着,通过这个核心转储文件我们就能知道这个进程在终止之前发生了什么。这对于开发者来说是非常宝贵的调试信息(可以借助gdb调试器加载其中的信息并调试)。

4.2生成Core文件

系统默认进程终止时不生产Core文件,因为core文件中可能包含用户密码等隐私信息,不安全。也就意味着,即使某个进程收到SIGQUIT信号(默认产生core文件)也不会生成core文件。但是在开发调试阶段可以使用ulimit指令改变这个限制,允许产生core文件。此外core文件的大小取决于进程的Resource Limit(这个信息保存 在PCB中,默认是0)。

此外,如果子进程终止之后生成了core文件,那么子进程的退出码中的core_dump标记位就会置为1。

  1. 首先使用ulimit -c 1024指令改变core文件的大小为1024字节,再使用-a选项查看
    在这里插入图片描述

  2. 写一个死循环的程序:

#include <iostream>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
using namespace std;
int main()
{
    while (true)
    {
        cout << "pid: " << getpid() << endl;
        sleep(1);
    }
    return 0;
}
  1. 运行之后在终端按下Ctrl+\(SIGQUIT信号):
    在这里插入图片描述
    在这里插入图片描述
    如果你发现没有看到这个core文件,那可能是默认的核心转储的位置不在当前目录,可以使用以下指令修改:
    echo 'core.%e.%p.%t' | sudo tee /proc/sys/kernel/core_pattern
    %e表示程序名,%p表示进程pid,%t表示时间戳。生成的core文件后缀就是.%e.%p.%t格式。可自行修改。

我们可以用gdb加载core文件信息并调试,在gdb中使用core-file指令可以加载core文件:在这里插入图片描述

5.初识捕捉信号

5.1signal函数

signal函数(库函数)是用于设置处理某个信号时所调用的处理函数,也被称为信号处理器。由头文件signal.h提供,这个头文件属于c标准库的一部分。该函数允许程序自定义特定信号的响应行为。这一点也证实了信号的特征。

 #include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
  • sighandler_t是一个函数指针(参数为int),作为signal的参数
  • signum表示的就是要捕捉的信号
  • 返回值是一个指向之前处理该信号的函数的指针,或者在错误情况下返回 SIG_ERR。
  • signal函数并不是系统调用,但是其内部封装了系统调用sigaction。一旦我们使用signal函数捕捉了某个信号,该进程响应该信号的方式就可以自己决定了。但是值得注意的是,有一些特殊的信号的响应方式并不会完全被sighandler替代。

6.产生信号的方式

6.1.通过终端按键产生信号

ctrl+c表示一个终止信号这个好理解,但是如何证明就是产生了信号SIGINT(2)呢?我们可以用signal函数捕捉SIGINT函数,然后在自定义该信号的响应方式,最后在进程运行时按下ctrl+c观察。给出实验代码:

#include <iostream>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
using namespace std;

void myhandler(int sig)
{
    cout <<"进程"<<getpid()<< " 接收到了sig信号: " << sig << endl;
}

int main()
{
    signal(2, myhandler);
    while (true)
    {
        cout << "hello" << endl;
        sleep(1);
    }

    return 0;
}

观察运行结果:
在这里插入图片描述
当我们按下ctrl+c之后发现并没有终止进程,而是执行了自定义的myhandler函数。

6.2.调用系统函数向进程发送信号

其实就是我们常用的kill指令发送信号给指定进程。kill指令本质上是调用了系统调用kill函数。kill系统调用可以发信号到某一个进程,也可以发送到某一组进程

kill函数

#include <sys/types.h>
#include <signal.h>

int kill(pid_t pid, int sig);
  • pid表示要信号发送到哪一个进程
  • sig表示发送信号的类型
  • 发送成功返回0,否则-1.

于是,我们可以在代码中使用kill函数发送信号了。此外再介绍一个raise函数,raise函数可以给当前进程发送指定的信号(自己给自己发信号)。

raise函数

#include<signal.h>
int raise(int sig);
  • sig表示发送信号的类型
  • 发送成功返回0,否则-1。

abort函数

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

这个函数没有参数,并且它也没有返回值。调用 abort 函数后,程序会立即异常终止。本质上这个函数调用之后,操作系统就会先该进程发送一个SIGABRT信号,这个信号会终止当前进程,且通常生成core文件。(其实就是调用raise发送一个SIGABRT信号

6.3.由软件条件产生信号

软件条件产生信号其实就是软中断的一种,就是因为某种软事件或程序内部逻辑触发的信号。比如管道读端关闭之后,写端就会收到一个SIGPIPE信号进而终止。 下面介绍alarm函数和SIGALRM信号。

6.3.1alarm函数

alarm函数是一个定时器,可以设置一个时间,这个定时器会在未来的一个时刻发送一个SIGALRM信号给当前进程(该信号默认处理动作是终止进程)。alarm函数原型具体如下:

#include<unistd.h>
unsigned int alarm(unsigned int seconds);
  • seconds表示的是一个时间,单位为秒。如果seconds为0,表示取消以前设置的定时器。
  • 返回值是0或者是以前设定的闹钟时间还余下的秒数

6.4硬件异常产生信号

硬件异常被硬件以某种方式被硬件检测到并通知内核,然后内核向当前进程发送适当的信号。例如当前进程执行了除以0的指令,CPU的运算单元会产生异常,内核将这个异常解释为SIGFPE信号发送给进程。再比如当前进程访问了非法内存地址,MMU会产生异常,内核将这个异常解释为SIGSEGV信号发送给进程。具体这个异常是怎么被检测出来的我们无需关心,我们只要知道发生硬件异常之后操作系统会向该进程发送一个信号来终止进程就行了。

为什么说除0是硬件异常?因为除0这个操作是CPU在执行的,除0之后会CPU触发异常信号。访问野指针也是类似。

下面来模拟一下野指针异常,给出实验代码:

#include <stdio.h>
#include <signal.h>
#include <unistd.h>

void handler(int sig)
{
    printf("catch a sig : %d\n", sig);
    sleep(1);
}

int main()
{
    signal(SIGSEGV, handler);
    sleep(1);
    int *p = NULL;
    *p = 100;
    return 0;
}

观察以上实验代码我们就会发现,我们访问了野指针之后会触发一个信号,这个信号是什么类型,我们可以通过hanler函数来显示。
在这里插入图片描述
信号11就是SIGSEGV信号。但是我们发现一个很奇怪的现象:为什么这个handler函数会一直执行下去呢?
按理来说,调用了一次signal函数捕捉了异常信号,hander应该只会执行一次。那为什么之前我们在捕捉Ctrl+c(信号2)时却只执行了一次呢?

代码int *p = NULL; *p = 100; 显然会引发一个 SIGSEGV 信号,因为它尝试向 NULL 指针所指向的内存地址写入一个值,这是非法的内存访问。之后CPU触发一个硬件异常信号,执行handler函数之前操作系统会保存触发信号的指令地址于上下文中,执行完handler之后,操作系统又会回到之前保存的地址中去,即又回到了信号发生时的状态,于是就又重新执行*p这个代码。于是就产生了死循环。

那这种问题该如何解决呢?
可以在在handler函数中调用exit()函数或者_exit()函数确保进程终止。

7总结

  • 所有的信号本质上都是又操作系统产生的 。只不过是进程委托操作系统将这个信号发送给另一个进程(也可以是自己)。

  • 所有的信号都是起到一个作用:告诉某个进程发生了什么。怎么做由开发者决定。

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

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

相关文章

手撕C语言题典——反转链表

目录 前言 一.思路 1&#xff09;创建新链表 2&#xff09;创建三个指针 二.代码实现 搭配食用更佳哦~~ 数据结构之单单单——链表-CSDN博客 数据结构之单链表的基本操作-CSDN博客 前面学了单链表的相关知识&#xff0c;我们来尝试做一下关于顺序表的经典算法题~ 前言 反转…

新能源行业网间数据交换,更好用更专业的工具是什么?

新能源行业涵盖了多个方面&#xff0c;包括但不限于新能源汽车、可再生能源技术等。新能源行业发展具有重要的意义&#xff0c;新能源企业的研发数据极其重要&#xff0c;为了保障网络安全和数据安全&#xff0c;许多新能源企业采用逻辑隔离的方式进行网络隔离&#xff0c;此时…

llama3 发布!大语言模型新选择 | 开源日报 No.251

meta-llama/llama Stars: 53.0k License: NOASSERTION llama 是用于 Llama 模型推理的代码。 提供了预训练和微调的 Llama 语言模型&#xff0c;参数范围从 7B 到 70B。可以通过下载脚本获取模型权重和 tokenizer。支持在本地快速运行推理&#xff0c;并提供不同规格的模型并…

基于STM32的IIC通信

IIC通信 • I2C&#xff08;Inter IC Bus&#xff09;是由Philips公司开发的一种通用数据总线 • 两根通信线&#xff1a;SCL&#xff08;串行时钟线&#xff09;、SDA&#xff08;串行数据线&#xff09; • 同步&#xff0c;半双工 • 带数据应答 • 支持总线挂载多…

TCP(TCP客户端、服务器如何通信)

一、TCP介绍 TCP的特点&#xff1a; 面向连接的协议&#xff1a;TCP是一种可靠的、面向连接的协议&#xff0c;在通信之前需要建立连接&#xff0c;以确保数据的可靠传输。这意味着在传输数据之前&#xff0c;发送方和接收方之间需要建立一条可靠的连接通道。流式协议&#x…

第 1 天_二分查找【算法基础】

第 1 天_二分查找 前言34. 在排序数组中查找元素的第一个和最后一个位置题解官方33. 搜索旋转排序数组题解官方74. 搜索二维矩阵 前言 这是陈旧已久的草稿2021-11-09 19:33:44 当时在学习数据结构&#xff0c;然后再LeetCode上找了一个算法基础。 但是后来又没做了。 现在20…

WEB后端复习——监听器、过滤器

Listener监听器 是Servlet规范中定义的一种特殊类&#xff0c;它用于监听web应用程序中的ServletContext, HttpSession和ServletRequest等域对象的创建与销毁事件&#xff0c;以及监听这些域对象中的属性发生修改的事件。 注解WebListener 1.ServletContextListener 监听Serv…

韩顺平0基础学Java——第10天

p202-233 类与对象&#xff08;第七章&#xff09; 成员方法 person类中的speak方法&#xff1a; 1.public表示方法是公开的 2.void表示方法没有返回值 3.speak&#xff08;&#xff09;中&#xff0c;speak表示方法名&#xff0c;括号是形参列表。 4.大括号为方法体&am…

2005-2022年各省居民人均可支配收入数据(含城镇居民人均可支配收入、农村居民人均可支配收入)(无缺失)

2005-2022年各省居民人均可支配收入数据&#xff08;含城镇居民人均可支配收入、农村居民人均可支配收入&#xff09;&#xff08;无缺失&#xff09; 1、时间&#xff1a;2005-2022年 2、来源&#xff1a;国家统计局、统计年鉴 3、指标&#xff1a;全体居民人均可支配收入、…

语言:C#

一、VSCode生成exe 二、

Spring Cloud学习笔记(Nacos):基础和项目启动

这是本人学习的总结&#xff0c;主要学习资料如下 - 马士兵教育 1、基础和版本选择2、启动项目2.1、源码启动项目2.2、命令行启动 1、基础和版本选择 Nacos是用于服务发现和注册&#xff0c;是Spring Cloud Alibaba的核心模块。 根据文档&#xff0c;Spring Cloud Alibaba的版…

ollama离线安装,在CPU运行它所支持的哪些量化的模型

在线安装的链接: Download Ollama on LinuxGet up and running with large language models.https://ollama.com/download/linux 离线安装教程: 下载install.sh: https://ollama.ai/install.sh

算法笔记——数位DP

一、前置知识 1.DP小知识 D P DP DP 是一种算法思想&#xff0c;用递推方程的方式解决问题。但是使用它要满足如下性质&#xff1a; 最优子结构&#xff1a; 子结构优秀&#xff0c;整个就优秀。无后效性&#xff1a;当前决策不会影响后面。 2.DP实现方法 众所周知&#xf…

STC8增强型单片机开发——串口调试UART

一、什么是串口 串口是一种在数据通讯中广泛使用的通讯接口&#xff0c;通常我们叫做UART (通用异步收发传输器Universal Asynchronous Receiver/Transmitter)&#xff0c;其具有数据传输速度稳定、可靠性高、适用范围广等优点。在嵌入式系统中&#xff0c;串口常用于与外部设备…

【算法】动态规划之线性DP问题

前言&#xff1a; 本系列是看的B站董晓老师所讲的知识点做的笔记 董晓算法的个人空间-董晓算法个人主页-哔哩哔哩视频 (bilibili.com) 树塔-记忆化搜索 特点&#xff08;前提&#xff09;&#xff1a;从上向下的累加和是不能重复使用的&#xff0c;从下向上的累加和是可以重…

Golang — map的使用心得和底层原理

map作为一种基础的数据结构&#xff0c;在算法和项目中有着非常广泛的应用&#xff0c;以下是自己总结的map使用心得、实现原理、扩容机制和增删改查过程。 1.使用心得&#xff1a; 1.1 当map为nil和map为空时&#xff0c;增删改查操作时会出现的不同情况 我们可以发现&#…

浅谈如何做好软件项目

如何做好软件项目&#xff0c;这是摆在软件实施团队每个人面前的关键问题。笔者在此提出一些浅见&#xff0c;供大家参考。欢迎在评论区交流&#xff01; 目录 【摘要】 【正文】 一、树立正确的需求调研理念 二、谋定而后动的开发工作 三、大道至简的系统设计 四、专注项…

Yoast SEO Premium插件下载,提升您的网站SEO排名

在当今数字化时代&#xff0c;网站的搜索引擎优化&#xff08;SEO&#xff09;至关重要。它不仅影响着网站的可见度&#xff0c;更直接关系到您的在线业务成功与否。如果您正在寻找一个能够显著提升网站SEO表现的工具&#xff0c;Yoast SEO Premium插件将是您的理想选择。 为什…

OpenHarmony 实战开发——如何编译OpenHarmony自带APP

概述 OpenHarmony 的主干代码是开源社区的重要学习资源&#xff0c;对于想进行应用开发和熟悉 OpenHarmony 能力的同学主干代码是非常重要的资源&#xff0c;在主干代码的 applications 目录里聚集了很多原生的应用实现&#xff0c;那么如何编译这些代码就是我们这篇文章的主要…

springboot+vue+mybatis灵活就业服务平台+PPT+论文+讲解+售后

随着网络科技的不断发展以及人们经济水平的逐步提高&#xff0c;网络技术如今已成为人们生活中不可缺少的一部分&#xff0c;而微信小程序是通过计算机技术&#xff0c;针对用户需求开发与设计&#xff0c;该技术尤其在各行业领域发挥了巨大的作用&#xff0c;有效地促进了灵活…