Linux下进程间的通信--信号

news2024/11/15 13:52:01

信号的概念:

在Linux操作系统中,信号是一种软件中断机制,用于通知进程某个事件已经发生。信号是Linux进程间通信(IPC)的一种简单且快速的方式,它可以用来处理各种异步事件,如用户输入、硬件事件或软件条件。

信号的特点:

进程可以选择阻塞某些信号,阻止这些信号的传递。

每个信号都有一个默认行为,例如终止进程、忽略或停止进程等

信号有不同的优先级,高优先级的信号会打断低优先级的信号处理。

信号可以跨越进程边界,由一个进程发送给另一个进程

信号机制是由操作系统内核实现的,与操作系统的调度和资源管理紧密相关。

信号的来源:

在Linux操作系统中,信号可以由多种来源产生,用于通知进程发生了某些重要事件。

  1. 硬件异常

    • SIGSEGV:尝试访问未分配的内存或无效的内存区域。
    • SIGFPE:发生浮点异常,如除以零。
    • SIGILL:执行了非法指令。
  2. 软件条件

    • SIGINT:通常由用户通过按下Ctrl+C产生,用于中断正在运行的程序。
    • SIGTERM:默认信号,用于请求程序自己终止。
    • SIGALRM:由alarm函数设置的计时器到期时产生。
  3. 用户干预

    • SIGKILL:由kill命令发送,用于立即终止程序,无法被捕获或忽略。
    • SIGSTOP:由kill命令发送,用于停止程序的执行,无法被捕获、忽略或由用户生成。
  4. 系统调用

    • SIGCHLD:子进程结束时,父进程会收到此信号。
    • SIGHUP:当控制终端关闭时,如关闭或拔出调制解调器,相关进程会收到此信号。
  5. 资源限制

    • SIGPIPE:写入一个没有读进程的管道时产生。
    • SIGXCPU:超过CPU时间限制。
    • SIGXFSZ:超过文件大小限制。
  6. I/O操作和设备状态变化

    • SIGURG:有紧急数据可从套接字读取时产生。
    • SIGIO:文件描述符上的I/O操作现在可进行时产生。
  7. 调度器事件

    • 某些实时调度器可能会在特定事件发生时发送信号。
  8. 外部设备或硬件设备

    • 特定硬件设备可能会在检测到特定事件时发送信号

信号的种类:

在Linux系统可以通过 kill -l 命令查看

 常用信号及其编号:

SIGHUP (1):挂起信号,通常在终端关闭时发送给前台进程组。

SIGINT (2):中断信号,通常由用户通过按下Ctrl+C产生。

SIGILL (4):非法指令信号,当进程执行非法指令时发送。

SIGABRT (6):中止信号,由abort()函数调用产生,用于异常终止进程。

SIGBUS (7):总线错误信号,当硬件异常,如内存访问错误时发送。

SIGFPE (8):浮点异常信号,如算术溢出、除以零等。

SIGKILL (9):杀死信号,用于立即终止进程,无法被捕获或忽略。

SIGUSR1 (10):用户定义信号1,用途由用户自定义。

SIGSEGV (11):段错误信号,当访问无效内存段时发送。

SIGUSR2 (12):用户定义信号2,用途由用户自定义。

SIGPIPE (13):管道信号,当写入一个没有读进程的管道时发送。

SIGTERM (15):终止信号,用于请求程序自己终止,可以被捕获或忽略。

SIGCHLD (17):子进程结束信号,当子进程结束时发送给父进程。

SIGCONT (18):继续信号,用于唤醒一个被停止的进程。

SIGSTOP (19):停止信号,用于停止进程的执行,无法被捕获或忽略。

SIGTTIN (21):后台进程试图读终端时发送。

SIGTTOU (22):后台进程试图写终端时发送。

SIGIO (29):I/O信号,当文件描述符上有可进行的I/O操作时发送。

信号处理的流程:

信号处理流程包含以下两个方面:

1. 信号的发送

信号的发送可以由以下几种方式触发:

  • 用户操作:用户可以通过键盘产生信号,如按下Ctrl+C通常发送SIGINT(中断信号)。
  • 软件生成:程序可以通过kill系统调用或raise函数发送信号给其他进程或自身。
  • 硬件异常:当程序执行非法操作(如除零、内存访问违规)时,硬件会触发信号,如SIGFPE(浮点异常)或SIGSEGV(段错误)。
  • 系统条件:系统在特定条件下也会发送信号,如SIGHUP(挂起信号)可能在控制终端关闭时发送。
  • 定时器超时:使用alarmsetitimertimer等定时器函数设置的定时器超时后,会发送如SIGALRMSIGVTALRM信号。

