【Linux】信号(signal)

news2024/11/13 11:04:39

目录

一、信号概念:

二、信号的常见状态:

信号递达:

信号未决:

阻塞信号:

忽略信号:

信号在内核中的表示:

三、信号相关函数:

sigset_t (类型):

信号集操作函数:

signal() 函数:

sigpending() 函数:

sigaction() 函数:

sigprocmask() 函数:

四、补充:SIGCHLD信号


一、信号概念:

在Linux系统中,信号(signal)是一种软件中断,它提供了一种机制来通知进程发生了某个事件。当系统检测到某种异常或特殊事件时,会向相关的进程发送一个信号。进程可以指定对每种信号的处理方式,包括忽略信号、捕捉信号、以及执行默认操作。

在Linux终端中我们可以通过 kill -l 来查询所有的信号

每一个信号都有一个名字和编号,都以SIG开头,其实本质上就是一个宏,例如2号信号其实就是:#define SIGINT 2          这些信号被定义在 signal.h 文件中。

非实时信号:(1 - 31号信号),也叫不可靠信号:

        它们的主要特点是不支持排队,即如果发送多次相同的信号,进程可能只能收到一次,信号可能会丢失。

实时信号:(32 - 64号信号),也叫可靠信号:

        它们的主要特点是支持排队,即如果发送多次相同的实时信号,进程可以收到多次,信号不会丢失。(一般用来实现更复杂的进程间通信和同步机制)

所以这篇主要围绕非实时信号做文章:

Core Dump(核心转储)

概念:

在OS中,当一个程序异常终止时(例如内存非法访问导致的段错误),OS会将此时进程地址空间的内容以及有关进程状态的其他信息写成一个磁盘文件(文件名一般为core)。这就是核心转储。

在事后可以通过调试器检查croe文件弄清错误原因,但是OS设置默认是不允许产生croe文件的,因为其中可能包含用户的一些隐私信息,不安全,可以通过ulimit命令修改成允许产生croe文件

可以在终端输入 ulimit -c 命令,如果结果为0,就是默认关闭croe dump,就算程序异常终止也不会产生croe文件。

可以通过ulimit -c unlimited开启croe dump功能,不限制croe文件的大小,如果要限制,就将unlimited改成你想要的文件大小(KB)即可,但注意,该命令的生命周期只在当前终端有效。

二、信号的常见状态:

信号递达:

信号递达就是指当信号被发送和接收后,信号的处理过程,当一个信号被发送到一个进程时,需要经过多个步骤才能被接收并处理。

例如给进程A发送系统调用kill():

1:发送信号:OS产生信号,准备发送到进程A。

2:信号递达:进程A收到信号的过程。

2:信号处理:进程A收到信号,并根据信号的类型和处理方式来进行对应的处理

注意:

        在信号递达和信号处理的过程中,可能会发生信号丢失或者信号被阻塞的情况,当一个进程处于阻塞状态时,它无法收到任何信号,直到解除阻塞状态,如果有多个信号同时到达进程时会出现信号排队的情况,此时进程会按照一定的规则来处理这些信号。

信号未决:

信号未决(Pending Signal)指的是一个进程接收到但尚未处理的信号。当一个信号被发送给一个进程时,如果该进程当前正在执行某个信号处理函数或者该信号已经处于未决状态,则该信号会被放入进程的信号未决位集(Pending Signal Mask,简称pending表)中,等待进程从信号处理函数返回后进行处理。

在pending表中,每个位代表一个信号默认为0,如果该位为1,则表示该位对应的信号处于未决状态。一个进程可以通过sigpending()系统调用来查询自己的信号未决位集。

如果一个进程接收到多个同类型的信号并且信号处理函数尚未返回,则这些信号将被合并成一个信号,并只记录一次信号未决。

当进程解除信号阻塞状态后,它必须处理所有未决的信号,否则这些信号将继续被保留在信号未决位集中,可能会导致信号丢失或者其他问题。所以要注意及时处理所有未决信号,避免信号积压导致系统异常。

