AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • 主页
  • 系统&网络
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • 主页
  • 系统&网络
    • 最新
    • 热门
    • 标签
  • Ubuntu
    • 最新
    • 热门
    • 标签
  • Unix
    • 最新
    • 标签
  • DBA
    • 最新
    • 标签
  • Computer
    • 最新
    • 标签
  • Coding
    • 最新
    • 标签
主页 / unix / 问题 / 693780
Accepted
AAA
AAA
Asked: 2022-03-10 12:25:23 +0800 CST2022-03-10 12:25:23 +0800 CST 2022-03-10 12:25:23 +0800 CST

关闭管道中的不同端

  • 772

我使用以下代码为 IPC 编写了以下代码pipe():

#include <unistd.h>
#include <stdio.h>
#include <sys/wait.h>
    
    
int main(void) {
    char message_buffer[15] = "Hello World \n";  
    char read_buffer[15];
    int fd[2]; 
   
    int return_value = pipe(fd);
   
    if (return_value < 0) {
        printf("Error creating the pipe");
    }
    
    int rc = fork();
    if (rc  < 0) {
        printf("Error forking a child");
    }
    
    if (rc > 0) {
        close(fd[0]);
        write(fd[1], message_buffer, 15);
        close(fd[1]);
        wait(NULL);
    } else {
        close(fd[1]);
        read(fd[0], read_buffer, 15);
        close(fd[0]);
        printf("The Message: %s", read_buffer);
    }

    return 0;
}

我是管道新手,我有以下问题:

  1. 我不明白为什么父母需要在写入之前关闭读取端,而需要在写入后关闭写入端?
  2. 孩子也一样,为什么在读之前要关闭写端,为什么读后要关闭读端呢?
  3. 由于父母和孩子同时运行,如果孩子在父母写消息的同时阅读会发生什么?
  4. 由于父母和孩子同时运行,如果孩子读取并且父母还没有在管道中写入任何东西会发生什么?

我的问题似乎很愚蠢,但请帮助回答它们,因为我正在为我的课程考试学习普通管道。

linux pipe
  • 3 3 个回答
  • 1539 Views

