Linux | 探索 Linux 信号机制:信号的产生和自定义捕捉

news2024/12/25 9:08:15

在这里插入图片描述

信号是 Linux 操作系统中非常重要的进程控制机制,用来异步通知进程发生某种事件。理解信号的产生、阻塞、递达、捕捉等概念,可以帮助开发者更好地编写健壮的应用程序,避免由于未处理的信号导致程序异常退出。本文将带你从基础概念开始,深入探讨信号处理的常见方式。

1. 信号的基本概念

在 Linux 系统中,信号是一种软中断机制,主要用于进程之间的异步通信。信号的产生和递达并不会按照进程的执行顺序发生,而是通过操作系统将某种事件(如用户输入、硬件异常等)通知进程。每个信号都有唯一的编号和宏定义名称。例如,SIGINT(编号2)是一个常见的信号,通常由按下 Ctrl+C 产生。

信号的产生的四种方式:

  1. 终端输入产生信号:例如,当用户在终端中按下 Ctrl+C 时,系统会发送 SIGINT 信号给当前前台进程,进而终止进程。
  2. 系统调用产生信号:开发者可以通过调用 kill 函数向指定进程发送信号,例如 kill -SIGKILL <PID>
  3. 软件条件产生信号:某些软件事件会自动触发信号。例如,SIGPIPE 信号在管道破裂时触发。
  4. 硬件异常产生信号:例如,执行非法内存访问会触发 SIGSEGV 信号。

信号处理一般有三种方式:

  • 忽略信号:进程忽略该信号。
  • 执行默认动作:进程按照信号的默认处理方式处理,例如 SIGKILL 会导致进程直接退出。
  • 捕捉信号:进程通过自定义函数捕捉信号并进行处理。

2. 信号的自定义捕捉

Linux 提供了 signalsigaction 系统调用,允许开发者自定义信号的处理函数,即捕捉信号。

a. 自定义捕捉终端产生的 SIGINT 2号信号:

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

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

int main() {
    signal(SIGINT, handler); // 捕捉SIGINT信号
    while (1) {
        printf("Waiting for signal...\n");
        sleep(1);
    }
    return 0;
}
程序解释

handler 函数捕捉到 SIGINT 信号并打印信号编号。当用户在终端中按下 Ctrl+C,进程不会立即终止,而是调用自定义的 handler 函数。
在这里插入图片描述

b. 自定义捕捉由软件条件产生的 SIGALRM 信号

SIGALRM 信号通常由 alarm() 函数产生,用于在设定的时间后通知进程。

alarm() 函数与 SIGALRM 信号
alarm() 函数的作用是设置一个闹钟,指定经过若干秒后系统向进程发送 SIGALRM 信号。此信号的默认处理行为是终止进程,但我们可以通过自定义信号处理函数来捕捉并处理 SIGALRM 信号。
alarm() 函数的定义如下:

#include <unistd.h>
unsigned int alarm(unsigned int seconds);
  • 参数 seconds:指定在多少秒后发送 SIGALRM 信号。如果参数为 0,则取消先前设置的闹钟。
  • 返回值:返回先前设置的闹钟还剩余的时间。如果没有设置过闹钟,返回值为 0。例如,设定闹钟为 30 秒,闹钟执行了 20 秒后取消并重设为 15 秒,之前的闹钟还剩下 10 秒,这个时间会作为 alarm() 函数的返回值。
#include <stdio.h>
#include <signal.h>
#include <unistd.h>

// 自定义的SIGALRM信号处理函数
void handle_alarm(int sig) {
    printf("Alarm signal received: %d. Time's up!\n", sig);
}

int main() {
    // 注册SIGALRM信号的处理函数
    signal(SIGALRM, handle_alarm);

    // 设置闹钟为1秒
    alarm(1);

    // 计数器,在闹钟响起之前一直计数
    int count = 0;
    while (1) {
        printf("Counting: %d\n", count++);
        sleep(1); // 每秒计数一次
    }

    return 0;
}
程序解释
  1. 信号处理函数 handle_alarm
    SIGALRM 信号产生时,操作系统会调用此函数。此函数接收信号编号作为参数,并打印出提示信息。

  2. 设置闹钟
    程序通过调用 alarm(1) 设置了一个 1 秒的闹钟。即 1 秒后,系统会发送 SIGALRM 信号,触发信号处理函数 handle_alarm

  3. 计数器
    程序进入一个无限循环,每秒钟打印一次计数。在 1 秒内,计数器会输出几次,直到接收到 SIGALRM 信号并终止循环。

运行该程序时,输出:

Counting: 0
Alarm signal received: 14. Time's up!

