【进程间通信】信号

news2025/1/13 6:25:19
  • (꒪ꇴ꒪ ),Hello我是祐言QAQ
  • 我的博客主页:C/C++语言,数据结构,Linux基础,ARM开发板,网络编程等领域UP🌍
  • 快上🚘,一起学习,让我们成为一个强大的攻城狮!
  • 送给自己和读者的一句鸡汤🤔:集中起来的意志可以击穿顽石!
  • 作者水平很有限,如果发现错误,请在评论区指正,感谢🙏


        当谈及进程间通信(IPC),我们需要寻找途径来使不同进程能够交换数据和信息。在操作系统中,这一通信机制被称为IPC,它有多种方式可以实现。本文将着重探讨其中之一信号(Signal)以及相关的概念、分类和使用方式。

一、什么是信号

        信号是一种操作系统层面对中断机制的软件模拟,作为一种异步通信方式它允许进程在某些事件发生时向其他进程发送通知。信号的生命周期包括信号的产生、注册、响应和处理以及注销。

        其中信号响应方式分为三类

  1. 忽略信号:某些信号可以被进程忽略,但需要注意的是,特殊的信号 SIGKILL(9)SIGSTOP (19)不能被忽略,也不能被捕捉,其默认操作无法修改。
  2. 捕捉信号响应函数:进程可以为特定信号注册自定义的信号处理函数。当进程接收到该信号时,会执行所注册的处理函数,从而实现特定的行为
  3. 执行缺省操作:Linux系统下的每种信号都有其预定义的默认操作,如果没有指定特定的信号处理方式,系统将会执行该信号的默认操作。

        换句话说,除了这两个信号之外的其他信号,接收信号的目标进程按照如下顺序来做出反应:

A :如果该信号被阻塞,那么将该信号挂起,不对其做任何处理,等到解除对其阻塞为止。否则进入 B。

B:如果该信号被捕捉,那么进一步判断捕捉的类型:

        B1:如果设置了响应函数,那么执行该响应函数。

        B2: 如果设置为忽略,那么直接丢弃该信号。

        否则进入 C。

C:执行该信号的缺省动作(默认操作)。

注意:

        信号的数值以及对应的名称可以通过命令 kill -l 查看。

信号缺省动作备注
SIGHUP1终止控制终端被关闭时产生
SIGINT2终止从键盘按键产生的中断信号(比如Ct+C)
SIGQUIT3终止并产生转储文件从键盘按键产生的退出信号(比如Ct+\)
SIGKILL9终止系统杀戮信号
SIGCONT18恢复运行系统恢复运行信号
SIGSTOP19暂停系统暂停信号

二、 信号分类

        信号可以分为非实时信号(前31个)实时信号(后31个)。这两者在响应方式上有所不同。

  1. 非实时信号(不可靠信号):非实时信号的响应不会排队,可能会发生嵌套。如果进程未及时响应一个非实时信号,之后的该信号将会被丢弃。每个非实时信号都与系统事件相关联,当事件发生时,对应的信号被产生。
  2. 实时信号(可靠信号):相比之下,实时信号的响应是按照接收顺序排队的,不会发生嵌套。即使同一种实时信号被多次发送,也不会被丢弃,而是依次响应。与实时信号相关联的没有特殊的系统事件。

三、信号的使用

1.发送信号

        使用 kill(pid, sig) 函数向指定进程发送信号。

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


2.响应方式注册

        使用 signal(sig, func) 函数注册信号的响应方式,其中 func 可以是普通响应函数。

#include <signal.h>
void (*signal(int sig, void (*func)(int)))(int);

typedef void (*sighandler_t)(int);

SIG_IGN          捕捉动作为:忽略;
SIG_DFL          捕捉动作为:执行该信号的缺省动作;
void (*p)(int)     捕捉动作为:执行由p指向的信号响应函数。

练习:司机和售票员模拟

        设计一个程序,通过父子进程模拟司机和售票员的互动。具体实现如下:

        (1)售票员捕获到 SIGINT 信号时,向司机发送 SIGUSR1 信号,司机收到后打印 "开车了...";
        (2)售票员捕获到 SIGQUIT 信号时,向司机发送 SIGUSR2 信号,司机收到后打印 "靠站...";
        (3)司机捕获到 SIGTSTP 信号时,向售票员发送 SIGUSR1 信号,售票员收到后打印 "终点站到了,请所有乘客下车!"。

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

int childpid;

