【Linux探索学习】第二十八弹——信号(下):信号在内核中的处理及信号捕捉详解

Linux学习笔记:

https://blog.csdn.net/2301_80220607/category_12805278.html?spm=1001.2014.3001.5482

前言:

在前面我们已经学习了有关信号的一些基本的知识点,包括:信号的概念、信号产生和信号处理等,今天我们重点来讲解一下信号在内核中的处理以及信号捕捉的相关知识点

在这篇文章中,我们将深入探讨 Linux 信号在内核中的处理流程,详细讲解信号递达、信号阻塞、未决信号、信号集操作、信号捕捉等内容,并通过大量的代码示例和实际场景来展示信号如何在 Linux 中运作。

与信号有关的还有一个很重要的知识点是有关用户态、内核态和状态切换的知识,本篇没有进行讲解,需要自己再去了解一下

目录

1. 信号在内核中的处理流程

1.1 信号在内核中的表示

1.2 信号的递达机制

信号递达的条件

信号递达过程

示例代码:信号递达

1.3 信号未决状态

信号未决队列的管理

示例代码:查看未决信号

1.4 信号集与 sigset_t

信号集的操作

示例代码:操作信号集

​编辑

1.5 信号屏蔽与 sigprocmask

示例代码:使用 sigprocmask() 阻塞和解除阻塞信号

1.6 获取未决信号:sigpending()

示例代码:使用 sigpending() 查看未决信号

2. 信号捕捉与处理

2.1 使用 signal() 捕捉信号

示例代码:使用 signal() 捕捉信号

2.2 使用 sigaction() 捕捉信号

示例代码:使用 sigaction() 捕捉信号

3. 总结


1. 信号在内核中的处理流程

信号是由内核或其他进程通过系统调用发送给目标进程的。当进程正在执行时,信号能够在不干扰进程当前操作的情况下打断它的执行,触发某种特定的行为。信号的处理流程在 Linux 内核中被设计得非常灵活,既支持异步信号处理,又能通过进程的信号屏蔽机制来控制信号的递达。

1.1 信号在内核中的表示

信号在内核中的表示示意图:

  • 每个信号都有两个标志位分别表示阻塞(block)和未决(pending),还有一个函数指针表示处理动作。信号产生时,内核在进程控制块中设置该信号的未决标志,直到信号递达才清除该标志。在上图的例子中,SIGHUP信号未阻塞也未产生过,当它递达时执行默认处理动作。
  • SIGINT信号产生过,但正在被阻塞,所以暂时不能递达。虽然它的处理动作是忽略,但在没有解除阻塞之前不能忽略这个信号,因为进程仍有机会改变处理动作之后再解除阻塞。
  • SIGOUIT信号未产生过,一旦产生SIGOUIT信号将被阻塞,它的处理动作是用户自定义函数sighandler。如果在进程解除对某信号的阻塞之前这种信号产生过多次,将如何处理?POSIX1允许系统递送该信号一次或多次。Linux是这样实现的:常规信号在递达之前产生多次只计一次,而实时信号在递达之前产生多次可以依次放在一个队列里。本章不讨论实时信号。

1.2 信号的递达机制

信号递达是信号机制中的核心概念,它是信号从信号源发送到目标进程的过程。信号递达的实现依赖于内核的进程调度机制。在进程执行过程中,内核需要判断该进程是否有需要处理的未决信号,信号的递达会在进程的上下文切换时被触发。

信号递达的条件

信号的递达取决于以下几个因素:

  1. 信号是否被屏蔽:每个进程都可以选择性地阻塞某些信号,当信号被阻塞时,它们会进入未决状态,直到信号被解除阻塞。
  2. 进程的当前状态:信号递达的时机还受进程状态的影响。如果进程处于不可中断的状态(例如执行系统调用),它可能无法立即处理信号,这时信号会被推迟递达,直到进程能够响应信号为止。
  3. 信号类型:标准信号和实时信号在递达的优先级上可能存在差异。实时信号(编号从 SIGRTMIN 开始)通常会比标准信号更快地递达,并且能够提供更多的控制选项。
信号递达过程

信号的递达过程通常包括以下几个步骤:

  1. 信号的发送:信号可以通过内核发送(例如内核事件或系统调用)或通过其他进程调用 kill() 函数发送。
  2. 信号的处理检查:当一个进程正在被调度执行时,内核会检查该进程是否有未决的信号。如果存在未决信号,内核会查看进程的信号屏蔽字,以决定这些信号是否可以递达。
  3. 信号的递送:如果信号未被屏蔽且能够递达,内核会根据进程的信号处理方式来决定是执行默认动作还是调用信号处理函数。
