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

向已关闭读端的Pipe写入为何有时返回成功?

问题:向已关闭读端的管道写入为何有时成功有时触发SIGPIPE?

我最近读了一篇Stack Overflow帖子,用户Soner关闭了父子进程的管道读端,但向管道写入时并未触发SIGPIPE信号。我修改代码确认管道读端已完全关闭,却发现write调用有时无错误返回,多次运行程序结果不一致。

第一次测试代码

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/param.h>
#include <signal.h>
#define BUFSIZE 100

char const * errMsgPipe = "signal handled SIGPIPE\n";
int errMsgPipeLen;

void handler(int x) {
    write(2, errMsgPipe, errMsgPipeLen);
}

int main(void) {
    errMsgPipeLen = strlen(errMsgPipe);
    char bufin[BUFSIZE] = "empty";
    char bufout[] = "hello soner";
    int bytesin;
    pid_t childpid;
    int fd[2];

    struct sigaction sa;
    memset(&sa, 0, sizeof(sa));
    sa.sa_flags = 0;
    sigfillset(&sa.sa_mask);
    sa.sa_handler = handler;
    sigaction(SIGPIPE, &sa, 0);

    if (pipe(fd) == -1) {
        perror("Failed to create the pipe");
        return 1;
    }
    bytesin = strlen(bufin);
    childpid = fork();
    if (childpid == -1) {
        perror("Failed to fork");
        return 1;
    }

    close(fd[0]);

    if (childpid) {
        int ret = write(fd[1], bufout, strlen(bufout)+1);
        if (ret < 0) {
            perror("write");
        } else {
                printf("write success, ret: %d\n", ret );
        }
        wait(NULL);
    }
    else{
        bytesin = read(fd[0], bufin, BUFSIZE);
        if(bytesin == -1) {
                perror("child: read");
        }
    }
    fprintf(stderr, "[%ld]:my bufin is {%.*s}, my bufout is {%s}\n",
            (long)getpid(), bytesin, bufin, bufout);
    return 0;
}

第一次运行输出

write success, ret: 12
child: read: Bad file descriptor
[9168]:my bufin is {empty}, my bufout is {hello soner}
[9167]:my bufin is {empty}, my bufout is {hello soner}

修改后的测试代码

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/param.h>
#include <signal.h>
#include <fcntl.h>
#include <errno.h>

#define BUFSIZE 100

char const * errMsgPipe = "signal handled SIGPIPE\n";
int errMsgPipeLen;

void handler(int x) {
    write(2, errMsgPipe, errMsgPipeLen);
}

int main(void) {
    errMsgPipeLen = strlen(errMsgPipe);
    char bufin[BUFSIZE] = "empty";
    char bufout[] = "hello soner";
    int bytesin;
    pid_t childpid;
    int fd[2];

    struct sigaction sa;
    memset(&sa, 0, sizeof(sa));
    sa.sa_flags = 0;
    sigfillset(&sa.sa_mask);
    sa.sa_handler = handler;
    sigaction(SIGPIPE, &sa, 0);

    if (pipe(fd) == -1) {
        perror("Failed to create the pipe");
        return 1;
    }
    close(fd[0]);
    bytesin = strlen(bufin);
    childpid = fork();
    if (childpid == -1) {
        perror("Failed to fork");
        return 1;
    }


    if (childpid) {
        int flag = fcntl(fd[0], F_GETFD);
        if(flag = -1) {
                printf("pid:[%d], fcntl: %s\n", getpid(), strerror(errno));
        } else {
                printf("pip: [%d], fd[0] is not closed\n");
        }
        if (write(fd[1], bufout, strlen(bufout)+1) < 0) {
            perror("write");
        }
        //
    }
    else{
        int flag = fcntl(fd[0], F_GETFD);
        if(flag = -1) {
                printf("pid:[%d], fcntl: %s\n", getpid(), strerror(errno));
        } else {
                printf("pip: [%d], fd[0] is not closed\n");
        }
        bytesin = read(fd[0], bufin, BUFSIZE);
        if(bytesin == -1) {
                perror("read");
        }
    }
    fprintf(stderr, "[%ld]:my bufin is {%.*s}, my bufout is {%s}\n",
            (long)getpid(), bytesin, bufin, bufout);
    return 0;
}

多次运行的不同结果

结果一

pid:[9200], fcntl: Bad file descriptor
signal handled SIGPIPE
write: Broken pipe
pid:[9201], fcntl: Bad file descriptor
[9200]:my bufin is {empty}, my bufout is {hello soner}
read: Bad file descriptor
[9201]:my bufin is {empty}, my bufout is {hello soner}

结果二

pid:[9189], fcntl: Bad file descriptor
[9189]:my bufin is {empty}, my bufout is {hello soner}
pid:[9190], fcntl: Bad file descriptor
read: Bad file descriptor
[9190]:my bufin is {empty}, my bufout is {hello soner}

请问为何向已关闭读端的Pipe写入会出现有时成功、有时触发SIGPIPE信号的不一致现象?


解答

这是因为管道的写端写入数据时,内核会先尝试将数据放入管道缓冲区,而SIGPIPE/EPIPE错误只会在缓冲区已满且无读端的情况下触发,或者当数据写入后发现没有读端时的后续操作才会触发。结合代码和进程调度的不确定性,具体原因如下:

  1. 管道缓冲区的存在
    管道默认带有内核缓冲区(通常几KB),当写入的数据小于缓冲区剩余空间时,write会直接把数据拷贝到缓冲区,此时内核可能还没检测到所有读端都已关闭——因为父子进程的调度顺序是不确定的。

  2. 进程调度的不确定性
    fork之后,父子进程的执行顺序由内核调度器决定:

    • 情况一:父进程先执行write,此时内核还没完全清理子进程的文件描述符(子进程还没完成close(fd[0])的后续清理),管道缓冲区能放下数据,write直接返回成功,之后内核才发现所有读端关闭,但此时write已经完成。
    • 情况二:父进程执行write时,子进程已经完成close(fd[0])且内核已标记管道读端全部关闭,此时写入操作会触发SIGPIPE,write返回EPIPE错误。
  3. 代码细节问题
    修改后的代码里存在bug:if(flag = -1)是赋值操作而非比较,应该改为if(flag == -1),不过这不会影响核心问题,但会导致错误的输出判断。

总结:管道写入时是否触发SIGPIPE,取决于写入操作发生时内核是否已检测到所有读端关闭,而内核的检测时机和进程调度的不确定性,导致了结果的不一致。当数据能被写入缓冲区且内核还没检测到读端全关时,write成功;当内核已经知道读端全关时,写入就会触发SIGPIPE并返回错误。


内容的提问来源于stack exchange,提问作者Gilbert Tian

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 22:44:50