File Systems and I/O Models
File Systems
Inodes
An inode identifies a file within one file system. It stores metadata such as:
- Permissions, owner UID, and group GID
- File size and timestamps:
ctimefor metadata changes,mtimefor content changes, andatimefor access - Pointers to data blocks
- A link count, which tracks hard links to the inode
To read a regular file, the OS resolves each component of its path through directory entries, finds the file’s inode, and uses its block pointers to locate the data. A symbolic link has its own inode and contains a target path, which the OS resolves in turn.
Hard Links and Symbolic Links
A hard link is another directory entry for the same inode. Removing one name decrements the link count; the file remains accessible through its other names. Hard links generally cannot cross file systems or point to directories.
A symbolic link is a separate file whose contents identify a path. Its target can disappear without removing the link itself.
File Descriptors
A file descriptor is an integer index in a process’s descriptor table. Entries refer to kernel open-file descriptions, which hold state such as the current offset and refer to the underlying file. Several descriptors can share one open-file description. The conventional descriptors are standard input (0), standard output (1), and standard error (2). The descriptor limit depends on the process configuration.
Page Cache
The OS caches recently accessed file data in memory. Reads can be served from this cache. Writes can update cached pages first; dirty pages are written to storage later. fsync asks the OS to synchronize a file’s pending changes rather than waiting for background writeback. Page sizes and writeback behavior depend on the system.
Direct I/O
On Linux, direct I/O can bypass the page cache for file data. This can be useful for applications that manage their own caching, but it comes with alignment and workload constraints.
I/O Models
For a socket read, it helps to distinguish waiting for data to arrive in a kernel buffer from copying available data into a user-space buffer.
Blocking I/O
A blocking read() waits until data is available and the call can complete. The calling thread cannot continue its own work during that wait.
Nonblocking I/O
A nonblocking read() returns immediately if no data is available. The application can retry later, usually after a readiness notification instead of continually polling.
I/O Multiplexing
select, poll, and epoll let one thread wait for readiness on multiple descriptors. Once a descriptor is ready, the thread calls read() or write() to perform the I/O. Readiness means an operation can proceed; it does not mean the data has already been copied to the application.
Signal-Driven I/O
An application can arrange to receive a signal such as SIGIO when a descriptor becomes ready. It then performs the read. This is a readiness notification, so the read itself is still a separate step.
Asynchronous I/O
With an asynchronous I/O interface, the application submits an operation and later receives a completion notification. The particular API and completion mechanism depend on the platform.
Readiness APIs
select
The process passes sets of descriptors to watch for read, write, or exceptional conditions. The OS updates the sets, and the process scans them to find ready descriptors. The number of descriptors is constrained by FD_SETSIZE on typical implementations, and the sets are copied between user and kernel space.
poll
poll uses an array of descriptor records. It avoids select’s fixed descriptor-set size, but the application still passes and scans the array on each call.
epoll
Linux’s epoll keeps a set of registered descriptors in the kernel and returns ready events:
epoll_create1()creates an epoll instance and returns its file descriptor.epoll_ctl()adds, changes, or removes watched descriptors.epoll_wait()waits and fills an array with up tomaxeventsready events.
| |
Here epfd is the epoll instance, events is the output array, and timeout is measured in milliseconds (-1 waits indefinitely, 0 returns immediately). The return value tells the application how many entries of events to process.