2. 信号的投递与处理

信号的投递与处理涉及以下几个步骤:

  • 信号队列:当一个信号被发送给进程时,它首先被放入进程的信号队列中。
  • 信号屏蔽:进程可以通过设置信号掩码(使用sigprocmask函数)来阻止某些信号的投递。被屏蔽的信号不会立即投递,直到它们被进程从屏蔽列表中移除。
  • 信号投递:内核负责将信号从队列中取出并投递给进程。如果信号未被屏蔽,内核会根据信号处理函数的设置来处理信号。
  • 默认处理:如果进程没有为信号设置处理函数,或者信号被忽略(使用signalsigaction函数设置为SIG_IGN),内核将执行信号的默认操作,如终止进程或忽略信号。
  • 用户定义的处理:如果进程为信号定义了处理函数,内核在信号到达时调用该函数。处理函数可以执行任何清理或响应操作。
  • 处理完成:信号处理函数执行完毕后,进程恢复到信号到达前的状态,继续执行。如果信号处理函数调用了如exit_Exit等函数,进程将终止。

在 Linux 中对信号的处理方式如下:

 在内核中的用于管理进程的结构为 task_struct ,

1.忽略信号

即对信号不做任何处理,但是有两个信号不能忽略:即SIGKILL及SIGSTOP。

SIGKILL用于立即终止进程,而SIGSTOP用于停止进程的执行。这两个信号是为了保证系统管理员能够控制所有进程而设计的。

2.捕捉信号

  • 进程可以捕捉信号,并定义自己的信号处理函数。当信号发生时,如果进程为该信号注册了处理函数,内核会暂停进程的执行,转而执行该信号的处理函数。
  • 信号处理函数可以是标准函数,如signal()函数
signal()函数
函数头文件
#include <signal.h>

函数原型
typedef void (*sighandler_t)(int);

sighandler_t signal(int signum, sighandler_t handler);

函数参数
signum:指定要处理的信号的编号。例如,SIGINT(通常由Ctrl+C产生)。
handler:指向一个函数的指针,该函数将被调用以处理指定的信号。这个函数可以是程序自定义的,也可以是几个标准函数之一:
SIG_DFL:执行信号的默认操作。
SIG_IGN:忽略该信号

函数返回值
signal函数返回之前为该信号设置的处理函数的指针。如果之前没有设置处理函数,或者设置的是默认操作,它将返回SIG_DFL。如果之前设置的是忽略该信号,它将返回SIG_IGN。
如果signal函数调用失败,它将返回SIG_ERR,errno将被设置以指示错误原因
示例代码:
#include <signal.h>
#include <stdio.h>
#include <unistd.h>

void signal_handler(int sig) {
    printf("Received signal %d\n", sig);
    // 执行必要的动作
}

int main() {
    // 设置SIGINT信号的处理函数
    signal(SIGINT, signal_handler);
    while(1) {
        sleep(1);
        printf("Looping...\n");
    }
    return 0;
}

3.执行默认操作

如果进程没有特别处理某个信号,Linux系统会为该信号执行默认操作。默认操作通常是终止进程、忽略信号或停止进程。

  • SIGTERM(信号15):默认操作是终止进程。
  • SIGCHLD(信号17):默认操作是忽略,通常用于子进程结束时通知父进程。
  • SIGCONT(信号18):默认操作是继续执行之前被停止的进程

信号发送操作

在Linux中,kill()raise() 函数是用于发送信号的两个常用方法。它们允许进程之间相互发送信号,或者进程可以向自己发送信号

1.kill()函数

kill() 函数可以向一个进程或进程组发送信号。这是最常用的信号发送方式,尤其是在需要向其他进程发送信号时。

​函数头文件
#include <sys/types.h>
#include <signal.h>

函数原型
int kill(pid_t pid, int sig);

函数参数
pid:目标进程的进程ID(PID)。
如果pid为正,则信号信号被发送到具有pid指定的ID的进程。
如果pid等于0,则向调用进程的进程组中的每个进程发送sig。
如果pid等于-1,则sig将发送到调用进程有权发送信号的每个进程,进程1 (init)除外
如果pid小于-1,则向进程组中ID为-pid的每个进程发送sig
sig:要发送的信号。可以是任何有效的信号,如SIGINT、SIGTERM等。

函数返回值
成功 返回0。
失败 返回-1,并设置errno以指示错误原因。

