澄清 Pipe() 和 dup2() 在 C 中如何工作

标签 c fork pipe dup2 execve

我正在编写一个处理管道的简单外壳。我有工作代码,但我不太明白它在幕后是如何工作的。这是我需要帮助理解的修改后的代码片段(我删除了错误检查以缩短它):

int fd[2];
pipe(fd);

if (fork()) { /* parent code */
    close(fd[1]);
    dup2(fd[0], 0);

    /* call to execve() here */

} else { /* child code */
    close(fd[0]);
    dup2(fd[1], 1);
}

我对我的问题有一些猜测,但仅此而已 - 猜测。以下是我的问题:

  1. 阻塞是在哪里执行的?在我见过的所有示例代码中,read()write()提供阻塞,但我不需要在这里使用它们。我只是复制 STDIN 以指向管道的读取端,并将 STDOUT 复制以指向管道的写入端。我猜测正在发生的是 STDIN 在 dup2(fd[0], 0) 之后进行阻塞被执行。它是否正确?
  2. 据我了解,每个正在运行的进程都有一个描述符表,指向文件表中打开的文件。当进程重定向 STDIN、STDOUT 或 STDERR 时会发生什么?这些文件描述符是否在所有进程的描述符表之间共享?或者每个流程都有副本?重定向其中一个是否会导致所有更改都反射(reflect)出来?
  3. 调用 pipe() 后然后随后调用 fork()打开的管道有 4 个“端”:由父级访问的读端和写端以及由子级访问的读端和写端。在我的代码中,我关闭了父级的写入端和子级的读取端。但是,在完成管道后,我不会关闭剩余的两端。该代码工作正常,因此我假设已完成某种隐式关闭,但这都是猜测工作。我应该像这样添加显式调用来关闭剩余的两端吗?

    int fd[2];
    pipe(fd);
    
    if (fork()) { /* parent code */
        close(fd[1]);
        dup2(fd[0], 0);
    
        /* call to execve() here */
    
        close(fd[0]);
    
    } else { /* child code */
        close(fd[0]);
        dup2(fd[1], 1);
        close(fd[1]);
    }
    
  4. 这更多的是关于管道过程如何工作的概念性问题。有管道的读取端,由文件句柄 fd[0] 引用。 ,以及管道的写入端,由文件句柄 fd[1] 引用。管道本身只是一个由字节流表示的抽象。文件句柄代表打开的文件,对吗?那么这是否意味着系统中的某个位置有一个文件(由 fd[1] 指向)包含我们想要通过管道发送的所有信息?通过字节流推送该信息后,有一个文件(由 fd[0] 指向)也写入了所有这些信息,从而创建了管道的抽象?

最佳答案

  1. 您提供的代码中没有任何内容被阻止。 forkdup2close 都会立即运行。该代码不会在您打印的行中的任何地方暂停执行。如果您观察到任何等待或挂起,则它位于代码中的其他位置(例如,在对 waitpidselectread 的调用中)。

  2. 每个进程都有自己的文件描述符表。文件对象在所有进程之间是全局的(文件系统中的文件可能会多次打开,用不同的文件对象表示),但文件描述符是每个进程的,这是每个进程引用文件对象的一种方式。因此,像“1”或“2”这样的文件描述符仅在您的进程中有意义 - “文件号 1”和“文件号 2”可能意味着与另一个进程不同的东西。但是进程可以引用相同的文件对象(尽管每个进程可能有不同的编号)。

    因此,从技术上讲,这就是为什么您可以在文件描述符上设置两组标志,即不在进程之间共享的文件描述符标志 (F_CLOEXEC) 和共享的文件对象标志(例如 O_NONBLOCK)即使在进程之间也是如此。

    除非你在 stdin/stdout/stderr 上做了一些奇怪的事情,比如 freopen(罕见),否则它们只是 fds 0,1,2 的同义词。当你想写入原始字节时,使用文件描述符号调用write;如果您想编写漂亮的字符串,请使用 stdin/stdout/stderr 调用 fprintf ——它们会到达同一个地方。

  3. 没有完成隐式关闭,你只是侥幸逃脱。是的,您应该在使用完文件描述符后关闭它们 - 从技术上讲,我会编写 if (fd[0] != 0) close(fd[0]); 只是为了使当然!

  4. 不,没有任何内容写入磁盘。它是一个内存支持的文件,这意味着缓冲区不会存储在任何地方。当您写入磁盘上的“常规”文件时,写入的数据由内核存储在缓冲区中,然后尽快传递到磁盘以提交。当您写入管道时,它会同样进入内核管理的缓冲区,但通常不会进入磁盘。它只是坐在那里,直到被管道的读取端读取为止,此时内核会丢弃它而不是保存它。

    管 Prop 有读取和写入端,因此写入的数据始终位于缓冲区的末尾,而读出的数据则从缓冲区的头部取出,然后删除。因此,水流有严格的顺序,就像在物理管道中一样:首先进入一端的水滴首先从另一端出来。如果远端的水龙头关闭(进程不读取),那么您无法将更多数据推送(写入)到管道的一端。如果数据没有被写入并且管道为空,则读取时必须等待,直到有更多数据通过。

关于澄清 Pipe() 和 dup2() 在 C 中如何工作,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21033301/

相关文章:

c - pthreads:如何断言代码在单线程上下文中运行

linux - Linux 编译错误(关于管道概念的简单演示代码)

c - posix 线程中的线程取消

java - 如何从我从 `java` 目标派生的 Ant 任务中停止 Selenium 服务器?

c - 在 C 语言中,为什么数组的地址等于它的值?

使用 fork() 创建具有指定数量子进程的多进程

python - OpenCV Python,从命名管道读取视频

c - 通过套接字流式传输 execvp 输出

c - Fread 中止 6 错误

c - 将功能指令依次放入程序存储器中