程序开始计数,并且在 1 秒后接收到 SIGALRM 信号,调用信号处理函数,输出“Alarm signal received”信息,随后程序被信号终止。

拓展
  1. 重新设置闹钟:如果在 SIGALRM 之前再次调用 alarm(),会取消之前的闹钟并重新设置。例如,假如 alarm(5)alarm(1) 之后被调用,系统将在 5 秒而非 1 秒后发送 SIGALRM 信号。

  2. 取消闹钟alarm(0) 可以取消先前设置的闹钟,程序将不会再收到 SIGALRM 信号。

3. 信号阻塞与未决信号

信号的三种状态:阻塞、未决和递达

阻塞(Block):

阻塞是指进程可以暂时不处理某些信号。当信号被阻塞时,即使信号产生了,也不会立即处理。信号会保持在阻塞状态,直到解除阻塞。

未决(Pending):

未决是指信号已经产生,但由于被阻塞,无法递达。信号会处于未决状态,等待解除阻塞。当信号解除阻塞后,未决信号会递达给进程。

递达(Delivery):

递达是指信号从产生到被进程处理的过程。当信号未被阻塞或解除阻塞后,信号会递达给进程,触发默认处理动作或自定义的信号处理函数。
在这里插入图片描述

进程可以选择阻塞(Block)某个信号。被阻塞的信号在产生时会处于未决状态,直到进程解除对该信号的阻塞后,才会执行相关的处理动作。
注意:阻塞和忽略是不同的概念。阻塞信号意味着信号不会被递达,直到解除阻塞。而忽略则是在信号递达后选择不进行处理的一种方式。

C语言中的信号相关函数

sigset_t 是一个用于表示信号集的数据类型,每个信号用一个位来表示它的状态。这个类型可以用来存储和操作进程的信号屏蔽字(阻塞信号集),以及未决信号集。

  1. sigemptyset(sigset_t *set): 初始化一个信号集,将其中的所有信号位清零,即该信号集不包含任何信号。

  2. sigfillset(sigset_t *set): 初始化一个信号集,将其中的所有信号位设置为1,即该信号集包含所有信号。

  3. sigaddset(sigset_t *set, int signo): 在信号集中添加一个信号,使其对应的位被设置为1。signo 是要添加的信号的编号。

  4. sigdelset(sigset_t *set, int signo): 从信号集中删除一个信号,使其对应的位被设置为0。signo 是要删除的信号的编号。

  5. sigismember(const sigset_t *set, int signo): 检查信号集中的某个信号是否被设置为1。返回值为非零表示信号被设置(即有效),为0表示信号未被设置(即无效)。

  6. sigprocmask(int how, const sigset_t *set, sigset_t *oset): 读取或更改进程的信号屏蔽字(阻塞信号集)。

  7. sigpending(sigset_t *set): 读取当前进程的未决信号集。

使用 sigprocmask添加block阻塞集

在某些场景下,进程可能不希望立即处理某个信号,这时可以选择阻塞该信号。当信号被阻塞时,信号会进入未决状态,直到解除阻塞后才会递达。可以使用 sigprocmask 函数来设置信号的阻塞状态。

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

int main() {
    sigset_t set;
    sigemptyset(&set);
    sigaddset(&set, SIGINT);  // 阻塞SIGINT信号
    sigprocmask(SIG_BLOCK, &set, NULL);
    
    printf("SIGINT is blocked, press Ctrl+C...\n");
    sleep(10); // 期间按下Ctrl+C不会终止程序
    return 0;
}

SIGINT 信号被阻塞,按下 Ctrl+C 时,进程不会立刻退出,而是被阻塞,不能被递达,需要等到信号被解除阻塞后才能处理信号。

如果将所有信号都使用sigprocmask函数添加到block阻塞位图当中,是不是就能产生一个无法被退出的无敌进程?
答:SIGKILL (9号信号)和 SIGSTOP(19号信号)是特殊的,无法被阻塞、忽略或捕获。我们依然可以通过这些信号杀死进程,所以这样操作并不能使进程成为“无敌”的进程。(操作系统设计者早就想到了~~)

4. 常见信号的递达过程和处理方式