​

2.raise()函数

函数头文件
#include <sys/types.h>
#include <signal.h>

函数原型
int raise(int sig);

函数参数
sig:要发送给调用进程的信号编号。这是一个整数,表示特定的信号,如SIGINT、SIGTERM等。

函数返回值
成功 返回0。
失败 返回-1,并设置errno以指示错误原因

等待信号操作

在Linux系统中,pause() 函数用于使调用它的进程挂起,直到它接收到一个信号。这是一个简单的阻塞调用,常用于进程初始化后等待异步信号的情况

pause()函数

函数头文件
#include <unistd.h>

函数原型
int pause(void);

函数描述
pause() 函数使调用它的进程挂起,直到它捕获到一个信号。该进程将一直停留在pause()调用处,直到有信号到达并被处理。
该函数没有参数。
它返回时,通常意味着一个信号已经被接收并处理。

函数返回值
pause() 在信号处理函数执行后返回。如果信号处理函数返回,pause() 将返回。
如果因错误而失败,它将返回-1,并设置errno以指示错误原因

总结:

示例代码:

下面程序展示了如何使用信号和fork来创建和协调多个进程。父进程创建两个子进程,然后向它们发送不同的信号。第一个子进程等待并接收SIGUSR1信号,第二个子进程等待并接收SIGUSR2信号。子进程使用pause挂起等待信号,接收到信号后继续执行并退出。父进程等待子进程结束后也退出。这个程序展示了信号在进程间通信中的应用,并且正确地处理了子进程的结束,使用了wait(NULL)来避免子进程成为僵尸进程。

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

typedef void (*sighandler_t)(int);
void sighandler(int signum)
{
        printf("sighandle:%s\n",strsignal(signum));
}
int main()
{
        sighandler_t sig = signal(SIGUSR1,sighandler);
        if(sig==SIG_ERR)
        {
                perror("signal sig");
                exit(EXIT_FAILURE);
        }
        sighandler_t sigt  = signal(SIGUSR2,SIG_DFL);
        if(sigt==SIG_ERR)
        {
                perror("signal sigt");
                exit(EXIT_FAILURE);
        }
        pid_t pid = fork();
        if (pid == -1)
        {
                perror("fork failed");
                return 1;
        }
        else if(pid == 0)
        {
                printf("<1>chilb process pid :%d\n",getpid());
                pause();
                printf("<1>chilb process end\n");
                exit(EXIT_SUCCESS);
        }
        else
        {
                pid_t Apid = fork();
                if(Apid==-1)
                {
                        perror("fork1:");
                        exit(EXIT_FAILURE);
                }
                else if(Apid==0)
                {
                        printf("<2>chilb process Apid :%d\n",getpid());
                        pause();
                        printf("<2>chilb process end\n");
                        exit(EXIT_SUCCESS);
                }
                else
                {
                        printf("parent process pid :%d\n",getpid());
                        sleep(1);
                        int result = kill(pid,SIGUSR1);
                        if(result==-1)
                        {
                                perror("kill pid");
                                exit(EXIT_FAILURE);
                        }
                        int ret = kill(Apid,SIGUSR2);
                        if(ret==-1)
                        {
                                perror("kill Apid");
                                exit(EXIT_FAILURE);
                        }
                        printf("parent process end\n");
                }
        }
        wait(NULL);
        return 0;
}

代码解读:

定义sighandler_t类型,它是一个指向函数的指针,该函数接受一个整数参数(信号编号)并且没有返回值。

定义sighandler函数,它是一个信号处理函数,当接收到信号时被调用。它使用strsignal函数将信号编号转换为信号名称的字符串,并打印出来。

main函数中,首先设置SIGUSR1信号的处理函数为sighandler。如果设置失败,打印错误信息并退出。

SIGUSR2信号的处理函数设置为默认操作(SIG_DFL)。如果设置失败,打印错误信息并退出。

 创建第一个子进程。如果fork失败,打印错误信息并返回1。

 如果当前是子进程(pid == 0),打印子进程的PID,然后调用pause挂起等待信号。接收到信号后,打印结束信息并退出。

如果当前是父进程,再创建第二个子进程。如果fork失败,打印错误信息并退出。

如果当前是第二个子进程(Apid == 0),打印子进程的PID,然后调用pause挂起等待信号。接收到信号后,打印结束信息并退出。

如果当前是父进程,等待1秒,然后向第一个子进程发送SIGUSR1信号,向第二个子进程发送SIGUSR2信号。如果发送信号失败,打印错误信息并退出。

