inode and file descriptor table Interaction
Inode tables interact with user process specific file descriptor tables and global file tables in Unix File System to facilitate the file handling. In this article we would discuss about these table in detail and build an understanding of the above mentioned data structures interaction with each other and in the process we would understand how this has provided us with a framework for the many file handling system calls. We would cover open file handling system calls to understand this interaction:
* Different data structures whose interaction we will discuss in this article
- Inode. Its a complex data-structure that contains all the necessary information to specify a file. It includes the memory layout of the file on disk, file permissions, access time, number of different links to the file etc.
- Global File table. It contains information that is global to the kernel e.g. the byte offset in the file where the user's next read/write will start and the access rights allowed to the opening process.
- Process File Descriptor table. It is local to every process and contains information like the identifiers of the files opened by the process. Whenever, a process creates a file, it gets an index from this table primarily known as File Descriptor.
* OPEN system call
open() system call returns an integer called the file descriptor. Other system calls use this file descriptor for reading, writing, seeking, duplicating, closing the file etc. etc.
Kernel parses the pathname given in the input and finds the inode of the file corresponding to the pathname. Then it allocates an entry into the global file table. This file table contains a pointer to the inode of the open file and a field that indicates the byte offset in the file where the kernel expects the next read/write to begin. It initializes this field to 0 by default, but it would initialize this field to the size of the file in case it is opened in append mode. Kernel then allocates an entry in the private user file-descriptor table and notes the index of this entry. This index itself is returned to the user. The entry in the file descriptor table points to the entry in the global file table.
* Interaction between these tables with an example:
fd1 = open("/var/file1", O_RDONLY);
fd2 = open("/var/file2", O_RDWR);
fd3 = open("/var/file1", O_WRONLY);
fd1 = open("/var/file1", O_RDONLY);
fd2 = open("/var/file3", O_RDONLY);
The following figure represents the interaction between the three tables described above :
Each open() returns a file descriptor to the process, and the corresponding entry in the user file descriptor table points to a unique entry in the global file table even though a file(/var/file1) is opened more then once. These global file table entries map to the in-core inode table entry. Every opened file has a unique entry in the global file table and the user file descriptor table but kernel keeps only single entry per file in the in-core inode table.
Note: In the above discussion, we can see that there is a direct one to one mapping between the file descriptor and the global file table. Which might mislead us to think that why at all this global file table is needed, we can keep the byte offset information in the file descriptor table itself. But, according to Thompson we need a separate global file table for special cases of dup() and fork() system calls. Follow this article on dup() system call.
The first three descriptors i.e. 0, 1 and 2 are called standard input, standard output and standard error file descriptors respectively. But, we have the liberty to change them as per our wish.
- With every open() system call, user gets its own private reference number known as file descriptor.
- Separate entries are created in user file descriptor and global file table, but only the reference count is increased in the inode table.
- all the system calls related to file handling use these tables for data manipulation.
Reference: The Design of the UNIX Operating System - by Maurice J. Bach
Happy Programming !!