3 个回答

  • Voted
  1. Best Answer
    VL-80
    2022-03-10T13:19:06+08:002022-03-10T13:19:06+08:00

    问题 1 和 2的答案在pipe 手册页(“示例”部分)中:

    在 fork 之后,每个进程都会关闭管道不需要的文件描述符(请参阅 pipe(7))。

    由于管道是单向的,它有指定的两端——读端和写端。如果父级将使用此管道向子级写入数据,则父级没有必要保持读取端打开。相反,如果孩子要从管道中读取数据,则不需要打开写端。

    编辑:

    您还问为什么父母需要在写完后关闭写端,为什么孩子需要在读完后关闭读端。

    他们不必这样做。如果两个程序要继续运行并使用管道交换数据,它们必须保持管道打开。在一个简短的示例程序中,它仅演示了管道的使用并在传输一条消息后终止,父子程序关闭管道文件描述符可能是为了在程序终止之前正确清理资源。

    问题 #3 和 #4 的答案在pipe(7) 手册页中。

    你的问题#3:

    由于父母和孩子可以同时运行,如果孩子在父母写消息的同时阅读会发生什么?

    子进程将能够读取管道中已由父进程写入的任何可用数据。根据手册页:

    POSIX.1 说小于 PIPE_BUF 字节的写入必须是原子的:输出数据作为连续序列写入管道。超过 PIPE_BUF 字节的写入可能是非原子的:内核可能会将数据与其他进程写入的数据交错。POSIX.1 要求 PIPE_BUF 至少为 512 字节。(在 Linux 上,PIPE_BUF 为 4096 字节。)

    你的问题#4:

    由于父母和孩子可以同时运行,如果孩子阅读并且父母还没有在管道中写入任何东西会发生什么?

    手册页说:

    如果一个进程试图从一个空管道中读取,那么 read(2) 将阻塞直到数据可用。如果一个进程试图写入一个完整的管道(见下文),那么 write(2) 会阻塞,直到从管道中读取了足够的数据以允许写入完成。

    对评论中的问题的回答:

    对于问题 1 和 2,这意味着如果我没有关闭不需要的端,这不会以任何方式影响程序吗?

    它不应该阻止管道工作,但它会占用程序使用的资源。通过关闭管道的不需要的末端,这些资源就不会被保留。

    对于问题3,这意味着孩子将阅读父母正在写的内容,孩子如何知道父母已经完成了它需要写的内容?

    手册页说:

    管道提供的通信通道是字节流:没有消息边界的概念。

    这意味着管道不关心您传输的数据。它不知道“消息”是什么意思,也不知道父级是否已经完成写入,或者它是否想要写入更多数据。

    您将需要实施自己的技术来确定什么是“完整消息”。例如,父母可以通过发送一个特殊字符来向孩子表明已经写入了完整的消息,例如,\0或者实际上在使用管道的特定上下文中有意义的任何其他内容。

    • 3
  2. ilkkachu
    2022-03-10T14:25:53+08:002022-03-10T14:25:53+08:00

    请参阅pipe(7)手册页。

    在“管道和 FIFO 上的 I/O”下,它说:

    如果引用管道写入端的所有文件描述符都已关闭,则尝试从管道读取(2)将看到文件结束(读取(2)将返回 0)。

    如果所有引用管道读取端的文件描述符都已关闭,则 write(2) 将导致为调用进程生成 SIGPIPE 信号。

    让孩子关闭其写入端的副本(它不会使用),使得孩子可以检测到父母何时这样做。如果孩子保持写端打开,它永远不会在管道上看到 EOF,因为它本质上是在等待自己。(2)

    同样,让父母关闭其读取端的副本也可以让父母检测孩子是否离开。(1)

    并不是说您那里的代码曾经检查过read()和write()或尝试读取/写入可变数量的数据的返回值,因此除了父级获得 SIGPIPE 信号之外,这基本上没有实际意义。

    在写入后关闭父级中的写入端并在子级中读取后关闭读取端只是常见的家务。如果进程无论如何都立即退出,则显式关闭不会产生任何影响。

    我不确定你的问题 3 和 4 是否相同,但是如果阅读器在没有什么可阅读的情况下阅读,系统调用将阻塞:

    如果一个进程试图从一个空管道中读取,那么 read(2) 将阻塞直到数据可用。

    如果写入器在读取器执行其他操作时写入,则数据将被复制到操作系统中的缓冲区,至少在有足够空间的情况下是这样。如果没有,那么作者将阻止:

    如果一个进程试图写入一个完整的管道(见下文),那么 write(2) 会阻塞,直到从管道中读取了足够的数据以允许写入完成。

    “下面”是“管道容量”部分。

    如果他们同时这样做,操作系统只会复制数据。

    • 1
  3. Andy Dalton
    2022-03-20T18:31:05+08:002022-03-20T18:31:05+08:00

    其他人提供了很好的答案。这是 ilkkachu 对其上述答案的评论的扩展,旨在通过实验帮助您。考虑以下程序,它是您最初发布的内容的略微修改版本:

    #include <stdio.h>
    #include <stdlib.h>
    #include <sys/wait.h>
    #include <time.h>
    #include <unistd.h>
    
    struct message {
        char content[15];
    };
    
    int main(void) {
        int fds[2];
    
        if (pipe(fds) < 0) {
            printf("Error creating the pipe\n");
            return 1;
        }
    
        srand(time(NULL));
    
        const pid_t pid = fork();
        if (pid < 0) {
            printf("Error forking a child");
            return 1;
        }
    
        if (pid > 0) {
            const int message_count = (rand() % 9) + 1;
            const struct message message_buffer = {
                .content = "Hello, World\n",
            };
    
            /* 1 */ close(fds[0]);
    
            for (int i = 0; i < message_count; ++i) {
                write(fds[1], &message_buffer, sizeof(message_buffer));
            }
    
            /* 2 */ close(fds[1]);
    
            wait(NULL);
        } else {
            struct message read_buffer;
    
            /* 3 */ close(fds[1]);
    
            while (read(fds[0], &read_buffer, sizeof(read_buffer)) > 0) {
                printf("The Message: %s", read_buffer.content);
            }
    
            /* 4 */ close(fds[0]);
        }
    
        return 0;
    }
    

    这个版本不发送消息,它发送 1 到 10 之间的随机消息数。这样,​​孩子不会提前知道要阅读多少条消息——当它从管道中读取所有内容时它会停止并且没有其他内容可以写入管道(即,当所有写入端都关闭并read返回负值时)。这是几个示例运行:

    $ ./a.out
    The Message: Hello, World
    $ ./a.out
    The Message: Hello, World
    The Message: Hello, World
    The Message: Hello, World
    The Message: Hello, World
    The Message: Hello, World
    The Message: Hello, World
    The Message: Hello, World
    The Message: Hello, World
    $ ./a.out
    The Message: Hello, World
    The Message: Hello, World
    The Message: Hello, World
    The Message: Hello, World
    $
    

    请注意,我在close()与管道关联的每个调用之前都添加了注释。如果仅注释掉第 (1) 行,则程序的行为没有明显变化:

    $ ./a.out
    The Message: Hello, World
    The Message: Hello, World
    The Message: Hello, World
    $ ./a.out
    The Message: Hello, World
    The Message: Hello, World
    $
    

    如果只注释掉第 (2) 行,则程序在运行时会死锁,例如:

    $ ./a.out
    The Message: Hello, World
    The Message: Hello, World
    (program hangs here)
    

    为什么?如果父进程没有关闭管道的写端,那么子进程将永远阻塞在调用中以read等待更多数据。(read如果有任何打开的文件描述符与管道的写入端相关联,则调用将阻塞。)然后父级将在调用wait. 父母和孩子都永远等待对方。

    如果仅注释掉第 (3) 行,则程序在运行时会死锁,例如:

    $ ./a.out
    The Message: Hello, World
    The Message: Hello, World
    The Message: Hello, World
    The Message: Hello, World
    (program hangs here)
    

    为什么?read同样,只要存在与管道的写端关联的任何打开的文件描述符,并且子进程有一个,调用就会阻塞。结果,子进程再次阻塞调用 toread而父进程阻塞调用 towait并且没有任何进一步的进展。

    最后,如果您只注释掉第 (4) 行,那么程序的行为没有明显变化:

    $ ./a.out
    The Message: Hello, World
    The Message: Hello, World
    The Message: Hello, World
    $ ./a.out
    The Message: Hello, World
    The Message: Hello, World
    The Message: Hello, World
    The Message: Hello, World
    The Message: Hello, World
    $
    

    通过确保所有进程关闭它们不需要的文件描述符,您可以确保没有进程最终被阻塞等待从管道中读取永远不会到来的数据。

    • 0

