10.4 UNIX File System Internals: Inodes, Direct Blocks & Indirect Pointers
💡 Core Intuition
🍳 The Everyday Analogy: The Central Property Registry & Title Deed
Imagine a metropolitan land registry managing millions of real estate properties:
The Real Estate Deed Indirection Hierarchy
How property management scales from direct plots to multi-tier land syndicates
Direct Land Parcels
A standard homeowner deed lists up to 12 individual plot numbers directly on the primary parchment. Looking up these plots requires zero indirection.
Ledger Volume Reference
When an estate exceeds 12 plots, the deed points to an auxiliary ledger book containing an entire page of 1,024 plot numbers.
Catalog of Ledger Books
For corporate estates, the deed references a Master Catalog listing 1,024 ledger volumes, each containing 1,024 plot numbers (~1,048,576 plots).
Registry Archive Building
For continental holdings, the deed points to an entire archive building of catalogs (~1 billion plots). Any size can be addressed without inflating small deeds.
In UNIX and POSIX-compliant operating systems (such as Linux, BSD, and macOS), the Inode (Index Node) is the fundamental metadata data structure representing an individual filesystem object.
Crucially:
- An inode stores all metadata about a file (permissions, ownership, timestamps, size, physical block locations).
- An inode never stores the filename! Filenames exist exclusively as directory entries mapping human-readable strings to inode numbers:
("report.pdf", 45210).
🏛️ Inode Architecture & Internal Structure
In traditional UNIX File Systems (UFS), Berkeley Fast File System (FFS), and Linux ext2/ext3, every file is governed by an on-disk struct inode:
| Inode Field | Size / Representation | Architectural Purpose |
|---|---|---|
| File Mode | 16 bits (uint16_t) | File type (regular, directory, symlink) and POSIX permissions (rwxr-xr-x) |
| Owner Identifiers | 32 bits each (UID, GID) | User ID and Group ID of the file owner |
| Timestamps | 3 64 bits | atime (last access), mtime (content modification), ctime (metadata change) |
| File Size | 64 bits (uint64_t) | Total logical size of the file in bytes |
| Hard Link Count | 32 bits (uint32_t) | Number of directory entries pointing to this inode (st_nlink) |
| Block Count | 64 bits (uint64_t) | Total number of 512-byte disk sectors allocated to this file |
| Pointer Array | 15 Addresses (60 bytes) | 12 Direct + 1 Single Indirect + 1 Double Indirect + 1 Triple Indirect |
🧮 Mathematical Framework: Inode Capacity Derivations
Let the filesystem parameters be defined as:
- Disk Block Size: bytes
- Disk Block Address / Pointer Size: bytes
- Number of Direct Pointers:
- Number of Single Indirect Pointers:
- Number of Double Indirect Pointers:
- Number of Triple Indirect Pointers:
Step 1: Number of Pointer Entries per Index Block ()
An index block is a regular disk block repurposed to store addresses:
Step 2: Total Addressable Data Blocks ()
Step 3: Maximum File Size ()
📐 Detailed Worked Example: Classic Standard UNIX Inode
Consider a classic UNIX-like filesystem configuration:
- Direct Pointers:
- Single Indirect Pointers:
- Double Indirect Pointers:
- Triple Indirect Pointers:
- Disk Block Size ():
- Disk Block Address ():
1. Calculate Pointers per Index Block ():
2. Tier-by-Tier Storage Breakdown:
Storage Capacity by Indirection Tier
Exact byte calculations for 4 KB blocks with 32-bit pointers
12 Direct Pointers
1 Single Indirect Pointer
1 Double Indirect Pointer
3. Total Maximum File Size:
Disk I/O Access Count per Byte Offset
To read a specific byte offset inside a file from cold disk cache:
| Byte Offset Range | Indirection Level | Disk I/O Count (Cold Cache) | Mechanics |
|---|---|---|---|
| Direct Block | 1 Disk I/O | Read data block directly from inode pointer | |
| Single Indirect | 2 Disk I/Os | 1 Index Block Read + 1 Data Block Read | |
| Double Indirect | 3 Disk I/Os | 2 Index Block Reads + 1 Data Block Read | |
| Triple Indirect | 4 Disk I/Os | 3 Index Block Reads + 1 Data Block Read |
Over of files in typical UNIX workloads are under . The asymmetric inode structure optimizes for the common case: small files never pay an indirection penalty, while multi-gigabyte database containers expand seamlessly.
🧹 Free Space Management
Because physical disk capacity is finite, when files are truncated or deleted, their allocated disk blocks must be reclaimed for future allocations. The operating system maintains a Free Space List tracking all available storage blocks.
The four primary free space management architectures are:
- Bit Vector / Bit Map
- Linked Free Space List
- Grouping
- Counting (Extents)
1. Bit Vector / Bit Map
Under a bit vector, the disk volume is modeled as an array of single-bit flags:
- Bit value
1Disk Block is Free - Bit value
0Disk Block is Allocated
| Block Index | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Bit Value | 0 | 0 | 1 | 1 | 1 | 1 | 0 | 0 | 1 | 1 | 1 | 1 | 0 | 0 | 0 | 1 | 0 | 1 | 0 | 0 | 1 |
| State | Alloc | Alloc | Free | Free | Free | Free | Alloc | Alloc | Free | Free | Free | Free | Alloc | Alloc | Alloc | Free | Alloc | Free | Alloc | Alloc | Free |
Fundamental Bitmap Formulas:
⏱️ Real-World Bitmap Capacity Calculations:
Algorithmic Advantage: Hardware Bit-Scan Instructions
Finding free contiguous blocks in a bitmap is blazing fast using native CPU vector intrinsics:
- In modern C / Linux kernels,
find_first_bit()or__builtin_ctzll()inspects a 64-bit word in a single CPU clock cycle. - If a word is
0x0000000000000000, all 64 blocks are allocated; skip instantly to the next word. - Finding contiguous blocks requires checking for consecutive runs of
1bits.
2. Linked Free Space List
All free disk blocks are chained together in a singly linked list.
Linked Free Space List Chain
Traversing reclaimed free disk blocks across storage platters
Free List Head
Superblock maintains anchor address of first available free block in memory.
First Free Block
Unallocated block storing link to next free disk address: Block 3.
Second Free Block
Stores link pointing to Block 4.
Third Free Block
Stores link pointing to Block 5.
Fourth Free Block
Stores link pointing to next free cluster on disk.
3. Grouping & Counting (Extents)
To overcome the latency of simple linked lists, modern file systems deploy two optimized variants:
💻 Systems Programming: Inspecting Linux Inodes via POSIX C
Below is a systems program demonstrating how to read raw inode attributes using stat() and parse file types, permissions, and disk block allocations:
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <time.h>
#include <unistd.h>
void print_inode_info(const char *filename) {
struct stat st;
if (stat(filename, &st) == -1) {
perror("stat failed");
return;
}
printf("====================================================\n");
printf("UNIX INODE METADATA: %s\n", filename);
printf("====================================================\n");
printf("Inode Number: %lu\n", (unsigned long)st.st_ino);
printf("Device ID: %lu\n", (unsigned long)st.st_dev);
printf("File Mode (octal): 0%o\n", st.st_mode & 0777);
printf("Hard Link Count: %lu\n", (unsigned long)st.st_nlink);
printf("Owner UID: %d\n", st.st_uid);
printf("Owner GID: %d\n", st.st_gid);
printf("Logical File Size: %ld bytes\n", (long)st.st_size);
printf("Allocated 512B Blks: %ld blocks (%ld bytes allocated)\n",
(long)st.st_blocks, (long)st.st_blocks * 512);
// Timestamps
char time_buf[64];
strftime(time_buf, sizeof(time_buf), "%Y-%m-%d %H:%M:%S", localtime(&st.st_mtime));
printf("Last Modification: %s\n", time_buf);
// Inode Type Verification
if (S_ISREG(st.st_mode)) printf("Object Type: Regular Data File\n");
else if (S_ISDIR(st.st_mode)) printf("Object Type: Directory\n");
else if (S_ISLNK(st.st_mode)) printf("Object Type: Symbolic Link\n");
printf("====================================================\n");
}
int main(int argc, char *argv[]) {
if (argc < 2) {
printf("Usage: %s <filename>\n", argv[0]);
return 1;
}
print_inode_info(argv[1]);
return 0;
}
🧮 Comprehensive Exam Numerical Problems
Problem 1: Inode Capacity with Triple Indirection & 48-bit Addresses
Question:
Consider a file system with the following specifications:
- Disk Block Size: ()
- Disk Block Pointer Address: ()
- An Inode contains:
- 10 Direct block pointers
- 2 Single Indirect block pointers
- 1 Double Indirect block pointer
- 1 Triple Indirect block pointer
Calculate:
- The number of pointers per index block.
- The maximum file size addressable via Single Indirect pointers.
- The absolute maximum file size supported by this inode.
Step-by-Step Solution:
-
Pointers per Index Block ():
-
Single Indirect Capacity: There are single indirect pointers:
-
Maximum File Size Supported:
- Direct:
- Single Indirect:
- Double Indirect:
- Triple Indirect:
Problem 2: Free Space Bitmap Memory Calculation
Question:
A server features a storage array of capacity (). The filesystem formats the disk with () blocks.
- How many total disk blocks exist on this volume?
- If free space is managed using a Bit Vector (Bitmap), how much memory (in Megabytes) is required to store the bitmap?
- If the block size is decreased to , how does the bitmap size change?
Step-by-Step Solution:
-
Total Disk Blocks ():
-
Bitmap Size with 4 KB Blocks: Each block requires .
-
Bitmap Size with 1 KB Blocks: If block size is reduced by a factor of ( instead of ):
- Number of blocks increases : .
- Bitmap size increases :
Smaller disk blocks drastically reduce internal fragmentation for small files, but quadruple the kernel RAM footprint required for the free-space bitmap!