示例代码:信号递达
#include <stdio.h>
#include <signal.h>
#include <unistd.h>

void signal_handler(int sig) {
    printf("Received signal: %d\n", sig);
}

int main() {
    signal(SIGINT, signal_handler);  // 捕捉 SIGINT 信号
    printf("Waiting for SIGINT...\n");
    while(1) {
        sleep(1);  // 进入等待状态,直到接收到信号
    }
    return 0;
}

在上面的代码中,进程会一直运行并等待 SIGINT 信号(通常由按下 Ctrl+C 触发)。一旦进程接收到 SIGINT 信号,内核会将其递送到进程,并触发信号处理函数 signal_handler

1.3 信号未决状态

当信号发送给进程时,如果该信号被进程的信号屏蔽字阻塞,那么该信号就会进入未决状态。未决信号是那些已经被发送但尚未被递达的信号。内核维护了每个进程的未决信号队列,并会在进程解除对该信号的阻塞时按顺序递送这些信号。

信号未决队列的管理

在 Linux 内核中,每个进程都有一个 task_struct 结构体,其中包含了当前进程的未决信号集合。每当一个信号发送给一个进程时,如果该信号被阻塞,内核不会立即递送它,而是将其存放在进程的未决信号队列中,直到进程解除对该信号的阻塞。

未决信号通常在进程解除信号屏蔽字后,由内核递送。递送顺序通常与信号发送顺序一致,且会按照优先级递送实时信号和标准信号。

示例代码:查看未决信号
#include <stdio.h>
#include <signal.h>
#include <unistd.h>

int main() {
    sigset_t pending;
    sigpending(&pending);  // 获取当前进程的未决信号
    
    if (sigismember(&pending, SIGINT)) {
        printf("SIGINT is pending.\n");
    } else {
        printf("No SIGINT pending.\n");
    }

    sleep(10);  // 稍作停顿,方便查看信号状态
    return 0;
}

通过使用 sigpending() 函数,我们可以查看当前进程的未决信号集。如果进程没有处理 SIGINT 信号,且信号被阻塞,则该信号会处于未决状态。

1.4 信号集与 sigset_t

信号集(sigset_t)是一个用于表示信号集合的数据结构,它通过位掩码的方式表示进程当前可以接受的信号集合。sigset_t 通常是一个整数或更大的数据类型,每一位对应一个信号。

信号集的操作

在 Linux 中,常用的信号集操作函数包括:

  • sigemptyset():初始化信号集为空集。
  • sigaddset():将某个信号添加到信号集中。
  • sigdelset():将某个信号从信号集中删除。
  • sigismember():判断某个信号是否在信号集中。
示例代码:操作信号集
#include <signal.h>
#include <stdio.h>

int main() {
    sigset_t set;
    sigemptyset(&set);         // 初始化为空集
    sigaddset(&set, SIGINT);   // 将 SIGINT 添加到信号集中
    sigaddset(&set, SIGTERM);  // 将 SIGTERM 添加到信号集中

    if (sigismember(&set, SIGINT)) {
        printf("SIGINT is in the set.\n");
    }

    sigdelset(&set, SIGINT);   // 从信号集中删除 SIGINT
    if (!sigismember(&set, SIGINT)) {
        printf("SIGINT is no longer in the set.\n");
    }

    return 0;
}

1.5 信号屏蔽与 sigprocmask

sigprocmask() 是一个用于修改进程信号屏蔽字的系统调用,它可以用来阻塞、解除阻塞或查询进程的信号屏蔽字。信号屏蔽字定义了哪些信号是被阻塞的,从而影响信号递达的时机。

sigprocmask() 具有以下操作模式:

  • SIG_BLOCK:将指定的信号添加到信号屏蔽字中,阻塞这些信号。
  • SIG_UNBLOCK:从信号屏蔽字中删除指定信号,解除阻塞。
  • SIG_SETMASK:将信号屏蔽字设置为指定值,替换当前的信号屏蔽字。
示例代码:使用 sigprocmask() 阻塞和解除阻塞信号
#include <signal.h>
#include <stdio.h>
#include<unistd.h>