父进程打印结束信息。

父进程调用wait(NULL)等待任一子进程结束。

结语:

无论你是初学者还是有经验的开发者,我希望我的博客能对你的学习之路有所帮助。如果你觉得这篇文章有用,不妨点击收藏,或者留下你的评论分享你的见解和经验,也欢迎你对我博客的内容提出建议和问题。每一次的点赞、评论、分享和关注都是对我的最大支持,也是对我持续分享和创作的动力

 

 

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

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

相关文章

DAG计算框架:实现业务编排

文章目录 DAG如何实现DAG计算框架Node的实现Engine的实现Graph的实现具体某个节点如何使用 在工作几年之后&#xff0c;大部分人如果还在继续做着 CRUD的简单重复工作&#xff0c;即使领导不提出对你更高的期望&#xff0c;自身也会感到焦虑吧。学如逆水行舟不进则退&#xff…

解锁Spring Boot、Prometheus与Grafana三合一:打造你的专属自定义指标炫酷大屏!

1. 集成Prometheus到Spring Boot 需要在Spring Boot应用中集成Prometheus。可以通过micrometer库来实现。micrometer是一个应用程序监控库&#xff0c;它支持多种监控系统&#xff0c;包括Prometheus。 一招制胜&#xff01;Spring Boot、Prometheus和Grafana三剑合璧&#x…

数仓工具使用Docker部署DolphinScheduler 3.2.0 (分布式任务调度系统)-单机模式

Apache DolphinScheduler——开源大数据调度器神器 Apache DolphinScheduler(海豚调度),国人之光&#xff0c;是许多国人雷锋开源在Apache的顶级项目&#xff0c;主要功能就是负责任务的调度处理&#xff0c;Apache DolphinScheduler是一个分布式去中心化&#xff0c;易扩展的可…

C++--map和set

目录 1. 关联式容器 2. 键值对 3. 树形结构的关联式容器 3.1 set 3.2 map 3.3 multiset 3.4 multimap 4.底层结构 4.3红黑树与AVL树的比较 1. 关联式容器 前面我们已经接触过 STL 中的部分容器&#xff0c;比如&#xff1a; vector 、 list 、 deque、 forward_list(…

自建CDN/WAF解决方案--GoEdge

目录 概述 核心功能 典型应用场景 优点 适用对象 安装 使用 域名准备 DNSPOD的API秘钥申请 添加DNS厂商账号 添加集群 添加节点 添加网站 工作原理 概述 GoEdge 是一款高性能的、支持多种功能的反向代理服务器&#xff0c;通常用于流量管理、负载均衡、安全防护等…

layui2.9 树组件默认无法修改节点图标,修改过程记录下

官方文档树组件 data 参数值&#xff0c;未提供icon属性配置 需要修改源码 layui.js, 搜索图片中标记部分 查找到之后&#xff0c;修改为 <i class“‘(i.icon || “layui-icon layui-icon-file”)’”> 如图&#xff1a; 修改完成后&#xff0c;即可在data中添加icon…

Mysql双主双从

双主双从 1.安装Mysql1.1 查看linux版本1.2 下载Mysql安装包1.3 上传并解压1.4 安装Mysql1.5 编辑端口号1.6 Mysql启动命令1.7 更新密码 2.搭建Mysql主从复制2.1 搭建Master主服务器2.1.1 修改mysql配置文件2.1.2 重启Mysql服务2.1.3 创建Slave用户, 并授权2.1.4 查看主服务器当…

RTOS实战项目之实现多任务系统

文章目录 一、RTOS引入二、任务的引入2.1 任务的定义2.2 理解C函数的内部机制2.3 ARM架构2.4 汇编指令2.5 怎么保存函数的现场①要保存什么②保存现场的几种场景 三、FreeRTOS中怎么创建任务四、通过链表深入理解调度机制4.1 优先级与状态4.2 调度方法 五、创建任务—伪造现场5…

Python青少年简明教程:赋值语句

Python青少年简明教程&#xff1a;赋值语句 变量赋值是指将一个值分配给变量的过程。Python 支持多种形式的赋值&#xff08;assignment&#xff09;&#xff0c;包括基本赋值、多重赋值、链式赋值和解包赋值等。 为了深入理解Python赋值语句机制&#xff0c;先了解一下id()函数…

[Meachines] [Easy] Legacy nmap 漏洞扫描脚本深度发现+MS08-067

