COMP 3150Operating Systems

01 Introduction

Lesson 10.1 introduced the file and directory abstractions and noted that a file's name is separate from its identity: a directory entry maps a name to an inode number, and the inode number identifies the file within the file system. Lesson 10.2 examined the metadata that the inode holds — the file's type, ownership, permissions, size, timestamps, and the other attributes that the struct stat interface exposes. This lesson turns to the inode itself: the file system's own structure for holding that metadata and for locating the file's data.

The inode (short for "index node") is the central data structure of a classical Unix file system. It is a fixed-size record that contains everything the file system knows about a file except the file's name: its type, its permission bits, its owner, its size, its timestamps, and — most importantly — the pointers to the blocks that hold the file's contents. The inode is what makes a file more than a sequence of bytes: it is the file's identity, its metadata, and its map to its data.

The inode's design is a response to a specific problem: how to represent a file whose size is not known in advance, on a storage device that allocates space in fixed-size blocks, without wasting space on files that are small and without giving up on files that are large. The classical answer is a hybrid pointer scheme: the inode holds a small number of direct pointers that point straight to the file's first data blocks, a single indirect pointer that points to a block full of pointers, a double indirect pointer that points to a block full of single-indirect blocks, and a triple indirect pointer that goes one level deeper. The scheme is efficient for small files (whose data fits in the direct pointers) and scalable for large files (whose data spills into the indirect pointers), and it has been refined but not fundamentally replaced in the decades since it was introduced.

This lesson presents the inode in detail. It defines the inode and the inode number, describes the inode table and the free-inode bitmap, and walks through the classical inode's fields and pointers. It traces the inode's lifetime from allocation to deallocation, explains the role of the link count in that lifetime, and shows how the inode is used in path resolution. It presents the VFS inode cache and the distinction between the on-disk inode and the in-memory inode. It examines the problem of inode exhaustion and the modern inode structures used in ext4, XFS, Btrfs, and ZFS. And it closes with a discussion of the trade-offs that the inode's design embodies and of the directions in which modern file systems are moving.

Prerequisites

This lesson builds directly on Lessons 10.1 and 10.2. The file and directory abstractions, the distinction between a file's name and its identity, and the standard file operations are assumed from Lesson 10.1. The metadata fields — the struct stat structure, the type and mode bits, the timestamps, and the permission-check algorithm — are assumed from Lesson 10.2. The discussion of directory entries in section 4.6 assumes the directory-as-a-file abstraction from Lesson 10.1. No prior knowledge of inodes, indirect block pointers, or the VFS is assumed; all are introduced here. The discussion of free-space management in section 4.4 assumes the fragmentation material from Lesson 8.5, but no new concepts are introduced.

02 Learning objectives

By the end of this lesson you will be able to

  • Define an inode and its role in the Unix file system, and describe the inode table and inode number.
  • Describe the classical inode layout, including the direct, single indirect, double indirect, and triple indirect block pointers.
  • Compute the maximum file size supported by a given inode layout and block size.
  • Explain inode allocation and deallocation, the link count, and the inode's lifetime.
  • Describe the VFS inode cache and the modern inode structures used by ext4, XFS, Btrfs, and ZFS.

This lesson contributes to U10.3 and U10.4, and to course learning outcomes CLO1, CLO5, and CLO6.

03 Required reading

  • Operating System Concepts — Chapter 14, Section 14.2, File-System Implementation. The standard treatment of the inode, the inode table, and the indirect block pointers.
  • OSTEP — Chapter 40, File System Implementation, and Chapter 41, Locality and The Fast File System. OSTEP's treatment of the inode as the file's metadata structure and of the multi-level index is the clearest available, and its discussion of the Fast File System's refinements is the basis for the worked example in this lesson.
  • Tanenbaum & Bos — Modern Operating Systems, Chapter 4, Section 4.3, File-System Implementation. The treatment of the inode, the i-node table, and the directory entry.
  • Silberschatz et al. — Chapter 14, Section 14.2. Read the discussion of the inode's pointers and the maximum file size calculation.
  • McKusick, M. K., Joy, W. N., Leffler, S. J., & Fabry, R. S. — A Fast File System for UNIX. ACM Transactions on Computer Systems, 1984. The paper that refined the classical inode's layout for performance, introduced cylinder groups, and established many of the design principles still in use. Read sections 2 and 3.
  • Bach, M. J. — The Design of the UNIX Operating System. Prentice Hall, 1986. Chapters 4 and 5. The classical reference for the System V inode, the inode table, and the file-system operations.
  • Kerrisk, M. — The Linux Programming Interface, Chapter 14, File Systems, and Chapter 15, File Attributes. The Linux view of the inode and the inode-related interfaces.
  • Love, R. — Linux Kernel Development, 3rd ed., Chapter 13, The Virtual File System. The VFS inode, the inode cache, and the distinction between the on-disk inode and the in-memory inode.
  • Linux man pages: man 2 stat, man 2 statx, man 2 link, man 2 unlink, man 2 rename, man 2 open, man 5 proc (for the /proc/<pid>/fd interface), man 8 tune2fs (for the -l option that reports file-system statistics, including the inode count), man 8 dumpe2fs, man 1 df (for the -i option that reports inode usage).

Reading strategy

Read the OSTEP chapter on file-system implementation first. Its presentation of the inode as a multi-level index, and its calculation of the maximum file size supported by the scheme, are the clearest available account, and they are the basis for the worked example in this lesson. Then read the Fast File System paper for the refinements that turned the classical inode into the inode we use today. The Bach book is the classical reference for the System V inode and the inode table; read it if you want the historical details. Finally, the Love chapter on the VFS is the practical view, showing how the kernel manages the in-memory inodes and how the on-disk inodes are bridged with the VFS interface. Pay particular attention to the distinction between the on-disk inode and the in-memory inode: they are related but not the same, and the difference matters for understanding the kernel's behaviour.

04 Core content

4.1 What is an inode?

An inode (index node) is a fixed-size data structure that contains all the information the file system knows about a file, except the file's name. It is the file's identity, its metadata, and — crucially — the map that locates the file's data on the storage device. Every file has exactly one inode, and every inode belongs to exactly one file.

The inode has three roles in the file system.

  • Identity. The inode's number is the file's identity. The name-to-inode mapping is stored in the directory; the inode number is what the file system uses internally to refer to the file. Two names that refer to the same inode refer to the same file; two names that refer to different inodes refer to different files, even if their contents are identical.
  • Metadata. The inode holds the file's metadata: its type, its permission bits, its owner, its group, its size, its timestamps, and the other attributes that the struct stat interface exposes. This is the metadata that Lesson 10.2 examined; the inode is where it lives.
  • Data map. The inode holds the pointers to the file's data blocks. Without the inode, the file system would not know where on the disk the file's contents are stored. The design of the pointers — the inode layout — determines how large a file can be and how efficiently the file system can access it.

The inode is not the file's name. The name is stored in the directory, as an entry that maps the name to an inode number. The inode number is a 32-bit or 64-bit integer that identifies the inode within the file system's inode table, a contiguous array of inodes on the disk. The inode number is the index into the table: inode k is at position k in the table, so the file system can locate any inode by its number with a single computation.

