信号-进程间通信

news2024/11/18 7:37:30

信号

1. 信号概述

在 Linux 操作系统中,信号是一种进程间通信的机制,用于通知进程发生了某个事件。信号可以由内核、其他进程,或者进程自身发送。每个信号都对应一个特定的事件或异常,例如进程终止、Ctrl+C 中断等。

本质上是一个整数,不同的信号对应不同的值,由于信号的结构简单所以天生不能携带很大的信息量,但是信号在系统中的优先级是非常高的。非常不建议使用信号进行进程间通信。

1.1 信号编号

通过 kill -l 命令可以察看系统定义的信号列表:

# 执行shell命令查看信号
$ kill -l
 1) SIGHUP       2) SIGINT       3) SIGQUIT      4) SIGILL       5) SIGTRAP
 6) SIGABRT      7) SIGBUS       8) SIGFPE       9) SIGKILL     10) SIGUSR1
11) SIGSEGV     12) SIGUSR2     13) SIGPIPE     14) SIGALRM     15) SIGTERM
16) SIGSTKFLT   17) SIGCHLD     18) SIGCONT     19) SIGSTOP     20) SIGTSTP
21) SIGTTIN     22) SIGTTOU     23) SIGURG      24) SIGXCPU     25) SIGXFSZ
26) SIGVTALRM   27) SIGPROF     28) SIGWINCH    29) SIGIO       30) SIGPWR
31) SIGSYS      34) SIGRTMIN    35) SIGRTMIN+1  36) SIGRTMIN+2  37) SIGRTMIN+3
38) SIGRTMIN+4  39) SIGRTMIN+5  40) SIGRTMIN+6  41) SIGRTMIN+7  42) SIGRTMIN+8
43) SIGRTMIN+9  44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9  56) SIGRTMAX-8  57) SIGRTMAX-7
58) SIGRTMAX-6  59) SIGRTMAX-5  60) SIGRTMAX-4  61) SIGRTMAX-3  62) SIGRTMAX-2
63) SIGRTMAX-1  64) SIGRTMAX

常见的 Linux 信号:

  1. SIGHUP (1): 终端挂起或控制进程终止。
  2. SIGINT (2): 用户发送中断信号(Ctrl+C)。
  3. SIGQUIT (3): 用户发送退出信号(Ctrl+\)。
  4. SIGILL (4): 检测到非法指令。
  5. SIGABRT (6): 调用 abort 函数,用于异常终止进程。
  6. SIGFPE (8): 浮点异常,如除以零。
  7. SIGKILL (9): 强制终止进程,无法被捕获或忽略。
  8. SIGSEGV (11): 段错误,试图访问未分配的内存。
  9. SIGPIPE (13): 向没有读取端口的管道写入数据。
  10. SIGALRM (14): 定时器超时。
  11. SIGTERM (15): 终止信号,用于请求进程正常终止。
  12. SIGUSR1 (10): 用户定义信号1。
  13. SIGUSR2 (12): 用户定义信号2。
1.2 查看信号信息

通过Linux提供的 man 文档可以查询所有信号的详细信息:

# 查看man文档的信号描述
$ man 7 signal

在信号描述中介绍了对产生的信号的五种默认处理动作,分别是:

  • Term:信号将进程终止

  • Ign:信号产生之后默认被忽略了

  • Core:信号将进程终止, 并且生成一个core文件(一般用于gdb调试)

  • Stop:信号会暂停进程的运行

  • Cont:信号会让暂停的进程继续运行

关于对信号的介绍有一句非常重要的描述:

The signals SIGKILL and SIGSTOP cannot be caught, blocked, or ignored.

9号信号和19号信号不能被 捕捉, 阻塞, 和 忽略

  • 9号信号: 无条件杀死进程
  • 19号信号: 无条件暂停进程
1.3 信号的状态

在 Linux 中,信号可以有三种基本状态:未决(Pending)、阻塞(Blocked)、以及默认状态。这些状态描述了信号的当前处理情况。

  1. 未决状态(Pending):
    • 当一个信号产生时,它首先进入未决状态,表示该信号已经发生但尚未被处理
    • 如果多次产生相同的信号,未决状态只记录一次,不会累积。
  2. 阻塞状态(Blocked):
    • 进程可以选择阻塞(屏蔽)某些信号,使得这些信号在阻塞状态时不会被处理,而是保持在未决状态。
    • 阻塞状态是一种对信号的暂时性忽略,当信号被解除阻塞后,它会根据相应的处理方式处理。
  3. 默认状态:
    • 对于每种信号,都可以设置默认的处理方式,包括终止进程(Terminate)、忽略信号(Ignore)或执行用户自定义的处理函数(Handler)。
    • 默认状态是在信号产生时,如果未设置其他处理方式,则采用的方式。

