进程间通信重要知识点

张开发
2026/4/18 6:56:52 15 分钟阅读

分享文章

进程间通信重要知识点
一、无名管道核心概念与基础用法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返回 02. 工程实践技巧循环读写处理大体积数据时使用循环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读端打开时无写端会立即成功写端打开时无读端会失败管道为空时立即返回 - 1errnoEAGAIN管道满时立即返回 - 1errnoEAGAIN异步通信需循环轮询读写八、有名管道 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.信号来源键盘如 CtrlCSIGINT、Ctrl\SIGQUIT硬件异常除零、非法内存访问内核 / 其他进程通过 kill、raise、alarm 发送软件事件定时器超时、子进程退出十二、常用信号必须记住信号名编号说明默认动作SIGINT2终端中断CtrlC终止进程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可以是SIG_IGN忽略SIG_DFL恢复默认自定义函数名捕获处理示例// 捕获 SIGINT执行自己的函数 signal(SIGINT, handler_func);2. kill () — 进程发信号int kill(pid_t pid, int signum);pid 0发给指定 PIDpid 0发给同组所有进程pid -1发给所有有权限发送的进程pid -1发给组 ID 为 -pid 的进程组3. raise () — 自己给自己发信号int raise(int signum); // 等价于 kill(getpid(), signum);4. alarm () — 定时发送 SIGALRMunsigned int alarm(unsigned int seconds);秒数后内核向当前进程发SIGALRM再次调用会覆盖旧定时器5. pause () — 挂起等待信号int pause(void);进程阻塞直到收到任意信号信号处理完后pause 返回 -1errnoEINTR十五、信号生命周期产生内核 / 进程发送信号递达Delivery信号到达进程并被处理未决Pending信号已产生但还没处理被阻塞时阻塞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);howSIG_BLOCK屏蔽 set 中的信号叠加SIG_UNBLOCK解除屏蔽SIG_SETMASK直接设置为 set3. sigpending () — 获取未决信号集int sigpending(sigset_t *set);十七、信号处理的重要特性信号会打断阻塞的系统调用如 read、wait、pause 等被信号打断后返回 -1errnoEINTR。信号处理函数要保证可重入不能调用不可重入函数malloc、printf、非线程安全的全局变量等。普通信号不排队同一信号多次触发只记录一次不会排队。实时信号34 以上支持排队考试 / 面试一般只考标准信号1~31。十八、典型面试问答信号能不能传递大量数据不能。信号只传事件不传数据。要传数据用管道、消息队列、共享内存。哪些信号不能捕获和忽略SIGKILL(9)、SIGSTOP(19)。子进程退出时父进程收到什么信号SIGCHLD默认忽略。向没有读端的管道写数据会产生什么信号SIGPIPE默认终止进程。

更多文章