The separation of the inode from the directory is the key design decision that makes the Unix file model work. It allows a file to have multiple names (hard links), because multiple directory entries can map to the same inode number. It allows a file to be renamed without changing its identity, because the rename changes the directory entry, not the inode. And it allows the file system to look up a file's metadata without reading the directory entry again, because the inode number identifies the inode directly.

4.2 The inode table and inode number

The inode table is the file system's array of inodes. It is a contiguous region of the disk whose size is determined when the file system is created: the administrator chooses how many inodes the file system will have, and the table's size is fixed for the life of the file system. Each inode is a fixed-size record — 128 bytes on ext2, 256 bytes on ext4, larger on some modern file systems — and the inode number is the index into the table.

The number of inodes is fixed at file-system creation time. This is a fundamental constraint of the classical design: a file system can hold at most as many files as it has inodes, regardless of how much free disk space it has. If the inode table is exhausted, the file system cannot create new files, even if there are gigabytes of free data blocks. This is the inode exhaustion problem, and it is a well-known operational issue on filesystems that hold many small files.

The number of inodes is chosen based on the expected workload. A file system that will hold a small number of large files (a database, a media library) needs few inodes and can use the space for data blocks instead. A file system that will hold many small files (a mail spool, a source-code repository, a web cache) needs many inodes and must reserve a proportionally larger fraction of the disk for the inode table. Modern file systems relax this constraint: ext4, for example, can allocate inodes dynamically, and ZFS and Btrfs have no inode table at all (they allocate metadata blocks on demand).

In addition to the inode table, the file system maintains an inode bitmap: a bitmap with one bit per inode, indicating whether the inode is free or allocated. The bitmap is used by the inode allocator to find a free inode when a new file is created. It is typically stored in a fixed location (or, on modern file systems, in a B-tree), and it is updated whenever an inode is allocated or freed.

4.3 The classical inode layout

The classical inode — the one introduced by the original Unix file system and refined by the Berkeley Fast File System — has a layout that is worth examining in detail, because the design has been influential and because the trade-offs it embodies are still relevant.

The inode has four groups of fields:

  1. Metadata. The file's type, mode, owner, group, size, timestamps, and link count. These fields are the ones that Lesson 10.2 examined. They occupy a fixed portion of the inode (typically the first 40 to 100 bytes, depending on the file system's version).
  2. Direct block pointers. A small number of pointers (typically 12 on the classical Unix file system) that point directly to the first data blocks of the file. For a small file — whose contents fit in the direct blocks — these pointers are all that is needed, and the file can be accessed with a single level of indirection.
  3. Indirect block pointers. Three additional pointers that point to blocks containing further pointers: the single indirect pointer points to a block of direct pointers, the double indirect pointer points to a block of single indirect blocks, and the triple indirect pointer points to a block of double indirect blocks. These pointers extend the file's maximum size without requiring the inode to hold an unbounded number of pointers.
  4. Extended attributes and other fields. Modern inodes include additional fields for extended attributes, checksums, and file-system-specific features.
The classical inode layout A diagram showing the classical inode and its pointers to data blocks. The inode contains metadata fields (mode, uid, gid, size, timestamps, link count), twelve direct block pointers, one single indirect pointer, one double indirect pointer, and one triple indirect pointer. The direct pointers point to data blocks 0 through 11. The single indirect pointer points to a block of pointers, each of which points to a data block. The double indirect pointer points to a block of pointers, each of which points to a block of pointers, each of which points to a data block. The triple indirect pointer goes one level deeper. The diagram shows the tree of blocks that the pointers reach. THE CLASSICAL INODE LAYOUT inode fixed-size record on disk metadata fields st_mode — type + mode st_uid — owner's UID st_gid — owner's GID st_size — size in bytes st_atim, st_mtim, st_ctim st_nlink — link count 12 direct pointers direct[0] .. direct[11] each points to a data block single indirect points to a block of pointers double indirect two levels of pointers triple indirect three levels of pointers Direct blocks first 12 blocks of the file data 0 data 1 data 2 … data 11 Single indirect one level of pointer blocks block of pointers data data data … Double indirect two levels of pointer blocks block of pointers to pointer blocks ptr blk ptr blk ptr blk … each pointer block points to more data blocks Triple indirect three levels of pointer blocks block of pointers to double-indirect blocks for very large files only The trade-off Direct pointers make small files fast: no indirection, one disk access to find any block. Indirect pointers make large files possible: the three levels extend the maximum size to thousands of gigabytes. The layout trades one disk access for very large files: accessing a block in a huge file requires up to three or four extra reads.
Figure 10.3 — The classical inode layout. The inode contains metadata fields, twelve direct block pointers, and three indirect block pointers (single, double, and triple). The direct pointers cover the first twelve blocks of the file with no indirection; the indirect pointers cover the rest with one, two, or three levels of pointer blocks. The layout is a trade-off: small files are fast because their blocks are found with a single read, and large files are possible because the indirect pointers extend the maximum size to thousands of gigabytes.

4.4 The maximum file size

The inode's layout determines the maximum size of a file that the file system can hold. The maximum size is the sum of the blocks reachable through the direct pointers, the single indirect pointer, the double indirect pointer, and the triple indirect pointer. The calculation depends on the block size and on the size of a block pointer.

Let B be the block size (typically 4 KB on modern systems) and let P be the size of a block pointer (typically 4 bytes on 32-bit systems and 8 bytes on 64-bit systems). The number of pointers that fit in a block is B / P. Let N be this number — on a 4 KB block size with 4-byte pointers, N = 1024.

The number of blocks reachable through each pointer is:

  • Direct: 12 blocks (assuming 12 direct pointers).
  • Single indirect: N blocks.
  • Double indirect: N2 blocks.
  • Triple indirect: N3 blocks.

The total number of blocks that the inode can address is 12 + N + N2 + N3, and the maximum file size is that number multiplied by the block size B.

For the classical Unix file system with 4 KB blocks and 4-byte pointers (N = 1024), the maximum file size is:

blocks = 12 + 1024 + 1024² + 1024³
       = 12 + 1024 + 1,048,576 + 1,073,741,824
       = 1,074,791,436 blocks

max size = 1,074,791,436 × 4 KB
         ≈ 4.3 TB

The maximum is dominated by the triple indirect pointer, which alone accounts for 4 TB of the total. The direct pointers cover only 48 KB and the single indirect pointer only 4 MB, so a file that fits in 4 MB is accessed with at most one level of indirection, and a file that fits in 4 GB is accessed with at most two. This is the trade-off that makes the classical inode efficient: it uses the simplest possible addressing for the common case (small and medium files) and reserves the more expensive addressing for the rare case (very large files).

The maximum size is not unlimited. A file system with 4 KB blocks and 4-byte pointers cannot hold a file larger than about 4 TB, even with the triple indirect pointer. Modern file systems extend the limit by using larger block sizes, 8-byte pointers (which reduce N to 512 but allow more address space), or entirely different mechanisms — extents, B-trees, or copy-on-write trees — that do not have a fixed upper bound.