信号的递达过程
  1. 信号抵达的检查: 当系统从内核态返回用户态时,会首先检查当前进程的未决信号(pending signals)。这时,系统处于内核态,有权限检查进程的信号状态。

  2. 处理未决信号:

    • 如果发现有未决信号,并且该信号没有被阻塞,系统会决定如何处理这些信号。
    • 对于默认处理动作或忽略的信号,系统会执行默认动作或忽略信号,然后清除对应的未决标志位。
      在这里插入图片描述
  3. 指定信号动作:

    • 如果信号的处理动作是用户自定义的,系统会返回用户态,执行用户定义的处理函数。执行完自定义处理函数后,用户态的处理程序会通过 sigreturn 系统调用返回内核态,清除对应的未决标志位。如果没有新的信号要处理,系统会直接返回用户态,从主控制流程中上次被中断的地方继续执行。
      在这里插入图片描述
      在这里插入图片描述
  4. 为什么执行自定义函数是需要由内核态切换到内核态:

    • 尽管内核态具有高权限,但操作系统设计中不允许直接在内核态执行用户代码。原因是用户代码可能包含非法操作,如清空数据库等,这在用户态时权限不足,但在内核态时可能会造成严重后果。
    • 操作系统必须确保用户代码的合法性,以防止安全风险。因此,操作系统会严格控制用户代码的执行,确保系统安全和稳定。
信号的处理方式
信号编号宏定义名称默认动作说明
1SIGHUP终止进程终端挂起时发送此信号
2SIGINT终止进程用户按下 Ctrl+C
9SIGKILL终止进程(不可捕捉)直接终止进程
11SIGSEGV终止进程并生成 core段错误,非法内存访问
15SIGTERM终止进程请求进程终止

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

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

相关文章

基于SpringBoot+Vue的牙科就诊管理系统(带1w+文档)

基于SpringBootVue的牙科就诊管理系统(带1w文档) 基于SpringBootVue的牙科就诊管理系统(带1w文档) 伴随着互联网发展&#xff0c;现今信息类型愈来愈多&#xff0c;信息量也非常大&#xff0c;那也是信息时代的缩影。近些年&#xff0c;电子元器件信息科学合理发展的趋势变的越…

【React】React18.2.0核心源码解读

前言 本文使用 React18.2.0 的源码&#xff0c;如果想回退到某一版本执行git checkout tags/v18.2.0即可。如果打开源码发现js文件报ts类型错误请看本人另一篇文章&#xff1a;VsCode查看React源码全是类型报错如何解决。 阅读源码的过程&#xff1a; 下载源码 观察 package…

C# System.BadImageFormatException问题及解决

C# System.BadImageFormatException问题 出现System.BadImageFormatException 异常有两种情况&#xff1a;程序目标平台不一致&引用dll文件的系统平台不一致。 异常参考 BadImageFormatException 程序目标平台不一致&#xff1a; 项目>属性>生成&#xff1a;x86 …

学LabVIEW编程,看编程书有些看不懂怎么办?

自学LabVIEW编程时&#xff0c;如果发现编程书籍内容难以理解&#xff0c;可以尝试以下几种方式来提高学习效果&#xff1a; 1. 从基础入手&#xff0c;逐步深入&#xff1a; LabVIEW是一种基于图形化编程的工具&#xff0c;不同于传统的编程语言&#xff0c;因此从基础概念开…

linux 操作系统下cupsenable命令介绍和使用案例

linux 操作系统下cupsenable命令介绍和使用案例 cupsenable 命令是 Linux 操作系统中用于启用 CUPS&#xff08;通用打印服务&#xff09;打印机的命令。它允许用户将指定的打印机重新启用&#xff0c;从而使其可以接受新的打印作业 cupsenable 命令概述 基本语法 bash cup…

LEAN 赋型唯一性(Unique Typing)之 n-provability 注解

《LEAN 赋型唯一性&#xff08;Unique Typing&#xff09;之 证明过程简介》 中&#xff0c;梳理了赋型唯一性&#xff08;Unique Typing&#xff09;牵涉的概念及相关推论与证明&#xff0c;此篇文章就先介绍 n-provability 的概念&#xff0c;记 ⊢ₙ 。其围绕的是赋型规则&a…

PHP创意无限一键生成小程序名片生成系统源码

创意无限&#xff0c;一键生成 —— 小程序名片生成系统&#xff0c;开启你的个性化商务新时代&#xff01; 一、告别千篇一律&#xff0c;拥抱个性化名片 你还在使用那些千篇一律的传统纸质名片吗&#xff1f;是时候做出改变了&#xff01;现在有了“创意无限一键生成小程序名…

Cisco Catalyst 9000 Series Switches, IOS XE Release 17.15.1 ED

Cisco Catalyst 9000 Series Switches, IOS XE Release 17.15.1 ED 思科 Catalyst 9000 交换产品系列 IOS XE 系统软件 请访问原文链接&#xff1a;https://sysin.org/blog/cisco-catalyst-9000/&#xff0c;查看最新版。原创作品&#xff0c;转载请保留出处。 作者主页&…

如何计算光伏在安装过程中的损耗程度?

