Linux系统:C语言进程间通信信号(Signal)

1. 引言:从"中断"到"信号"

想象一下,你正在书房专心致志地写代码,这时厨房的水烧开了,鸣笛声大作。你会怎么做?你会暂停(Interrupt) 手头的工作,跑去厨房关掉烧水壶,然后再回来继续 coding。

在Linux系统中,信号(Signal) 就是一种类似的异步中断机制。它允许一个进程(或内核)向另一个进程发送一个简单的消息,通知其某个特定事件的发生。接收信号的进程通常会暂停当前正在执行的指令流,转而去执行一个特殊的信号处理函数,处理完毕后(如果没退出)再回来继续执行。这就是信号最基本的概念。

本文将深入探讨信号的产生、处理以及如何利用它来构建一个简单的音乐播放器控制器。

2. 进程间通信(IPC)与信号概述

进程是操作系统资源分配和独立运行的基本单位。每个进程都拥有自己独立的地址空间,一个进程无法直接访问另一个进程的数据。因此,进程之间需要一种机制来进行通信(Communication) 与同步(Synchronization),这就是进程间通信(IPC, Inter-Process Communication)

常见的IPC方式包括:

  • 信号(Signal): 本文焦点,一种异步的、简单的通知机制。

  • 管道(Pipe) / 命名管道(FIFO): 单向或双向的字节流通信。

  • 套接字(Socket): 功能最强大,可用于网络通信和不同主机间的进程通信。

  • IPC对象: 包括共享内存信号量集消息队列,源自System V IPC标准。

信号是其中最轻量、最古老的一种方式。它携带的信息量很小,通常只是一个信号编号,但其响应非常迅速。

3. 信号的深度解析

3.1 信号列表与分类

在Linux系统中,可以使用 kill -l 命令查看所有支持的信号。

$ kill -l1) SIGHUP       2) SIGINT       3) SIGQUIT      4) SIGILL       5) SIGTRAP6) SIGABRT      7) SIGBUS       8) SIGFPE       9) SIGKILL     10) SIGUSR1
11) SIGSEGV     12) SIGUSR2     13) SIGPIPE     14) SIGALRM     15) SIGTERM
...

信号可分为两大类:

  • 不可靠信号(1 ~ 31): 源于UNIX早期版本,也称为非实时信号。它们可能会丢失。如果同一个不可靠信号在短时间内多次产生,进程可能只能接收到一次。因为内核可能使用位图来记录它们的发生,多次相同的信号在处理之前会被合并为一次。

  • 可靠信号(34 ~ 64): 在POSIX.1标准中定义,也称为实时信号。它们支持排队,只要信号发送的速度不超过系统队列的上限,信号就不会丢失。

3.2 信号的产生方式

信号的产生源头多种多样:

  1. 用户终端

    • Ctrl + C -> 产生 SIGINT (Interrupt) 信号,通常用于终止前台进程。

    • Ctrl + \ -> 产生 SIGQUIT (Quit) 信号,不仅终止进程,还会生成core dump文件。

    • Ctrl + Z -> 产生 SIGTSTP (Terminal Stop) 信号,暂停前台进程。

  2. 系统命令

    • kill -SIGNO PID: 向指定PID的进程发送信号。kill -9 1234 是强制杀死进程1234的经典命令。

  3. 硬件异常

    • 进程执行了非法操作,如访问非法内存(段错误) -> 内核会向其发送 SIGSEGV

    • 执行了错误的算术运算(如除以0) -> 内核会向其发送 SIGFPE

  4. 软件事件

    • 子进程退出时,内核会向其父进程发送 SIGCHLD

    • 由 alarm 或 setitimer 设置的定时器超时后,会发送 SIGALRM

3.3 核心API函数详解

3.3.1 kill() - 发送信号
#include <sys/types.h>
#include <signal.h>int kill(pid_t pid, int sig);
  • 功能: 向指定进程(或进程组)发送一个信号。

  • 参数

    • pid > 0: 目标进程的PID。

    • pid == 0: 发送给与调用进程同进程组的所有进程。

    • pid == -1: 发送给所有有权限发送的进程(除init进程外)。

    • sig: 要发送的信号编号,如 SIGINTSIGKILL

  • 返回值: 成功返回0,失败返回-1并设置errno。

3.3.2 raise() - 给自己发信号
#include <signal.h>int raise(int sig);

功能: kill(getpid(), sig) 的简化版,向当前进程自身发送信号。

  • 参数: sig - 信号编号。