4.5 Accessing a block in the classical inode

To access a specific block of a file, the file system must follow the appropriate pointer chain from the inode. The number of disk accesses depends on which chain is used:

Accessing a block of a file through the classical inode
Block offset Pointer chain Disk accesses (inode cached)
0 to 11 direct 1
12 to 12 + 1023 single indirect 2
12 + 1024 to 12 + 1024 + 1024² double indirect 3
beyond that triple indirect 4

The table assumes the inode itself is already in memory (which it usually is, because the file is open), and it counts only the disk accesses needed to read the pointer blocks and the data block. For a file whose block is reachable through the direct pointers, the file system can access the data with a single disk read: the inode is in memory, the pointer is in the inode, and the data block is fetched. For a block in the triple indirect range, the file system must read the triple indirect block, the double indirect block, the single indirect block, and the data block — four disk reads to access one block.

The extra disk accesses are the cost of the multi-level addressing. On a system with a mechanical disk, four reads are much more expensive than one; on an SSD, the difference is smaller but still significant. Modern file systems reduce the cost by using extents — a range of contiguous blocks with a single descriptor — which allows a single pointer to cover many blocks and eliminates the multi-level chains for most files. Extents are the subject of Lesson 10.5 and Lesson 10.7; here we note only that the classical inode's multi-level addressing is one of the design decisions that modern file systems have moved away from.

4.6 Inodes and path resolution

The inode plays a central role in path resolution, the process by which the kernel converts a path into a reference to a file (Lesson 10.1). The resolution processes each path component in turn, and at each step it reads the directory's data (which is a file with its own inode) to find the entry for the next component, then reads the inode that the entry points to.

The steps for a component name in a directory D are:

  1. Read the directory entry. The kernel looks up name in the directory's data (which it accesses through D's inode, using the same multi-level addressing as any other file). The entry contains an inode number, say k.
  2. Locate the inode. The kernel computes the location of inode k in the inode table. The location is a simple function of the inode number and the inode size: inode k is at offset k × sizeof(inode) from the start of the inode table.
  3. Read the inode. The kernel reads the inode from disk (if it is not already in the inode cache) and checks its type, its permissions, and whether it is a directory (if there are further components to process).
  4. Continue with the next component. If there are further components, the kernel uses the inode's data pointers to read the next directory and repeats from step 1.

At each step, the kernel performs one or more disk accesses: one to read the directory's data (through the directory's inode), one to read the inode's metadata, and potentially more if the inode's data spans multiple blocks. The directory entry cache (dcache) and the inode cache (icache) reduce the cost by holding recently used entries in memory: once a path has been resolved, the resolution of the same path (or a path that shares a prefix) is much faster, because the entries are already cached.

The dcache and the icache are the file system's equivalent of the TLB (Lesson 9.3) and the page cache (Lesson 9.4): they hold the results of recent lookups so that subsequent lookups can be served without disk access. On a busy system, the hit rate is very high, and the cost of path resolution is dominated by the cached case.

4.7 Inode lifetime and the link count

An inode's life begins when the file is created and ends when the file is deleted. The inode's lifetime is managed by the link count, the field that records how many hard links point to the inode. The rules are:

  • Creation. When a new file is created, the file system allocates a free inode from the inode bitmap, initializes its fields (type, permissions, owner, timestamps), sets its link count to 1, and adds an entry to the containing directory that maps the file's name to the new inode's number.
  • Link. When a hard link is created (link, or ln without -s), the file system adds a new directory entry that maps the new name to the existing inode's number and increments the inode's link count. The inode itself is not modified in any other way; the file's contents and metadata are shared by both names.
  • Unlink. When a name is removed (unlink, or rm), the file system removes the directory entry and decrements the inode's link count. If the count is now zero, the file system checks whether any process has the file open. If not, the file's data blocks are freed and the inode is marked free. If the count is still positive, the inode is not freed: the file is still reachable through other names.
  • Open files. If a process has the file open when the link count reaches zero, the inode is not freed until the process closes the file. This allows a program to delete a temporary file and continue to use it: the file's name is gone, but the file's contents are still accessible through the open file descriptor. The space is reclaimed when the last file descriptor referring to the file is closed.

The link count is what makes the Unix file model's handling of deletion intuitive. Deleting a file does not immediately destroy it; it removes one of the file's names. The file's contents persist as long as there is some way to reach them — another hard link, or an open file descriptor — and they are freed only when the last reference is gone. This is what allows multiple names for a file, and it is what makes the deletion operation cheap (it updates a few fields in the inode and the directory, rather than walking the file's data blocks).

4.8 The VFS inode cache

The inode on disk is not the same as the inode in memory. The on-disk inode is a fixed-size record in the inode table, optimized for compact storage; the in-memory inode is a kernel data structure (the struct inode in the Linux kernel) that holds the same information plus additional fields used by the kernel: the file's reference count, the device and file-system that own it, pointers to the file's operations, and the state that the kernel needs to manage the open file.

The Linux kernel's Virtual File System (VFS) is the layer that unifies the different file systems the kernel supports. Every file system implements a common interface — a set of operations for reading and writing inodes, directories, and data — and the VFS presents a single, uniform view of the file system to user space. The VFS is what allows a Linux system to mount an ext4 file system, an XFS file system, and a network file system side by side, and to access them all through the same system calls.

The VFS maintains an inode cache (the icache): a hash table of in-memory inodes keyed by the pair (device, inode number). When the kernel needs an inode, it first checks the cache; if the inode is present, the kernel uses the cached copy; if it is not, the kernel reads the on-disk inode, constructs an in-memory inode, and inserts it into the cache. The cache is the reason a subsequent access to a file is faster than the first: the inode is already in memory, and the kernel does not need to read it from disk again.

The inode cache is bounded by a tunable limit, and when the limit is reached, the kernel evicts inodes that are not currently in use. An inode is in use if at least one open file descriptor refers to it, or if it is a directory that is currently being traversed; the kernel does not evict in-use inodes, because doing so would invalidate the references that the open files hold. The eviction policy is a variant of LRU: inodes that have not been accessed recently are the first candidates for eviction.

The inode cache is what makes the file system's operations fast in the common case. On a busy system, the cache hit rate is very high, and the cost of most file operations is dominated by the memory access rather than by the disk access. The same principle applies as for the TLB and the page cache: the file system's performance is determined by the locality of the workload, and the caches are what turn that locality into speed.

4.9 Inode exhaustion

The classical file system's inode table is fixed in size: the number of inodes is chosen when the file system is created and does not change. This creates a problem that is easy to overlook: a file system can run out of inodes while it still has plenty of free data blocks. When the inode table is exhausted, the file system cannot create new files, even if it has gigabytes of free space.

The problem is well-known in practice. A mail spool, a source-code repository, a web cache, and a directory of small images are all workloads that create many small files and consume inodes rapidly. On a traditional Unix file system with a fixed inode table, such a workload can exhaust the inodes long before the data blocks. The symptom is confusing: df reports plenty of free space, but every attempt to create a new file fails with ENOSPC (no space left on device).

