Linux 进程信号初识

news/2024/11/16 13:17:42/

目录

0.前言

1.什么是信号

1.1生活中的信号

1.2 OS中的信号

2.认识信号

2.1信号概念

2.2查看信号

2.3 signal函数

2.4代码示例

3. 信号处理方式

3.1 忽略信号

3.2 默认处理

3.3 自定义处理

4.小结


(图像由AI生成) 

0.前言

在之前的学习中,我们介绍了 Linux 进程间通信 的多种方式,如管道、消息队列、共享内存等。接下来,我们将关注另一种进程间的重要机制:信号。信号是 Linux 系统中一种重要的异步通信手段,用于通知进程发生了某些事件,比如用户按下 Ctrl+C 或操作系统的某些异常情况。在本文中,我们将从基础知识出发,一步步认识信号的作用和处理方式。

1.什么是信号

1.1生活中的信号

生活中,我们常常通过“信号”获取信息并采取行动。例如:

外卖通知
你通过手机订了一份外卖。以下是外卖到来过程中的几个场景:

  1. 下单后等待外卖送达:你知道外卖会来,但不知道具体什么时候会送到。此时,你继续忙自己的事情,比如工作、学习,甚至小憩。
  2. 接到送餐员的通知:手机收到送餐员的短信或电话通知时,你立刻知道外卖到了。但如果你正在开会,可能会让送餐员稍等几分钟。
  3. 处理外卖
    • 你亲自下楼取外卖,这相当于采取了默认动作。
    • 你让同事帮忙取外卖,类似于自定义动作。
    • 外卖到后,你暂时不处理,继续工作,这等于忽略了外卖。

整个过程中,“外卖通知”就是一种“信号”。

  • 它是异步的:你不知道通知会在什么时候发来。
  • 它需要处理:当收到通知时,你决定如何响应。
  • 它可能被忽略:你可以选择不处理这个信号(比如延后取外卖)。

这与操作系统中的信号非常相似,信号是用于通知进程发生了某种事件,而处理与否、如何处理由程序决定。

1.2 OS中的信号

信号在操作系统中的概念
在操作系统中,信号是用来通知进程发生特定事件的一种机制。例如:

  • 用户按下 Ctrl+C 中断程序。
  • 某些硬件或软件事件(如定时器超时、文件访问异常)触发信号。

举例:死循环程序与 Ctrl+C 中断

让我们用一个简单的死循环程序来演示信号的基本作用:

#include <stdio.h>
#include <unistd.h>int main() {while (1) {printf("Running... Press Ctrl+C to stop.\n");sleep(1);}return 0;
}
  1. 运行程序:执行该程序后,它会不断输出 Running... 并每隔一秒更新一次。
  2. 按下 Ctrl+C:此时,操作系统会向程序发送一个 SIGINT 信号(中断信号)。
  3. 响应信号:程序收到信号后,会执行默认的信号处理动作:终止程序。

以下是一次完整的信号交互过程:

  • 信号发送:按下 Ctrl+C,操作系统检测到键盘事件并发送 SIGINT 信号给程序。
  • 信号处理:程序执行默认动作,终止运行。
  • 异步性:信号的到来是非确定的,程序无法预测用户何时按下 Ctrl+C

总结:

  • 信号在操作系统中是一种异步事件通知机制。
  • 它类似于生活中的通知机制,可以在某个事件发生时通知进程并交由其处理。

2.认识信号

2.1信号概念

信号是操作系统中的一种进程间通信机制,用于通知进程发生了某些事件。
它的核心特性包括:

  1. 异步性:信号的发送和接收是异步的,进程无法预知信号何时到达。
  2. 触发性:信号通常与特定事件相关联,如用户输入、进程状态变化、硬件异常等。
  3. 有限性:信号的传递只携带类型信息(如 SIGINTSIGKILL),而不包括详细的数据。

信号的生命周期:

  1. 发送信号:由内核或进程触发。
  2. 等待处理:信号到达目标进程时,可能暂时被挂起。
  3. 信号处理:进程可以选择处理信号、忽略信号或采取默认操作。

信号类似于生活中的通知,例如系统提示电量不足(异步)、提醒需要充电(触发),但没有告知具体电量值(有限性)。

2.2查看信号

Linux 系统提供了多个标准信号,可以通过 kill -l 命令查看完整列表。

信号分类:

  1. 通用信号(1-31):常用于进程的基本控制,例如中断、终止、挂起等。

    • SIGHUP:挂起信号。
    • SIGINT:中断信号(如按下 Ctrl+C)。
    • SIGKILL:强制终止信号。
    • SIGTERM:终止信号,可被捕获和处理。
    • SIGSEGV:非法内存访问。
  2. 实时信号(34-64):支持更高的灵活性和用户定义,用于实时任务通信。

    • SIGRTMIN:最小的实时信号。
    • SIGRTMAX:最大的实时信号。