int main() {
    sigset_t new_mask, old_mask;
    sigemptyset(&new_mask);
    sigaddset(&new_mask, SIGINT);  // 阻塞 SIGINT

    sigprocmask(SIG_BLOCK, &new_mask, &old_mask);  // 阻塞 SIGINT

    // 信号屏蔽后,可以进行一些操作
    printf("SIGINT is blocked. Press Ctrl+C to send SIGINT.\n");
    sleep(10);  // 暂停,等待 Ctrl+C 输入

    // 恢复信号屏蔽字
    sigprocmask(SIG_SETMASK, &old_mask, NULL);  // 恢复原信号屏蔽字
    printf("SIGINT is unblocked.\n");

    sleep(10);  // 等待信号递达
    return 0;
}

在这段代码中,SIGINT 信号在前 10 秒内被阻塞,用户按下 Ctrl+C 时信号不会立即递达。10 秒后,信号屏蔽被解除,SIGINT 信号会被递送并触发相应的处理。

1.6 获取未决信号:sigpending()

sigpending() 函数用于获取当前进程的未决信号,它返回一个信号集,表示该进程尚未处理的信号集合。sigpending() 的实现依赖于进程的信号队列,它可以用于调试和监控进程的信号处理状态。

示例代码:使用 sigpending() 查看未决信号
#include <signal.h>
#include <stdio.h>
#include <unistd.h>

int main() {
    sigset_t pending;
    sigemptyset(&pending);
    
    // 阻塞 SIGINT 信号
    sigset_t mask;
    sigemptyset(&mask);
    sigaddset(&mask, SIGINT);
    sigprocmask(SIG_BLOCK, &mask, NULL);

    // 发送 SIGINT 信号,但它会被阻塞
    kill(getpid(), SIGINT);

    // 获取未决信号
    sigpending(&pending);
    if (sigismember(&pending, SIGINT)) {
        printf("SIGINT is pending.\n");
    } else {
        printf("No SIGINT pending.\n");
    }

    sleep(5);  // 稍作等待,防止信号丢失
    return 0;
}

此程序模拟了阻塞 SIGINT 信号并通过 sigpending() 查看进程的未决信号状态。如果信号被阻塞,它将在信号屏蔽字解除后递达。


2. 信号捕捉与处理

信号捕捉是指进程通过自定义信号处理函数来响应特定的信号。Linux 提供了 signal()sigaction() 两种方式来捕捉信号。signal() 是一种简单的接口,而 sigaction() 提供了更为复杂的配置选项,使得开发者能够在处理信号时获得更多的控制权。

2.1 使用 signal() 捕捉信号

signal() 是最基础的信号捕捉方式,它允许开发者指定一个信号处理函数来响应特定信号。signal() 的使用非常简单,但它并不支持所有高级功能,如信号的重入处理或复杂的信号控制。

示例代码:使用 signal() 捕捉信号
#include <signal.h>
#include <stdio.h>
#include<unistd.h>

void signal_handler(int sig) {
    printf("Received signal: %d\n", sig);
}

int main() {
    signal(SIGINT, signal_handler);  // 捕捉 SIGINT 信号
    printf("Waiting for SIGINT...\n");
    while (1) {
        sleep(1);  // 程序将一直运行,直到接收到信号
    }
    return 0;
}

在这个示例中,signal_handler 函数会在接收到 SIGINT 信号时被调用。

2.2 使用 sigaction() 捕捉信号

sigaction() 提供了比 signal() 更灵活的方式来处理信号。它允许开发者在捕捉信号时设定更多的参数,比如如何处理重入信号、是否需要恢复默认行为等。

sigaction() 的结构体定义如下:

struct sigaction {
    void (*sa_handler)(int);
    sigset_t sa_mask;
    int sa_flags;
    void (*sa_restorer)(void);
};
  • sa_handler:指定信号处理函数。
  • sa_mask:指定在信号处理期间需要阻塞的信号集。
  • sa_flags:设定信号处理的行为。
示例代码:使用 sigaction() 捕捉信号
#include <signal.h>
#include <stdio.h>
#include<unistd.h>

void signal_handler(int sig) {
    printf("Received signal: %d\n", sig);
}

int main() {
    struct sigaction sa;
    sa.sa_handler = signal_handler;  // 指定信号处理函数
    sa.sa_flags = 0;
    sigemptyset(&sa.sa_mask);  // 初始化信号集

    sigaction(SIGINT, &sa, NULL);  // 捕捉 SIGINT 信号
    printf("Waiting for SIGINT...\n");
    
    while (1) {
        sleep(1);  // 程序将一直运行,直到接收到信号
    }
    return 0;
}