The classic remedies are to increase the inode count (by reformatting the file system with a larger table, which is destructive), to move the workload to a file system with more inodes, or to change the workload's structure (for example, by storing many small items in a database rather than as individual files). The modern remedy is to use a file system whose inode count is not fixed: ext4 allocates inodes dynamically (within the constraints of its block groups), and Btrfs and ZFS allocate metadata on demand, with no inode table at all.

Inode exhaustion is a useful illustration of a general principle: the fixed partitioning of a resource into separate pools is a design decision that has consequences. The classical file system separates the inode table (a fixed pool of metadata) from the data block area (a fixed pool of data), and the two pools can be exhausted independently. Modern file systems blur the distinction by allocating metadata from the same free space as data, which makes the resource more flexible but which also makes the file system's management more complex. The trade-off is between simplicity and flexibility, and the modern file systems have chosen flexibility.

4.10 Modern inode structures

Modern file systems have moved away from the classical inode's fixed layout and multi-level pointers, but they retain the inode as a concept: a per-file structure that holds the file's metadata and locates its data. The differences are in how the structure is stored and how the file's data is located.

ext4
The default Linux file system for many years. Ext4 uses a variable-size inode (typically 256 bytes) and supports two allocation schemes: the classical multi-level pointers (for compatibility with ext2 and ext3) and extents (for new files). An extent is a descriptor of a range of contiguous blocks; a single extent can cover many blocks, which is much more efficient than a single pointer per block. Ext4 also supports delayed allocation, which allows the file system to allocate blocks in larger runs, and it supports extents for the inode's data, so the multi-level pointer chains are used less often.
XFS
A high-performance file system originally developed by SGI. XFS uses a variable-size inode stored in a B-tree, and it uses extents for data allocation. The B-tree structure allows the inode to grow as needed: a file with many extents has a larger inode than a file with few, and the file system allocates metadata blocks to hold the additional extents. The result is that XFS has no fixed upper bound on the number of extents a file can have, and it can support very large files and very fragmented files efficiently.
Btrfs
A modern copy-on-write file system for Linux. Btrfs does not have a separate inode structure: the file's metadata is stored in a B-tree (the fs tree), and the tree is updated atomically by copy-on-write. When a file is modified, the B-tree nodes on the path from the root to the modified leaf are copied and updated, and the new versions are written to new locations on disk. The old versions remain until the tree is committed, which gives Btrfs its crash-consistency guarantees and its snapshot capability. The absence of a separate inode table means that Btrfs cannot run out of inodes independently of its data space: both are allocated from the same free space.
ZFS
A file system originally developed by Sun Microsystems and now maintained by the OpenZFS project. Like Btrfs, ZFS uses copy-on-write and a tree of metadata blocks, and it has no separate inode table. A ZFS file is represented by a dnode, a structure analogous to an inode but stored within the file system's metadata tree. The dnode holds the file's attributes and the pointers to its data blocks, and it can be updated, moved, and copied by the file system's copy-on-write machinery. ZFS also provides checksums on all data and metadata, and it uses RAID-Z for redundancy, so it is significantly more complex than the classical inode but also more reliable.

The common trend across modern file systems is a move away from the fixed-size, fixed-location, multi-level inode and toward a variable-size, tree-based structure that can grow as needed and that is updated by copy-on-write. The change gives the file systems more flexibility and better crash-consistency, at the cost of more complex internal structures and more metadata overhead. The inode as a concept — a per-file metadata structure that identifies the file and locates its data — remains the same.

4.11 Common misconceptions

"An inode contains the file's name."

The name is stored in the directory, not in the inode. The inode holds the file's identity (its inode number), its metadata, and its data pointers, but not its name.

"An inode contains the file's data."

The inode contains pointers to the file's data, not the data itself. The data is stored in separate blocks on the disk, which the inode's pointers locate.

"Every file system has inodes."

The concept of a per-file metadata structure is universal, but the structure is not always called an inode. ZFS uses a dnode; Btrfs stores metadata in B-trees; the classical Unix file system uses the inode. The name is a convention, not a requirement.

"The inode number is the file's location on disk."

The inode number is the file's identity within the file system, not its location. It is an index into the inode table, and from the index the file system computes the inode's location. The file's data blocks are located through the inode's pointers, not through the inode number.

"A file with a large logical size always uses many data blocks."

Not necessarily. A sparse file (Lesson 10.2) can have a large logical size and few data blocks, because the holes in the file are not allocated.

"The number of inodes is unlimited."

On classical file systems, the number of inodes is fixed at creation time, and a file system can run out of inodes while it still has free data blocks. On modern file systems (ext4, Btrfs, ZFS), inodes are allocated dynamically and the limit is much less restrictive.

"The classical inode's pointer scheme is obsolete."

The classical inode's multi-level pointer scheme has been largely replaced by extents in modern file systems, but the inode concept — a per-file structure that holds metadata and locates data — remains, and the classical scheme is still used on files that were created by older versions of ext4 or its predecessors.

"The kernel reads the inode from disk on every access."

The kernel maintains an inode cache (the icache) that holds recently used inodes in memory. On a busy system, the cache hit rate is very high, and most accesses are served from the cache without any disk access.

05 Worked example

The following example computes the maximum file size of a classical inode, traces the disk accesses for three different block offsets, and demonstrates the inode's lifetime across a sequence of operations.

Maximum file size for a classical inode

Consider a classical Unix file system with a 4 KB block size and 4-byte block pointers. The inode has 12 direct pointers, and one pointer each for the single, double, and triple indirect blocks. We compute the maximum file size.

The number of pointers that fit in a block is:

N = block_size / pointer_size
  = 4096 / 4
  = 1024

The number of blocks reachable through each pointer class is:

Pointer class Blocks reachable Bytes reachable
Direct (12 pointers) 12 48 KB
Single indirect 1,024 4 MB
Double indirect 1,048,576 4 GB
Triple indirect 1,073,741,824 4 TB
Total 1,074,791,436 ≈ 4.3 TB

The maximum file size is dominated by the triple indirect pointer, which alone accounts for 4 TB of the total. The direct pointers cover only 48 KB, and the single indirect pointer only 4 MB. This is the reason the classical inode is efficient for typical files: a file that fits in 48 KB uses no indirect blocks at all, a file that fits in 4 MB uses at most one, and only files larger than 4 MB need the double indirect pointer.

Now consider the same file system but with 8-byte block pointers (as on a modern 64-bit system). The number of pointers per block is halved (N = 512), and the maximum file size changes:

Pointer class Blocks reachable (N=512) Bytes reachable
Direct (12 pointers)1248 KB
Single indirect5122 MB
Double indirect262,1441 GB
Triple indirect134,217,728512 GB
Total134,480,396≈ 513 GB

The maximum file size drops from 4.3 TB to 513 GB because each pointer is larger and fewer pointers fit in a block. This is a real constraint: the classical Unix file system's 4.3 TB limit assumes 32-bit pointers, and a 64-bit system with the same inode layout would have a much smaller maximum. Modern file systems address this by using extents (which cover many blocks with a single descriptor) rather than indirect pointers.