2.信号相关函数

2.1 kill/raise/abort

kill 函数:

  • 用于向指定进程或进程组发送信号。

  • #include <signal.h>
    int kill(pid_t pid, int sig);
    
  • 参数 pid 表示进程或进程组的 ID,sig 表示要发送的信号编号。

raise 函数:

  • 用于向当前进程自身发送信号。

  • #include <signal.h>
    int raise(int sig);
    
  • 参数 sig 表示要发送的信号编号。

abort函数:

  • 给当前进程发送一个固定信号 (SIGABRT)
// 这是一个中断函数, 调用这个函数, 发送一个固定信号 (SIGABRT), 杀死当前进程
#include <stdlib.h>
void abort(void);
2.2 定时器(信号捕捉)
alarm函数

alarm 函数是一个定时器函数,用于在指定时间后产生 SIGALRM 信号。它允许程序员设置一个定时器,当定时器超时时,内核将向进程发送 SIGALRM 信号。通常,SIGALRM 的默认操作是终止进程。

以下是 alarm 函数的基本形式:

#include <unistd.h>

unsigned int alarm(unsigned int seconds);
  • seconds 参数表示设置的定时器时间,以秒为单位。
  • 函数返回上一次设置的剩余时间(如果有),或者0(如果没有之前的定时器)。

使用 alarm 函数的一个常见用途是在程序中设置一个超时时间,以便在等待某个事件发生时避免无限期地阻塞。

下面是一个简单的例子,演示了如何使用 alarm 函数:

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

// SIGALRM 信号处理函数
void alarm_handler(int signum) {
    printf("Received SIGALRM\n");
}

int main() {
    // 设置 SIGALRM 的处理函数
    // 当程序接收到 SIGALRM 信号时,将调用 alarm_handler 函数来处理这个信号。
    signal(SIGALRM, alarm_handler);

    // 设置定时器,5 秒后发送 SIGALRM 信号
    unsigned int remaining_time = alarm(5);
    printf("Alarm set. Remaining time: %u seconds\n", remaining_time);

    // 模拟一些工作,等待超时
    sleep(7);

    // 如果在超时前收到了 SIGALRM,处理函数将被调用
    printf("Work done\n");

    return 0;
}
setitimer函数

setitimer () 函数可以进行周期性定时,每触发一次定时器就会发射出一个信号

// 这个函数可以实现周期性定时, 每个一段固定的时间, 发出一个特定的定时器信号
#include <sys/time.h>

struct itimerval {
	struct timeval it_interval; /* 时间间隔 */
	struct timeval it_value;    /* 第一次触发定时器的时长 */
};
// 举例: luffy有一个闹钟, 并且使用这个闹钟定时:
// 早晨7点中起床, 第一次闹钟响起时可能起不来, 之后每隔5分钟再响一次
//  - it_value: 当前设置闹钟的时间点 到 明天早晨7点 对应的总秒数
//  - it_interval: 闹钟第一次响过之后, 每隔5分钟响一次

// 这个结构体表示的是一个时间段: tv_sec + tv_usec
struct timeval {
	time_t      tv_sec;         /* 秒 */
	suseconds_t tv_usec;        /* 微妙 */
};

int setitimer(int which, const struct itimerval *new_value, 
              struct itimerval *old_value);

参数:

  • which: 定时器使用什么样的计时法则, 不同的计时法则发出的信号不同
    • ITIMER_REAL: 自然计时法, 最常用, 发出的信号为SIGALRM, 一般使用这个宏值,自然计时法时间 = 用户区 + 内核 + 消耗的时间(从进程的用户区到内核区切换使用的总时间)
    • ITIMER_VIRTUAL: 只计算程序在用户区运行使用的时间,发射的信号为 SIGVTALRM
    • ITIMER_PROF: 只计算内核运行使用的时间, 发出的信号为SIGPROF
  • new_value: 给定时器设置的定时信息, 传入参数
  • old_value: 上一次给定时器设置的定时信息, 传出参数,如果不需要这个信息, 指定为NULL

例子:

#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
#include <sys/time.h>
#include <unistd.h>

void timer_handler(int signum) {
    printf("Timer expired!\n");
}

