AlgoMaster Logo

Inodes

35 min readUpdated August 7, 2026
Listen to this chapter
Unlock Audio

A backend service opens:

Later, a log-rotation tool renames that pathname. The service can still write through its existing file descriptor because the descriptor does not depend on repeatedly finding the characters events.log.

Behind the open file description, the file system has a persistent record for the underlying object. On Unix-style file systems, that record is called an inode, short for index node.

An inode represents one file-system object and stores its metadata plus the information needed to find its data.

The pathname is not stored in that inode. A directory connects a component name such as events.log to an inode number. Separating names from inodes is the key to understanding file identity, renames, multiple names, deleted-but-open files, and inode exhaustion.

Why the File System Needs an Object Record

A file system must remember more than application bytes.

For each object, it needs information such as:

  • Is this a regular file, directory, symbolic link, or special object?
  • How large is it?
  • Who owns it, and what access mode is recorded?
  • When was it last modified?
  • Where can its file data be found?
  • How many directory entries refer to it?

Storing all of this inside a pathname would be impractical. A name can change, and more than one name can refer to the same object. Storing it as a header at the beginning of every regular file would also expose file-system internals as application data and make other object types awkward.

Instead, an inode is the file system's managed record for the object:

The name and the metadata live in different places. That separation is what allows a file to have several names, or none at all while still being open.

Applications normally reach the inode indirectly. Path resolution walks directory entries to the final object. An open file description then retains a reference to that object so later operations do not need the pathname.

The exact on-disk format varies among file systems. “Inode” is a conceptual and Unix-family term, not a promise that every implementation stores one identical C structure.

File-System-Local Inode Numbers

Each inode has an inode number that identifies it within its file system.

Linux tools expose this number:

A possible result is:

The exact number differs across systems. More importantly, inode 131203 is not globally unique. Another mounted file system can have its own inode with the same number.

The practical identity is the pair:

POSIX exposes these as st_dev and st_ino in struct stat.

This is why tools that compare underlying file identity use both fields rather than the inode number alone.

Even the pair is not a permanent ID across all time. After an object is deleted and its inode is reclaimed, the file system can reuse that inode number for a newly created object. A stored (device, inode) pair is meaningful only while the original object is known to remain alive.

Directory-Entry Mappings from Names to Inodes

A directory entry associates one component name with an inode number in the same file system:

Path resolution repeatedly applies this mapping:

The final name and the final inode are distinct pieces of state.

Renaming events.log within the same file system changes directory entries but normally leaves inode 8412 unchanged. Creating a separate copy usually creates a new inode, even when the copied bytes are identical.

More than one directory entry can refer to the same inode. In that case, the names reach one object rather than independent byte-for-byte copies. The inode does not record which name is the “original,” because no such distinction exists at the object level.

What an Inode Contains

The exact fields differ, but a regular file's inode commonly records the following categories.

Object type

The inode identifies whether the object is a regular file, directory, symbolic link, device, pipe-like object, or another supported type.

The type determines which operations make sense. A directory organizes names, while a regular file presents an application byte sequence.

Access metadata

The inode records ownership and access mode information. On a traditional Unix file system, that includes a user ID, group ID, and permission bits.

Storing these values does not itself decide every access request. The kernel evaluates them together with process credentials and any additional access-control mechanisms.

Logical size

For a regular file, the inode records the length of the logical byte sequence. If st_size is 8,192, EOF is at offset 8,192.

Logical size is not necessarily the same as physically allocated storage. Sparse regions can contribute to the logical size without requiring a stored data block for every zero byte.

Timestamps

Traditional inode metadata includes:

  • Access time (atime): associated with data access.
  • Modification time (mtime): associated with changes to file data.
  • Status-change time (ctime): associated with changes to inode status, such as ownership, mode, link count, or file data.

ctime means change time, not creation time. Some file systems expose a separate creation or birth timestamp, but it is not the meaning of traditional ctime.

Operating systems can defer or suppress some access-time updates for performance. Timestamp granularity and update policy therefore depend on the mounted file system and its options.

The inode records how many directory entries refer to it. Removing one name decrements this count. The count helps determine when an unreferenced object can be reclaimed.

Directory link counts have additional rules because the hierarchy includes parent relationships. The important starting point is that st_nlink is a count of directory-entry references, not a count of open file descriptors.

Data-location information

For a regular file or directory, the inode contains mapping information that connects logical file blocks to storage locations. Classic inode designs use direct and indirect block pointers. Modern file systems often use extents or trees.

The inode has limited space, so large mapping structures cannot all fit directly inside the inode record. Indirection lets a small fixed-size inode describe a much larger file.

File-system-specific metadata

An inode can also carry flags or references to extended attributes, access-control data, checksums, project accounting, or other implementation-specific state.

Applications should use supported system calls to query this information rather than depending on a particular on-disk inode layout.

What an Inode Does Not Contain