光伏系统在实际安装和运营过程中&#xff0c;会受到多种因素的影响&#xff0c;导致电能损耗。这些损耗包括线缆损耗、逆变器效率、组件品质、灰尘积累、入射角损失等。 一、光伏系统损耗的分类 光伏系统的损耗大致可以分为以下几类&#xff1a; 1、线缆损耗&#xff1a;光伏…

响应式网站和自适应网站有什么区别?

响应式网站和自适应网站在技术基础、用户体验以及开发成本等方面存在显著差异。具体分析如下&#xff1a; 响应式网站和自适应网站有什么区别? 技术基础 响应式网站&#xff1a;通过CSS3的媒体查询&#xff08;Media Query&#xff09;来检测设备屏幕尺寸&#xff0c;并加载…

全网最适合入门的面向对象编程教程:49 Python函数方法与接口-函数与方法的区别和lamda匿名函数

合集 - Python面向对象编程(51) 1.可能是全网最适合入门的面向对象编程教程&#xff1a;Python实现-嵌入式爱好者必看&#xff01;06-232.全网最适合入门的面向对象编程教程&#xff1a;00 面向对象设计方法导论06-243.全网最适合入门的面向对象编程教程&#xff1a;01 面向对…

make 程序规定的 makefile 文件的书写语法(5)

&#xff08;40&#xff09;接着学习自动变量&#xff0c;就是 make 程序执行时&#xff0c;自动定义和产生的变量&#xff0c;来描述 makefile 文件&#xff0c;可以直接拿来用&#xff1a; 补充 D 与 F 的使用&#xff0c;前者只获得目录&#xff0c;后者只获得文件名&#x…

【C++算法】滑动窗口

长度最小的子数组 题目链接&#xff1a; 209. 长度最小的子数组 - 力扣&#xff08;LeetCode&#xff09;https://leetcode.cn/problems/minimum-size-subarray-sum/description/ 算法原理 代码步骤&#xff1a; 设置left0&#xff0c;right0设置sum0&#xff0c;len0遍历l…

深度学习-13-小语言模型之SmolLM的使用

文章附录 1 SmolLM概述1.1 SmolLM简介1.2 下载模型2 运行2.1 在CPU/GPU/多 GPU上运行模型2.2 使用torch.bfloat162.3 通过位和字节的量化版本3 应用示例4 问题及解决4.1 attention_mask和pad_token_id报错4.2 max_new_tokens=205 参考附录1 SmolLM概述 1.1 SmolLM简介 SmolLM…

六西格玛咨询:石油机械制造企业的成本控制与优化专家

一、石油机械制造行业现状及主要困扰 随着全球能源需求的日益增长&#xff0c;石油开采和生产设备需求不断增加&#xff0c;石油机械制造行业在过去数十年里得到了迅猛发展。然而&#xff0c;石油机械制造作为一个高度复杂且技术密集的行业&#xff0c;也面临着多重挑战。首先…

路由策略原理与配置

&#x1f423;个人主页 可惜已不在 &#x1f424;这篇在这个专栏 华为_可惜已不在的博客-CSDN博客 &#x1f425;有用的话就留下一个三连吧&#x1f63c; 目录 一. 原理概述 二. 实验目的 实验内容 实验拓扑 实验配置 三. 实验结果 一. 原理概述 路由策略Route-P…

STM32中的计时与延时

前言 在裸机开发中,延时作为一种规定循环周期的方式经常被使用,其中尤以HAL库官方提供的HAL_Delay为甚。刚入门的小白可能会觉得既然有官方提供的延时函数,而且精度也还挺好,为什么不用呢?实际上HAL_Delay中有不少坑,而这些也只是HAL库中无数坑的其中一些。想从坑里跳出来…

刻意练习:舒尔特方格提升专注力

1.功能描述 刻意练习&#xff1a;舒尔特方格提升专注力 如果发现自己存在不够专注的问题&#xff0c;可以通过一个小游戏来提升自己专注力--舒尔特方格。 舒尔特方格的实施步骤如下&#xff1a; 一张纸上画出5X5的空方格。在方格中&#xff0c;没有任何规律的随机填写数字1…

[数据集][目标检测]葡萄成熟度检测数据集VOC+YOLO格式1123张3类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;1123 标注数量(xml文件个数)&#xff1a;1123 标注数量(txt文件个数)&#xff1a;1123 标注…

C++——多态的原理

多态的原理 多态的原理引入虚函数表 多态的原理 引入 如下代码的输出结果为&#xff08;&#xff09; A.编译报错 B.运行报错 C.8 D.12 上⾯题⽬运⾏结果12bytes&#xff0c;除了_b和_ch成员&#xff0c;还多⼀个__vfptr放对象的前⾯(注意有些平台可能会放到对象的最后⾯&am…