int main() {
    // 设置 SIGALRM 信号处理函数
    signal(SIGALRM, timer_handler);

    // 设置定时器初始值和溢出后的处理方式
    struct itimerval timer;
    timer.it_value.tv_sec = 2;      // 初始值为2秒, 2s后第一次触发
    timer.it_value.tv_usec = 0;
    timer.it_interval.tv_sec = 1;   // 间隔为1秒,即每秒触发一次
    timer.it_interval.tv_usec = 0;

    // 设置定时器
    if (setitimer(ITIMER_REAL, &timer, NULL) == -1) {
        perror("setitimer");
        exit(EXIT_FAILURE);
    }

    // 让程序持续运行,观察定时器的触发
    while (1) {
        sleep(5);
    }

    return 0;
}

在这里插入图片描述

3. 信号集

3.1 信号集概述

信号集(Signal Set)是用于表示一组信号的数据结构。在 Unix/Linux 操作系统中,信号集被广泛用于进程管理中,主要用于设置、查询和修改进程的信号状态。信号集通常是一个二进制位图,每个位表示一个信号,位的状态表示信号的状态(屏蔽或未屏蔽)。

信号集的主要目的是允许进程选择性地阻塞或解除阻塞一组特定的信号,以实现对信号的灵活控制。以下是在 C 语言中使用信号集的相关函数和数据结构:

sigset_t 数据类型:

  • sigset_t 是用于存储信号集的数据类型,通常是一个整数或类似的位图结构。
  • 在头文件 <signal.h> 中定义,具体实现可能因系统而异。

在PCB中有两个非常重要的信号集。一个称之为“阻塞信号集”,另一个称之为“未决信号集”。这两个信号集体现在内核中就是两张表。

在阻塞信号集中,描述这个信号有没有被阻塞

  • 默认情况下没有信号是被阻塞的, 因此信号对应的标志位的值为 0
  • 如果某个信号被设置为了阻塞状态, 这个信号对应的标志位 被设置为 1

在未决信号集中, 描述信号是否处于未决状态

  • 如果这个信号被阻塞了, 不能处理, 这个信号对应的标志位被设置为1
  • 如果这个信号的阻塞被解除了, 未决信号集中的这个信号马上就被处理了, 这个信号对应的标志位值变为0
  • 如果这个信号没有阻塞, 信号产生之后直接被处理, 因此不会在未决信号集中做任何记录
3.2 信号集函数

阻塞信号集可以通过系统函数进行读写操作,未决信号集只能对其进行读操作。

读/写阻塞信号集的函数:

#include <signal.h>
// 使用这个函数修改内核中的阻塞信号集
// sigset_t 被封装之后得到的数据类型, 原型:int[32], 里边一共有1024给标志位, 每一个信号对应一个标志位
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

参数:

how:

  • SIG_BLOCK: 将参数 set 集合中的数据追加到阻塞信号集中
  • SIG_UNBLOCK: 将参数 set 集合中的信号在阻塞信号集中解除阻塞
  • SIG_SETMASK: 使用参 set 结合中的数据覆盖内核的阻塞信号集数据

oldset: 通过这个参数将设置之前的阻塞信号集数据传出,如果不需要可以指定为NULL

返回值:函数调用成功返回0,调用失败返回-1

sigprocmask() 函数有一个 sigset_t 类型的参数,对这种类型的数据进行初始化需要调用一些相关的操作函数:

#include <signal.h>
// 如果在程序中读写 sigset_t 类型的变量
// 阻塞信号集和未决信号集都存储在 sigset_t 类型的变量中, 这个变量对应一块内存
// 阻塞信号集和未决信号集, 对应的内存中有1024bit = 128字节

// 将set集合中所有的标志位设置为0
int sigemptyset(sigset_t *set);
// 将set集合中所有的标志位设置为1
int sigfillset(sigset_t *set);
// 将set集合中某一个信号(signum)对应的标志位设置为1
int sigaddset(sigset_t *set, int signum);
// 将set集合中某一个信号(signum)对应的标志位设置为0
int sigdelset(sigset_t *set, int signum);
// 判断某个信号在集合中对应的标志位到底是0还是1, 如果是0返回0, 如果是1返回1
int sigismember(const sigset_t *set, int signum);

读未决信号集的操作函数:

#include <signal.h>
// 这个函数的参数是传出参数, 传出的内核未决信号集的拷贝
// 读一下这个集合就指定哪个信号是未决状态
int sigpending(sigset_t *set);

下面举一个简单的例子,演示一下信号集操作函数的使用:

需求: 
在阻塞信号集中设置某些信号阻塞, 通过一些操作产生这些信号, 然后读未决信号集, 最后再解除这些信号的阻塞
假设阻塞这些信号: 
  - 2号信号: SIGINT: ctrl+c
  - 3号信号: SIGQUIT: ctrl+\
  - 9号信号: SIGKILL: 通过shell命令给进程发送这个信号 kill -9 PID
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>