信息收集 IP AddressOpening Ports10.10.10.4TCP:135,139,445 $ nmap -p- 10.10.10.4 --min-rate 1000 -sC -sV -Pn PORT STATE SERVICE VERSION 135/tcp open msrpc Microsoft Windows RPC 139/tcp open netbios-ssn Microsoft Windows n…

战略合作篇白皮书:深度革新,赋能企业跃迁

01背景 企业数字化转型已经成为当今商业环境中不可避免的趋势&#xff0c;主要有以下几个原因&#xff1a; 技术发展&#xff1a;随着信息技术的迅猛发展和普及&#xff0c;企业面临着数字化转型的迫切需求。云计算、大数据、人工智能等技术正在改变商业模式和运营方式&#xf…

【C++第十四章】进阶模板

【C第十四章】进阶模板 非类型模板参数&#x1f9d0; 我们创建一个类&#xff0c;可以用模板开一个大小的为N的数组&#xff0c;这样优于用宏来定义N&#xff0c;因为可以在创建对象时可以根据需求更改数组大小。我们称在模板定义中使用的不依赖于模板类型的参数为非类型模板参…

当前A股平均市盈率

再写一篇【不务正业】的 2023-08-23A股平均市盈率 来自乐咕乐股网 当前A股市盈率是否为低点&#xff1f; 不言而喻 ‌当前A股市场的市盈率确实处于相对低位。‌ 根据东方财富Choice最新数据显示数据&#xff0c;截至2024年8月23日&#xff0c;全A市盈率为13.06倍&#xff0c;…

(贪心) LeetCode 45. 跳跃游戏 II

原题链接 一. 题目描述 给定一个长度为 n 的 0 索引整数数组 nums。初始位置为 nums[0]。 每个元素 nums[i] 表示从索引 i 向前跳转的最大长度。换句话说&#xff0c;如果你在 nums[i] 处&#xff0c;你可以跳转到任意 nums[i j] 处: 0 < j < nums[i] i j < n …

《加油吧少年》热播 编剧蔡璧鸿:创作需要对幽默保持高度关注

近日&#xff0c;校园剧《加油吧少年》正在热播中&#xff0c;该剧以学渣视角&#xff0c;讲述他在高中校园与学霸&#xff0c;女神&#xff0c;死党一起学习&#xff0c;成长和努力拼搏的故事&#xff0c;《加油吧少年》自播出后&#xff0c;便以轻松幽默&#xff0c;诙谐搞笑…

2024口碑最好的四大游泳耳机大揭秘,游泳教练全方位测评分析!

游泳&#xff0c;作为一种全身性的锻炼方式&#xff0c;越来越受到人们的青睐。在水下&#xff0c;人们渴望能够聆听到美妙的音乐&#xff0c;让游泳变得更加有趣和放松。游泳耳机的出现&#xff0c;正是为了满足这一需求。它们不仅能够提供防水、防汗的功能&#xff0c;还能在…

软件测试——自动化测试selenium常用函数

目录 元素的定位cssSelectorxpathxpath语法&#xff1a; 元素定位函数 操作测试对象窗口切换窗口窗口设置大小窗口切换屏幕截图关闭窗口 等待强制等待隐式等待显示等待 浏览器导航弹窗警告弹窗确认弹窗提示弹窗 文件上传浏览器参数设置 元素的定位 web⾃动化测试的操作核⼼是能…

RFID光触发标签在多行业的应用与效益差异

在当今数字化和智能化的浪潮下&#xff0c;RFID技术已成为众多行业优化运营、提升竞争力的关键手段。RFID光触发标签作为这一技术的创新成果&#xff0c;以其独特的性能特点&#xff0c;正逐渐在各个领域发挥着重要作用。 一、RFID光触发标签的特点与参数 &#xff08;一&…

优惠券秒杀项目

一、添加优惠券的同时&#xff0c;将优惠券信息&#xff0c;以及用户列表放到redis中 Override Transactional public void addSeckillVoucher(Voucher voucher) {// 保存优惠券save(voucher);// 保存秒杀信息SeckillVoucher seckillVoucher new SeckillVoucher();seckillVou…

linux dig域名DNS 查询与iptables域名ip访问流量限制;PTR 反向解析从 IP 地址到域名的映射

一、域名 dns查询 在 Linux 系统中&#xff0c;你可以使用多种工具和技术来进行 DNS 查询和 IP 限制。以下是一些常用的方法和工具&#xff1a; DNS 查询 dig 命令&#xff1a; dig 是一个强大的命令行工具&#xff0c;用于查询 DNS 信息。 dig example.com你可以指定查询类型…