Seeking a simple description regarding 'file descriptor' after fork()
Asked Answered
S

2

39

In "Advanced Programming in the Unix Environment", 2nd edition, By W. Richard Stevens. Section 8.3 fork function.

Here's the description:

It is important that the parent and the child share the same file offset.

Consider a process that forks a child, then waits for the child to complete. Assume that both processes write to standard output as part of their normal processing. If the parent has its standard output redirected (by a shell, perhaps) it is essential that the parent's file offset be updated by the child when the child writes to standard output.

My responses:

{1} What does it mean? if parent's std output is redirected to a 'file1' for example, then what should child update after child writes? parent's original std output offset or redirected ouput(i.e file1) offset? Can't be the later, right?

{2} How is the update done? by child explicitly, by OS implicitly, by files descriptor itself? After fork, I thought parent and child went their own ways and has their own COPY of file descriptor. So how does child update offset to parent side?

In this case, the child can write to standard output while the parent is waiting for it; on completion of the child, the parent can continue writing to standard output, knowing that its output will be appended to whatever the child wrote. If the parent and the child did not share the same file offset, this type of interaction would be more difficult to accomplish and would require explicit actions by the parent.

If both parent and child write to the same descriptor, without any form of synchronization, such as having the parent wait for the child, their output will be intermixed (assuming it's a descriptor that was open before the fork). Although this is possible, it's not the normal mode of operation.

There are two normal cases for handling the descriptors after a fork.

  1. The parent waits for the child to complete. In this case, the parent does not need to do anything with its descriptors. When the child terminates, any of the shared descriptors that the child read from or wrote to will have their file offsets updated accordingly.

  2. Both the parent and the child go their own ways. Here, after the fork, the parent closes the descriptors that it doesn't need, and the child does the same thing. This way, neither interferes with the other's open descriptors. This scenario is often the case with network servers.

My response:

{3} When fork() is invoked, all i understand is that child get a COPY of what parent has, file descriptor in this case, and does its thing. If any offset changes to file descriptor that parent and child share, it can only be because the descriptor remember the offset itself. Am I right?

I am kind of new to the concepts.

Superiority answered 31/7, 2012 at 5:11 Comment(0)
D
95

It's important to distinguish between the file descriptor, which is a small integer that the process uses in its read and write calls to identify the file, and the file description, which is a structure in the kernel. The file offset is part of the file description. It lives in the kernel.

As an example, let's use this program:

#include <unistd.h>
#include <fcntl.h>
#include <sys/wait.h>

int main(void)
{
    int fd;

    fd = open("output", O_CREAT|O_TRUNC|O_WRONLY, 0666);

    if(!fork()) {
        /* child */
        write(fd, "hello ", 6);
        _exit(0);
    } else {
        /* parent */
        int status;

        wait(&status);
        write(fd, "world\n", 6);
    }
}

(All error checking has been omitted)

If we compile this program, call it hello, and run it like this:

./hello

here's what happens:

The program opens the output file, creating it if it didn't exist already or truncating it to zero size if it did exist. The kernel creates a file description (in the Linux kernel this is a struct file) and associates it with a file descriptor for the calling process (the lowest non-negative integer not already in use in that process's file descriptor table). The file descriptor is returned and assigned to fd in the program. For the sake of argument suppose that fd is 3.

The program does a fork(). The new child process gets a copy of its parent's file descriptor table, but the file description is not copied. Entry number 3 in both processes' file tables points to the same struct file.

The parent process waits while the child process writes. The child's write causes the first half of "hello world\n" to be stored in the file, and advances the file offset by 6. The file offset is in the struct file!

The child exits, the parent's wait() finishes, and the parent writes, using fd 3 which is still associated with the same file description that had its file offset updated by the child's write(). So the second half of the message is stored after the first part, not overwriting it as it would have done if the parent had a file offset of zero, which would be the case if the file description was not shared.

Finally the parent exits, and the kernel sees that the struct file is no longer in use and frees it.

Damnatory answered 31/7, 2012 at 6:28 Comment(4)
Alan, the explanation is great! Thanks for taking the time and explaining it in such detail for a newbie.Superiority
@AlanCurry What if I don't use wait() in the parent program? Would the file description be free as the parent process exit?Panocha
"gets a copy of its parent's file descriptor table, but the file description is not copied." Is this a specific behavior for file descriptors (the fact that the kernel description is shared)? Because global variables for example are copied, not shared, afaik.Bleat
@Allan Curry, why do you call _exit instead of exit? Could you explain the relations with your answer, please?Carlisle
W
5

In the same section, of book, there is a diagram showing three tables which are there when a file is opened.

The user filedescriptor table(part of the process table entry), filetable and inode table (v-node table). Now a filedescriptor (which is an index to the file descriptor table) entry points to a file table entry,which points to a inode table entry.
Now the file offset (the position from where the next read/write happens) is there in the File table.

So say you have a file opened in parent, that means it is having a descriptor, a file table entry and an inode reference too.
Now when you are creating a child the file descriptor table is copied for the child. So the reference count in the file table entry (for that open descriptor) is increased that means now there are two references for the same file table entry.

This descriptor is now available in both parent and child, pointing to the same File Table entry, hence sharing the offset. Now having this background let us see your questions,

  1. What does it mean? if parent's std output is redirected to a 'file1' for example, then what should child update after child writes? parent's original std output offset or redirected ouput(i.e file1) offset? Can't be the later, right?]

The child explicitly need not update anything. The author of the book is trying to
tell that, suppose parents's standard out put is redirected to a file and the a fork call is made. After that the parent is wating.So the descriptor is now duplicated, that is the file offset is also shared. Now whenever now the child writes anything to standard out, the written data is saved in the redirected file. The offset automatically is incremented by the write call.

Now say the child exits. So the parent come out of waiting and writes something on the standard out(which is redirected). Now where the parent 's write call's output will be placed -> after the data, which was written by the child. Why -> since the offset's current value is now changed after the child has written.

 Parent ( )
  {
    open a file for writing, that is get the 
    descriptor( say fd);
    close(1);//Closing stdout
    dup(fd); //Now writing to stdout  means writing to the file
    close(fd)
        //Create a child that is do a  fork call.
    ret = fork();
    if ( 0 == ret )
    {
        write(1, "Child", strlen("Child");
        exit ..
    }
        wait(); //Parent waits till child exit.

         write(1, "Parent", strlen("Parent");
    exit ..
}

Pl. see the above pseudo code, the final data that the opened file contains will be ChildParent. So you see that the file offset got changed when the child has written and this was avilable to the parent's write call, since the offest is shared.

2.How is the update done? by child explicitly, by OS implicitly, by files descriptor itself? After fork, i thought parent and child went their own ways and has their own COPY of file descriptor. So how does child update offset to parent side?]

Now I think the answer is clear-> by the system call that is by the OS.

[3. When fork() is invoked, all i understand is that child get a COPY of what parent has, file descriptor in this case, and does its thing. If any offset changes to file descriptor that parent and child share, it can only be because the descriptor remember the offset itself. Am i right?]

This should also be clear. The entry of the user file table points to the file table table entry (which contains the offset).

In other words, the system calls can fetch the offset from the descriptor.

Whitebeam answered 31/7, 2012 at 6:55 Comment(1)
"the reference count in the file table entry (for that open descriptor) is increased". Can you point to a reference for this? It means fork copies the parent memory is not a shallow copy.Athenaathenaeum

© 2022 - 2024 — McMap. All rights reserved.