【问题标题】:Why does redirection (or piping) change the program's behavior为什么重定向(或管道)会改变程序的行为
【发布时间】:2014-11-27 22:09:39
【问题描述】:

考虑一个程序,它创建一个无限循环打印的子进程,并在一秒钟后将其杀死:

#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>

int main(void) {
    pid_t pid = fork();

    if (pid == 0) {
        while (1)
            puts("I'm still alive");
    } else {
        sleep(1);
        puts("Dispatching...");
        kill(pid, SIGTERM);
        puts("Dispatched!");
    }

    return 0;
}

正如我所料,输出是:

I'm still alive
I'm still alive
...
Dispatching...
I'm still alive
I'm still alive
...
Dispatched!

这是有道理的,因为子进程可能不会在父亲发送信号后立即终止

但是,一旦我通过管道运行程序,或将输出重定向到另一个文件,例如

$ ./prog | tail -n 20
$ ./prog > out.txt

输出变成:

I'm still alive
I'm still alive
...
Dispatching...
Dispatched!

也就是说,似乎是父进程杀死子进程后没有输出

造成这种差异的原因是什么?

【问题讨论】:

  • 我认为下一个是有道理的:stackoverflow.com/questions/9553628/piping-and-redirection?rq=1,虽然第一个是发送到输出缓冲区,但在 kill 之后缓冲区仍然有一些数据,而管道正在阻塞并直接通过通道,所以,一旦被杀死,就没有什么可看的了。这有意义吗?
  • 我认为情况正好相反:如果stdout 是一个 tty 刷新发生在每个换行符上,而重定向输出分流器的刷新频率较低,尤其是。在只打印两行的主进程的情况下,刷新只会在它终止后发生,所以所有子进程的输出都在此之前写入
  • @HartmutHolzgraefe 这实际上是有道理的 - 因为stdout 在绑定到文件或管道时没有行缓冲,所以只有在它终止后才刷新父亲的输出,而孩子的输出是在kill() 之后立即刷新。

标签: c unix process io-redirection piping


【解决方案1】:

puts 使用stdio,可以是buffered。通常,stdout 在连接到终端时是行缓冲的,这意味着每次打印换行符时都会刷新缓冲区。因此,当您运行程序而不重定向其输出时,每行都会在puts 调用时打印。当程序的标准输出被重定向到文件或管道时,stdout 变为完全缓冲:输出数据在缓冲区中累积,并且仅在缓冲区已满时才被写入。程序在有时间填充缓冲区之前被杀死,所以你看不到任何输出。

您可以通过调用setvbuf(stdout, NULL, _IOLBF, BUFSIZ) 在输出任何内容之前将stdout 设置为行缓冲模式来确认您正在观察的内容。然后,无论输出到终端、文件还是管道,您都应该看到相同数量的行。

也可以观察到其他效果;在这种规模下,行为非常依赖于调度程序的微调。例如,您的终端渲染输出需要多长时间、同时运行的其他程序、运行程序的 shell 最近是否一直在执行 CPU 密集型或 IO 密集型的事情可能很重要……

【讨论】:

  • 子进程的输出是不是被杀掉了?正如您所说,孩子在有时间填充缓冲区之前就被杀死了,但是我确实看到了输出,因为它的终止导致了刷新。在我看来,不同之处在于,正如您所说,由于不同的缓冲模式,但原因是当stdout 未绑定到终端时,孩子的输出在@987654327 之后立即刷新@,而父亲的输出仅在它终止时才被刷新。因此,在看到父亲的任何输出之前,我们会先看到孩子的所有输出。
  • @Avidanborisov 当一个进程被杀死时,它的 stdio 缓冲区也会随之消失。刷新 stdio 缓冲区是进程在正常退出时执行的操作。
  • 你说得对,SIGTERM 不是正常的退出。但是,如果我从代码中删除无限循环并只打印一次,即使stdout 是文件或管道,我怎么能看到孩子的输出呢?输出没有刷新,应该在孩子终止时丢弃,不是吗?
  • @Avidanborisov 如果您让孩子调用puts 一次然后退出,那么即使在标准输出完全缓冲时看到输出表明孩子已经有足够的时间自行退出(或在在父级杀死它之前至少刷新标准输出缓冲区。
猜你喜欢
  • 2017-02-18
  • 2015-09-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多