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
", 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
"
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.