int main()
{
    // 1. 初始化信号集
    sigset_t myset;
    sigemptyset(&myset);
    // 设置阻塞的信号
    sigaddset(&myset, SIGINT);  // 2
    sigaddset(&myset, SIGQUIT); // 3
    sigaddset(&myset, SIGKILL); // 9 测试不能被阻塞

    // 2. 将初始化的信号集中的数据设置给内核
    sigset_t old;
    sigprocmask(SIG_BLOCK, &myset, &old);

    // 3. 让进程一直运行, 在当前进程中产生对应的信号
    int i = 0;
    while(1)
    {
        // 4. 读内核的未决信号集
        sigset_t curset;
        sigpending(&curset);
        // 遍历这个信号集
        for(int i=1; i<32; ++i)
        {
            int ret = sigismember(&curset, i);
            printf("%d", ret);
        }
        printf("\n");
        sleep(1);
        i++;
        if(i==10)
        {
            // 解除阻塞, 重新设置阻塞信号集
            //sigprocmask(SIG_UNBLOCK, &myset, NULL);
            sigprocmask(SIG_SETMASK, &old, NULL);
        }
    }
    return 0;
}

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

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

相关文章

“To-Do Master“ GPTs:重塑任务管理的趣味与效率

有 GPTs 访问权限的可以点击链接进行体验&#xff1a;https://chat.openai.com/g/g-IhGsoyIkP-to-do-master 部署私人的 To-Do Master 教程&#xff1a;https://github.com/Reborn14/To-Do-Master/tree/main 引言 在忙碌的日常生活中&#xff0c;有效地管理日常任务对于提高生…

【makedown自带语法技巧】

这里写自定义目录标题 欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题&#xff0c;有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants 创建一个自定义列表如何创建一个…

从虚拟到现实:数字孪生驱动智慧城市可持续发展

随着科技的飞速发展&#xff0c;智慧城市已经成为未来城市发展的重要趋势。数字孪生技术作为智慧城市建设中的关键技术之一&#xff0c;正在发挥着越来越重要的作用。本文将探讨数字孪生如何从虚拟走向现实&#xff0c;驱动智慧城市的可持续发展。 一、数字孪生技术&#xff1…

AIGC ChatGPT4 教你如何制作动态图表,完整代码

图表效果如下: 根据选择不同的年份图表会进行自动显示。 这样的效果直接可以用ChatGPT 4来完成。 代码单独复制出来。 分为两部分a.html与a.js HTML部分如下: <!DOCTYPE html> <html><head><meta charset="utf-8"><title>EChart…

华为mstp、vrrp、ospf、isis、bgp等综合一起排错

最终实现左边私网和右边私网全部ping通 SW1 vlan batch 12 34 stp region-configuration //mstp配置 region-name test instance 12 vlan 12 instance 34 vlan 34 active region-configuration interface GigabitEthernet0/0/3 port link-type trunk port trunk allow-pass …

烟火检测AI边缘计算智能分析网关V4在安防项目中的应用及特点

一、行业背景 随着社会和经济的发展&#xff0c;公共安全和私人安全的需求都在不断增长。人们需要更高效、更准确的安防手段来保障生命财产安全&#xff0c;而人工智能技术正好可以提供这种可能性&#xff0c;通过智能监控、人脸识别、行为分析等手段&#xff0c;大大提高了安防…

firewall防火墙(二)

一、IP伪装与端口转发&#xff1a; 当用户数据包经过NAT设备时&#xff0e;NAT设备将源地址替换为公网P地址&#xff0e;而返回的数据包就可以被路由&#xff0c;NAT技术一般都是在企业边界路由器或者防火墙上来配置. Firewaild支持两种类型的NAT;P地址伪装和端口转发. 1.1 I…

【云计算】云计算概述

1. 云计算概述 1.1 云计算的定义 美国国家标准与技术研究院(NIST)定义 云计算是一种按使用量付费的模式&#xff0c;这种模式提供可用的、便捷的、按需的网络访问&#xff0c;进入可配置的计算资源共享池(资源包括网络&#xff0c;服务器&#xff0c;存储&#xff0c;应用软件…

Netdata安装

先上一张部署成功的效果 一、Netdata是什么&#xff1f; Netdata是一款Linux性能实时监测工具&#xff0c;web页面可视化展示系统及应用程序的实时运行状态&#xff0c;包括CPU、内存、硬盘I/O及网络等性能数据。 二、安装 方式一&#xff1a;直接安装 安装基本编译环境 […

来了!私域流量转化差4大的原因