通过 sigaction(),程序能够灵活地处理信号,并控制信号捕捉的行为,甚至允许在处理信号时阻塞其他信号。

3. 总结

本文我们讲解了信号的处理机制,并且对信号捕捉进行了更详细的补充,结合上篇内容,基本上将信号部分的内容进行了大概的讲解,认真看一下相信会对你有所帮助

感谢各位大佬观看,创作不易,还望各位大佬点赞支持!!!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Redis】主从模式,哨兵,集群

主从复制 单点问题&#xff1a; 在分布式系统中&#xff0c;如果某个服务器程序&#xff0c;只有一个节点&#xff08;也就是一个物理服务器&#xff09;来部署这个服务器程序的话&#xff0c;那么可能会出现以下问题&#xff1a; 1.可用性问题&#xff1a;如果这个机器挂了…

1.PPT:天河二号介绍【12】

目录 NO1 NO2.3.4.5 NO6.7.8.9​ NO1 PPT&#xff1a;新建一个空白演示文档→保存到考生文件夹下&#xff1a;天河二号超级计算机.pptx幻灯片必须选择一种设计主题&#xff1a;设计→主题&#xff08;随便选中一种&#xff09;幻灯片的版式&#xff1a;开始→版式&#x…

基于python的体育新闻数据可视化及分析

项目 &#xff1a;北京冬奥会体育新闻数据可视化及分析 摘 要 随着社会的不断进步与发展&#xff0c;新时代下的网络媒体获取的信息也更加庞大和繁杂&#xff0c;相比于传统信息来源更加难以分析和辨别&#xff0c;造成了新时代媒体从业者撰写新闻的难度。在此背景下&#xff…

WPS计算机二级•幻灯片的配色、美化与动画

听说这是目录哦 配色基础颜色语言❤️使用配色方案&#x1fa77;更改PPT的颜色&#x1f9e1;PPT动画添加的原则&#x1f49b;PPT绘图工具&#x1f49a;自定义设置动画&#x1f499;使用动画刷复制动画效果&#x1fa75;制作文字打字机效果&#x1f49c;能量站&#x1f61a; 配色…

数据结构实战之线性表(三)

目录 1.顺序表释放 2.顺序表增加空间 3.合并顺序表 4.线性表之链表实现 1.项目结构以及初始代码 2.初始化链表(不带头结点) 3.链表尾部插入数据并显示 4.链表头部插入数据 5.初始化链表&#xff08;带头结点&#xff09; 6.带头结点的链表头部插入数据并显示 7.带头结…

SwiftUI 在 Xcode 预览修改视图 FetchedResults 对象的属性时为什么会崩溃?

概览 从 SwiftUI 诞生那天起&#xff0c;让秃头码农们又爱又恨的 Xcode 预览就在界面调试中扮演了及其重要的角色。不过&#xff0c;就是这位撸码中的绝对主角却尝尝在关键时刻“掉链子”。 比如当修改 SwiftUI 视图中 FetchedResults 对象的属性时&#xff0c;Xcode 预览可能…

【字节青训营-7】:初探 Kitex 字节微服务框架(使用ETCD进行服务注册与发现)

本文目录 一、Kitex概述二、第一个Kitex应用三、IDL四、服务注册与发现 一、Kitex概述 长话短说&#xff0c;就是字节跳动内部的 Golang 微服务 RPC 框架&#xff0c;具有高性能、强可扩展的特点&#xff0c;在字节内部已广泛使用。 如果对微服务性能有要求&#xff0c;又希望…

51单片机入门_05_LED闪烁(常用的延时方法:软件延时、定时器延时;while循环;unsigned char 可以表示的数字是0~255)

本篇介绍编程实现LED灯闪烁&#xff0c;需要学到一些新的C语言知识。由于单片机执行的速度是非常快的&#xff0c;如果不进行延时的话&#xff0c;人眼是无法识别(停留时间要大于20ms)出LED灯是否在闪烁所以需要学习如何实现软件延时。另外IO口与一个字节位的数据对应关系。 文…

JVM的GC详解

获取GC日志方式大抵有两种 第一种就是设定JVM参数在程序启动时查看&#xff0c;具体的命令参数为: -XX:PrintGCDetails # 打印GC日志 -XX:PrintGCTimeStamps # 打印每一次触发GC时发生的时间第二种则是在服务器上监控:使用jstat查看,如下所示&#xff0c;命令格式为jstat -gc…

基于python去除知乎图片水印