3.3.3 alarm() - 设置闹钟
#include <unistd.h>unsigned int alarm(unsigned int seconds);
  • 功能: 设置一个定时器(闹钟),在 seconds 秒后,内核会向当前进程发送 SIGALRM 信号。该信号的默认动作是终止进程。

  • 特点: 重置性。如果一个进程之前调用过 alarm() 且闹钟还未超时,再次调用会重置闹钟,新的 seconds 值会覆盖旧值。

  • 返回值: 返回上一次设置的闹钟的剩余秒数,如果之前没有闹钟则返回0。

示例

#include <stdio.h>
#include <unistd.h>int main() {printf("First alarm set for 5 seconds.\n");unsigned int ret = alarm(5); // ret = 0sleep(2); // Sleep for 2 secondsprintf("Resetting alarm for 3 seconds from now.\n");ret = alarm(3); // ret = 5 - 2 = 3 (seconds left from previous alarm)printf("Previous alarm had %u seconds left.\n", ret);sleep(10); // Sleep longer than the alarmprintf("This line will not be printed because SIGALRM terminated the process.\n");return 0;
}
3.3.4 signal() - 信号处理
#include <signal.h>typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
  • 功能: 修改进程对特定信号 signum 的处理方式。

  • 参数

    • signum: 要捕获的信号编号。

    • handler

      • SIG_IGN: 忽略此信号。

      • SIG_DFL: 恢复对此信号的默认处理。

      • 函数指针: 程序员自定义的信号处理函数地址。该函数必须具有 void func(int sig_num) 的格式。

  • 返回值: 成功时返回上一次的信号处理函数指针,失败返回 SIG_ERR

捕获处理示例

#include <stdio.h>
#include <signal.h>
#include <unistd.h>// 自定义信号处理函数
void my_handler(int sig_num) {printf("\nCaught signal %d! I'm not going to die!\n", sig_num);// 注意:在信号处理函数中使用printf等标准IO函数可能是不安全的,这里仅作演示
}int main() {// 捕获SIGINT信号 (Ctrl+C)if (signal(SIGINT, my_handler) == SIG_ERR) {perror("Signal setup failed");return 1;}printf("Process PID: %d. Try pressing Ctrl+C...\n", getpid());while(1) {pause(); // 无限期休眠,等待任何信号到来}return 0;
}

3.4 重要补充知识

3.4.1 waitpid() 与进程退出状态

waitpid 不仅可以等待子进程结束,还能获取其详细的退出信息。

#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *wstatus, int options);
wstatus 是一个输出参数,由内核填充状态信息。需要使用一系列宏来解析:WIFEXITED(wstatus): 如果子进程正常终止(通过 exit 或 return),则返回真。WEXITSTATUS(wstatus): 如果 WIFEXITED 为真,此宏提取子进程的退出码(exit 的参数)。WIFSIGNALED(wstatus): 如果子进程是被信号杀死的,则返回真。WTERMSIG(wstatus): 如果 WIFSIGNALED 为真,此宏提取导致子进程终止的信号编号。WIFSTOPPED(wstatus) / WSTOPSIG(wstatus): 用于检查暂停的信号。

示例

pid_t pid = fork();
if (pid == 0) {// Child process// ... maybe do something that causes a segfaultexit(10);
} else {int wstatus;waitpid(pid, &wstatus, 0);if (WIFEXITED(wstatus)) {printf("Child exited normally with code: %d\n", WEXITSTATUS(wstatus));} else if (WIFSIGNALED(wstatus)) {printf("Child was killed by signal: %d\n", WTERMSIG(wstatus));}
}
3.4.2 atexit() - 注册退出清理函数
#include <stdlib.h>
int atexit(void (*function)(void));
  • 功能: 注册一个函数,当进程通过 exit() 函数正常退出时,该注册函数会被自动调用。

  • 特点: 可以注册多个函数,它们的执行顺序与注册顺序相反(LIFO,后进先出)。

  • 注意: 如果进程是被信号杀死的,这些函数不会被执行。

示例

#include <stdio.h>
#include <stdlib.h>void cleanup1() { printf("Performing cleanup 1...\n"); }
void cleanup2() { printf("Performing cleanup 2...\n"); }int main() {atexit(cleanup1);atexit(cleanup2); // This will be called firstprintf("Main function is running...\n");// exit(0); // atexit functions will be called// If we use _exit(0) or are killed by a signal, cleanup won't happen.return 0; // return calls exit implicitly
}
// Output:
// Main function is running...
// Performing cleanup 2...
// Performing cleanup 1...

