4

Consider a program that creates a child process printing in an infinite loop, and kills it after one second:

#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;
}

The output, as I expected, was:

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

which makes sense, as the child process probably isn't instantly terminated after the father sends the singal.

However, as soon as I run the program through a pipe, or redirect the output to another file, e.g.

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

The output becomes:

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

That is, it appears to be that there's no output from the child process after the father kills it.

What is the reason for this difference?

Avidan Borisov
  • 3,235
  • 4
  • 24
  • 27
  • 1
    I think the next makes sense: http://stackoverflow.com/questions/9553628/piping-and-redirection?rq=1 , while the first is sending to an output buffer, after the kill the buffer still has some data, while the piping is blocking and sending data directly trough the channel, so, once killed, there is nothing more to be seen. Does that make sense? – fernando.reyes Nov 27 '14 at 22:31
  • 1
    I think it is the opposite: if `stdout` is a tty flushing happens on each newline, while when redirecting output biffers are flushed less often, esp. in the case of the main process that only prints two lines flushing only happens after it terminates, so all subprocess output is written before that – Hartmut Holzgraefe Nov 27 '14 at 22:57
  • @HartmutHolzgraefe That actually makes sense - since `stdout` isn't line-buffered when it's bound to a file or a pipe, the father's output is flushed only after it terminates, while the child's output is flushed right after the `kill()`. – Avidan Borisov Nov 27 '14 at 23:36

1 Answers1

2

puts uses stdio, which can be buffered. Usually, stdout is line-buffered when it's connected to a terminal, meaning that the buffer is flushed each time a newline is printed. Thus when you run the program without redirecting its output, each line is printed at the time of the puts call. When the program's standard output is redirected to a file or to a pipe, stdout becomes fully buffered: the output data accumulates in the buffer and is only written out when the buffer is full. The program is killed before it has time to fill the buffer, so you don't see any output.

You can confirm that this is what you're observing by calling setvbuf(stdout, NULL, _IOLBF, BUFSIZ) to set stdout to line buffered mode before outputting anything. Then you should see the same amount of lines whether the output is going to a terminal, to a file or to a pipe.

It would be possible to observe other effects too; at this scale, the behavior is very dependent on scheduler fine-tuning. For example it may matter how long your terminal takes to render the ouptut, what other programs are running at the same time, whether the shell that you're running the program from has been doing CPU-intensive or IO-intensive things lately…

Gilles 'SO- stop being evil'
  • 104,111
  • 38
  • 209
  • 254
  • Isn't the output of child process flushed after it's killed? As you've said, the child is killed before it has time to fill the buffer, but I *do* see output, because its termination is causing a flush. The way I see it, the difference is, as you've said, due to the different buffering modes, but the reason is because when `stdout` isn't bound to a terminal, the child's output is flushed right after the `kill()`, while the father's output is flushed only when it terminates. Therefore, we see all the output from the child before we see any output from the father. – Avidan Borisov Nov 28 '14 at 13:04
  • @Avidanborisov When a process is killed, its stdio buffers die with it. Flushing the stdio buffers is something the process does on a normal exit. – Gilles 'SO- stop being evil' Nov 28 '14 at 13:37
  • You're right, a SIGTERM isn't a normal exit. But how come I get to see output from the child if I remove the infinite loop from the code and just print it once, even when `stdout` is a file or a pipe? The output isn't flushed, and should be discarded when the child's terminated, isn't it? – Avidan Borisov Nov 28 '14 at 14:22
  • @Avidanborisov If you make the child call `puts` once and then exit, then seeing the output even when stdout is fully buffered indicates that the child has had enough time to exit on its own (or at least got as far as flushing the stdout buffer) before the parent killed it. – Gilles 'SO- stop being evil' Nov 28 '14 at 15:09