示例命令:

kill -l

输出如下:

 

2.3 signal函数

函数简介

signal 是一个 ANSI C 标准函数,用于设置进程对特定信号的处理方式。通过该函数,可以定义信号处理程序,或选择默认行为、忽略信号。

函数原型

#include <signal.h>typedef void (*sighandler_t)(int);sighandler_t signal(int signum, sighandler_t handler);
  • 参数:

    • signum:信号编号,例如 SIGINT(中断信号)。
    • handler:指定的信号处理方式,可以是以下三种之一:
      • SIG_IGN:忽略信号。
      • SIG_DFL:采用信号的默认处理方式。
      • 自定义处理函数的地址。
  • 返回值:

    • 成功时返回之前的信号处理方式(SIG_IGNSIG_DFL 或处理函数地址)。
    • 失败时返回 SIG_ERR,同时设置 errno 以说明错误原因。

函数行为

  • 当进程接收到信号 signum 时,signal 函数会决定如何处理:
    • 忽略信号SIG_IGN):直接跳过信号处理。
    • 默认处理SIG_DFL):执行信号的系统默认行为(如终止进程)。
    • 自定义函数:调用程序员定义的函数处理信号。
  • 特殊信号:SIGKILLSIGSTOP 无法被捕获或忽略。

注意事项

  • 不同 UNIX 系统对 signal 的实现存在差异,Linux 的行为在历史版本中也有变化。
  • 推荐使用 sigaction 替代 signal,因为它提供了更强的功能和一致性。

2.4代码示例

以下代码演示了如何使用 signal 函数捕获 SIGINT 信号,并自定义处理方式:

#include <stdio.h>
#include <signal.h>
#include <unistd.h>// 自定义信号处理函数
void handle_sigint(int signum) {printf("Caught SIGINT (signal number: %d). Exiting gracefully...\n", signum);// 释放资源并退出_exit(0);
}int main() {// 使用 signal 函数捕获 SIGINT 信号if (signal(SIGINT, handle_sigint) == SIG_ERR) {perror("Error setting signal handler");return 1;}printf("Press Ctrl+C to trigger SIGINT...\n");// 无限循环,等待信号while (1) {printf("Running...\n");sleep(1);}return 0;
}

代码分析

  1. 自定义信号处理函数

    • handle_sigint 是一个函数,接收信号编号作为参数。
    • SIGINT 信号到达时,程序会调用该函数,输出提示信息并优雅退出。
  2. 设置信号处理

    • signal(SIGINT, handle_sigint) 指定 SIGINT 信号的处理方式为调用 handle_sigint 函数。
    • 如果 signal 调用失败,返回 SIG_ERR 并设置 errno,在此处打印错误信息并退出程序。
  3. 等待信号

    • 程序进入无限循环,每秒打印一次 Running...,等待用户按下 Ctrl+C 触发 SIGINT 信号。

运行结果

  • 正常运行时,程序会不断输出 Running...
  • 当按下 Ctrl+C 时,程序捕获到 SIGINT 信号,调用 handle_sigint 函数,输出提示并退出。

3. 信号处理方式

在 Linux 系统中,当进程接收到信号时,可以采用三种不同的方式处理信号:忽略默认处理自定义处理。进程对信号的处理方式决定了程序如何响应特定事件或异常。

3.1 忽略信号

进程可以选择忽略某些信号,这意味着即使信号被发送到进程,它也会被直接丢弃,进程的行为不会受到任何影响。

  • 常用场景

    • 忽略某些不重要的信号,如 SIGPIPE(管道断裂信号),避免进程因无关紧要的错误而终止。
    • 对用户输入的某些信号(如 SIGINT)做出特定策略,暂时忽略信号而不中断当前任务。
  • 代码示例

#include <signal.h>
#include <stdio.h>
#include <unistd.h>int main() {// 忽略 SIGINT 信号signal(SIGINT, SIG_IGN);printf("SIGINT signal is ignored. Try pressing Ctrl+C.\n");while (1) {printf("Running...\n");sleep(1);}return 0;
}

运行此程序后,按下 Ctrl+C 时进程不会终止,因为 SIGINT 信号被忽略了。

3.2 默认处理

系统为每种信号定义了默认的处理方式,进程可以直接使用这些默认动作:

  • 常见默认行为

    • 终止进程:SIGTERMSIGKILL
    • 生成核心转储文件并终止进程:SIGSEGVSIGABRT
    • 停止进程:SIGSTOP
  • 特点

    • 简单快捷。
    • 适用于无需自定义处理逻辑的场景。
  • 代码示例

#include <signal.h>
#include <stdio.h>
#include <unistd.h>int main() {printf("Default behavior for SIGINT. Press Ctrl+C to terminate.\n");while (1) {printf("Running...\n");sleep(1);}return 0;
}

运行此程序时,按下 Ctrl+C 会发送 SIGINT 信号,进程将采用默认行为:立即终止。

3.3 自定义处理

通过定义信号处理函数,程序可以指定如何响应某些信号。这种方式为程序提供了灵活性,可以在信号到达时执行特定逻辑。

  • 常见用途

    • 释放资源(文件句柄、内存)并优雅退出。
    • 记录日志以便诊断问题。
    • 延迟或改变信号的处理逻辑。
  • 代码示例

#include <signal.h>
#include <stdio.h>
#include <unistd.h>// 自定义信号处理函数
void custom_handler(int signum) {printf("Caught signal %d. Cleaning up before exiting...\n", signum);// 释放资源// 退出程序_exit(0);
}int main() {// 捕获 SIGINT 信号并设置自定义处理函数signal(SIGINT, custom_handler);printf("Custom handler for SIGINT. Press Ctrl+C.\n");while (1) {printf("Running...\n");sleep(1);}return 0;
}

运行此程序后,按下 Ctrl+C 时会触发自定义的信号处理逻辑,打印提示信息并优雅退出。

4.小结

信号是 Linux 系统中重要的进程间通信机制,具有异步性和灵活性。在本篇博客中,我们从生活中的类比入手,深入了解了信号的概念、种类及其处理方式,包括忽略信号、使用默认处理、以及自定义处理函数。通过实际代码示例,我们掌握了如何使用 signal 函数捕获并处理信号,为开发更高效、更健壮的 Linux 应用程序奠定了基础。


http://www.ppmy.cn/news/1547457.html

相关文章

SHELL脚本编写基础(2)永久环境变量和字符串显位

声明&#xff01; 学习视频来自B站up主 **泷羽sec** 有兴趣的师傅可以关注一下&#xff0c;如涉及侵权马上删除文章&#xff0c;笔记只是方便各位师傅的学习和探讨&#xff0c;文章所提到的网站以及内容&#xff0c;只做学习交流&#xff0c;其他均与本人以及泷羽sec团队无关&a…

M4 lotus 源码编译安装

查看系统版本 sw_vers ProductName: macOS ProductVersion: 15.1安装依赖 xcode-select -p /Library/Developer/CommandLineToolsbrew install go jq pkg-config hwloc coreutilscurl --proto https --tlsv1.2 -sSf https://sh.rustup.rs | sh设置环境变量 export LIBRARY…

直接映射缓存配置

对于一个直接映射&#xff08;Direct-Mapped&#xff09;缓存&#xff0c;其缓存总大小为16字节&#xff0c;而每条cache line的大小为4字节&#xff0c;可以理解为以下几个方面&#xff1a; 1. 缓存结构 缓存大小&#xff08;Cache Size&#xff09;&#xff1a;整个缓存空间…

Ceph 中Crush 算法的理解

Crush&#xff08;Controlled Replication Under Scalable Hashing&#xff09;算法是一种可扩展的、分布式的副本数据放置算法&#xff0c;广泛用于存储系统中&#xff0c;特别是Ceph分布式存储系统中。以下是对CRUSH算法的详细解释&#xff1a; 一、算法原理 CRUSH算法根据…

【LeetCode】每日一题 2024_11_15 最少翻转次数使二进制矩阵回文 I(模拟、矩阵遍历(竖着遍历))

前言 每天和你一起刷 LeetCode 每日一题~ 决定在前言里面加上新内容&#xff01;新增模块&#xff1a;“本期看点” 本期看点&#xff1a;如何竖着遍历矩阵&#xff1f; LeetCode 启动&#xff01; 题目&#xff1a;最少翻转次数使二进制矩阵回文 I 代码与解题思路 先读题…

生成自签名证书并配置 HTTPS 使用自签名证书

生成自签名证书 1. 运行 OpenSSL 命令生成证书和私钥 在终端中输入以下命令&#xff0c;生成自签名证书和私钥文件&#xff1a; sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout self_signed.key -out self_signed.pem-x509&#xff1a;生成自签名证书。…

DB-GPT系列(五):DB-GPT六大基础应用场景part2

前面文章《DB-GPT系列&#xff08;四&#xff09;&#xff1a;DB-GPT六大基础应用场景part1》讲了DB-GPT六大基础应用场景中的基础问答、知识库问答、Chat Excel功能&#xff0c;这篇文章继续介绍剩下的3个基础应用场景&#xff1a;Chat DB、Chat Data、Chat Dashboard。 一、…

C++ Primer Plus第三章笔记《数据处理》

这里的笔记区别于精简基础&#xff0c;会记录较多C的细节 文章目录 前言一、简单变量1.1 变量名以下是一些有效和无效的C名称 1.2 整型1.3 整型short、int、long和long long数据类型对应的字节和能存储的最大值 1.4 无符号类型越界问题 1.5 整型字面值进制转换 1.6 char类型:字…