当前位置: 首页 > news >正文

进程间通信重要知识点

一、无名管道:核心概念与基础用法

1. 什么是无名管道?

无名管道(也叫匿名管道,Pipe)是 Linux 中最基础的进程间通信(IPC)方式,本质是内核中的一块内存缓冲区,类比成一条带两个端口的 “水管”:

  • 一端是读端(fd[0],只能读取数据
  • 一端是写端(fd[1],只能写入数据
  • 管道是半双工通信:同一时间只能单向传输数据(要么父写子读,要么子写父读)
  • 数据是字节流形式,写入的数据会被内核按顺序缓存,读取时按先进先出(FIFO)的顺序取出

2. 核心限制(新手必记)

  • 只能用于有亲缘关系的进程(父子进程、兄弟进程)通信,因为只有fork()后子进程能继承父进程的文件描述符,从而拿到管道的读写端
  • 管道是 “一次性” 的,数据被读取后就会从内核缓冲区中移除,无法重复读取
  • 管道的生命周期随进程:所有持有管道文件描述符的进程都关闭后,内核会自动释放管道资源,无需手动清理

3.基础 API 与流程

// 1. 创建管道 int pipe(int fd[2]); // 参数:存放读写端文件描述符的数组,成功返回0,失败返回-1 // fd[0] → 读端,fd[1] → 写端 // 2. 核心通信流程(父写子读为例) // 父进程:创建管道 → fork子进程 → 关闭读端(fd[0]) → write写数据 → 关闭写端(fd[1]) → wait回收子进程 // 子进程:继承管道fd → 关闭写端(fd[1]) → read读数据 → 关闭读端(fd[0]) → exit退出

二、无名管道:通信细节与阻塞特性

1. 读写端的关闭原则(关键!)

管道通信必须遵循 **“单方向单端口”** 原则,否则会出现阻塞或异常:

  • 读进程:必须关闭写端(fd[1],否则read可能永远阻塞
  • 写进程:必须关闭读端(fd[0],否则write会一直等待读端读取数据,甚至阻塞
  • 原理:管道的内核缓冲区会根据两端的文件描述符状态判断是否还有进程持有端口,从而决定读写的行为

2. 读写的阻塞 / 非阻塞特性

场景行为
管道为空,读进程读数据读进程阻塞,直到有数据写入管道
管道写端全部关闭,读进程读数据read立即返回 0,表示读到文件末尾(EOF)
管道写满,写进程写数据写进程阻塞,直到读进程读取数据释放缓冲区空间
管道读端全部关闭,写进程写数据写进程会收到SIGPIPE信号,默认直接退出进程

3. 进阶场景:双向通信

管道是半双工的,无法直接双向通信。如果需要父子进程互相发送数据,必须创建两个管道

  • 管道 A:父写子读(父进程写端、子进程读端)
  • 管道 B:子写父读(子进程写端、父进程读端)
  • 两个管道的文件描述符需要分别管理,避免混淆端口

三、无名管道:常见问题与工程实践

1. 常见错误与避坑指南

错误场景问题原因解决方案
read一直阻塞,不返回读进程没有关闭写端,内核认为还有进程可能写入数据读进程必须关闭写端,确保写端全部关闭后,read会读到 EOF 返回 0
写进程突然崩溃退出读端已经全部关闭,写进程写入时收到SIGPIPE信号写进程处理SIGPIPE信号,或提前判断读端状态
子进程变成僵尸进程父进程没有调用wait()/waitpid()回收子进程父进程必须等待子进程退出,或设置信号处理回收僵尸进程
数据丢失或读取不全单次write的数据超过管道的原子写大小(默认 4096 字节)分多次写入数据,或使用循环读取直到read返回 0

2. 工程实践技巧

  • 循环读写:处理大体积数据时,使用循环read直到返回 0,循环write直到数据全部写入
  • 错误处理:每次调用pipe/fork/read/write都必须判断返回值,并用perror()打印错误信息
  • 端口管理:通信完成后必须关闭所有管道文件描述符,避免文件描述符泄漏
  • 进程回收:父进程必须调用wait(NULL)或waitpid()回收子进程,防止产生僵尸进程

四、有名管道基础概念与本质

  • 定义:也叫命名管道、FIFO(先进先出),是一种特殊的文件类型(文件类型标记为p),存在于文件系统中,具有可见的路径名。
  • 本质:和无名管道底层一样是内核中的环形缓冲区,数据不存磁盘,仅在内存中传输;但通过文件系统的路径名实现了跨进程的可见性。
  • 核心作用:实现无亲缘关系进程间的半双工通信,任意有权限的进程都可通过路径名打开管道读写。

五、有名管道核心特点

  • 文件系统可见:拥有独立路径名,可通过ls -l查看,生命周期独立于创建进程(除非显式删除)。
  • 半双工通信:数据单向流动;如需双向通信,需创建两个独立的 FIFO 管道。
  • 先进先出(FIFO):数据按写入顺序读取,无随机访问,不支持lseek()定位。
  • 阻塞特性(默认):

以只读模式打开管道:阻塞直到有进程以写模式打开。

以只写模式打开管道:阻塞直到有进程以读模式打开。

管道为空时读操作阻塞,管道满时写操作阻塞。

  • 写入原子性:单次写入不超过 PIPE_BUF(通常 4096 字节)时,数据不会被其他写进程打断,保证数据完整性。
  • 权限控制:创建时可指定权限,支持chmod修改,只有有权限的进程才能打开。

六、有名管道创建与使用流程

1. 创建有名管道

命令行方式

mkfifo /tmp/my_pipe # 创建名为my_pipe的管道文件

C 语言函数方式

#include <sys/types.h> #include <sys/stat.h> // 函数原型:创建FIFO文件 int mkfifo(const char *pathname, mode_t mode); // 示例:创建管道,权限为0664(所有者可读写,组和其他可读) if (mkfifo("/tmp/my_pipe", 0664) == -1) { perror("mkfifo failed"); return 1; }

参数说明:pathname是管道路径,mode是文件权限(如 0664)。
注意:管道已存在时调用会失败,需提前用access()或stat()检查。

2. 打开管道

使用标准文件操作open(),可指定阻塞 / 非阻塞模式:

// 读进程:以只读模式打开(默认阻塞) int fd_r = open("/tmp/my_pipe", O_RDONLY); // 写进程:以只写模式打开(默认阻塞) int fd_w = open("/tmp/my_pipe", O_WRONLY); // 非阻塞模式:添加O_NONBLOCK标志 int fd_r_nonblock = open("/tmp/my_pipe", O_RDONLY | O_NONBLOCK);

3. 读写数据

和普通文件一样使用read()和write():

// 写进程示例:写入数据 char buf[] = "Hello, FIFO!"; write(fd_w, buf, sizeof(buf)); // 读进程示例:读取数据 char recv_buf[100]; read(fd_r, recv_buf, sizeof(recv_buf)); printf("收到数据:%s\n", recv_buf);

4. 关闭与删除

  • 通信结束后,用close()关闭文件描述符。
  • 管道文件不会自动删除,需手动调用unlink("/tmp/my_pipe")删除。

七、阻塞 / 非阻塞模式对比

模式打开行为读操作写操作适用场景
阻塞(默认)读 / 写端未打开时阻塞,直到另一端打开管道为空时阻塞管道满时阻塞简单同步通信,保证数据可靠传输
非阻塞(O_NONBLOCK)读端打开时无写端会立即成功;写端打开时无读端会失败管道为空时立即返回 - 1(errno=EAGAIN)管道满时立即返回 - 1(errno=EAGAIN)异步通信,需循环轮询读写

八、有名管道 vs 无名管道

特性有名管道(FIFO)无名管道(Pipe)
通信范围任意进程(无亲缘关系限制)仅父子 / 兄弟进程
文件系统有可见路径名,永久存在无实体文件,随进程销毁
半双工支持,可通过两个 FIFO 实现双向半双工,单向通信
打开方式open()打开路径名pipe()创建,继承文件描述符
适用场景不同程序间的进程通信同一程序内父子进程通信

九、常见问题与注意事项

  • 管道破裂(SIGPIPE 信号):写进程向读端已关闭的管道写数据时,内核会发送SIGPIPE信号,进程默认会终止。可通过signal()捕获该信号处理。
  • 多进程读写:多个写进程同时写入时,单次写入不超过 PIPE_BUF 的数据不会被打断,超过则可能出现数据交叉。
  • 非阻塞模式下的错误处理:非阻塞读 / 写失败时,需检查errno是否为EAGAIN,表示暂时无数据 / 缓冲区满,可稍后重试。
  • 权限问题:创建管道时的mode会受进程 umask 影响,实际权限为mode & ~umask,如需明确权限可手动设置。

十、简单代码示例

写进程(writer.c)

#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <fcntl.h> #include <sys/stat.h> #include <sys/types.h> #define FIFO_PATH "/tmp/test_fifo" int main() { // 1. 创建管道(不存在时创建,存在则忽略错误) if (mkfifo(FIFO_PATH, 0664) == -1 && errno != EEXIST) { perror("mkfifo failed"); exit(EXIT_FAILURE); } // 2. 以只写模式打开管道 int fd = open(FIFO_PATH, O_WRONLY); if (fd == -1) { perror("open failed"); exit(EXIT_FAILURE); } // 3. 写入数据 char msg[] = "Hello from writer!"; write(fd, msg, strlen(msg) + 1); // +1 包含'\0' close(fd); printf("数据已写入管道\n"); return 0; }

读进程(reader.c)

#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <errno.h> #define FIFO_PATH "/tmp/test_fifo" int main() { // 1. 以只读模式打开管道(阻塞等待写端打开) int fd = open(FIFO_PATH, O_RDONLY); if (fd == -1) { perror("open failed"); exit(EXIT_FAILURE); } // 2. 读取数据 char buf[100]; read(fd, buf, sizeof(buf)); printf("收到数据:%s\n", buf); close(fd); unlink(FIFO_PATH); // 删除管道文件 return 0; }

💡 补充:使用场景与优缺点
优点
实现简单,基于文件接口,和普通文件操作一致,学习成本低。
支持任意进程间通信,不受亲缘关系限制。
自带同步机制(阻塞模式),无需额外实现互斥锁。
缺点
半双工通信,双向通信需两个管道。
数据无持久化,进程退出或管道关闭后数据丢失。
不支持多进程复杂同步,大规模并发场景性能一般。

十一、信号基本概念

1.本质

内核发给进程的一个事件通知,进程收到后必须暂停当前执行,去处理该事件。

2.特点

  • 异步:进程不知道信号何时到来
  • 轻量级:不传递复杂数据,只传递 “事件编号”
  • 每个信号有固定编号和默认行为

3.信号来源

  • 键盘:如 Ctrl+C(SIGINT)、Ctrl+\(SIGQUIT)
  • 硬件异常:除零、非法内存访问
  • 内核 / 其他进程:通过 kill、raise、alarm 发送
  • 软件事件:定时器超时、子进程退出

十二、常用信号(必须记住)

信号名编号说明默认动作
SIGINT2终端中断Ctrl+C终止进程
SIGQUIT3终端退出Ctrl+\终止 + core 转储
SIGKILL9强制杀死进程(不可捕获、不可忽略立即终止
SIGALRM14闹钟信号(alarm()终止进程
SIGUSR110用户自定义信号 1终止进程
SIGUSR212用户自定义信号 2终止进程
SIGCHLD17子进程退出 / 暂停时发给父进程忽略
SIGPIPE13向无读端的管道写数据终止进程
SIGSTOP19暂停进程(不可捕获、不可忽略暂停进程

重点:9 号 SIGKILL、19 号 SIGSTOP 不能被捕获、忽略、阻塞。

十三、进程对信号的三种处理方式

  • 默认动作(Default)

终止、暂停、忽略、core dump 等。

  • 忽略(Ignore)

信号来了直接丢弃,不做处理。
注意:SIGKILL / SIGSTOP 不能忽略。

  • 捕获(Catch / 自定义处理)

进程注册一个信号处理函数,信号到来时自动调用该函数。

十四、信号相关核心函数

1. signal () — 注册信号处理函数

#include <signal.h> typedef void (*sighandler_t)(int); sighandler_t signal(int signum, sighandler_t handler);
  • handler可以是:
  1. SIG_IGN:忽略
  2. SIG_DFL:恢复默认
  3. 自定义函数名:捕获处理

示例:

// 捕获 SIGINT,执行自己的函数 signal(SIGINT, handler_func);

2. kill () — 进程发信号

int kill(pid_t pid, int signum);
  • pid > 0:发给指定 PID
  • pid = 0:发给同组所有进程
  • pid = -1:发给所有有权限发送的进程
  • pid < -1:发给组 ID 为 -pid 的进程组

3. raise () — 自己给自己发信号

int raise(int signum); // 等价于 kill(getpid(), signum);

4. alarm () — 定时发送 SIGALRM

unsigned int alarm(unsigned int seconds);
  • 秒数后内核向当前进程发SIGALRM
  • 再次调用会覆盖旧定时器

5. pause () — 挂起等待信号

int pause(void);
  • 进程阻塞,直到收到任意信号
  • 信号处理完后,pause 返回 -1,errno=EINTR

十五、信号生命周期

  1. 产生:内核 / 进程发送信号
  2. 递达(Delivery):信号到达进程并被处理
  3. 未决(Pending):信号已产生但还没处理(被阻塞时)
  4. 阻塞(Block / 信号屏蔽字):暂时不让信号递达,先存为未决

十六、信号集与阻塞(重点)

1. 信号集操作函数

int sigemptyset(sigset_t *set); // 清空 int sigfillset(sigset_t *set); // 全部置1 int sigaddset(sigset_t *set, int signo);// 添加信号 int sigdelset(sigset_t *set, int signo);// 删除信号 int sigismember(const sigset_t *set, int signo);// 判断是否在集合

2. sigprocmask () — 屏蔽 / 解除屏蔽信号

int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

how

  • SIG_BLOCK:屏蔽 set 中的信号(叠加)
  • SIG_UNBLOCK:解除屏蔽
  • SIG_SETMASK:直接设置为 set

3. sigpending () — 获取未决信号集

int sigpending(sigset_t *set);

十七、信号处理的重要特性

  • 信号会打断阻塞的系统调用如 read、wait、pause 等,被信号打断后返回 -1,errno=EINTR。
  • 信号处理函数要保证可重入不能调用不可重入函数:malloc、printf、非线程安全的全局变量等。
  • 普通信号不排队同一信号多次触发,只记录一次,不会排队。
  • 实时信号(34 以上)支持排队考试 / 面试一般只考标准信号(1~31)

十八、典型面试问答

  • 信号能不能传递大量数据?不能。信号只传事件,不传数据。要传数据用管道、消息队列、共享内存。
  • 哪些信号不能捕获和忽略?SIGKILL(9)、SIGSTOP(19)。
  • 子进程退出时父进程收到什么信号?SIGCHLD,默认忽略。
  • 向没有读端的管道写数据会产生什么信号?SIGPIPE,默认终止进程。
http://www.jsqmd.com/news/659580/

相关文章:

  • API安全攻防实战:40个真实世界漏洞模型与2026年防御全景
  • 避开这些坑,你的蓝桥杯C/C++就能多拿20分:从‘送分题’失分到稳定省二的复盘
  • LeetCode 选择排序 题解
  • StructBERT模型压力测试与性能调优指南
  • 队列进行迷宫求解
  • 静态资源缓存策略与 Cache-Control 指令深度指南
  • 零代码!用Nano-Banana产品拆解引擎为技术文档自动配图
  • **发散创新:基于Go语言的协同计算框架设计与实践**在现代分布式系统中,**协同计算(Coll
  • Zotero文献格式化插件:让杂乱文献库变得井井有条的智能管家
  • Z-Image-Turbo-辉夜巫女入门指南:专为辉夜主题设计的轻量级文生图LoRA模型解析
  • FreeCAD绘图尺寸标注插件深度解析:从工程图到专业图纸的终极指南
  • Unity3D粒子系统进阶:从属性解析到动态烟雾特效实战
  • 74LS138芯片的5种典型应用场景,Multisim仿真带你玩转数字电路设计
  • Less如何实现CSS响应式导航栏_利用嵌套与媒体查询实现
  • Lychee Rerank MM惊艳效果:手写体图片Query匹配印刷体政策文档高分案例
  • 我的移动代码实验室:C4droid + GCC插件实战入门,从安装到写出第一个图形程序
  • Qsign签名服务:企业级QQ机器人开发签名验证解决方案与架构深度解析
  • iPaaS平台如何助力企业?2026年最新平台创新应用盘点
  • gte-base-zh能力展示:一键为百条短文本生成向量,效果直观可见
  • 2026年,探寻专业AI培训公司的独特魅力与价值
  • 6 文件保存功能优化
  • Phi-4-mini-reasoning企业审计合规:推理日志留存与敏感信息过滤方案
  • SQL更新日期格式不统一_利用DATE_FORMAT函数批量修正
  • Pixel Couplet Gen应用场景:线下展会扫码生成专属像素春联互动装置
  • Open Claw AI 零代码构建企业 HTML5 网站实战指南:30分钟从部署到上线
  • 精读双模态视频融合论文系列十一|湖南大学原创 UAVD-Mamba 封神!可变形 Token+Mamba 跨模态融合碾压 !
  • 除了场景切换,Unity的淡入淡出还能这么玩?创意应用与性能优化小贴士
  • 【2026奇点智能技术大会权威解码】:AI测试代码生成的5大颠覆性突破与落地陷阱预警
  • 解读核心Maintainer观点|Presto 不只是版本升级!从查询引擎到湖仓执行层,AI Infra 新方向
  • S2-Pro命令行工具开发:使用Node.js构建模型管理CLI