很多做私域的朋友常常苦恼的问题&#xff0c;辛辛苦苦把流量从公域引到私域&#xff0c;但是转化特别差&#xff1b;私域里躺着大量的沉默用户&#xff0c;不知道该如何激活&#xff1b;私域业务整体产出特别低&#xff0c;在犹豫要不要放弃等...... 要解决这些问题&#xff0c…

VS QT 创建新的QT类后,编译报错无法解析的外部符号 “public: virtual struct QMetaObject const *

问题描述&#xff1a; 新建QT的 Widgets 类&#xff0c;创建新的窗口 在编译的时候出现以下报错信息&#xff1a; 1>vfhclassifydialog.obj : error LNK2001: 无法解析的外部符号 "public: virtual struct QMetaObject const * __cdecl VfhClassifyDialog::metaObject…

机器视觉在OCR字符检测的应用

在产品质量 检测过程中&#xff0c;对于字符、条码等标识信息的识别、读取、检测是非常重要的一部分&#xff0c;比如在食品饮料包装检测中&#xff0c;生产日期 、保质期 、生产批号 、条码等字符信息是产品管理和追溯必不可缺的&#xff0c;因此利用机器视觉技术进行OCR字符采…

Web APIs知识点讲解

学习目标: 能获取DOM元素并修改元素属性具备利用定时器间歇函数制作焦点图切换的能力 一.Web API 基本认知 1.作用和分类 作用: 就是使用 JS 去操作 html 和浏览器分类&#xff1a;DOM (文档对象模型)、BOM&#xff08;浏览器对象模型&#xff09; 2.DOM DOM(Document Ob…

一文了解Git(所有命令)附带图片

我是南城余&#xff01;阿里云开发者平台专家博士证书获得者&#xff01; 欢迎关注我的博客&#xff01;一同成长&#xff01; 一名从事运维开发的worker&#xff0c;记录分享学习。 专注于AI&#xff0c;运维开发&#xff0c;windows Linux 系统领域的分享&#xff01; 其他…

每家企业都需要掌握的软文写作技巧,媒介盒子分享

从本质上来说&#xff0c;文案写作是利用文字来影响、说服和受众建立联系的技能&#xff0c;不管是引起用户共鸣还是提高产品销量&#xff0c;有说服力的文案都是推动购买、支持并取得成功的关键&#xff0c;今天媒介盒子就来和大家聊聊&#xff1a;每家企业都需要掌握的软文技…

机器学习 -- 余弦相似度

场景 我有一个 页面如下&#xff08;随便找的&#xff09;&#xff1a; 我的需求是拿到所有回答的链接&#xff0c; 再或者我在找房子网上&#xff0c;爬到所有的房产信息&#xff0c;我们并不想做过多的处理&#xff0c;我只要告诉程序&#xff0c;请帮我爬一个类似 xxx 相似…

【粉丝福利社】一书读懂物联网:基础知识+运行机制+工程实现(文末送书-进行中)

&#x1f3c6; 作者简介&#xff0c;愚公搬代码 &#x1f3c6;《头衔》&#xff1a;华为云特约编辑&#xff0c;华为云云享专家&#xff0c;华为开发者专家&#xff0c;华为产品云测专家&#xff0c;CSDN博客专家&#xff0c;CSDN商业化专家&#xff0c;阿里云专家博主&#xf…

小型内衣裤洗衣机哪个牌子好?市面上内衣洗衣机便宜好用的牌子推荐

这两年&#xff0c;内衣洗衣机这个小型的家电逐渐走进热门的视线里&#xff0c;很多小伙伴使用过它之后不仅轻松了很多&#xff0c;还可以很好的少数一些细菌。但这个时候也有人说内衣洗衣机就是智商税&#xff0c;根本没有作用&#xff0c;只能心理在作怪&#xff0c;而面对这…

【STM32】STM32学习笔记-USART串口数据包(28)

00. 目录 文章目录 00. 目录01. 串口简介02. HEX数据包03. 文本数据包04. HEX数据包接收05. 文本数据包接收06. 预留07. 附录 01. 串口简介 串口通讯(Serial Communication)是一种设备间非常常用的串行通讯方式&#xff0c;因为它简单便捷&#xff0c;因此大部分电子设备都支持…

通过Windows事件查看器查询系统关机和重启日志

文章目录 前言一、事件查看器是什么&#xff1f;二、怎么使用事件查看器查看开机和关机日志三、Windows常用事件ID总结 前言 昨天服务器应用程序的接口突然都访问不了&#xff0c;远程进入了服务器发现程序没有问题&#xff0c;但服务器的桌面非常干净&#xff08;一般总有未关…