管道写入时进程挂起问题排查:IPC程序异常分析
管道写入阻塞排查:缓冲区有剩余空间却挂起
我正在开发一个进程间通信(IPC)程序,遇到了一个奇怪的问题:管道明明还有剩余空间,但写入操作却阻塞了进程。
远程主机的管道缓冲区大小为8192字节,我通过以下代码确认:
#define _GNU_SOURCE #include <stdio.h> #include <unistd.h> #include <fcntl.h> int main() { int fd[2]; pipe(fd); printf("Pipe size: %d\n", fcntl(fd[1], F_GETPIPE_SZ)); close(fd[1]); close(fd[0]); return 0; }
程序逻辑说明
我创建了16个进程,每个进程对应一个独立管道。进程按树形结构组织:
- 根进程编号为0
- 子进程编号遵循规则:
2*k+1、2*k+2(k为父进程编号)
流程分为两步:
- 每个进程向自己的子进程管道写入512B数据,子进程读取这些消息
- 最后每个进程向所有管道各发送一条512B消息
按理论计算:
- 根进程的管道会被写入
16*512B=8192字节,刚好填满缓冲区 - 其他管道会被写入
(16+1)*512B=8704字节,但其中额外的512B消息会被读取,所以实际占用应该不会超过8192字节,不会触发阻塞
最小复现代码
以下是复现问题的最小示例(无实际业务功能,仅用于复现阻塞问题):
#include <stdlib.h> #include <sys/wait.h> #include <unistd.h> #include <stdio.h> #include <string.h> #include <sys/ioctl.h> #define NO_OF_PROCESSES 16 #define NO_OF_MESSAGES 1 #define ROOT 0 #define ERROR_CHECK(result) \ do { \ if ((result) == -1) { \ fprintf(stderr, "Error at line %d\n", __LINE__); \ exit(1); \ } \ } while (0) #define NOT_PARTIAL(result) \ do { \ if ((result) != 512) { \ fprintf(stderr, "Error at line %d\n", __LINE__); \ exit(1); \ } \ } while (0) void close_pipes(int fd[NO_OF_PROCESSES][2]) { for (int i = 0; i < NO_OF_PROCESSES; i++) { ERROR_CHECK(close(fd[i][0])); ERROR_CHECK(close(fd[i][1])); } } void child_code(int fd[NO_OF_PROCESSES][2], int child_id) { void* message = malloc(512); if (message == NULL) exit(EXIT_FAILURE); memset(message, 0, 512); int l = 2 * child_id + 1; int r = 2 * child_id + 2; // 每个进程向子进程发送消息 if (child_id == ROOT || l < NO_OF_PROCESSES) { // 根进程或其他父进程 if (child_id != ROOT) for (int i = 0; i < NO_OF_MESSAGES; i++) NOT_PARTIAL(read(fd[child_id][0], message, 512)); if (l < NO_OF_PROCESSES) for (int i = 0; i < NO_OF_MESSAGES; i++) NOT_PARTIAL(write(fd[l][1], message, 512)); if (r < NO_OF_PROCESSES) for (int i = 0; i < NO_OF_MESSAGES; i++) NOT_PARTIAL(write(fd[r][1], message, 512)); } else { // 叶子进程 for (int i = 0; i < NO_OF_MESSAGES; i++) NOT_PARTIAL(read(fd[child_id][0], message, 512)); } printf("Ok, process %d\n", child_id); // 进程向所有管道发送一条消息 for (int i = 0; i < NO_OF_PROCESSES; i++) { int pipe_size = 0; ioctl(fd[i][1], FIONREAD, &pipe_size); printf("Check_1, process %d, there are %d bytes in the pipe, iteration %d\n", child_id, pipe_size, i); NOT_PARTIAL(write(fd[i][1], message, 512)); printf("Check_2, process %d\n", child_id); fflush(stdout); } free(message); printf("Finished, process %d\n", child_id); } int main() { // 每个子进程对应一个管道 int fd[NO_OF_PROCESSES][2]; for (int i = 0; i < NO_OF_PROCESSES; i++) { ERROR_CHECK(pipe(fd[i])); } // 创建子进程 for (int i = 0; i < NO_OF_PROCESSES; i++) { int fork_result = fork(); ERROR_CHECK(fork_result); if (fork_result == 0) { // 子进程逻辑 child_code(fd, i); close_pipes(fd); return 0; } } close_pipes(fd); // 等待所有子进程结束 for (int i = 0; i < NO_OF_PROCESSES; i++) { ERROR_CHECK(wait(NULL)); } return 0; }
问题现象
程序无法正常终止,部分进程挂起。输出的最后几行如下:
Ok, process 12 Check_1, process 2, there are 7168 bytes in the pipe, iteration 15 Check_2, process 2 Check_1, process 12, there are 7680 bytes in the pipe, iteration 0 Finished, process 2 Check_2, process 12 Check_1, process 12, there are 7680 bytes in the pipe, iteration 1
可以看到,进程12在写入操作时挂住了,没有输出后续的Check_2, process 12。而所有16个进程都打印了Ok,说明树形结构的消息已经全部被读取完成。
补充测试结果:
- 当进程数为15或更少时,程序可以正常运行(此时管道最多写入8192字节)
- 在管道缓冲区容量更大的系统上,代码也能正常运行
请问我哪里出错了?进程为什么会挂起?如果你运行代码没问题,大概率是你的管道缓冲区大小和我的不同。
内容的提问来源于stack exchange,提问作者Aleksander Wojsz
相关产品推荐
相关产品推荐

