In many cases (as in the examples shown in the previous lesson), the mapping of file descriptor to an entry in the open file table is a one-to-one mapping. For example, when a process runs, it might decide to open a file, read it, and then close it; in this example, the file will have a unique entry in the open file table. Even if some other process reads the same file at the same time, each will have its own entry in the open file table. In this way, each logical reading or writing of a file is independent, and each has its own current offset while it accesses the given file.
However, there are a few interesting cases where an entry in the open file table is shared.
fork()
One of those cases occurs when a parent process creates a child process with fork()
. Shown below is a small code snippet in which a parent creates a child and then waits for it to complete. The child adjusts the current offset via a call to lseek()
and then exits. Finally, after waiting for the child, the parent checks the current offset, and prints out its value.
int main(int argc, char *argv[]) {int fd = open("file.txt", O_RDONLY);assert(fd >= 0);int rc = fork();if (rc == 0) {rc = lseek(fd, 10, SEEK_SET);printf("child: offset %d\n", rc);} else if (rc > 0) {(void) wait(NULL);printf("parent: offset %d\n", (int) lseek(fd, 0, SEEK_CUR));}return 0;}
When we run this program, we see the following output:
prompt> ./fork-seekchild: offset 10parent: offset 10prompt>
The diagram below shows the relationships that connect each process’s private descriptor array, the shared open file table entry, and the reference from it to the underlying file-system inode. Note that we finally make use of the reference count here. When a file table entry is shared, its reference count is incremented; only when both processes close the file (or exit) will the entry be removed.
Sharing open file table entries across parent and child is occasionally useful. For example, if you create a number of processes that are cooperatively working on a task, they can write to the same output file without any extra coordination. For more on what is shared by processes when fork()
is called, please see the man pages.
dup()
One other interesting, and perhaps more useful, case of sharing occurs with the dup()
system call (and its cousins, dup2()
and dup3()
).
The dup()
call allows a process to create a new file descriptor that refers to the same underlying open file as an existing descriptor. Shown below is a small code snippet that shows how dup()
can be used.
int main(int argc, char *argv[]) {int fd = open("README", O_RDONLY);assert(fd >= 0);int fd2 = dup(fd);// now fd and fd2 can be used interchangeablyreturn 0;}
The dup()
call (and, in particular, dup2()
) is useful when writing a UNIX shell and performing operations like output redirection; spend some time and think about why! And now, you are thinking: why didn’t they tell me this when I was doing the shell project? Oh well, you can’t get everything in the right order, even in an incredible book about operating systems. Sorry!
Get hands-on with 1400+ tech skills courses.