27

fork() creates a new process and the child process starts to execute from the current state of the parent process.

This is the thing I know about fork() in Linux.

So, accordingly the following code:

int main() {
  printf("Hi");
  fork();
  return 0;
}

needs to print "Hi" only once as per the above.

But on executing the above in Linux, compiled with gcc, it prints "Hi" twice.

Can someone explain to me what is happening actually on using fork() and if I have understood the working of fork() properly?

Cœur
  • 37,241
  • 25
  • 195
  • 267
Mor Eru
  • 1,129
  • 3
  • 18
  • 35

5 Answers5

36

(Incorporating some explanation from a comment by user @Jack) When you print something to the "Standard Output" stdout (computer monitor usually, although you can redirect it to a file), it gets stored in temporary buffer initially.

Both sides of the fork inherit the unflushed buffer, so when each side of the fork hits the return statement and ends, it gets flushed twice.

Before you fork, you should fflush(stdout); which will flush the buffer so that the child doesn't inherit it.

stdout to the screen (as opposed to when you're redirecting it to a file) is actually buffered by line ends, so if you'd done printf("Hi\n"); you wouldn't have had this problem because it would have flushed the buffer itself.

Paul Tomblin
  • 179,021
  • 58
  • 319
  • 408
  • Can U please explain your answer in detail sir. I am new to doing c programs in gcc. So i am not able to comprehend your answer!! – Mor Eru Aug 18 '10 at 14:43
  • You should be able to test the accuracy of this by `fflush(stdin)` – torak Aug 18 '10 at 14:43
  • 1
    @Shyam: When you print something to STDOUT (computer monitor usually), it get stored in temporary buffer initially. When you are doing fork, that buffer is inherited by child. When buffer is flushed, you get to see it from both process. If you use fflush manually, buffer is cleaned and child does not inherit that. You will see only one print then. – Jack Aug 18 '10 at 14:49
  • @Jack, that's such a good explanation. Do you mind if I incorporate it in my answer? – Paul Tomblin Aug 18 '10 at 14:52
  • If stdout is a regular file, it will usually be block buffered. Don't conflate stdout with a tty. – William Pursell Aug 18 '10 at 14:59
  • @William, 99.999% of the time somebody asking basic questions about "printf" isn't redirecting stdout to a file. – Paul Tomblin Aug 18 '10 at 15:03
  • @Paul: Sure.. pls go ahead.. – Jack Aug 19 '10 at 06:17
  • Thanks for this beautiful answer. – Ahtisham Mar 14 '18 at 11:23
23

printf("Hi"); doesn't actually immediately print the word "Hi" to your screen. What it does do is fill the stdout buffer with the word "Hi", which will then be shown once the buffer is 'flushed'. In this case, stdout is pointing to your monitor (assumedly). In that case, the buffer will be flushed when it is full, when you force it to flush, or (most commonly) when you print out a newline ("\n") character. Since the buffer is still full when fork() is called, both parent and child process inherit it and therefore they both will print out "Hi" when they flush the buffer. If you call fflush(stout); before calling fork it should work:

int main() {
  printf("Hi");
  fflush(stdout);
  fork();
  return 0;
}

Alternatively, as I said, if you include a newline in your printf it should work as well:

int main() {
  printf("Hi\n");
  fork();
  return 0;
}
Stephen
  • 6,027
  • 4
  • 37
  • 55
  • Can someone more experienced than I in `C` confirm that the stdout buffer flushes when a newline character is entered into it? I have this knowledge from somewhere but I'm not 100% sure. – Stephen Aug 18 '10 at 14:47
  • 2
    C knows 3 buffering modes: `unbuffered`, `fully buffered` and `line buffered`. From C99 §7.19.3 (7): "[...] As initially opened, the standard error stream is not fully buffered; the standard input and standard output streams are fully buffered if and only if the stream can be determined not to refer to an interactive device." – schot Aug 18 '10 at 14:52
8

In general, it's very unsafe to have open handles / objects in use by libraries on either side of fork().

This includes the C standard library.

fork() makes two processes out of one, and no library can detect it happening. Therefore, if both processes continue to run with the same file descriptors / sockets etc, they now have differing states but share the same file handles (technically they have copies, but the same underlying files). This makes bad things happen.

Examples of cases where fork() causes this problem

  • stdio e.g. tty input/output, pipes, disc files
  • Sockets used by e.g. a database client library
  • Sockets in use by a server process - which can get strange effects when a child to service one socket happens to inherit a file handle for anohter - getting this kind of programming right is tricky, see Apache's source code for examples.

How to fix this in the general case:

Either

a) Immediately after fork(), call exec(), possibly on the same binary (with necessary parameters to achieve whatever work you intended to do). This is very easy.

b) after forking, don't use any existing open handles or library objects which depend on them (opening new ones is ok); finish your work as quickly as possible, then call _exit() (not exit() ). Do not return from the subroutine that calls fork, as that risks calling C++ destructors etc which may do bad things to the parent process's file descriptors. This is moderately easy.

c) After forking, somehow clear up all the objects and make them all in a sane state before having the child continue. e.g. close underlying file descriptors without flushing data which are in a buffer which is duplicated in the parent. This is tricky.

c) is approximately what Apache does.

MarkR
  • 62,604
  • 14
  • 116
  • 151
3

printf() does buffering. Have you tried printing to stderr?

swegi
  • 4,046
  • 1
  • 26
  • 45
  • 2
    Buffering is an attribute of the stream's default. You could just turn off the buffering... `cerr` is known as `fprintf(stderr)` in this context. – Matt Joiner Aug 18 '10 at 14:46
2

Technical answer:

when using fork() you need to make sure that exit() is not called twice (falling off of main is the same as calling exit()). The child (or rarely the parent) needs to call _exit instead. Also, don't use stdio in the child. That's just asking for trouble.

Some libraries have a fflushall() you can call before fork() that makes stdio in the child safe. In this particular case it would also make exit() safe but that is not true in the general case.

Joshua
  • 40,822
  • 8
  • 72
  • 132