Accessing a block through the inode

Consider the same file system with 4 KB blocks and 4-byte pointers. We trace the disk accesses needed to read three specific blocks of a file, assuming the inode is already in memory (because the file is open):

Block offset Chain Disk accesses What is read at each access
5 direct 1 data block 5 (through direct[5])
500 single indirect 2 1. the single indirect block; 2. the data block
500,000 double indirect 3 1. the double indirect block; 2. the single indirect block; 3. the data block
500,000,000 triple indirect 4 1. the triple indirect block; 2. the double indirect block; 3. the single indirect block; 4. the data block

The pattern is clear. Accessing a block in the direct range costs one disk access, in the single-indirect range two, in the double-indirect range three, and in the triple-indirect range four. The extra accesses are the price of the multi-level addressing, and they are why the classical inode's performance degrades as the file's offset increases.

On a modern file system with extents, the calculation is different. A file with a single extent (its blocks are contiguous on disk) can be accessed with one or two disk accesses regardless of the block's offset, because the extent descriptor in the inode specifies the range of blocks directly. Only files with many extents (because they are fragmented) need additional accesses, and even then the number is bounded by the number of extents, not by the file's size.

Inode lifetime across a sequence of operations

We trace the inode's state through a sequence of operations on a file.

Step Operation Link count Open count Inode state
0 — 0 0 free (in the free bitmap)
1 open("file", O_CREAT) 1 1 allocated; directory entry added
2 write(fd, ..., n) 1 1 data written; size, mtime, ctime updated
3 link("file", "alias") 2 1 new directory entry; ctime updated
4 unlink("file") 1 1 directory entry removed; ctime updated
5 close(fd) 1 0 still allocated (link count 1)
6 unlink("alias") 0 0 freed; data blocks released

The trace shows the interplay of the link count and the open count. The inode is allocated as long as either count is positive; it is freed only when both are zero. At step 4, the file's original name is removed, but the inode is not freed because the link count is still positive (through the alias) and the open count is positive (the file is still open). At step 6, the last link is removed and the file is no longer open, so the inode is freed and the data blocks are released.

A different sequence illustrates the "delete an open file" pattern that is common in Unix:

Step Operation Link count Open count Inode state
1fd = open("temp", O_CREAT)11allocated
2unlink("temp")01not freed: file is open
3write(fd, ..., n)01still accessible via fd
4read(fd, ..., n)01still accessible via fd
5close(fd)00freed; data blocks released

This is the Unix idiom for a temporary file: create a file, open it, unlink it immediately, and use the open file descriptor for the rest of the program's life. The file has no name, but it has an open file descriptor, and the inode is kept alive by the descriptor. When the program closes the descriptor (or exits, which closes all descriptors automatically), the file's space is reclaimed. The idiom is used by tmpfile() in C, by the O_TMPFILE flag in Linux, and by many applications that need a scratch file.

Demonstrating inode exhaustion

Inode exhaustion is easy to demonstrate on a small file system. Create a small file system with a limited number of inodes, mount it, and create many small files until the inode table is exhausted. The df -i command reports the inode usage.

# Create a 20 MB file system with a small inode count
dd if=/dev/zero of=/tmp/small.img bs=1M count=20
mkfs.ext4 -N 1000 /tmp/small.img    # 1000 inodes

# Mount it
sudo mkdir /mnt/small
sudo mount -o loop /tmp/small.img /mnt/small

# Check the inode usage
df -i /mnt/small
# Filesystem    Inodes IUsed IFree IUse% Mounted on
# /dev/loop0      1000    11   989    2% /mnt/small

# Create files until the inode table is exhausted
cd /mnt/small
for i in $(seq 1 1000); do
    touch file$i 2>&1 | head -1
done

# Check the inode usage again
df -i /mnt/small
df -h /mnt/small    # data blocks are still free

# Clean up
cd /
sudo umount /mnt/small
rm /tmp/small.img

After creating the files, df -i shows that the inode usage is at or near 100 percent, while df -h shows that plenty of data blocks are still free. The next attempt to create a file fails with ENOSPC, even though the file system has free space. This is the inode exhaustion problem in action.

The failure message is the same as the one for running out of data blocks, which is why inode exhaustion is sometimes confusing: the user sees "no space left on device" and checks the free space, which is fine. The remedy is to check df -i as well as df -h.

06 Interactive activity

Activity · explore inodes on a live system