阻塞信号:

阻塞信号是指进程可以选择暂时延迟处理某些特定信号的传递和处理。在Linux中,进程可以通过设置信号阻塞掩码(Signal Mask)来达到这一目的。

当一个信号被发送给一个进程时,内核会首先检查该信号是否在进程的信号阻塞掩码中。如果信号在阻塞掩码中,则该信号将被暂时挂起,直到该信号从阻塞状态解除后才能被处理。可以使用sigprocmask()系统调用来修改信号阻塞掩码。

当进程解除对某个信号的阻塞时,如果有多个该类型的信号在阻塞期间到达,那么这些信号将按照某种规则进行排队,等待进程逐个处理。另外,即使信号被阻塞,但仍然会记录在信号未决位集中,等待进程除阻塞后处理。

忽略信号:

忽略信号是指进程可以选择不对某些特定信号进行处理,即忽略该信号的传递和默认处理行为。在Linux中,进程可以通过设置信号处理函数为SIG IGN来达到这一目的。

当一个信号被发送给一个进程时,内核会首先检查该信号的处理方式。如果进程将该信号的处理函数设置为SIGIGN(忽略信号),则内核将不对该信号进行任何处理,直接丢弃该信号。

但是并不是所有的信号都可以被忽略或捕捉的。如SIGKILL和SIGSTOP,它们具有固定的处理行为。防止用户特意屏蔽所有信号导致进程无法退出

信号在内核中的表示:

示意图:

如图:

SIGHUP信号未阻塞也未产生过,当它递达时执行默认处理动作(SIG_DFL)。

SIGINT信号产生过,但正在被阻塞,所以暂时不能递达。虽然它的处理动作是忽略,但在没有解除阻塞之前不能忽略这个信号,因为进程仍有机会改变处理动作之后再解除阻塞。

SIGQUIT信号未产生过,一旦产生SIGQUIT信号将被阻塞,它的处理动作是用户自定义函数sighandler()。

 

三、信号相关函数:

sigset_t (类型):

sigset_t是一个数据类型,用于表示一个信号集,他是由一个位图组成的数据结构,用于跟踪和管理多个信号的状态,通常以无符号整数或者数组的形式实现

在 Linux 中,sigset_t 是通过使用位操作来表示信号集的。每个信号对应 sigset_t中的一个位(bit),如果某个位被设置为 1,则表示相应的信号在该信号集中被包含;如果某个位被设置为0,则表示相应的信号在该信号集中不被包含。

信号集操作函数:

上述函数都有相同的返回值:成功返回 0,失败返回 -1,并设置相应的错误码(errno)

signal() 函数:

signal() 函数是一个用于信号处理的POSIX标准C函数,该函数的主要作用是向操作系统注册一个信号处理函数,用于在特定信号发生时执行相应的处理逻辑。

#include <signal.h>
void (*signal(int sig, void (*func)(int)))(int);
  • sig:指定要处理的信号编号。
  • func:指向一个函数的指针,该函数在信号发生时被调用,其返回类型为void,参数为int类型,表示接收到的信号编号。
  • 返回值:返回之前对 sig 信号的处理函数指针,如果没有则返回SIG_ERR,并设置errno。

代码演示:(使用signal函数捕捉SIG_INT,并设置handler函数自定义处理)

#include <iostream>
#include <unistd.h> // getpid
#include <signal.h> // signal函数头文件

void handler(int signum)
{
    std::cout << "signum: " << signum << std::endl;
}

int main()
{
    // 捕捉2号信号并进行自定义函数处理
    if (signal(SIGINT, handler) == SIG_ERR)
    {
        perror("signal error!");
        exit(1);
    }

    std::cout << "pid: " << getpid() << std::endl;
    while (true)
    {
        sleep(1);
    }

    return 0;
}

sigpending() 函数:

sigpending() 函数是Linux系统中的一个系统调用,用于查询当前进程的未决信号集合。

未决信号集和:指已经发送给进程但尚未被处理的信号。这些信号可能因为被屏蔽而无法立即传递给进程,但会停留在待处理状态,等待进程解除对它们的阻塞时进行处理。

#include <signal.h>  
int sigpending(sigset_t *set);

set:指向一个sigset_t类型的信号集合的指针,用于存储当前进程的未决信号集合。调用成功后,该信号集合将包含所有未决信号的集合。

返回值:成功返回 0,失败返回 -1。

该函数可以在下两个函数中用到,这里就将代码融入到下面。

sigaction() 函数:

sigaction()函数UNIX 和类 UNIX 系统(如 Linux)中用于检查或改变与信号相关联的处理动作的函数。该函数允许程序捕获由系统产生的信号,并定义这些信号的处理方式(如忽略、终止程序或执行用户定义的函数等)。

#include <signal.h>  
int sigaction(int sig, const struct sigaction *act, struct sigaction *oact);
  • sig指定要操作的信号的编号。这是一个整型值,对应于不同的信号(如 SIGINT 表示中断信号, SIGTERM 表示终止请求等)。
  • act指向 struct sigaction 结构的指针,该结构定义了新的信号处理动作。如果此参数为 NULL,则不会改变信号的处理方式。
  • oact如果非 NULL,则指向的 struct sigaction 结构会被填充为该信号当前的处理方式。
  • 返回值:成功时返回 0;失败时返回 -1 并设置 errno。

 

struct sigaction 结构:

struct sigaction {  
    void     (*sa_handler)(int);  
    void     (*sa_sigaction)(int, siginfo_t *, void *);  
    sigset_t   sa_mask;  
    int        sa_flags;  
    void     (*sa_restorer)(void);  // 历史遗留字段,可直接忽略。
};
  • sa_handler一个函数指针,指向信号处理函数。当信号发生时,会调用这个函数。如果设置为 SIG_IGN,则忽略该信号;如果设置为 SIG_DFL,则使用信号的默认处理方式。
  • sa_sigaction与 sa_handler 类似,但它提供了更多的上下文信息,包括信号的 siginfo_t 结构和上下文指针(ucontext_t)。要使用 sa_sigaction,sa_flags 必须包含 SA_SIGINFO 标志。
  • sa_mask在信号处理函数执行期间,系统会阻塞此信号集中包含的信号。这有助于防止在信号处理函数执行期间发生信号重入问题。
  • sa_flags控制信号处理的各种选项。常见的选项包括 SA_RESTART(如果信号处理函数返回后,被中断的系统调用会自动重启),SA_NODEFER(防止信号被自动阻塞),以及 SA_SIGINFO(使用 sa_sigaction 而不是 sa_handler)。

 

代码演示:(使用sigaction函数捕捉SIG_INT,并设置handler函数自定义处理,打印pindeng表检测2号信号进入信号未决状态。)

#include <iostream>
#include <signal.h>
#include <unistd.h> // getpid

void Print(sigset_t &pending)
{
    for (int sig = 31; sig > 0; sig--)
    {
        if (sigismember(&pending, sig)) // 检测特定对应的信号是否在pending集中
        {
            std::cout << 1;
        }
        else
        {
            std::cout << 0;
        }
    }
    std::cout << std::endl;
}

int main() {
    sigset_t newset, oldset;


    // 设置要阻塞的信号集
    sigemptyset(&newset);
    sigaddset(&newset, SIGINT);

    // 阻塞 SIGINT 信号
    if (sigprocmask(SIG_BLOCK, &newset, &oldset) == -1) {
        perror("sigprocmask");
        return 1;
    }

    printf("SIGINT is blocked. Press Ctrl+C to send the signal.\n");

    // 挂起进程,等待信号到达
    while(true)
    {
        sigset_t pending;
        sigpending(&pending);
        Print(pending);

        sleep(2);
    }


    // 恢复原来的信号屏蔽字
    if (sigprocmask(SIG_SETMASK, &oldset, NULL) == -1) {
        perror("sigprocmask");
        return 1;
    }

    printf("SIGINT is unblocked. Signal handling resumed.\n");

    return 0;
}