// 当售票员捕获到SIGINT信号时,发送信号10给父进程(司机)
void func1(int sig)
{
	kill(getppid(), 10);
}

// 当司机捕获到SIGUSR1信号时,打印“开车了...”
void func2(int sig)
{
	printf("开车了...\n");
}

// 当售票员捕获到SIGQUIT信号时,发送信号12给子进程(售票员)
void func3(int sig)
{
	kill(getppid(), 12);
}

// 当司机捕获到SIGUSR2信号时,打印“靠站...”
void func4(int sig)
{
	printf("靠站...\n");
}

// 当售票员捕获到SIGTSTP信号时,发送信号10给子进程(售票员)
void func5(int sig)
{
	kill(childpid, 10);
}

// 当售票员捕获到SIGUSR1信号时,打印“终点站到了,请全部下车...”,然后退出进程
void func6(int sig)
{
	printf("终点站到了,请全部下车...\n");
	kill(getppid(), 9); // 给父进程(司机)发送SIGKILL信号
	kill(getpid(), 9);  // 给自己发送SIGKILL信号,终止进程
}

int main(int argc, char const *argv[])
{
	pid_t x = fork();
	if (x == 0) // 售票员进程
	{
		// 设置售票员的信号处理函数
		signal(2, func1);
		signal(3, func3);
		signal(20, SIG_IGN); // 忽略SIGCHLD信号
		signal(10, func6);
		
		while(1); // 持续等待信号
	}
	if (x > 0) // 司机进程
	{
		childpid = x;
		// 设置司机的信号处理函数
		signal(2, SIG_IGN); // 忽略SIGINT信号
		signal(10, func2);
		signal(3, SIG_IGN); // 忽略SIGQUIT信号
		signal(12, func4);
		signal(20, func5);
		
		while(1); // 持续等待信号
	}

	return 0;
}


3.给自己发送信号

        使用 raise(sig) 函数向自己发送信号。

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

int main(int argc, char const *argv[])
{
	printf("进程开始运行\n");
	sleep(5);
	raise(9);//给自己发送一个信号 9 杀死自己
	printf("进程结束运行\n");
	return 0;
}


4.等待信号

        使用 pause() 函数使进程进入等待状态,直到收到一个信号。

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

int main(int argc, char const *argv[])
{
	printf("进程%d开始运行\n", getpid());
	
	pause();//多个pause只有一个有效
	
	printf("进程%d结束运行\n", getpid());
	return 0;
}


5.信号集和阻塞

        使用 sigprocmask(how, set, oldset) 函数来设置信号的阻塞状态。

信号集:

        sigset_t mysigset;//信号集

        int sigemptyset(sigset_t *set);//清空信号集

        int sigfillset(sigset_t *set);//将所有信号添加到信号集

        int sigaddset(sigset_t *set, int signum);//添加指定的一个信号到信号集

        int sigdelset(sigset_t *set, int signum);//将指定信号从信号集中删除

阻塞:

        int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

        下面我们就来用一用信号集与阻塞,设计两个进程:

        (1)进程A负责不断向进程B发送信号(排除 SIGSTOP 和 SIGKILL);
        (2)进程B接收信号,并将每个信号注册到同一个响应函数中,打印信号值。在发送信号之前,进程B阻塞了所有信号,然后进程A发送所有信号,延时5秒后,进程B解除对信号的阻塞。

#include <sys/types.h>
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/wait.h>

// 信号处理函数,打印接收到的信号值
void func(int sig)
{
    printf("sig = %d\n", sig);
}

int main(int argc, char const *argv[])
{
    // 定义一个信号集:存放信号
    sigset_t set;

    // 清空信号集
    sigemptyset(&set);

    // 将信号添加到信号集中(排除一些特殊信号)
    for (int i = 1; i < 65; ++i)
    {
        if (i == 9 || i == 19 || i == 32 || i == 33)
        {
            continue;
        }
        sigaddset(&set, i);
    }

    // 创建子进程
    pid_t x = fork();
    if (x > 0) // 父进程
    {
        sleep(1);
        // 向子进程发送各种信号
        for (int i = 1; i < 65; ++i)
        {
            if (i == 9 || i == 19 || i == 32 || i == 33)
            {
                continue;
            }
            kill(x, i);
        }
        // 等待子进程结束
        wait(NULL);
    }
    if (x == 0) // 子进程
    {
        // 为每种信号注册信号处理函数
        for (int i = 1; i < 65; ++i)
        {
            if (i == 9 || i == 19 || i == 32 || i == 33)
            {
                continue;
            }
            signal(i, func);
        }
        // 阻塞所有信号
        sigprocmask(SIG_BLOCK, &set, NULL);
        sleep(5); // 让子进程保持阻塞状态一段时间
        // 解除对信号的阻塞
        sigprocmask(SIG_UNBLOCK, &set, NULL);
    }

    return 0;
}

