Search code examples
clinuxforkposixfile-descriptor

File descriptor handling during Fork syscall


I am new to the world of POSIX and I'm trying to understand how fork syscall works in C, especially how it copies file descriptors and buffers across parent to child. Specifically these two cases:

Case 1: I have a simple program that prints something

printf "start"
fork
parent:
  print something
child 
  print something

This ends up printing "start" once and then the parent and child blocks. Which means fork was flushing i/o buffers before copying them across.

Case 2: Same program but now I am writing to a file instead of stdout. The file results in start printing twice, which is strange to me. Does fork only flush std buffers and not file buffers?

Program used for testing:

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <fcntl.h>

int main() {
    pid_t pid;
    FILE *fd;

    // Open a file in writing mode
    fd = fopen("filename.txt", "w");

    // Write some text to the file
    fprintf(fd, "Started\n");

    // Create a child process
    pid = fork();

    if (pid < 0) {
        // Fork failed
        fprintf(stderr, "Fork failed\n");
        fclose(fd);
        return 1;
    } else if (pid == 0) {
        // This is the child process
        const char *child_message = "Hello from the child process!\n";
        fprintf(fd, child_message);
        fclose(fd); // Close the file descriptor in the child process
        exit(0);
    } else {
        // This is the parent process
        const char *parent_message = "Hello from the parent process!\n";
        fprintf(fd, parent_message);

        // Wait for the child process to complete
        wait(NULL);

        // Write a final message from the parent
        const char *completion_message = "Child process completed.\n";
        fprintf(fd, completion_message);

        fclose(fd); // Close the file descriptor in the parent process
    }

    return 0;
}

Solution

  • First, fork() doesn't flush buffers. (The term "buffer" across my answer refers to the C stdio buffer)

    Second, keep these two points in mind

    1. The output stream is always line buffered if and only if it is connected to a terminal device. Such as stdout and stderr, they both are this case. The output stream is fully buffered otherwise.

    2. fork() copy all the memory from parent to child. (We don't go into COW here for less complexity)

    Now, back to your question.

    In your first example, only one "Start\n" is printed. It is because before fork(), the buffer of stdout has been flushed (line buffered). The buffer has nothing when it is copied to the child process.

    In your second example, two "Start\n" is printed. It is because before fork(), the buffer of the regular file has not been flushed (fully buffered). The buffer containing "Start\n" is copied to the child process.