The goal of this activity is to use the Linux command line to observe inodes in action, to examine their layout, and to demonstrate the inode's role in file identity and lifetime. Work through the steps in your course VM.

  1. Observe inode numbers. Use ls -i and stat to see the inode number of files, and confirm that the number identifies the file regardless of its name.
    ls -li /etc/passwd
    ls -li /etc/passwd /etc/shadow
    stat -c 'inode=%i links=%h name=%n' /etc/passwd /bin/ls /tmp
    Create a file, note its inode number, rename it, and confirm that the inode number is unchanged.
    touch /tmp/inode-test.txt
    stat -c 'inode=%i name=%n' /tmp/inode-test.txt
    mv /tmp/inode-test.txt /tmp/inode-test-renamed.txt
    stat -c 'inode=%i name=%n' /tmp/inode-test-renamed.txt
  2. Examine the inode table. Use df -i and tune2fs -l to see the inode table's size, usage, and free count.
    df -i /
    df -i /tmp
    sudo tune2fs -l /dev/sda1 2>/dev/null | grep -i inode
    sudo dumpe2fs /dev/sda1 2>/dev/null | grep -i inode | head
    The df -i output reports the total inodes, the used inodes, the free inodes, and the percentage used. The tune2fs output reports additional file-system parameters, including the inode size, the inodes per group, and the first inode.
  3. Examine the inode's metadata. Use stat to see all the metadata that the inode holds, and identify each field.
    stat /etc/passwd
    stat /bin/ls
    stat /tmp
    For each file, identify: its inode number, its size, its block count, its mode, its owner, its link count, and its three timestamps. Note that the stat output is the user-space view of the inode's contents.
  4. Demonstrate the link count. Create a file, create a hard link to it, and observe how the link count changes.
    echo "hello" > /tmp/link-demo.txt
    stat -c 'inode=%i links=%h' /tmp/link-demo.txt
    
    ln /tmp/link-demo.txt /tmp/link-demo-alias.txt
    stat -c 'inode=%i links=%h' /tmp/link-demo.txt
    
    rm /tmp/link-demo.txt
    stat -c 'inode=%i links=%h' /tmp/link-demo-alias.txt
    
    rm /tmp/link-demo-alias.txt
    Observe that both names have the same inode number, that creating the link increments the count, and that removing one name decrements it without deleting the file (the file is still accessible through the other name).
  5. Demonstrate the "delete an open file" pattern. Write a program that creates a temporary file, unlinks it, and continues to use it through the open file descriptor.
    #include <fcntl.h>
    #include <stdio.h>
    #include <unistd.h>
    
    int main(void) {
        int fd = open("tempfile", O_RDWR | O_CREAT | O_TRUNC, 0600);
        if (fd < 0) { perror("open"); return 1; }
    
        /* write some data */
        write(fd, "hello, world\n", 13);
    
        /* unlink the file — but the inode is still alive */
        if (unlink("tempfile") == -1) { perror("unlink"); return 1; }
    
        /* the file is gone from the directory but still accessible */
        system("ls -l tempfile 2>&1");
    
        /* seek back to the beginning and read the data */
        lseek(fd, 0, SEEK_SET);
        char buf[32];
        ssize_t n = read(fd, buf, sizeof(buf) - 1);
        buf[n] = '\0';
        printf("read: %s", buf);
    
        /* close the file — now the inode is freed */
        close(fd);
        return 0;
    }
    Compile and run the program. Observe that the file is gone from the directory, but the data is still accessible through the file descriptor. When the program closes the descriptor, the inode is freed and the space is reclaimed.
  6. Demonstrate inode exhaustion. Create a small file system with a limited number of inodes, mount it, and create files until the inode table is exhausted. Observe the error messages.
    # Create a 20 MB file system with 1000 inodes
    dd if=/dev/zero of=/tmp/small.img bs=1M count=20
    mkfs.ext4 -N 1000 /tmp/small.img
    
    # Mount it
    sudo mkdir -p /mnt/small
    sudo mount -o loop /tmp/small.img /mnt/small
    
    # Check the inode usage
    df -i /mnt/small
    df -h /mnt/small
    
    # Create files until the inode table is exhausted
    cd /mnt/small
    for i in $(seq 1 1100); do
        touch file$i 2>/dev/null || { echo "failed at file$i"; break; }
    done
    
    # Check the inode usage again
    df -i /mnt/small
    df -h /mnt/small
    
    # Clean up
    cd /
    sudo umount /mnt/small
    rm /tmp/small.img
    After the loop, df -i should show nearly 100 percent inode usage while df -h shows plenty of free space. The error message on the failed creation is "No space left on device", which is confusing given the free space — this is the inode exhaustion problem.
  7. Observe the inode cache. The Linux kernel exposes the inode cache through the /proc/sys/fs/inode-* files.
    cat /proc/sys/fs/inode-nr
    cat /proc/sys/fs/inode-state
    cat /proc/sys/fs/inode-max 2>/dev/null
    The inode-nr file reports the number of inodes currently allocated; the inode-state file reports additional information, including the number of free inodes (that the kernel can evict) and the number of in-use inodes. Run a workload that opens many files and observe the counters.
  8. Read the man pages. Read the man pages that document the inode-related interfaces.
    man 2 stat
    man 2 link
    man 2 unlink
    man 2 rename
    man 8 tune2fs
    man 8 dumpe2fs
    man 1 df
    For each, write a sentence describing its main contribution to your understanding of the inode. The man 2 link and man 2 unlink pages document the operations that change the link count; the man 8 tune2fs and man 8 dumpe2fs pages document the tools for inspecting the inode table; and the man 1 df page documents the tool for checking inode usage.

Reflect: the inode is one of those structures that is simple enough to be understood in a few minutes but whose consequences are subtle enough to take years to appreciate. The separation of the inode from the directory, the multi-level pointer scheme, the link count, the in-memory cache, and the fixed size of the inode table — each of these design decisions has consequences that reach deep into the system's behaviour. Which of them do you think is the most important? Which would you change if you were designing a file system today? And what would be the cost of the change?

07 Knowledge check

Check your understanding

Question 1

Define an inode and describe its three roles in the file system. What is the inode table, and what is the inode number?

Show answer
An inode (index node) is a fixed-size data structure that contains all the information the file system knows about a file, except the file's name. It has three roles. (1) Identity. The inode's number is the file's identity. Two names that refer to the same inode refer to the same file; two names that refer to different inodes refer to different files. (2) Metadata. The inode holds the file's type, permission bits, owner, group, size, timestamps, and link count — the attributes that the struct stat interface exposes. (3) Data map. The inode holds the pointers to the file's data blocks: the direct pointers, and the indirect pointers (single, double, and triple) that extend the file's maximum size. The inode table is the file system's array of inodes. It is a contiguous region of the disk whose size is determined when the file system is created, and each inode is a fixed-size record within it. The inode number is the index into the inode table: inode k is at position k in the table, so the file system can locate any inode by its number with a single computation. The number of inodes is fixed at file-system creation time on classical file systems, which means the file system can hold at most as many files as it has inodes, regardless of the amount of free data blocks. Modern file systems (ext4, Btrfs, ZFS) relax this constraint by allocating inodes dynamically.

Question 2

Describe the classical inode's layout, including the direct and indirect pointers. Compute the maximum file size for a file system with a 4 KB block size and 4-byte pointers.

Show answer
The classical inode has three groups of pointers. The direct pointers (typically 12) point directly to the first data blocks of the file. The single indirect pointer points to a block full of pointers, each of which points to a data block. The double indirect pointer points to a block full of single-indirect blocks, so a block reachable through it requires two levels of pointer blocks between the inode and the data. The triple indirect pointer goes one level deeper. The layout is a trade-off: small files (whose data fits in the direct pointers) are accessed with no indirection, and large files (whose data spills into the indirect pointers) are accessed with one, two, or three levels of indirection. For a file system with a 4 KB block size and 4-byte pointers, the number of pointers per block is 4096 / 4 = 1024. The blocks reachable through each pointer class are:
  • Direct: 12 blocks = 48 KB
  • Single indirect: 1024 blocks = 4 MB
  • Double indirect: 1024² = 1,048,576 blocks = 4 GB
  • Triple indirect: 1024³ = 1,073,741,824 blocks = 4 TB
The total is approximately 4.3 TB. The maximum is dominated by the triple indirect pointer, which alone accounts for 4 TB. The direct and single indirect pointers cover only 48 KB and 4 MB respectively, which is why the classical inode is efficient for typical files: most files use only the direct pointers, and only files larger than 4 MB need the double indirect pointer.

Question 3

Explain the inode's lifetime and the role of the link count. What happens when a file is unlinked while it is still open by a process?

Show answer
An inode's life begins when the file is created and ends when the file is deleted. The link count (st_nlink) is the number of hard links that point to the inode, and it governs the inode's lifetime. When the file is created, the file system allocates a free inode, initializes its fields, sets the link count to 1, and adds a directory entry that maps the file's name to the inode's number. When a hard link is created, the file system adds a new directory entry and increments the link count; the inode itself is not otherwise modified, so both names refer to the same file. When a name is removed (unlink), the file system removes the directory entry and decrements the link count. If the count is now zero, the file system checks whether any process has the file open. If not, the file's data blocks are freed and the inode is marked free. If the count is still positive, the inode is not freed: the file is still reachable through other names. If a process has the file open when the link count reaches zero, the inode is not freed until the process closes the file. This is the "delete an open file" pattern, and it is used for temporary files:
fd = open("temp", O_RDWR | O_CREAT);
unlink("temp");      /* the file is gone from the directory */
write(fd, ...);       /* but the data is still accessible */
read(fd, ...);        /* through the file descriptor */
close(fd);            /* now the inode is freed */
The idiom is supported by the kernel because the inode is kept alive by the open file descriptor even after the link count reaches zero. This is what makes temporary files easy to manage: the application does not need to remember to delete them, because they are already gone from the directory. When the program closes the file (or exits, which closes all descriptors), the space is reclaimed.

Question 4

What is the VFS inode cache? How does it relate to the on-disk inode, and what is the difference between the on-disk inode and the in-memory inode?

Show answer
The VFS inode cache (the icache) is the Linux kernel's cache of in-memory inodes. It is a hash table keyed by the pair (device, inode number), and it holds the in-memory inodes that the kernel has recently used. When the kernel needs an inode, it first checks the cache; if the inode is present, the kernel uses the cached copy; if it is not, the kernel reads the on-disk inode, constructs an in-memory inode, and inserts it into the cache. The cache is bounded by a tunable limit, and when the limit is reached, the kernel evicts inodes that are not currently in use (an inode is in use if at least one open file descriptor refers to it, or if it is a directory being traversed). The relationship between the on-disk inode and the in-memory inode is that the latter is a richer, kernel-specific structure that holds the same information as the former plus additional fields: the inode's reference count, the device and file-system that own it, pointers to the file's operations (the file system's inode operations table), and the state that the kernel needs to manage the open file. The on-disk inode is a fixed-size record in the inode table, optimized for compact storage. The in-memory inode is a variable-size structure (the struct inode) that exists only while the file is in use or cached, and is discarded when the file is closed and the cache's space is needed. The distinction matters because the kernel's operations on the inode (such as the permission check and the data-block lookup) work on the in-memory inode, and the on-disk inode is read and written only when the in-memory inode is loaded or flushed. The VFS is what allows the kernel to support many different file systems through the same interface: each file system provides its own functions for reading and writing its inodes, and the VFS presents a uniform view to the rest of the kernel.

Question 5

What is inode exhaustion? Why does the classical file system have this problem, and how do modern file systems address it?