6.发送信号带数据

int sigqueue(pid_t pid, int sig, const union sigval value);

kill(pid, sig);

union sigval
{
    int sival_int;
    void *sival_prt;
 }

举例:

//定义一个联合体变量,用来存放要发送的数据

union sigval data;

data.sival_int = 100;

//发送信号,带数据

kill(atoi(argv[1]), SIGUSR1);

sigqueue(atoi(argv[1]), SIGUSR1, data);

7 捕捉信号,获取数据

        捕捉一个指定的信号,且可以通过扩展响应函数来获取信号携带的额外数据。

int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

struct sigaction

{

        void    (*sa_handler)(int);

        void    (*sa_sigaction)(int, siginfo_t *, void *);

        sigset_t    sa_mask;

        int    sa_flags;

        void     (*sa_restorer)(void);

};

注意:

        sigqueue()函数相当于扩展版的 kill 函数;

        sigaction()函数相当于扩展版的 signal 函数。

举例:

//普通响应函数
void func(int sig)
{
	printf("【%d】 普通响应函数\n", sig);
}

//扩展响应函数
void sac_func(int sig, siginfo_t *siginfo, void *ptr)
{
	//如果是用sigqueue函数发送的信号,那么我们是可以拿到传过来的数据的
	if (siginfo->si_code == SI_QUEUE)
	{
		printf("获取到信号【%d】传过来的数据:%d\n", 
			sig, siginfo->si_int);
	}
}

int main(int argc, char const *argv[])
{	
	printf("pid = [%d]\n", getpid());

	//扩展信号响应函数注册
	struct sigaction act;
	//设置普通响应函数
	act.sa_handler = func;
	//设置扩展响应函数
	act.sa_sigaction = sac_func;
	//设置掩码(可以不设置)
	sigset_t myset;
	sigaddset(&myset, SIGUSR2);
	act.sa_mask = myset;
	//设置响应方式是扩展响应函数方式
	act.sa_flags |= SA_SIGINFO;

	//普通信号响应函数的注册
	// signal(SIGUSR1, func);
	//扩展信号响应函数注册
	sigaction(SIGUSR1, &act, NULL);

	pause();
	return 0;
}

8.信号的内核数据模型

        信号在操作系统内核中有一个数据模型,用于表示和管理进程间通信的信号。这个数据模型包括信号的产生、传递、处理以及信号控制块等要素。下面就是信号的内核数据模型的介绍:

        信号控制块(Signal Control Block,SCB): 信号控制块是操作系统内核中用于管理和维护信号信息的数据结构。每个进程都有一个关联的信号控制块,它存储了进程接收到的信号以及相关的处理和状态信息。这个数据结构通常包括以下字段:

  • 信号位图(Signal Bitmap): 用于表示进程当前处于阻塞状态的信号。每个信号都对应一个位,如果某个信号的位为1,则表示该信号被阻塞。

  • 信号队列(Signal Queue): 用于存储进程接收到但尚未处理的信号。信号队列采用队列的形式,其中每个节点存储了信号的类型、时间戳以及其他相关信息。

        信号的产生和传递: 信号的产生通常是由特定事件触发,如硬件异常、软件条件等。当这些事件发生时,操作系统内核会将相应的信号发送给相应的进程。信号会按照进程的层次结构向父进程或子进程传递,或者向指定的进程传递。

        信号的处理: 当进程接收到一个信号时,它可以按照事先注册的信号处理方式来响应。这可以通过调用 signal()sigaction() 函数来实现。处理方式可以是忽略信号、执行默认操作、或执行自定义的信号处理函数。在执行信号处理函数期间,进程可以根据信号的类型和处理函数的内容来进行特定的操作,从而实现对信号的处理。

        信号的阻塞和解除阻塞: 进程可以通过阻塞信号来暂时屏蔽某些信号的传递和处理。这在某些情况下很有用,比如在临界区代码中防止特定信号的干扰。进程可以使用 sigprocmask() 函数来设置信号的阻塞状态,以及使用 SIG_BLOCKSIG_UNBLOCK 来分别添加和解除信号的阻塞。

        信号的排队和处理顺序: 对于非实时信号,当多个信号被发送到同一个进程时,它们可能会排队等待被处理。对于实时信号,系统会保证信号按照发送顺序排队,不会发生嵌套。因此,进程需要按照信号排队的顺序来处理它们。

