news 2026/4/22 11:51:26

Linux进程间通信新姿势:用sigaction和sigqueue实现带数据的信号传递(C语言实战)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Linux进程间通信新姿势:用sigaction和sigqueue实现带数据的信号传递(C语言实战)

Linux进程间通信进阶:利用sigaction和sigqueue实现带数据的信号传递

在Linux系统编程中,信号(Signal)是最基础的进程间通信(IPC)机制之一。传统的信号处理方式通常只能传递简单的通知,而无法携带额外的数据信息。本文将深入探讨如何通过sigactionsigqueue这对黄金组合,实现带有自定义数据的信号传递,为进程间通信提供更强大的能力。

1. 信号通信的基础与局限

信号是Linux系统中用于通知进程异步事件发生的基本机制。传统的kill()函数和signal()函数虽然简单易用,但存在明显的局限性:

  • 只能传递信号编号,无法附带任何额外数据
  • 缺乏发送者信息的反馈机制
  • 信号处理函数的上下文信息有限
// 传统信号处理示例 void simple_handler(int sig) { printf("Received signal: %d\n", sig); } int main() { signal(SIGUSR1, simple_handler); pause(); // 等待信号 return 0; }

这种简单的信号处理方式在很多场景下已经足够,但当我们需要在进程间传递更多信息时(如任务ID、状态码或共享内存地址),就显得力不从心了。

2. 增强型信号处理:sigaction详解

sigactionsignal的增强版,提供了更精细的信号控制能力。其核心在于struct sigaction结构体,特别是当配合SA_SIGINFO标志使用时,可以获取丰富的信号上下文信息。

2.1 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:传统的信号处理函数,只能接收信号编号
  • sa_sigaction:增强型处理函数,可接收额外信息
  • sa_flags:控制信号行为的标志位,SA_SIGINFO是关键

2.2 使用SA_SIGINFO获取完整信号信息

设置SA_SIGINFO标志后,信号处理函数将收到三个参数:

  1. 信号编号
  2. siginfo_t结构体指针
  3. 上下文信息指针(通常可忽略)

siginfo_t结构体包含了丰富的信号相关信息:

