You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C语言父子进程管道IPC 分块传输实现与段错误问题排查

问题根因

你的代码出现竞态、输出错乱、段错误、读长度为0的问题,全部来自以下几个编码错误,和pipe本身的机制无关:

  • 缓冲区类型声明错误:char *write_buffer[size]声明的是长度为size的char指针数组,不是存储数据的字符数组,每个指针占8字节,实际可访问的有效内存仅为size*8字节,调用fill和write时会直接越界写内存,是段错误的核心诱因。
  • 重复关闭管道描述符:你在每次测试迭代中都执行close(pipefd[0])/close(pipefd[1]),第一次迭代结束后管道fd就被释放,后续读写操作全部作用在无效fd上,直接返回失败,表现为读数据长度为0。
  • 父子进程逻辑未分离:fork后你没有拆分父子进程的执行分支,两个进程同时跑外层的包大小循环、内层测试循环,同时执行打印、读写管道操作,完全没有同步,必然出现输出错乱、竞态。
  • 栈溢出:用变长数组(VLA)在栈上分配最大100MB的缓冲区,远超系统默认栈大小(通常为8MB),直接触发内存访问违规。
  • 未处理字节流特性:pipe是字节流协议,不保证一次read/write能传输完指定长度的数据,你之前尝试的分块逻辑没有处理读写返回值,遇到一次传输不足整Chunk的情况就会出现数据错位,导致方案失效。
  • 计时逻辑错误:clock()统计的是进程自身占用的CPU时间,不是真实传输的墙上时间,父子进程各自统计自己的CPU时间,计算出的耗时完全不准。
4096字节分块有序传输修复方案

实现遵循以下规则:

  • 固定分块大小为4096字节,发送/接收端都循环处理直到整包数据传输完成,保证顺序、不丢包、不半包
  • fork后立刻拆分父子逻辑:子进程仅负责接收数据,父进程仅负责发送、统计耗时,两边不执行重复逻辑
  • 管道fd仅在所有传输开始前创建,所有传输结束后关闭,不在循环内重复开关
  • 用堆内存分配大缓冲区,避免栈溢出
  • 校验所有系统调用的返回值,避免静默失败
  • 用CLOCK_MONOTONIC统计真实传输耗时,不受系统时间跳变影响

修复后的完整代码:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <string.h>
#include <fcntl.h>
#include <time.h>

#define SIZES 6
#define KB 1024
#define CHUNK_SIZE 4096 // 固定4KB分块大小

// 测试包大小:1KB, 10KB, 100KB, 1MB, 10MB, 100MB
int PACK_SIZES[SIZES] = {1*KB, 10*KB, 100*KB, 1*KB*KB, 10*KB*KB, 100*KB*KB};
int PACK_TESTS[SIZES] = {6,5,4,3,2,1};

void fill(char * bufptr, size_t size);
// 分块写完整数据:返回写入的总字节数,失败返回-1
ssize_t write_all(int fd, const void *buf, size_t len);
// 分块读完整数据:返回读取的总字节数,失败返回-1
ssize_t read_all(int fd, void *buf, size_t len);