Several important pieces of state live elsewhere.

The filename or full pathname

A directory entry stores the component name that reaches an inode. The same inode can have multiple names, and renaming an entry need not modify the inode's file data.

The current file offset

The current offset belongs to an open file description. Opening the same inode twice can create two independent offsets.

The file descriptor number

A file descriptor belongs to a process's descriptor table. Many processes can use different descriptor numbers that ultimately reach the same inode.

Application-level format information

The inode knows the object is a regular file, but it does not normally know whether the bytes form JSON, a JPEG image, an executable, or a database.

A complete list of open processes

The kernel tracks live references in its runtime structures. The persistent inode is not a list of PIDs currently using the file.

The distinction can be summarized as:

Inode Metadata for Every File-System Object

Inodes are not limited to ordinary data files.

A directory has an inode whose type says “directory.” Its associated directory data stores name-to-object mappings.

A symbolic link has its own inode and link target data. It is a distinct object, not merely a special flag on the target.

A device file has an inode whose metadata identifies the relevant device interface. It does not need a regular-file sequence of stored data blocks.

Named pipes and local socket names can also occupy directory entries and have object metadata even though their useful communication state lives in the running kernel.

This explains why stat() can report ownership, timestamps, and an inode number for several object types. The inode is an object record; regular-file bytes are only one possible kind of associated data.

From a Byte Offset to a Data Block

A regular file presents byte offsets, but storage is managed in larger units. To access an offset, the file system conceptually:

  1. Determines which logical file block contains the byte.
  2. Consults the inode's mapping information.
  3. Finds the corresponding storage block, if one is allocated.
  4. Locates the byte within that block.

For a 4 KiB file-system block:

The logical block number is relative to the file. The inode mapping answers which physical storage region, if any, supplies that logical block.

Logical neighbors do not have to be physical neighbors. The application still sees one continuous byte sequence.

Direct and Indirect Block Pointers

A classic Unix inode contains a fixed number of pointer fields. Small files should be cheap to access, while large files must still be representable. Direct and indirect pointers provide both properties.

A direct pointer identifies a data block directly.

A single-indirect pointer identifies a block filled with data-block addresses.

A double-indirect pointer identifies a block whose entries point to single-indirect blocks.

A triple-indirect pointer adds one more level.

The direct region serves small files without reading separate pointer blocks. Indirection expands capacity exponentially while keeping the inode itself a fixed size.

To find a block in an indirect region, the file system uses parts of the logical block number as indexes at each level. The deeper the region, the more mapping blocks are conceptually involved.

These extra mapping blocks are metadata, not application contents. Reading every application byte does not return the stored block addresses.

Loading simulation...

Worked Capacity Example

Consider a classic inode with:

  • 12 direct pointers
  • One single-indirect pointer
  • One double-indirect pointer
  • One triple-indirect pointer
  • 4 KiB blocks
  • 8-byte block addresses

One 4 KiB indirect block can store:

The 12 direct pointers address:

The single-indirect pointer addresses:

The double-indirect pointer addresses:

The triple-indirect pointer addresses:

The total theoretical data capacity is the sum of all four regions:

This is a structural capacity calculation, not a guarantee that the implementation permits exactly that maximum. File-size fields, block-number widths, reserved values, and file-system limits can impose smaller bounds.

The growth pattern is the important insight:

where N is the number of addresses in one pointer block.

Extent Descriptions in Modern Inodes

Per-block pointers become metadata-heavy for large contiguous files. Many modern file systems instead describe runs of logical blocks with extents.

Conceptually:

One extent can represent that range more compactly than 128 separate block addresses.

The inode can store a few extent records directly or hold the root of a larger mapping tree. From the application's perspective, the contract remains the same:

A logical file offset passes through the inode-associated mapping to reach a stored data location.

Direct and indirect pointers remain important because they clearly illustrate inode-based indexed allocation and appear in classic Unix designs. Extents change the mapping representation, not the inode's fundamental role as the object's metadata and mapping root.

Logical Size vs. Allocated Blocks

The inode's logical size answers where EOF occurs. It does not necessarily report how much physical storage has been allocated.

Consider a sparse regular file with one byte written at offset 1 GiB:

The inode can record a size slightly larger than 1 GiB while leaving most logical blocks unmapped. Reads from those holes produce zeros.

Linux stat exposes both views:

For a sparse file, st_size can be much larger than:

Compression, inline data, metadata, and file-system accounting can complicate physical-usage interpretation further. Logical size should not be used as a universal measure of consumed storage.

An empty regular file demonstrates the opposite boundary clearly: it requires an inode and a directory entry even when it has zero data bytes and no ordinary data blocks.

Reclamation Through Link Counts and Open References

An inode can remain alive for two different reasons:

  1. One or more directory entries name it.
  2. One or more live kernel references, such as open file descriptions, still use it.