Show answer
Inode exhaustion is the situation in which a file system runs out of inodes while it still has free data blocks. Because the classical file system's inode table is fixed in size — the number of inodes is chosen when the file system is created and does not change — a file system can hold at most as many files as it has inodes. A workload that creates many small files (a mail spool, a source-code repository, a web cache, a directory of small images) can exhaust the inode table long before it exhausts the data blocks. The symptom is confusing: df -h reports plenty of free space, but every attempt to create a new file fails with ENOSPC ("no space left on device"). The error message is the same as the one for running out of data blocks, so the user must check df -i as well as df -h to diagnose the problem. The classical remedies are to increase the inode count (which requires reformatting the file system, a destructive operation), to move the workload to a different file system, or to change the workload's structure (for example, by storing many small items in a database rather than as individual files). Modern file systems address the problem in two ways. First, some file systems (ext4) allocate inodes dynamically from within their block groups, so the inode count is not fixed at creation and can grow as needed (within the constraints of the file system's layout). Second, some file systems (Btrfs, ZFS) have no separate inode table at all: their metadata is stored in trees (B-trees or copy-on-write trees), and the metadata blocks are allocated from the same free space as data. The distinction between "metadata space" and "data space" is therefore blurred, and the file system cannot run out of one independently of the other. The change gives the file system more flexibility at the cost of more complex internal structures.

08 Unit discussion

Discussion prompt

The inode is one of the most influential data structures in the history of computing. It was designed for the original Unix file system in the early 1970s, refined by the Berkeley Fast File System in the 1980s, and it has been adapted and extended by every Unix-like file system since. Its design decisions — the separation of the inode from the directory, the fixed size of the inode record, the multi-level pointer scheme, the link count as the file's lifetime reference — have shaped the way file systems work for five decades.

But the inode is not a perfect design. Its fixed size limits the number of pointers it can hold, which limits the maximum file size and the number of extents a file can have. Its fixed location in the inode table means that the inode's position is determined by its number, which makes it hard to allocate inodes in a way that matches the file's data layout. And its fixed count in the inode table means that a file system can run out of inodes before it runs out of data, which is a well-known operational problem.

Modern file systems have moved away from the classical inode's fixed structure in several directions: variable- size inodes (XFS), extents instead of indirect pointers (ext4, XFS), B-tree-based metadata (XFS, Btrfs), and copy-on-write trees (Btrfs, ZFS). Each of these changes addresses one or more of the classical inode's limitations, at the cost of more complex internal structures and more metadata overhead.

Which of the classical inode's design decisions do you think is the most worth preserving, and which is the most worth changing? Is there a fundamental reason that file systems still have a per-file metadata structure, or is the inode a historical accident that future systems could do without? Consider the cloud storage services that have displaced traditional file systems for many workloads: they typically have a much simpler metadata model, and they sacrifice the classical inode's richness for scalability and simplicity. Is the classical inode the right structure for the next decade? Post your response to the Unit 10 discussion forum and reply to at least two classmates. Supports CLO1, CLO5, and CLO6.

09 Practice exercise

This ungraded exercise consolidates the lesson and prepares you for Lesson 10.4. Aim to spend 60–90 minutes on it.

  1. Compute the maximum file size. For each of the following file-system configurations, compute the maximum file size supported by a classical inode with 12 direct pointers and the three indirect pointers:
    • 1 KB blocks, 4-byte pointers
    • 2 KB blocks, 4-byte pointers
    • 4 KB blocks, 4-byte pointers
    • 4 KB blocks, 8-byte pointers
    • 8 KB blocks, 8-byte pointers
    • 16 KB blocks, 8-byte pointers
    Present the results in a table with the number of pointers per block (N), the number of blocks reachable through each pointer class, and the total maximum size.
  2. Trace a block access. For a file system with a 4 KB block size and 4-byte pointers (12 direct pointers), state which pointer chain is used to access each of the following block offsets, and how many disk accesses are needed (not counting the inode itself):
    • 0
    • 11
    • 12
    • 1023
    • 1035
    • 1,048,588
    • 1,073,742,000
    For each, show the calculation.
  3. Trace the inode's lifetime. A program performs the following operations on a file. Trace the inode's link count and open count after each step, and state whether the inode is allocated or free.
    • open("f", O_CREAT) → fd1
    • link("f", "g")
    • open("g", O_RDONLY) → fd2
    • unlink("f")
    • close(fd1)
    • unlink("g")
    • close(fd2)
    At which step is the inode freed? What would have happened if the unlink("g") had been performed before the close(fd1)?
  4. Demonstrate the delete-an-open-file pattern. Write a C program that creates a temporary file, unlinks it, writes to it, seeks back, reads the data back, and then closes it. Verify with ls that the file is gone from the directory while it is open. What does /proc/<pid>/fd/ show for the open file descriptor?
    #include <fcntl.h>
    #include <stdio.h>
    #include <unistd.h>
    
    int main(void) {
        int fd = open("tempfile", O_RDWR | O_CREAT | O_TRUNC, 0600);
        unlink("tempfile");
    
        write(fd, "hello, world\n", 13);
    
        /* Observe the file descriptor */
        char path[64];
        snprintf(path, sizeof(path), "/proc/%d/fd/%d",
                 getpid(), fd);
        char target[256];
        ssize_t n = readlink(path, target, sizeof(target) - 1);
        if (n > 0) { target[n] = '\0'; printf("fd → %s\n", target); }
    
        lseek(fd, 0, SEEK_SET);
        char buf[32];
        n = read(fd, buf, sizeof(buf) - 1);
        buf[n] = '\0';
        printf("read: %s", buf);
    
        close(fd);
        return 0;
    }
    The readlink on /proc/<pid>/fd/<fd> will show something like /path/to/tempfile (deleted) — the file's name is gone but the inode is still alive.
  5. Demonstrate inode exhaustion. Follow the steps in the worked example to create a small file system with a limited number of inodes, mount it, and exhaust the inode table. Then compute the number of inodes per megabyte of storage, and compare it to the number on your root file system (using tune2fs -l and df -h). What is the inodes-per-byte ratio? Which workload would exhaust the file system first: many small files or a few large files?
  6. Compare inode structures. Write a short paragraph (200–250 words) comparing the classical Unix inode with the modern structures used by ext4, XFS, Btrfs, and ZFS. Your paragraph should address: (a) the size and location of the metadata structure; (b) how the file's data blocks are located (indirect pointers, extents, B-trees, copy-on-write trees); (c) whether the file system has a fixed inode table; (d) the trade-offs of each design.
  7. Investigate the VFS inode cache. On Linux, the kernel's inode cache statistics are available in /proc/sys/fs/inode-*. Run a workload that opens many files (for example, a recursive find over the file system), and observe how the counters change.
    cat /proc/sys/fs/inode-nr
    find /usr -type f 2>/dev/null | head -10000
    cat /proc/sys/fs/inode-nr
    cat /proc/sys/fs/inode-state
    What does inode-nr report? What is the difference between the first number and the second number in inode-state? How does the cache behave after a large number of file accesses?
  8. Reflect. In your own words, explain why the classical inode's pointer scheme is a trade-off. What does it optimize for, and what does it sacrifice? Then describe one situation in your own experience — a program you have written or a system you have used — where the inode's design mattered. What was the situation, and what did the inode's design make easy or hard?

10 Summary

  • An inode is a fixed-size data structure that contains all the information the file system knows about a file, except the file's name. It has three roles: it is the file's identity (its number identifies the file within the file system), it holds the file's metadata (the fields that struct stat exposes), and it provides the data map (the pointers to the file's data blocks).
  • The inode table is the file system's array of inodes. It is a contiguous region of the disk whose size is fixed at file-system creation time, and the inode number is the index into the table. The classical file system's inode table is fixed in size, which creates the inode exhaustion problem.
  • The classical inode's layout consists of metadata fields, 12 direct block pointers, one single indirect pointer, one double indirect pointer, and one triple indirect pointer. The direct pointers cover the first 12 blocks of the file with no indirection; the indirect pointers cover the rest with one, two, or three levels of pointer blocks.
  • The maximum file size supported by the classical inode is the sum of the blocks reachable through the four pointer classes. For a 4 KB block size and 4-byte pointers, it is approximately 4.3 TB. The maximum is dominated by the triple indirect pointer; the direct pointers cover only 48 KB.
  • Accessing a block of a file requires 1 to 4 disk accesses depending on the block's offset: 1 for a direct block, 2 for a single-indirect block, 3 for a double-indirect block, and 4 for a triple-indirect block. The extra accesses are the price of the multi-level addressing.
  • The inode plays a central role in path resolution: at each step, the kernel reads the directory entry (through the directory's inode), obtains the inode number, and reads the inode. The directory entry cache (dcache) and the inode cache (icache) reduce the cost of repeated resolutions.
  • The inode's lifetime is governed by the link count and the open count. An inode is allocated as long as either count is positive, and it is freed only when both reach zero. The "delete an open file" pattern uses this behaviour for temporary files.
  • The VFS inode cache (the icache) is the Linux kernel's cache of in-memory inodes, keyed by the pair (device, inode number). The on-disk inode is a fixed-size record in the inode table; the in-memory inode is a richer structure that holds the same information plus the kernel's own state.
  • Inode exhaustion is the situation in which a file system runs out of inodes while it still has free data blocks. It is a consequence of the classical file system's fixed inode table. Modern file systems (ext4, Btrfs, ZFS) address it by allocating metadata dynamically.
  • Modern inode structures have moved away from the classical inode's fixed layout and multi-level pointers. Ext4 uses variable-size inodes and extents. XFS uses a B-tree-based inode with extents. Btrfs and ZFS use copy-on-write trees with no separate inode table at all. The inode as a concept — a per-file structure that holds metadata and locates data — remains.

Looking ahead

This lesson presented the inode: its role, its classical layout, its lifetime, its relationship to the VFS inode cache, and the modern inode structures that have replaced it. It referred several times to the directory as the structure that maps names to inode numbers, but did not examine the directory's internal structure in detail. Lesson 10.4 presents directory organization: how a directory's entries are stored, how they are searched, how they scale to millions of entries, and the data structures — linear lists, hash tables, B-trees — that modern file systems use to make directory operations fast. The lesson follows directly from the inode material, because the directory entries are what connect names to inodes.

11 References

  • Silberschatz, A., Galvin, P. B., & Gagne, G. Operating System Concepts, 10th ed., Chapter 14, Section 14.2.
  • Arpaci-Dusseau, R. H., & Arpaci-Dusseau, A. C. Operating Systems: Three Easy Pieces, Chapters 40 and 41. Available online at ostep.org.
  • Tanenbaum, A. S., & Bos, H. Modern Operating Systems, 4th ed., Chapter 4, Section 4.3.
  • McKusick, M. K., Joy, W. N., Leffler, S. J., & Fabry, R. S. A Fast File System for UNIX. ACM Transactions on Computer Systems, 1984. The paper that refined the classical inode and introduced cylinder groups.
  • Bach, M. J. The Design of the UNIX Operating System. Prentice Hall, 1986. Chapters 4 and 5. The classical reference for the System V inode, the inode table, and the file-system operations.
  • Ritchie, D. M., & Thompson, K. The UNIX Time-Sharing System. Communications of the ACM, 1974. The original paper that established the inode and the Unix file model.
  • Kerrisk, M. The Linux Programming Interface. No Starch Press, 2010. Chapters 14 and 15.
  • Love, R. Linux Kernel Development, 3rd ed., Chapter 13, The Virtual File System. The VFS inode, the inode cache, and the distinction between the on-disk and in-memory inodes.
  • Bovet, D. P., & Cesati, M. Understanding the Linux Kernel, 3rd ed. O'Reilly, 2005. Chapters 12 and 18. The VFS inode and the Ext2/Ext3 inode.
  • Linux man-pages: man 2 stat, man 2 statx, man 2 link, man 2 unlink, man 2 rename, man 2 open, man 5 proc, man 8 tune2fs, man 8 dumpe2fs, man 1 df.
👤