The Role of Drivers
It’s not just special files that are implemented by drivers. In a modern system, regular files may be stored on various types of hardware devices with different underlying access semantics; additionally, file metadata and contents can be organized according to a variety of file system formats. On POSIX systems, popular file system formats typically mirror the physical file system model that the kernel uses: A superblock is stored at the beginning of the disk or partition and contains file system metadata like the name of the volume, the size of a sector, the number of inodes, etc. Following the superblock is typically an array of inode structures that is pre-allocated to a certain size when the file system is first created. Finally, the rest of the disk contains actual file data. There are of course many file systems that are not implemented in a way that so directly mirrors the Unix file system model–for example, the NTFS format that Windows uses, and legacy file system formats like FAT.
In order for a system to work with all of these different hardware devices and file system formats, the kernel contains various drivers which translate all of this into the abstract interface that the kernel wants to use. Drivers are standalone modules of code that are added to the kernel; some are so critical that they are built into the kernel itself when it is compiled, and others may be loaded dynamically if the kernel supports that functionality. As part of the kernel, drivers have privileged low-level access to hardware. Accessing a particular file system typically involves at least a hardware driver and a file system driver, specific to that particular file system and storage device.
A hardware driver talks directly to a disk or other storage device and presents a view of it as a contiguous sequence of bytes–like a giant file. This interface is exposed as a block special device file which can be used to directly inspect the contents of the storage device; for example, /dev/sdX
files are generated for SATA disk drives. In some cases, a storage device’s block special file might be used for raw data access without interpreting any file system format. In such a scenario, the special device file can be used to directly access and manipulate that data, mediated by the hardware driver. For example, this scenario occurs when backing up or wiping a disk, which can be accomplished using only raw disk access.
In most cases, however, the raw data is organized into a file system which must be interpreted by a separate file system driver in order for processes to work with that data. The file system driver generates the physical file system objects that we discussed earlier–superblocks, inodes, direntries, and (open) files. The kernel’s virtual file system (VFS) implementation takes these objects and unifies them into a single file system through mount tables, and finally the symbolic file system layer provides the familiar path-based access semantics.
When the underlying file system is Unix-based, the file system driver usually has little work to do, since it can directly load the raw file system data into corresponding physical file system objects for the kernel to use. In other cases, it may need to actively translate between an incompatible file system and the Unix physical file system interface. This means that all of the fundamental file system operations like reading, writing, opening and closing, are also implemented by each file system driver, just as we saw with special file drivers.