int main() {
    int pipefd[2];
    pid_t pid;
    int r;

    r = pipe(pipefd);
    if (r < 0){
        perror("pipe create failed");
        exit(EXIT_FAILURE);
    }

    printf("Pipe IPC 4KB chunked transfer test result:\n\n");

    pid = fork();
    if (pid == (pid_t)-1){
        perror("fork failed");
        exit(EXIT_FAILURE);
    }

    if (pid == 0) {
        // 子进程:接收端
        close(pipefd[1]); // 关闭不需要的写端
        char *read_buffer = NULL;
        for (int i = 0; i < SIZES; i++) {
            size_t size = PACK_SIZES[i];
            int test_cnt = PACK_TESTS[i];
            read_buffer = realloc(read_buffer, size);
            if (!read_buffer) {
                perror("recv buffer alloc failed");
                exit(EXIT_FAILURE);
            }
            for (int j = 0; j < test_cnt; j++) {
                ssize_t r_bytes = read_all(pipefd[0], read_buffer, size);
                if (r_bytes != (ssize_t)size) {
                    perror("read data failed");
                    exit(EXIT_FAILURE);
                }
                // 如需校验数据可打开下面注释
                // if (strlen(read_buffer) != size-1) {
                //     fprintf(stderr, "data corrupt, expected len %zu, got %zu\n", size-1, strlen(read_buffer));
                //     exit(EXIT_FAILURE);
                // }
            }
        }
        free(read_buffer);
        close(pipefd[0]);
        exit(EXIT_SUCCESS);
    } else {
        // 父进程:发送端
        close(pipefd[0]); // 关闭不需要的读端
        char *write_buffer = NULL;
        struct timespec start, end;

        for (int i = 0; i < SIZES; i++) {
            size_t size = PACK_SIZES[i];
            int test_cnt = PACK_TESTS[i];
            double total_time = 0;

            write_buffer = realloc(write_buffer, size);
            if (!write_buffer) {
                perror("send buffer alloc failed");
                exit(EXIT_FAILURE);
            }
            fill(write_buffer, size);

            for (int j = 0; j < test_cnt; j++) {
                clock_gettime(CLOCK_MONOTONIC, &start);
                ssize_t w_bytes = write_all(pipefd[1], write_buffer, size);
                clock_gettime(CLOCK_MONOTONIC, &end);

                if (w_bytes != (ssize_t)size) {
                    perror("write data failed");
                    exit(EXIT_FAILURE);
                }

                double cost = (end.tv_sec - start.tv_sec) + (end.tv_nsec - start.tv_nsec)/1e9;
                total_time += cost;
            }

            printf("Size: %zu Bytes, Test count: %d, Average latency: %f sec\n\n",
                   size, test_cnt, total_time/test_cnt);
        }

        free(write_buffer);
        close(pipefd[1]);
        waitpid(pid, NULL, 0); // 回收子进程,避免僵尸进程
    }

    return 0;
}

void fill(char * bufptr, size_t size){
    static char ch = 'A';
    if (size == 0) return;
    memset(bufptr, ch, size);
    bufptr[size-1] = '\0';
    ch = (ch >= 'Z') ? 'A' : ch+1;
}

ssize_t write_all(int fd, const void *buf, size_t len) {
    size_t total_w = 0;
    const char *ptr = (const char *)buf;
    while (total_w < len) {
        size_t chunk_len = (len - total_w) < CHUNK_SIZE ? (len - total_w) : CHUNK_SIZE;
        ssize_t w = write(fd, ptr + total_w, chunk_len);
        if (w <= 0) return -1;
        total_w += w;
    }
    return total_w;
}

ssize_t read_all(int fd, void *buf, size_t len) {
    size_t total_r = 0;
    char *ptr = (char *)buf;
    while (total_r < len) {
        size_t chunk_len = (len - total_r) < CHUNK_SIZE ? (len - total_r) : CHUNK_SIZE;
        ssize_t r = read(fd, ptr + total_r, chunk_len);
        if (r <= 0) return -1;
        total_r += r;
    }
    return total_r;
}
关键逻辑说明
  • 分块传输保证有序:write_all每次最多写4096字节,记录已写入的偏移量,循环直到整包发送完成;read_all每次最多读4096字节,按相同偏移量写入接收缓冲区,天然保证数据顺序和完整性,不会出现半包、错序。
  • 管道自带同步机制:当管道缓冲区写满时发送端会自动阻塞,当管道无数据时接收端会自动阻塞,不需要额外加锁或者同步原语,就可以保证收发节奏匹配。
  • 大内存用堆分配:所有缓冲区用realloc在堆上分配,支持最大100MB的测试包,不会出现栈溢出。
  • 资源正确释放:所有fd在传输完成后统一关闭,子进程退出后父进程主动回收,没有资源泄漏。

内容的提问来源于stack exchange,提问作者Santiago Vallejo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.03 01:45:43