我最近读到了一个post.在这篇文章中,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}

推荐答案

    close(fd[0]);

    if (childpid > 0) { /* parent code */
        int ret = write(fd[1], bufout, strlen(bufout)+1);

由于父进程中的close(fd[0]);恰好立即发出write(2)调用,因此子进程有可能尚未开始运行(实际上,这是最有可能发生的事情,因为一旦内核获知子进程ID,父进程就准备好运行,而子进程中仍有内务工作要做),因此,当在父进程中的写入完成时,子进程没有时间自己执行close(fd[0]);.事实上,父级继续执行而子级甚至还没有被调度的情况非常频繁,这可能是最频繁的事情.

如果在上面的close(fd[0]);语句后面加上sleep(1)(在父进程和子进程中),您将看到所需的输出,因为到那时,父进程和子进程都已经启动,并且都有机会进行close()调用,远远早于父进程中发生write()系统调用.

事件的顺序(W.O.睡眠())为:

  1. 父级启动并创建管道.
  2. 父叉.
  3. 家长关闭阅读结束.
  4. 家长写道.
  5. 子元素们被安排了
  6. 子元素们结束了阅读. (并且Parent获得了它的写入权限)

随着睡眠的进行,序列变成:

  1. 父级启动并创建管道.
  2. 父叉.
  3. 家长关闭阅读结束.
  4. 父母睡着了.
  5. 子元素们被安排了.
  6. 子元素们结束了阅读.
  7. 子元素睡着了.
  8. 父母从睡梦中醒来.
  9. 父级写入并收到错误.(并且信号处理程序在从已处于用户模式的写入调用返回之前执行)

值得考虑的是,要达到的第一个进程是:

  • 父级中的write(2)系统调用.
  • close(2)系统调用子进程.

将锁定inode,并使其他进程等待完成系统调用.这将使写入过程写入完整的消息,即使在子项中开始关闭之前消息尚未完全写入,或者如果子项中的关闭首先开始,则根本不写入任何消息.

C++相关问答推荐

是否可以在C中进行D3 D12申请?

ISO_C_BINDING,从Fortran调用C

如何在C宏中确定Windows主目录?

在函数中使用复合文字来初始化C语言中的变量

如何创建由符号组成的垂直结果图形?

为什么双重打印与C中的float具有不同的大小时具有相同的值?

变量>;-1如何在C中准确求值?

C是否用0填充多维数组的其余部分?

Flose()在Docker容器中抛出段错误

Go和C中的数据 struct 对齐差异

为什么二进制文件的大小不会随着静态数据的大小而增加?

使用mmap为N整数分配内存

当读取可能会阻塞管道中的父进程时,为什么要等待子进程?

按字典顺序打印具有给定字符的所有可能字符串

Struct 内的数组赋值

C Makefile - 如何避免重复提及文件名

如何正确探测平台设备?

比 * 更快的乘法

获取 struct 中匿名 struct 的大小

当循环变量在溢出时未定义时,可以进行哪些优化?