sigprocmask() 函数:

sigprocmask() 函数用于修改进程的信号屏蔽字集合,进而控制哪些信号可以被进程接收和处理。

#include <signal.h>  
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
  • how:指定对信号屏蔽字的操作方式,可以是以下三个值之一:
    • SIG_BLOCK:将set指向的信号集合添加到当前进程的信号屏蔽字中。
    • SIG_UNBLOCK:从当前进程的信号屏蔽字中移除set指向的信号集合。
    • SIG_SETMASK:将当前进程的信号屏蔽字设置为set指向的信号集合。
  • set:指向一个信号集合的指针,用于指定需要修改的信号集合。根据how参数的不同,这个集合的意义也会有所不同。
  • oldset:是一个可选的输出参数,用于获取调用sigprocmask函数前的旧的信号屏蔽字。如果不需要获取旧的信号屏蔽字,可以将其设置为NULL。
  • 返回值:成功返回 0,失败返回 -1,并设置errno。

 

代码演示:(使用sigprocmask()函数屏蔽SIG_INT信号,并打印pindeng表检测2号信号进入信号未决状态。)

#include <iostream>
#include <signal.h>
#include <unistd.h> // getpid

void Print(sigset_t &pending)
{
    for (int sig = 31; sig > 0; sig--)
    {
        if (sigismember(&pending, sig)) // 检测特定对应的信号是否在pending集中
        {
            std::cout << 1;
        }
        else
        {
            std::cout << 0;
        }
    }
    std::cout << std::endl;
}

int main()
{
    sigset_t newset, oldset;

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

    // 添加2号信号
    sigaddset(&newset, SIGINT);

    // 屏蔽信号集
    if (sigprocmask(SIG_BLOCK, &newset, &oldset) == -1)
    {
        perror("sigprocmask");
        return 1;
    }

    std::cout << "Signal No.2 successfully blocked!" << "\n"
              << "pid: " << getpid() << std::endl;


    // 挂起进程,等待信号到达
    while (true)
    {
        sleep(2);

        sigset_t pending;
        sigpending(&pending);
        Print(pending);
    }

    // 恢复原来的信号屏蔽字
    if (sigprocmask(SIG_SETMASK, &oldset, NULL) == -1)
    {
        perror("sigprocmask");
        return 1;
    }

    return 0;
}

四、补充:SIGCHLD信号

这里简单补充一下SIGCHLD信号:

我们都知道在子进程结束前,父进程要等待子进程的结束并进行资源回收,否则会产生僵尸进程,父进程可以阻塞等待子进程结束,也可以非阻塞地查询是否有子进程结束等待清理(也就是轮询的方式)。

采用第一种方式,父进程阻塞了就不能处理自己的工作了;

采用第二种方式,父进程在处理自己的工作的同时还要记得时不时地轮询一 下,程序实现复杂。

其实,子进程在终止时会给父进程发SIGCHLD信号,该信号的默认处理动作是忽略,父进程可以自定义SIGCHLD信号的处理函数,这样父进程只需专心处理自己的工作,不必关心子进程了,子进程终止时会通知父进程,父进程在信号处理函数中调用wait清理子进程即可

简单实现的话,代码形式如下:

​​​​​​​

执行图:

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

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

相关文章

2024.7.19 作业