typedef struct { int si_signo; // 信号编号 int si_code; // 信号代码 pid_t si_pid; // 发送进程PID uid_t si_uid; // 发送进程UID union sigval si_value; // 伴随数据 // ... 其他成员 } siginfo_t;

其中si_value是一个联合体,可以传递整型或指针数据:

union sigval { int sival_int; // 传递整型数据 void *sival_ptr; // 传递指针数据 };

3. 发送带数据的信号:sigqueue实战

sigqueuekill的增强版,专门用于发送带有附加数据的信号。其函数原型如下:

int sigqueue(pid_t pid, int sig, const union sigval value);

参数说明:

  • pid:目标进程ID
  • sig:要发送的信号
  • value:要传递的附加数据

3.1 完整示例:带数据的信号通信

下面我们通过一个完整示例演示如何实现带数据的信号传递:

发送端程序(sender.c)

#include <stdio.h> #include <stdlib.h> #include <signal.h> #include <unistd.h> int main(int argc, char *argv[]) { if (argc != 2) { fprintf(stderr, "Usage: %s <receiver_pid>\n", argv[0]); exit(EXIT_FAILURE); } pid_t receiver_pid = atoi(argv[1]); union sigval value; // 发送三个带不同数据的信号 for (int i = 0; i < 3; i++) { value.sival_int = 100 + i; // 发送递增的整数值 if (sigqueue(receiver_pid, SIGUSR1, value) == -1) { perror("sigqueue"); exit(EXIT_FAILURE); } printf("Sent signal with data: %d\n", value.sival_int); sleep(1); } return 0; }

接收端程序(receiver.c)

#include <stdio.h> #include <stdlib.h> #include <signal.h> #include <unistd.h> void enhanced_handler(int sig, siginfo_t *info, void *ucontext) { printf("Received signal %d from PID %d\n", sig, info->si_pid); printf("Accompanying data: %d\n", info->si_value.sival_int); // 可以访问更多信息 printf("Signal code: %d\n", info->si_code); printf("Sender UID: %d\n", info->si_uid); } int main() { struct sigaction sa; // 设置信号处理 sa.sa_sigaction = enhanced_handler; sigemptyset(&sa.sa_mask); sa.sa_flags = SA_SIGINFO; // 关键标志 if (sigaction(SIGUSR1, &sa, NULL) == -1) { perror("sigaction"); exit(EXIT_FAILURE); } printf("Receiver PID: %d\n", getpid()); printf("Waiting for signals...\n"); while (1) { pause(); // 等待信号 } return 0; }

3.2 编译与运行

  1. 首先编译接收端程序:

    gcc receiver.c -o receiver
  2. 运行接收端获取其PID:

    ./receiver Receiver PID: 12345 Waiting for signals...
  3. 在另一个终端编译并运行发送端程序:

    gcc sender.c -o sender ./sender 12345
  4. 观察接收端输出:

    Received signal 10 from PID 12346 Accompanying data: 100 Signal code: 0 Sender UID: 1000

4. 高级应用场景与技巧

4.1 传递复杂数据结构

除了简单的整型数据,我们还可以通过指针传递更复杂的数据结构。需要注意的是,指针必须在发送和接收进程间共享有效的地址空间(如共享内存)。

// 发送端 typedef struct { int id; char name[32]; } CustomData; CustomData data = {1, "Test"}; union sigval value; value.sival_ptr = &data; sigqueue(receiver_pid, SIGUSR1, value); // 接收端 void handler(int sig, siginfo_t *info, void *ucontext) { CustomData *data = (CustomData *)info->si_value.sival_ptr; printf("Received data: id=%d, name=%s\n",>// 使用实时信号 #define MY_RT_SIGNAL (SIGRTMIN + 5) // 发送端 sigqueue(receiver_pid, MY_RT_SIGNAL, value); // 接收端 sigaction(MY_RT_SIGNAL, &sa, NULL);

4.3 错误处理与边界情况

在实际应用中,需要考虑以下边界情况:

  1. 目标进程不存在sigqueue会返回-1,并设置errno为ESRCH
  2. 权限不足:发送进程需要有权限向目标进程发送信号
  3. 信号被阻塞:如果目标进程阻塞了该信号,信号将保持挂起状态
  4. 信号处理时间:长时间运行的信号处理函数可能影响程序响应性
// 健壮的sigqueue调用 if (sigqueue(pid, sig, value) == -1) { switch (errno) { case EINVAL: fprintf(stderr, "Invalid signal\n"); break; case EPERM: fprintf(stderr, "Permission denied\n"); break; case ESRCH: fprintf(stderr, "No such process\n"); break; default: perror("sigqueue"); } exit(EXIT_FAILURE); }

5. 性能考量与替代方案比较

5.1 性能特点

带数据的信号通信具有以下性能特点:

  • 低延迟:信号是异步的,接收方能立即得到通知
  • 低开销:相比管道或套接字,信号不需要建立连接
  • 数据量小:适合传递少量数据(整型或小指针)

5.2 与其他IPC机制对比

特性信号+数据管道消息队列共享内存
最大数据量1个整型/指针无限制系统限制系统限制
通信方向单向单向双向双向
是否需要同步
内核持久性进程生命周期系统生命周期进程生命周期
适合场景简单通知+小数据流式数据传输结构化消息大数据量共享

5.3 最佳实践建议

  1. 数据量控制:适合传递简单的状态码、ID或共享内存指针,不适合大数据传输
  2. 信号选择:对可靠性要求高的场景使用实时信号(SIGRTMIN-SIGRTMAX)
  3. 错误处理:始终检查sigqueuesigaction的返回值
  4. 线程安全:在多线程环境中,信号处理要特别小心,考虑使用pthread_sigmask
  5. 信号屏蔽:在处理关键信号时,适当使用sigprocmask防止重入
// 安全的信号处理设置示例 struct sigaction sa; sa.sa_sigaction = enhanced_handler; sigemptyset(&sa.sa_mask); sigaddset(&sa.sa_mask, SIGUSR1); // 在处理期间屏蔽同类信号 sa.sa_flags = SA_SIGINFO | SA_RESTART; // 自动重启被中断的系统调用 if (sigaction(SIGUSR1, &sa, NULL) == -1) { perror("sigaction"); exit(EXIT_FAILURE); }

在实际项目中,我经常使用这种技术来实现轻量级的进程间状态通知。特别是在需要将共享内存地址传递给工作进程的场景下,这种方法既高效又可靠。一个常见的陷阱是忘记设置SA_SIGINFO标志,导致无法接收到附加数据,因此建议在代码中添加明确的断言检查。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/22 11:48:43

Windows下用Fyne开发桌面应用,第一步搞定C编译器(MSYS2保姆级配置)

Windows下Fyne开发环境配置&#xff1a;MSYS2深度配置指南 在Windows平台上使用Fyne框架开发跨平台GUI应用时&#xff0c;C编译器的配置往往是第一个拦路虎。不同于Mac和Linux系统自带完善的开发工具链&#xff0c;Windows开发者需要额外处理MSYS2环境配置、Path变量设置等一系…

作者头像 李华
网站建设 2026/4/22 11:46:43

2026飞书AI先锋大赛收官,制造业借AI实现“智能化”跃迁

【导语&#xff1a;4月21日&#xff0c;“2026飞书AI先锋大赛先进制造专场”决赛在北京收官。十家制造业领军企业凭借AI解决方案斩获“制造AI先锋”荣誉&#xff0c;预示着中国制造业在AI Agent时代加速“智能化”跃迁。】AI Agent“入职”制造业&#xff0c;从PPT走向产线过去…

作者头像 李华
网站建设 2026/4/22 11:46:21

FigmaCN:让Figma界面瞬间变中文的终极解决方案

FigmaCN&#xff1a;让Figma界面瞬间变中文的终极解决方案 【免费下载链接】figmaCN 中文 Figma 插件&#xff0c;设计师人工翻译校验 项目地址: https://gitcode.com/gh_mirrors/fi/figmaCN 你是否曾因Figma的英文界面而频繁切换翻译工具&#xff1f;当面对"Frame…

作者头像 李华
网站建设 2026/4/22 11:44:30

告别WPF大泥球:用Prism框架(.NET Framework)重构遗留桌面应用的实战心得

重构WPF遗留系统的模块化实战&#xff1a;从"大泥球"到Prism架构的蜕变之路 当接手一个维护多年的WPF项目时&#xff0c;最令人头疼的莫过于面对那个被称为"大泥球"的代码库——各种业务逻辑与UI代码纠缠不清&#xff0c;新增功能如同在已经摇摇欲坠的积木…

作者头像 李华