四、总结

         信号作为一种进程间通信的手段,允许进程以异步的方式相互通知。通过掌握信号的基本概念、分类和使用方式,我们可以更好地实现进程间的通信和协调,从而提升系统的整体效率和稳定性。

        更多C/C++语言Linux系统数据结构ARM板实战相关文章,关注专栏:

   手撕C语言

            玩转linux

                    脚踢数据结构

                            系统、网络编程

                                     探索C++

                                             6818(ARM)开发板实战

📢写在最后

  • 今天的分享就到这啦~
  • 觉得博主写的还不错的烦劳 一键三连喔~
  • 🎉🎉🎉感谢关注🎉🎉🎉

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

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

相关文章

vsftpd使用遇到的问题

1.正常创建 安装到配置 yum install -y vsftpd systemctl start vsftpd useradd -d /home/ftpuser ftpuer passwd ftpuser vim /etc/vsftpd/vsftpd.conf i chroot_local_userYES allow_writeable_chrootYES chroot_list_enableYES chroot_list_file/etc/vsftpd/chroot_list2.连…

【计算机网络】序列化与反序列化

文章目录 1. 如何处理结构化数据&#xff1f;序列化 与 反序列化 2. 实现网络版计算器1. Tcp 套接字的封装——sock.hpp创建套接字——Socket绑定——Bind将套接字设置为监听状态——Listen获取连接——Accept发起连接——Connect 2. 服务器的实现 ——TcpServer.hpp初始化启动…

UDP 多播(组播)

前言&#xff08;了解分类的IP地址&#xff09; 1.组播&#xff08;多播&#xff09; 单播地址标识单个IP接口&#xff0c;广播地址标识某个子网的所有IP接口&#xff0c;多播地址标识一组IP接口。单播和广播是寻址方案的两个极端&#xff08;要么单个要么全部&#xff09;&am…

商城系统以拼团、砍价、分销为场景的用户增长,裂变和转化才是关键

其实&#xff0c;用户增长是一个非常大的话题。 用户增长不再是传统互联网意义上&#xff0c;一味地追求用户数量上的增长。用户增长是不断的提出增长假设&#xff0c;通过做实验与数据分析&#xff0c;验证假设的正确性&#xff0c;以此循环往复&#xff0c;反复迭代&#xf…

使用智能电磁流量计的时候有哪些方面要注意的?

在各大工业行业领域&#xff0c;流量计量的技术含量和多元性非常高。科学研究它们对提升产品质量、减少企业经营成本、环保节能和处理生态环境保护具有十分重要的意义。智能电磁流量计具备无摩擦阻力、没压力的优势&#xff0c;充足降低了管道里的摩擦阻力&#xff0c;合乎节能…

【微服务部署】02-配置管理

文章目录 1.ConfigMap1.1 创建ConfigMap方式1.2 使用ConfigMap的方式1.3 ConfigMap使用要点建议 2 分布式配置中心解决方案2.1 什么时候选择配置中心2.2 Apollo配置中心系统的能力2.2.1 Apollo创建配置项目2.2.2 项目使用2.2.3 K8s中使用Apollo 1.ConfigMap ConfigMap是K8s提供…

Yolov8-pose关键点检测:模型轻量化创新 | DCNV3结合c2f | CVPR2023

💡💡💡本文解决什么问题:模型轻量化创新引入DCNV3 DCNV3| GFLOPs从9.6降低至8.6,参数量从6482kb降低至5970kb, mAP50从0.921提升至0.926 Yolov8-Pose关键点检测专栏介绍:https://blog.csdn.net/m0_63774211/category_12398833.html ✨✨✨手把手教你从数据标记到…

Java 集合框架1

一、集合框架 1.概念 二、Collection接口 Collection接口之下有两个子接口:List接口/Set接口 List接口是用来处理有序的单列数据&#xff0c;可以有重复的元素。 Set接口是用来处理无序的单列数据&#xff0c;没有重复的元素,重复的元素算一个 三、List接口 …

Redis 7 第三讲 数据类型 进阶篇

⑥ *位图 bitmap 1. 理论 由0和1 状态表现的二进制位的bit 数组。 说明:用String 类型作为底层数据结构实现的一种统计二值状态的数据类型 位图本质是数组,它是基于String 数据类型的按位操作。该数组由多个二进制位组成,每个二进制位都对应一个偏…