1.链表的排序 int list_sort(NodePtr L) {if(NULLL || L->len<1){printf("排序失败");return -1;}int lenL->len1;NodePtr p;int i,j;for( i1;i<len;i){for( j0,pL;j<len-i;j,pp->next){if( p->data > p->next->data ){datatype tp-&…

基于51单片机的步进电机控制系统proteus仿真

地址&#xff1a;https://pan.baidu.com/s/1jFlIJ9I5qxjW8sYKd6vrBQ?pwd9d6q 提取码&#xff1a;1234 仿真图&#xff1a; 芯片/模块的特点&#xff1a; AT89C52/AT89C51简介&#xff1a; AT89C52/AT89C51是一款经典的8位单片机&#xff0c;是意法半导体&#xff08;STMic…

阿里开源的音频模型_原理与实操

英文名称: FunAudioLLM: Voice Understanding and Generation Foundation Models for Natural Interaction Between Humans and LLMs 中文名称: FunAudioLLM: 人与LLMs之间自然互动的语音理解和生成基础模型 论文地址: http://arxiv.org/abs/2407.04051v3 相关论文&#xff1a;…

1、springboot3 vue3开发平台-后端-项目构建

文章目录 1. 创建项目1.1 前置环境条件1.2 项目创建 2. 模块配置2.1 父工程配置概述2.2 配置启动模块2.3 父工程相关依赖管理 1. 创建项目 1.1 前置环境条件 idea2023, jdk17 1.2 项目创建 创建父工程并删除不需要的文件目录&#xff1a; 右键父工程依次创建其他模块 最…

Java | Leetcode Java题解之第260题只出现一次的数字III

题目&#xff1a; 题解&#xff1a; class Solution {public int[] singleNumber(int[] nums) {int xorsum 0;for (int num : nums) {xorsum ^ num;}// 防止溢出int lsb (xorsum Integer.MIN_VALUE ? xorsum : xorsum & (-xorsum));int type1 0, type2 0;for (int n…

vue2.0结合使用 el-scrollbar 和 v-for实现一个横向滚动的元素列表,并且能够自动滚动到指定元素(开箱即用)

效果图&#xff1a; 代码&#xff1a; <div class"gas-mode-item-body"><el-scrollbar style"width: 300px;height: 100%;" wrap-style"overflow-y:hidden" ref"scrollbarRef"><div style"display: flex&quo…

python-最小公倍数(PythonTip)

[题目描述] 编写一个程序&#xff0c;找出能被从1到给定数字n&#xff08;包括n&#xff09;的所有数字整除的最小正数(即最小公倍数)。 定义函数smallest_multiple()的函数&#xff0c;参数为n。 在函数内&#xff0c;返回能被从1到给定数字n&#xff08;包括n&#xff09;的…

珈和科技完成全国首个农险服务类数据产品入表,实现数据资产化

近日&#xff0c;珈和科技与东湖大数据合作&#xff0c;完成全国首个保险服务类数据产品入表&#xff0c;标志着我国商业卫星遥感应用领域迈出了数据资产化的关键一步。 此次入表的数据产品为“华北农业保险服务数据集数据产品”&#xff0c;是珈和科技融合卫星遥感与无人机等…

数据结构----栈

前言 Hello&#xff0c;小伙伴们&#xff0c;今天我们继续数据结构的学习&#xff0c;前面我们学习了顺序表和链表的实现&#xff0c;今天的栈知识也是和前面的知识相辅相成。 如果你喜欢我的内容的话&#xff0c;就请不要吝啬自己手中的三连哟&#xff0c;万分感谢&#xff…

# Redis 入门到精通(七)-- redis 删除策略

Redis 入门到精通&#xff08;七&#xff09;-- redis 删除策略 一、redis 删除策略–过期数据的概念 1、Redis 中的数据特征 Redis 是一种内存级数据库&#xff0c;所有数据均存放在内存中&#xff0c;内存中的数据可以通过TTL指令获取其状态。 XX &#xff1a;具有时效性…

Android Studio引入ndk编译的so库, 通过jni给Java程序使用

前言 工作要求将一个C老项目的函数用ndk打包成库给安卓同事的java程序调用。 这个任务我debuff拉满&#xff1a; 自己之前从来没接触过安卓开发&#xff0c;问了老板为什么不让安卓开发来干&#xff0c;老板说安卓开发不懂c&#xff0c;公司就我一个是懂c的。。。项目开发年…

【STM32嵌入式系统设计与开发---拓展】——1_10矩阵按键

这里写目录标题 1、矩阵按键2、代码片段分析 1、矩阵按键 通过将4x4矩阵按键的每一行依次设为低电平&#xff0c;同时保持其它行为高电平&#xff0c;然后读取所有列的电平状态&#xff0c;可以检测到哪个按键被按下。如果某列变为低电平&#xff0c;说明对应行和列的按键被按下…

day2 单机并发缓存

文章目录 1 sync.Mutex2 支持并发读写3 主体结构 Group3.1 回调 Getter3.2 Group 的定义3.3 Group 的 Get 方法 4 测试 本文代码地址&#xff1a; https://gitee.com/lymgoforIT/gee-cache/tree/master/day2-single-node 本文是7天用Go从零实现分布式缓存GeeCache的第二篇。 …

go 实现websocket以及详细设计流程过程,确保通俗易懂

websocket简介&#xff1a; WebSocket 是一种网络传输协议&#xff0c;可在单个 TCP 连接上进行全双工通信&#xff0c;位于 OSI 模型的应用层。WebSocket 协议在 2011 年由 IETF 标准化为 RFC 6455&#xff0c;后由 RFC 7936 补充规范。 WebSocket 使得客户端和服务器之间的数…

昇思学习打卡-21-生成式/Diffusion扩散模型

文章目录 Diffusion扩散模型介绍模型推理结果 Diffusion扩散模型介绍 关于扩散模型&#xff08;Diffusion Models&#xff09;有很多种理解&#xff0c;除了本文介绍的离散时间视角外&#xff0c;还有连续时间视角、概率分布转换视角、马尔可夫链视角、能量函数视角、数据增强…

《样式设计003:布局-自定义view模块》

描述&#xff1a;在开发小程序过程中&#xff0c;发现一些不错的案例&#xff0c;平时使用也比较多&#xff0c;稍微总结了下经验&#xff0c;以下内容可以直接复制使用&#xff0c;希望对大家有所帮助&#xff0c;废话不多说直接上干货&#xff01; 一、布局-自定义view模块 …

el-popover嵌套select弹窗点击实现自定义关闭

需求 el-popover弹窗内嵌套下拉选择框&#xff0c;点击el-popover弹出外部区域需关闭弹窗&#xff0c;点击查询、重置需关闭弹窗&#xff0c; 实现 根据需求要自定义弹窗的关闭和显示&#xff0c;首先想到的是visible属性&#xff0c;在实现过程中经过反复的测验&#xff0…

服务级别协议SLA与运营水平协议OLA

使用美团或饿了么在线订餐时&#xff0c;您将体验到即时的送餐提醒服务。首先&#xff0c;选择您想要的食品。系统会根据餐厅与您的位置、所选食品的种类&#xff0c;以及下单的具体时间&#xff0c;计算预计的等待时间和送餐费用&#xff0c;并将这些信息与您共享。这种信息的…

剖析SGI-STL二级空间配置器

概述 SGI-STL与C标准库提供的STL一样&#xff0c;都通过空间配置器allocator来申请或释放容器的空间。空间配置器的作用可以参考&#xff1a;浅谈C空间配置器allocator及其重要性 // C标准库的vector template < class T, class Alloc allocator<T> > class vec…

混淆专题一——简单AA,JJ,JSFuck混淆处理办法

以AA混淆为例 网址&#xff1a;Scrape | NBA 想要获取球员的信息&#xff0c;但找不到包。 刷新页面&#xff0c;main.js中找到混淆的代码&#xff0c;这串混淆代码就是球员信息。 如何处理&#xff1a; 复制下来&#xff0c;去除最后的笑脸 (_)&#xff0c;然后在控制台打…