相关问题

  • 为什么管道`mysql`到'tail'会改变输出格式?

  • 需要一些系统调用

  • astyle 不会更改源文件格式

  • `tee` 和 `bash` 进程替换顺序

  • 通过标签将根文件系统传递给linux内核

Sidebar

Stats

  • 问题 205573
  • 回答 270741
  • 最佳答案 135370
  • 用户 68524
  • 热门
  • 回答
  • Marko Smith

    模块 i915 可能缺少固件 /lib/firmware/i915/*

    • 3 个回答
  • Marko Smith

    无法获取 jessie backports 存储库

    • 4 个回答
  • Marko Smith

    如何将 GPG 私钥和公钥导出到文件

    • 4 个回答
  • Marko Smith

    我们如何运行存储在变量中的命令?

    • 5 个回答
  • Marko Smith

    如何配置 systemd-resolved 和 systemd-networkd 以使用本地 DNS 服务器来解析本地域和远程 DNS 服务器来解析远程域?

    • 3 个回答
  • Marko Smith

    dist-upgrade 后 Kali Linux 中的 apt-get update 错误 [重复]

    • 2 个回答
  • Marko Smith

    如何从 systemctl 服务日志中查看最新的 x 行

    • 5 个回答
  • Marko Smith

    Nano - 跳转到文件末尾

    • 8 个回答
  • Marko Smith

    grub 错误:你需要先加载内核

    • 4 个回答
  • Marko Smith

    如何下载软件包而不是使用 apt-get 命令安装它?

    • 7 个回答
  • Martin Hope
    user12345 无法获取 jessie backports 存储库 2019-03-27 04:39:28 +0800 CST
  • Martin Hope
    Carl 为什么大多数 systemd 示例都包含 WantedBy=multi-user.target? 2019-03-15 11:49:25 +0800 CST
  • Martin Hope
    rocky 如何将 GPG 私钥和公钥导出到文件 2018-11-16 05:36:15 +0800 CST
  • Martin Hope
    Evan Carroll systemctl 状态显示:“状态:降级” 2018-06-03 18:48:17 +0800 CST
  • Martin Hope
    Tim 我们如何运行存储在变量中的命令? 2018-05-21 04:46:29 +0800 CST
  • Martin Hope
    Ankur S 为什么 /dev/null 是一个文件?为什么它的功能不作为一个简单的程序来实现? 2018-04-17 07:28:04 +0800 CST
  • Martin Hope
    user3191334 如何从 systemctl 服务日志中查看最新的 x 行 2018-02-07 00:14:16 +0800 CST
  • Martin Hope
    Marko Pacak Nano - 跳转到文件末尾 2018-02-01 01:53:03 +0800 CST
  • Martin Hope
    Kidburla 为什么真假这么大? 2018-01-26 12:14:47 +0800 CST
  • Martin Hope
    Christos Baziotis 在一个巨大的(70GB)、一行、文本文件中替换字符串 2017-12-30 06:58:33 +0800 CST

热门标签

linux bash debian shell-script text-processing ubuntu centos shell awk ssh

Explore

  • 主页
  • 问题
    • 最新
    • 热门
  • 标签
  • 帮助

Footer

AskOverflow.Dev

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve