【问题标题】:Difference between pipe system call and reading/writing to stdin/out管道系统调用和读/写到标准输入/输出的区别
【发布时间】:2021-09-18 22:38:17
【问题描述】:

管道将一个进程的标准输出连接到另一个进程的标准输入:https://superuser.com/a/277327

这是一个从标准输入获取输入并打印的简单程序:

int main( ) {
   char str[100];
   gets( str );
   puts( str );
   return 0;
}

我可以使用 unix 管道来传递来自另一个进程的输入:

echo "hi" | ./a.out 

我的问题是,上面的简单代码和使用pipe() 系统调用有什么区别?系统调用本质上是否在不写入终端的情况下完成相同的工作?有关管道的更多信息:https://tldp.org/LDP/lpg/node11.html

【问题讨论】:

标签: c pipe


【解决方案1】:

pipe() 系统调用允许您为允许通过多个进程流式传输字节的通道(管道)获取文件描述符(一个用于读取,一个用于写入)。这是一个示例,其中父进程创建了一个管道,并且它的子进程写入它以便父进程可以从中读取:

int main() {
    int fd[2];
    pipe(fd);
    int pid = fork();
    if (pid == 0) { // Child:
        close(fd[0]); // Close reading descriptor as it's not needed
        write(fd[1], "Hello", 5);
    } else { // Parent:
        char buf[5];
        close(fd[1]); // Close writing descriptor as it's not needed
        read(fd[0], buf, 5); // Read the data sent by the child through the pipe
        write(1, buf, 5); // print the data that's been read to stdout
    }
}

当 shell 遇到管道 (|) 运算符时,它会使用 pipe() 系统调用,但还会执行其他操作,以便将左操作数的标准输出和右操作数的标准输入重定向到管道。下面是 shell 对命令 echo "hi" | ./a.out 执行操作的简化示例(请记住,在复制文件描述符时,它会复制到进程的打开文件结构中可用的第一个索引):

int main() {
    int fd[2];
    pipe(fd);
    int pid_echo = fork();
    if (pid_echo == 0) {
        // Close reading descriptor as it's not needed
        close(fd[0]);
        // Close standard output
        close(1);
        // Replace standard output with the pipe by duplicating its writing descriptor
        dup(fd[1]);
        // Execute echo;
        // now when echo prints to stdout it will actually print to the pipe
        // because now file descriptor 1 belongs to the pipe
        execlp("echo", "echo", "hi", (char*)NULL);
        exit(-1);
    }
    int pid_aout = fork();
    if (pid_aout == 0) {
        // Close standard input
        close(0);
        // Replace standard input with the pipe by duplicating its reading descriptor
        dup(fd[0]);
        // Execute a.out;
        // Now when a.out reads from stdin it will actually read from the pipe
        // because now file descriptor 0 belongs to the pipe
        execl("./a.out", "./a.out", (char*)NULL);
        exit(-1);
    }
}

【讨论】:

  • 为什么每个人都在用管道谈论子/父进程?我的程序没有创建子进程,它只是想与终端命令行中| 之后的另一个进程通信。你是说unix管道|本身正在使用pipe()系统调用实现?如果我有a | b,终端分叉ab 并使用 pipe() 系统调用在它们之间进行通信?
  • " 如果我​​有 a | b,它会分叉 a 和 b 并使用 pipe() 系统调用在它们之间进行通信?" - 确切地。您的程序不需要处理管道,shell 在调用 exec 之前会处理它。当您的程序运行时,它会写入索引为1 的文件描述符,通常为stdout,但shell 已将其替换为管道。标准输入也是如此。
  • 当您需要与另一个进程通信但您还需要写入屏幕或从键盘读取时使用它。请参阅我的回答中的第一个示例:使用管道而不用它替换 stdinstdout。此外,pipe() 被 shell 用来实现 |,如您在第二个示例中所见。
  • 如果你以echo "hello" | ./a.out 运行你的程序,那么你不需要做任何事情。只需从标准输入读取就可以了。但是,如果您以./a.out 运行它并且您希望a.out 自动生成一个echo,然后它会读取其输出,您需要fork 并在子进程上运行execlp("echo", "echo", "hello", (char*)NULL);,在替换它之后stdout 与管道(通过closedup)。相反,如果您已经有一个正在运行的echo,并且您希望您的进程从它的stdout 中读取,那么您就不能。
  • 在任何进程之间: - 管道的创建者; - 管道创建者的任何后代
【解决方案2】:

管道是一种利用 I/O 重定向的进程间通信机制。但是,管道并不参与所有 I/O 重定向。

由于子进程可能会从其父进程继承文件描述符,因此父进程可能会更改子进程的标准流指向的文件,而子进程并不知道。这是 I/O 重定向。

【讨论】:

  • 假设我没有以任何方式使用 fork(),我想做的只是从管道获取输入并将输出写回它,此时我需要的只是 stdin/out (像我的例子eabve)?
  • 如果您询问单个进程是否可以写入和读取它自己设置的管道 - 完全不切实际,但是是的。
  • 我不明白。 ./a.out 从另一个进程 echo 的输出中读取其输入,这不就是管道吗? a pipe it set up with itself 是什么意思?
  • 同样,I/O 重定向——管道或无管道——对于子进程实际上是不知道的。孩子可能从标准输入读取或写入标准输出,但不知道每个标准流所指的文件。
猜你喜欢
  • 2019-09-03
  • 1970-01-01
  • 2014-05-13
  • 2016-04-21
  • 1970-01-01
  • 2013-11-04
  • 1970-01-01
  • 2013-07-23
  • 1970-01-01
相关资源
最近更新 更多