4. 实战任务:音乐播放器控制器

现在,我们综合运用 forkexecwaitpidsignal 等知识,实现一个简单的后台音乐播放器控制器。

4.1 需求分析

父进程作为控制器,负责:

  1. 显示菜单:1:上一首 2:下一首 3:暂停 4:继续 0:退出

  2. 接收用户输入,根据输入向子进程(播放器)发送不同的控制信号。

  3. 优雅地处理子进程的退出。

子进程负责:

  1. 使用 execlp 调用 mpg123 程序来播放音乐。

  2. 根据父进程发来的信号做出反应(播放、暂停、切歌)。

4.2 核心设计思路与流程图

父进程通过 fork + exec 创建子进程来播放音乐。父进程通过信号 (SIGINTSIGSTOPSIGCONT 等) 来控制子进程的状态(暂停、继续、终止)。同时,父进程需要捕获 SIGCHLD 信号,以便在子进程意外结束时(比如一首歌放完了)能及时知晓并可能播放下一首。

图表

代码

4.3 代码实现框架

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
#include <glob.h> // For finding music filespid_t player_pid = -1;
int current_song_index = 0;
int song_count = 0;
char **song_list = NULL;// 自定义SIGCHLD处理函数
void child_handler(int sig) {int wstatus;pid_t pid;// 非阻塞地等待所有结束的子进程while ((pid = waitpid(-1, &wstatus, WNOHANG)) > 0) {if (pid == player_pid) {printf("Music player process (PID: %d) ended.\n", player_pid);player_pid = -1;// 如果不是父进程主动杀的(比如歌曲放完了),则播下一首if (WIFEXITED(wstatus) || WIFSIGNALED(wstatus)) {// 简单策略:一首歌放完就播下一首current_song_index = (current_song_index + 1) % song_count;printf("Moving to next song: %d\n", current_song_index);}}}
}// 退出清理函数
void cleanup() {system("stty echo");    // 恢复终端回显printf("\033[?25h");    // 显示光标if (player_pid > 0) {kill(player_pid, SIGKILL); // 确保子进程被杀死}// 释放song_list内存...
}// 启动播放器子进程
void start_player() {if (player_pid > 0) {kill(player_pid, SIGINT); // 先杀死之前的播放进程// wait for it to die... (handled by SIGCHLD)sleep(1);}player_pid = fork();if (player_pid == 0) {// Child process: become the music playerexeclp("mpg123", "mpg123", "-q", song_list[current_song_index], NULL);perror("execlp failed");exit(1);} else if (player_pid < 0) {perror("fork failed");}
}int main() {// 1. 查找音乐文件 (e.g., *.mp3)glob_t glob_result;glob("*.mp3", GLOB_TILDE, NULL, &glob_result);song_count = glob_result.gl_pathc;song_list = glob_result.gl_pathv;if (song_count == 0) {printf("No MP3 files found!\n");exit(1);}// 2. 设置信号处理和清理函数signal(SIGCHLD, child_handler);atexit(cleanup);// 3. 启动第一首歌start_player();// 4. 主控制循环int choice;while (1) {printf("\n1:Prev | 2:Next | 3:Pause | 4:Resume | 0:Exit\n");scanf("%d", &choice);switch (choice) {case 0: // Exitif (player_pid > 0) {kill(player_pid, SIGKILL);}return 0;case 1: // Previouscurrent_song_index = (current_song_index - 1 + song_count) % song_count;start_player();break;case 2: // Nextcurrent_song_index = (current_song_index + 1) % song_count;start_player();break;case 3: // Pauseif (player_pid > 0) kill(player_pid, SIGSTOP);break;case 4: // Resumeif (player_pid > 0) kill(player_pid, SIGCONT);break;default:printf("Invalid choice.\n");}}return 0;
}

编译与运行

gcc music_player.c -o music_player
./music_player

(确保系统已安装 mpg123sudo apt-get install mpg123)

5. 注意事项

5.1 信号处理的安全问题

信号处理函数是在异步环境中执行的,这意味着它可能在主程序执行的任何点被调用。因此,在信号处理函数中调用诸如 printfmalloc 等非异步信号安全(async-signal-safe)的函数是不安全的。POSIX.1 标准定义了一个异步信号安全的函数列表,详见 man 7 signal-safety。在信号处理函数中,应尽量只做简单的标志设置,或者使用 write 函数向标准输出写入简单消息。

5.2 更现代的信号处理接口:sigaction

虽然 signal() 函数简单易用,但它在不同Unix版本中的行为可能略有差异(可移植性问题)。更现代、更强大的替代者是 sigaction() 函数,它提供了对信号处理更精确的控制,例如:

  • 指定在处理信号时是否自动阻塞其他信号。

  • 获取信号被触发时的各种上下文信息。

  • 避免信号处理函数执行后被重置为默认行为(某些系统下signal()会有此问题)。

建议在新代码中使用 sigaction

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

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

相关文章

LoRa 网关组网方案(二)

LoRa 网关组网方案 现有需求&#xff1a;网关每6秒接收不同节点的数据&#xff0c;使用SX1262芯片。 以下是完整的组网方案&#xff1a;1. 网络架构设计 采用星型拓扑&#xff1a; 网关&#xff1a;作为中心节点&#xff0c;持续监听多个信道节点&#xff1a;分布在网关周围&am…

服装外贸系统软件怎么用才高效防风险?

服装外贸系统软件概述 服装外贸系统软件&#xff0c;如“艾格文ERP”&#xff0c;是现代外贸企业不可或缺的管理工具。它整合了订单处理、库存管理、客户资源保护、财务控制等多功能模块&#xff0c;旨在全面提升业务运营效率。通过系统化的管理方式&#xff0c;艾格文ERP能够从…

【沉浸式解决问题】peewee.ImproperlyConfigured: MySQL driver not installed!

目录一、问题描述二、原因分析三、解决方案✅ 推荐&#xff1a;安装 pymysql&#xff08;纯 Python&#xff0c;跨平台&#xff0c;安装简单&#xff09;✅ 可选&#xff1a;安装 mysqlclient&#xff08;更快&#xff0c;但需要本地编译环境&#xff09;✅ 总结四、mysql-conn…

C++进阶-----C++11

作者前言 &#x1f382; ✨✨✨✨✨✨&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f382; ​&#x1f382; 作者介绍&#xff1a; &#x1f382;&#x1f382; &#x1f382; &#x1f389;&#x1f389;&#x1f389…

(论文速读)航空轴承剩余寿命预测:多生成器GAN与CBAM融合的创新方法

论文题目&#xff1a;Remaining Useful Life Prediction Approach for Aviation Bearings Based on Multigenerator Generative Adversarial Network and CBAM&#xff08;基于多发生器生成对抗网络和CBAM的航空轴承剩余使用寿命预测方法&#xff09;期刊&#xff1a;IEEE TRAN…

3ds Max 流体模拟终极指南:从创建到渲染,打造真实液体效果

流体模拟是提升 3D 场景真实感的重要技术之一。无论是模拟飞瀑流泉、杯中溢出的饮料&#xff0c;还是黏稠的蜂蜜或熔岩&#xff0c;熟练掌握流体动力学无疑能为你的作品增色不少。本文将以 3ds Max 为例&#xff0c;系统讲解流体模拟的创建流程与渲染方法&#xff0c;帮助你实现…

《算法导论》第 35 章-近似算法

大家好&#xff01;今天我们深入拆解《算法导论》第 35 章 ——近似算法。对于 NP 难问题&#xff08;如旅行商、集合覆盖&#xff09;&#xff0c;精确算法在大规模数据下往往 “力不从心”&#xff0c;而近似算法能在多项式时间内给出 “足够好” 的解&#xff08;有严格的近…

系统架构设计师-操作系统-避免死锁最小资源数原理模拟题

写在前面&#xff1a;银行家算法的核心目标是确保系统始终处于“安全状态”。一、5个进程各需2个资源&#xff0c;至少多少资源避免死锁&#xff1f; 解题思路 根据死锁避免的资源分配公式&#xff0c;不发生死锁的最少资源数为&#xff1a; 最少资源数k(n−1)1 \text{最少资源…

Preprocessing Model in MPC 2 - 背景、基础原语和Beaver三元组

参考论文&#xff1a;SoK: Multiparty Computation in the Preprocessing Model MPC (Secure Multi-Party Computation) 博士生入门资料。抄袭必究。 本系列教程将逐字解读参考论文(以下简称MPCiPPM)&#xff0c;在此过程中&#xff0c;将论文中涵盖的40篇参考文献进行梳理与讲…

ACCESS/SQL SERVER保存软件版本号为整数类型,转成字符串

在 Access 中&#xff0c;若已将版本号&#xff08;如1.3.15&#xff09;转换为整数形式&#xff08;如10315&#xff0c;即1*10000 3*100 15&#xff09;&#xff0c;可以通过 SQL 的数学运算反向解析出原始版本号格式&#xff08;主版本.次版本.修订号&#xff09;。实现思…

编程语言学习

精通 Java、Scala、Python、Go、Rust、JavaScript ✅ 1. Java 面向对象编程&#xff08;OOP&#xff09;、异常处理、泛型JVM 原理、内存模型&#xff08;JMM&#xff09;、垃圾回收&#xff08;GC&#xff09;多线程与并发&#xff08;java.util.concurrent&#xff09;Java 8…

软件测试:如何利用Burp Suite进行高效WEB安全测试

Burp Suite 被广泛视为 Web 应用安全测试领域的行业标准工具集。要发挥其最大效能&#xff0c;远非简单启动扫描即可&#xff0c;而是依赖于测试者对其模块化功能的深入理解、有机组合及策略性运用。一次高效的测试流程&#xff0c;始于精细的环境配置与清晰的测试逻辑。测试初…

华为认证 HCIA/HCIP/HCIE 全面解析(2025 版)

说实话&#xff0c;想在IT行业站稳脚跟&#xff0c;没有过硬的技术和资历&#xff0c;光凭热情和一腔干劲根本不行。 而华为认证&#xff0c;作为业内公认的“技术护照”&#xff0c;已经成了许多人打开职场大门的关键。 你会发现&#xff0c;越来越多的企业在招聘时&#xff0…

ComfyUI-3D-Pack:3D创作的AI神器

一、应用介绍 单图转3D网格&#xff1a;输入一张角色图&#xff0c;能输出基本成型的3D Mesh&#xff0c;还自带UV展开和贴图输出&#xff0c;可直接导入到Blender等软件中使用。多视角图像生成&#xff1a;可以基于算法生成围绕3D模型的多视角图像&#xff0c;用于3D模型展示…

【java面试day15】mysql-聚簇索引

文章目录问题&#x1f4ac; Question 1&#x1f4ac; Question 2相关知识问题 &#x1f4ac; Question 1 Q&#xff1a;什么是聚簇索引&#xff0c;什么是非聚簇索引&#xff1f; A&#xff1a;聚簇索引主要是指数据与索引放到一块&#xff0c;B树的叶子节点保存了整行数据&a…

【typenum】 16 无符号整数标记

一、源码 这段代码是 Rust 中用于实现编译时无符号整数的核心部分。它定义了一个 Unsigned trait 并为两种类型实现了该 trait&#xff1a;UTerm&#xff08;表示零&#xff09;和 UInt<U, B>&#xff08;表示非零数字&#xff09;。 定义&#xff08;marker_traits.rs&a…

重温k8s基础概念知识系列四(服务、负载均衡和联网)

文章目录1、Kubernetes 网络模型2、为什么需要 Service&#xff1f;2.1、定义service2.2、Service的类型2.3、Service 工作原理2.4、Service 与 DNS3、Ingress&#xff08;高级流量管理&#xff09;3.1、定义Ingress 资源3.2、Ingress 规则4、常见面试高频问答5、总结1、Kubern…

基于SpringBoot的停车场管理系统【2026最新】

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、Vue项目源码、SSM项目源码、微信小程序源码 精品专栏&#xff1a;…

Nginx前后端分离反代(VUE+FastAPI)

原文链接&#xff1a;Nginx前后端分离反代&#xff08;VUEFastAPI&#xff09; < Ping通途说 0.前言 工作需求自己全栈开发了一个后台后端&#xff0c;要求前后端分离&#xff0c;即nginx静态代理前端文件&#xff0c;再代理后端接口。以前自己也遇过这种情况&#xff0c;但…

豆包1.5 Vision Lite 对比 GPT-5-min,谁更适合你?实测AI模型选型利器 | AIBase

“团队要上线一个智能客服系统&#xff0c;预算有限&#xff0c;中文场景为主&#xff0c;偶尔需要读图——该选豆包1.5还是GPT-5-min&#xff1f;” “个人开发者想接大模型API做写作助手&#xff0c;要求响应快、成本低&#xff0c;Claude Haiku、Moonshot、GPT-5-min 哪个更…