基于python去除知乎图片水印 背景&#xff1a;看到知乎技术文章里面的图片非常好&#xff0c;但是下载下来都是带有水印的&#xff0c;并不是不尊重别人的版权和劳动成果&#xff0c;纯粹的是洁癖&#xff0c;总感觉水印打上去很难受~~~ 实在想去掉水印&#xff0c;但是又不会P…

Android学习20 -- 手搓App2(Gradle)

1 前言 昨天写了一个完全手搓的&#xff1a;Android学习19 -- 手搓App-CSDN博客 后面谷歌说不要用aapt&#xff0c;d8这些来搞。其实不想弄Gradle的&#xff0c;不过想着既然开始了&#xff0c;就多看一些。之前写过一篇Gradle&#xff0c;不过是最简单的编译&#xff0c;不涉…

【愚公系列】《循序渐进Vue.js 3.x前端开发实践》051-案例:教务系统学生列表页面

标题详情作者简介愚公搬代码头衔华为云特约编辑&#xff0c;华为云云享专家&#xff0c;华为开发者专家&#xff0c;华为产品云测专家&#xff0c;CSDN博客专家&#xff0c;CSDN商业化专家&#xff0c;阿里云专家博主&#xff0c;阿里云签约作者&#xff0c;腾讯云优秀博主&…

毕业设计:基于深度学习的高压线周边障碍物自动识别与监测系统

目录 前言 课题背景和意义 实现技术思路 一、算法理论基础 1.1 卷积神经网络 1.2 目标检测算法 1.3 注意力机制 二、 数据集 2.1 数据采集 2.2 数据标注 三、实验及结果分析 3.1 实验环境搭建 3.2 模型训练 3.2 结果分析 最后 前言 &#x1f4c5;大四是整个大学…

2025.2.1——八、Web_php_wrong_nginx_config

题目来源&#xff1a;攻防世界 Web_php_wrong_nginx_config 目录 一、打开靶机&#xff0c;整理信息 二、解题思路 step 1&#xff1a;找找解题入口 step 2&#xff1a;抓包修改信息&#xff0c;得到配置文件 step 3&#xff1a;找到突破口&#xff0c;进行文件遍历 st…

Netty中用了哪些设计模式?

大家好&#xff0c;我是锋哥。今天分享关于【Netty中用了哪些设计模式&#xff1f;】面试题。希望对大家有帮助&#xff1b; Netty中用了哪些设计模式&#xff1f; 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 Netty 是一个基于 Java 的高性能网络应用框架&#…

【办公类-99-01】20250201学具PDF打印会缩小一圈——解决办法:换一个PDF阅读器

背景需求&#xff1a; 2024年1月13日&#xff0c;快要放寒假了&#xff0c;组长拿着我们班的打印好的一叠教案来调整。 “前面周计划下面的家园共育有调整&#xff0c;你自己看批注。” “还有你这个教案部分的模版有问题&#xff0c;太小&#xff08;窄&#xff09;了。考虑…

【BUUCTF杂项题】FLAG

一.FLAG 一张图片扔进随波逐流 发现RGB似乎隐藏压缩包 优先考虑RBG中的LSB隐写&#xff0c;用Stegsolve打开文件 1.查看每个颜色通道下的图片&#xff0c;未发现异常 2.LSB最低位提取&#xff0c;用Stegsolve的Data Extrace功能&#xff0c;确实存在一个压缩包&#xff0c;sa…

ros 创建Node

1、使用catkin_create_pkg创建一个软件包 catkin_create_pkg ssr_pkg roscpp rospy std_msgs 2、在软件包的src文件夹下创建一个节点的cpp源码文件 3、在CMakeLists.txt中设置节点源码的编译规则 4.编译运行 编译&#xff1a;shiftctrlB 运行&#xff1a; rosrun ssr_pkg …

给AI用工具的能力——Agent

ReAct框架&#xff1a; Reason Action&#xff0c;推理与行动结合 可以借助思维链&#xff0c;用小样本提示展示给模型一个ReAct框架 推理&#xff1a;针对问题或上一步观察的思考 行动&#xff1a;基于推理&#xff0c;与外部环境的一些交互&#xff08;调用外部工具&…

实验十 Servlet(一)

实验十 Servlet(一) 【实验目的】 1&#xff0e;了解Servlet运行原理 2&#xff0e;掌握Servlet实现方式 【实验内容】 1、参考课堂例子&#xff0c;客户端通过login.jsp发出登录请求&#xff0c;请求提交到loginServlet处理。如果用户名和密码相同则视为登录成功&#xff0c…