DC/DC开关电源学习笔记(一)开关电源技术概述

&#xff08;一&#xff09;开关电源技术概述 1.什么是开关电源&#xff1f;2.开关电源技术概述2.1 小型化、薄型化、轻量化、高频化2.2 高可靠性2.3 低噪声2.4 采用计算机辅助设计和控制 1.什么是开关电源&#xff1f; 开关模式电源&#xff08;Switch Mode Power Supply&…

[JAVA学习笔记]常用类

String类&#xff1a; 一、存放位置&#xff1a; 字符串对象创建好后不能修改 String是引用数据类型&#xff0c;但是这里作为方法参数传递的时候&#xff0c;效果跟基本数据类型是一样的。也就是说在堆中创建出来的字符串”monkey”是不能被改变的&#xff0c;如果…

hadoop 学习:mapreduce 入门案例一:WordCount 统计一个文本中单词的个数

一 需求 这个案例的需求很简单 现在这里有一个文本wordcount.txt&#xff0c;内容如下 现要求你使用 mapreduce 框架统计每个单词的出现个数 这样一个案例虽然简单但可以让新学习大数据的同学熟悉 mapreduce 框架 二 准备工作 &#xff08;1&#xff09;创建一个 maven 工…

Node爬虫项目精简版 wallhaven网站实操 2023.8.29

练习地址&#xff1a; https://wallhaven.cc/toplist const express require(express); const axios require(axios); const cheerio require(cheerio); const schedule require(node-schedule); const fs require(fs);async function downloadImage(url) {const response…

全国工业和信息化应用人才考试-- 服务外包 软件测试复习整理

试卷 201服务外包软件测试考试样卷(1)【附答案】 单项选择题&#xff08;每题1分&#xff0c;共40题40分&#xff09; 多项选择题&#xff08;每题2分&#xff0c;共10题20分&#xff09; 判断题&#xff08;每题1分&#xff0c;共10题10分&#xff09; 填空题&#xff…

基于stm32的ADS1292R 心电波形采集

一、前言 ADS1292R是TI公司早在几年前出产的一款医用级ADC芯片&#xff0c;它主要应用在医疗仪器(心电图ECG),可以监护患者以及病人护理和健身监视器。ADS1292R集成了心电采集所需要的部件&#xff0c;方便设备小型化。它的功耗极低&#xff0c;使得可以作为长时间监控成为可能…

CausalEGM安装使用

1代码来源 github&#xff1a;https://github.com/SUwonglab/CausalEGM/tree/main/src pip&#xff1a;Tutorial for Python Users — CausalEGM documentation 安装&#xff1a;Installation — CausalEGM documentation 版本&#xff1a; 2原理 关于CausalEGM 根据观察…

[文本挖掘和知识发现] 01.红楼梦主题演化分析——文献可视化分析软件CiteSpace入门

八月太忙&#xff0c;还是写一篇吧&#xff01; 本文是作者2023年8月底新开的专栏——《文本挖掘和知识发现》&#xff0c;主要结合Python、大数据分析和人工智能分享文本挖掘、知识图谱、知识发现、图书情报等内容。此外&#xff0c;这些内容也是作者《文本挖掘和知识发现&…

vector实现遇到的问题

前言&#xff1a;vector是表示可变大小数组的序列容器。就像数组一样&#xff0c;vector也采用的连续存储空间来存储元素。也就是意味着可以采用下标对vector的元素进行访问&#xff0c;和数组一样高效。但是又不像数组&#xff0c;它的大小是可以动态改变的&#xff0c;而且它…

【C/C++】课程设计:通讯录管理系统源码,C语言链表实现

大家好呀&#xff0c;亲爱的小伙伴们&#xff01;你们今天有在编写代码吗&#xff1f; 如果有熟悉的小伙伴看到我&#xff0c;就会知道又到了学习源码项目的好时机了&#xff01;没错&#xff0c;今天要分享的同样是一个经典的管理系统项目&#xff1a;通信录管理系统&#xf…

小白学Linux都能学会

文章目录 1. 初识Linux1.1 操作系统1.2 Linux发展历程1.3 Linux简介1.3.1 什么是 Linux1.3.2 Linux的特点 1.4 Linux和Unix区别1.5 Linux和Windows区别1.6 Linux发行商和常见发行版1.7 Linux 应用领域**1.8 Linux之CentOS**1.9 总结 2. 系统与设置命令2.1 学习命令的原因2.2 Li…