The inode's link count records directory-entry references. The kernel separately tracks live open references.

Suppose events.log has one name and is open by a service:

Another process unlinks the name:

New path resolution cannot find the old name, but the service's descriptor still reaches the inode. The object and its data cannot yet be reclaimed.

When the service closes its final descriptor:

the file system can reclaim the inode and its allocated data.

This is why deleting a log pathname may not free disk space when a long-running process still has the old file open. The name is gone, but the inode remains reachable through an open file description.

The inode link count alone does not reveal how many descriptors are open. Those are different reference systems maintained for different purposes.

Inode-Number Reuse

Once an inode becomes reclaimable, its number can later be assigned to another object.

This makes a bare inode number unsuitable as a permanent application ID. It can help correlate observations while the object exists, but it is not equivalent to a globally unique identifier.

The reuse also explains why low-level debugging tools must combine inode information with timing and process context. Seeing the same number in two observations does not prove that both observations refer to one continuously existing object.

Inodes as a Finite Metadata Resource

Creating an empty file consumes little or no regular-file data space, but it still requires an inode and a directory entry.

Some file systems reserve or preallocate a bounded population of inodes when they are created. Such a file system can run out of inodes while substantial data-block capacity remains free.

The error says the file system lacks a resource needed for creation; it does not always mean every storage byte is occupied.

On Linux, compare block-space and inode-space reports:

df -h reports storage-capacity usage. df -i reports inode counts when the file system provides meaningful fixed or tracked inode totals.

Modern file systems differ in how dynamically they allocate inode metadata, so the exact interpretation varies. The general production lesson remains: workloads with huge numbers of small files can exhaust metadata capacity or suffer metadata overhead long before their aggregate byte size looks large.

Persistent and In-Memory Inode State

An inode is persistent file-system metadata, but the kernel also needs a runtime representation while the object is active.

Conceptually:

Persistent inode metadata on storage is loaded when needed into an in-memory kernel representation, which open file descriptions and file-system operations then reference.

The in-memory representation can include runtime synchronization and bookkeeping that do not belong in the persistent on-disk record. The kernel can retain recently used metadata to avoid rereading it for every operation.

These representations should not be mistaken for independent files. They are persistent and runtime views of the same logical file-system object.

After metadata changes, the in-memory and durable on-storage states may temporarily differ. The exact caching, write-back, and crash-recovery rules are separate from the inode's conceptual contents.

Observing Inodes on Linux

Create a temporary directory and file:

Inspect its device, inode, link count, size, and allocated blocks:

A possible result is:

The values depend on the file system. size=11 is the logical byte count. blocks=8 with a 512-byte reporting unit represents 4 KiB of allocated space for the data under this example's accounting.

Rename the file within the same file system:

The device and inode number normally remain unchanged. The directory entry's name changed; the underlying object did not.

Create a copy:

The two files have equal logical contents immediately after the copy but normally have different inode numbers.

Check inode capacity for the containing file system:

This reports metadata availability separately from df -h.

Observing a Deleted-but-Open Inode

The shell can hold a descriptor open while its pathname is removed.

Create and open a file on descriptor 7:

Remove the name:

The pathname no longer resolves:

But Linux still shows the shell's open descriptor:

The /proc target commonly ends with (deleted). Descriptor 7 still reaches the open inode, so the second write succeeds.

Close the final open reference:

At that point, with no directory entries and no remaining open references, the file system can reclaim the inode and its data.

This sequence separates three events that are often incorrectly treated as one:

Inspecting Inode Metadata in C

The following program uses lstat() to display commonly used inode metadata for the final file-system object named by a pathname, without following a final symbolic link:

Compile and run:

On a symbolic-link pathname, lstat() describes the link's own inode. By contrast, stat() normally follows the final link and describes its target.

The 512-byte unit for st_blocks is a reporting convention. It should not be confused with st_blksize, which is an implementation-provided preferred I/O block size, or with the file system's internal allocation unit.

Summary

An inode is the file-system record for one object. It stores type, ownership and access metadata, logical size, timestamps, link count, and the mapping information needed to locate associated data. It does not store the object's filename, a process's descriptor number, or an open file description's current offset.

Directories map component names to inode numbers. An inode number identifies an object only within one file system, so practical identity combines the device identifier with the inode number. Numbers can be reused after an object is fully reclaimed.

Classic inodes use direct, single-indirect, double-indirect, and triple-indirect pointers to represent files of increasing size. Modern file systems often use extents or mapping trees, but the inode remains the root of the object's metadata and data mapping.

Link counts track directory-entry references, while the kernel separately tracks open references. An unlinked inode remains alive while an open file description still reaches it. Empty files still consume inodes, and inode exhaustion can prevent file creation even when byte capacity remains available.

The central mental model is:

Directory entries give an object names; the inode gives the object identity, metadata, and a route to its data.

Quiz

Inodes Quiz

5 quizzes