FS Structure
ext2 Layout
mkfs, dumpe2fs
Key Terms:
What is a File System?
A file system is an organised structure that the OS uses to store and retrieve data on a storage device (partition). It defines how files are named, stored, and organised. Linux supports dozens of file system types — ext2, ext3, ext4, XFS, Btrfs, FAT32, NTFS, NFS, and more.
You create a file system with mkfs:
# Create an ext4 filesystem on partition sda2:
mkfs -t ext4 /dev/sda2
# Short form:
mkfs.ext4 /dev/sda2
Every ext2 file system has these four sections on disk:
| Boot Block Block 0 |
Superblock Block 1 |
I-node Table One entry per file |
Data Blocks Actual file content, directory entries |
| Not used by FS. OS boot info. |
FS size, block size, i-node table size. | Metadata for each file: permissions, size, timestamps, data block pointers. | The majority of disk space. Stores actual bytes of files and directory listing data. |
🥾 Boot Block (Block 0)
Always the very first block. Not used by the file system itself. It holds boot loader code (e.g., GRUB stage 1) for the OS. Every partition has one, but most are unused.
📋 Superblock
The “control centre” of the file system. Stored in block 1 (immediately after boot block). Contains:
- Total size of the file system (in logical blocks)
- Logical block size (1024, 2048, or 4096 bytes for ext2)
- Size of the i-node table
- Number of free blocks and free i-nodes
- Mount count and last mount time
📇 I-node Table (Index Node Table)
One entry per file/directory. Each i-node stores all metadata except the filename. The filename is stored in the directory data block instead. See Topic 4 for full i-node details.
📦 Data Blocks
The bulk of the filesystem. Stores the actual content of files. Also stores directory entries (which are just special data blocks listing filename → i-node number mappings).
Real ext2 is more complex than the simple layout above. After the boot block, the filesystem is split into equal-sized block groups. Each group has its own copy of the superblock plus its own i-node table and data blocks.
| Boot Block | Block Group 0 | Block Group 1 | Block Group 2 … | |||||||||
| Superblock copy | Group Desc | I-node Table | Data Blocks | Superblock copy | Group Desc | I-node Table | Data Blocks | Same structure… | ||||
Why block groups? By storing a file’s data blocks and its i-node in the same block group, ext2 reduces disk head movement (seek time) when reading a file.
The file system works in logical blocks, not physical 512-byte sectors. A logical block is a multiple of the physical sector size. You choose the block size when creating the filesystem:
# Create ext4 with 4096-byte blocks (default):
mkfs.ext4 /dev/sdb1
# Create with 1024-byte blocks (for many small files):
mkfs.ext4 -b 1024 /dev/sdb1
# View filesystem details including block size:
dumpe2fs /dev/sdb1 | grep -E "Block size|Block count|Inode count"
# Output:
# Block size: 4096
# Block count: 2621440
# Inode count: 655360
#include <stdio.h>
#include <sys/vfs.h>
int main(int argc, char *argv[]) {
struct statfs sf;
const char *path = (argc > 1) ? argv[1] : "/";
if (statfs(path, &sf) == -1) {
perror("statfs");
return 1;
}
printf("File system info for: %s\n\n", path);
printf("Block size: %ld bytes\n", sf.f_bsize);
printf("Total blocks: %lu\n",
(unsigned long)sf.f_blocks);
printf("Free blocks: %lu\n",
(unsigned long)sf.f_bfree);
printf("Available blocks: %lu\n",
(unsigned long)sf.f_bavail);
printf("Total i-nodes: %lu\n",
(unsigned long)sf.f_files);
printf("Free i-nodes: %lu\n",
(unsigned long)sf.f_ffree);
/* Calculate total filesystem size */
double total_gb = (double)sf.f_blocks * sf.f_bsize
/ (1024.0*1024.0*1024.0);
double free_gb = (double)sf.f_bfree * sf.f_bsize
/ (1024.0*1024.0*1024.0);
printf("\nTotal size: %.2f GB\n", total_gb);
printf("Free space: %.2f GB\n", free_gb);
printf("Used: %.1f%%\n",
100.0 * (sf.f_blocks - sf.f_bfree) / sf.f_blocks);
/* Filesystem type magic numbers */
printf("\nFS type magic: 0x%lx", sf.f_type);
if (sf.f_type == 0xef53) printf(" (ext2/3/4)");
else if (sf.f_type == 0x4d44) printf(" (FAT)");
else if (sf.f_type == 0x6969) printf(" (NFS)");
else if (sf.f_type == 0x52654973) printf(" (ReiserFS)");
printf("\n");
return 0;
}
/* Sample output for /:
Block size: 4096 bytes
Total blocks: 10321920
Free blocks: 2456891
Total i-nodes: 2621440
Free i-nodes: 2345123
Total size: 39.37 GB
Free space: 9.37 GB
Used: 76.2%
FS type magic: 0xef53 (ext2/3/4)
*/
#include <stdio.h>
#include <string.h>
/* The kernel lists supported filesystems in /proc/filesystems
"nodev" means it doesn't need a block device (virtual FS) */
int main(void) {
FILE *fp;
char line[128];
int virtual_count = 0, real_count = 0;
fp = fopen("/proc/filesystems", "r");
if (!fp) { perror("fopen"); return 1; }
printf("Virtual FS (nodev):\n");
while (fgets(line, sizeof(line), fp)) {
/* Lines with "nodev" = virtual filesystems */
if (strncmp(line, "nodev", 5) == 0) {
printf(" %s", line + 6); /* Skip "nodev\t" */
virtual_count++;
}
}
rewind(fp);
printf("\nReal FS (requires block device):\n");
while (fgets(line, sizeof(line), fp)) {
if (strncmp(line, "nodev", 5) != 0 && line[0] == '\t') {
printf(" %s", line + 1); /* Skip leading tab */
real_count++;
}
}
printf("\nTotal: %d virtual, %d real filesystems\n",
virtual_count, real_count);
fclose(fp);
return 0;
}
/* Typical output includes:
Virtual FS (nodev):
sysfs, tmpfs, bdev, proc, cgroup, devtmpfs, etc.
Real FS:
ext3, ext4, vfat, btrfs, xfs, etc.
*/
#!/bin/bash
# Demonstrate full filesystem lifecycle
# Step 1: Create a disk image file (acts like a partition)
dd if=/dev/zero of=/tmp/myfs.img bs=1M count=100
# Creates a 100MB blank file
# Step 2: Format it as ext4
mkfs.ext4 /tmp/myfs.img
# This writes: boot block, superblock, inode table, data blocks
# Step 3: Mount it
mkdir -p /mnt/mytest
mount -o loop /tmp/myfs.img /mnt/mytest
# loop device maps the file as a block device
# Step 4: Use it like a real filesystem
echo "Hello from ext4!" > /mnt/mytest/hello.txt
mkdir /mnt/mytest/mydir
# Step 5: Check superblock info
dumpe2fs /tmp/myfs.img 2>/dev/null | head -30
# Step 6: Unmount
umount /mnt/mytest
# Step 7: Check filesystem integrity
e2fsck -f /tmp/myfs.img
A privileged program can use FIBMAP ioctl to find where on disk a specific logical block of a file is stored.
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/fs.h>
/* FIBMAP: given logical block number in file,
returns starting physical block number on disk.
Requires CAP_SYS_RAWIO (root) */
int main(int argc, char *argv[]) {
int fd;
int logical_block;
int physical_block;
if (argc < 3) {
fprintf(stderr, "Usage: %s <file> <logical_block_num>\n",
argv[0]);
return 1;
}
fd = open(argv[1], O_RDONLY);
if (fd == -1) { perror("open"); return 1; }
logical_block = atoi(argv[2]);
physical_block = logical_block; /* Input: logical block # */
/* FIBMAP replaces physical_block with the actual
physical disk block number */
if (ioctl(fd, FIBMAP, &physical_block) == -1) {
perror("ioctl FIBMAP (need root)");
close(fd);
return 1;
}
if (physical_block == 0)
printf("Logical block %d is in a FILE HOLE (no disk storage)\n",
logical_block);
else
printf("Logical block %d -> Physical block %d on disk\n",
logical_block, physical_block);
close(fd);
return 0;
}
/* Run as root:
./fibmap /etc/passwd 0
Output: Logical block 0 -> Physical block 1234567 on disk
*/
🎯 Interview Questions — File System Structure
Q1. What are the four main components of an ext2 file system?
Boot block (OS boot info, first block), Superblock (filesystem parameters: size, block size, inode count), I-node table (one entry per file with metadata), and Data blocks (actual file content, takes up most of the space).
Q2. What is stored in the superblock?
The superblock stores filesystem parameters: total size in logical blocks, the logical block size (1024/2048/4096 bytes), size of the i-node table, number of free blocks, number of free inodes, last mount time, and mount count. It’s critical — if corrupted, the filesystem is unreadable.
Q3. What is a logical block and how does it differ from a physical block?
A physical block is the smallest unit the disk hardware can read/write (usually 512 bytes). A logical block is the unit the filesystem uses, and is a multiple of physical blocks. For ext2, it’s 1024, 2048, or 4096 bytes. The filesystem always reads/writes whole logical blocks.
Q4. Why does ext2 use block groups?
By grouping a file’s inode and data blocks within the same block group (a contiguous region of disk), ext2 reduces disk head seek time when reading a file sequentially. Each block group also has its own superblock backup for resilience.
Q5. If you make the logical block size larger, what are the tradeoffs?
Larger block size (e.g., 4096 bytes) improves performance for large files (fewer seeks, less metadata overhead). But it wastes space for small files — a 1-byte file still occupies a full 4096-byte block. The filesystem also supports fewer total files for the same disk space.
Q6. What system call/function gives you filesystem statistics (like block count and free space)?
statfs() and fstatfs() system calls (Linux-specific) or the POSIX-standard statvfs() and fstatvfs() functions. They fill a structure with f_blocks (total blocks), f_bfree (free blocks), f_files (total inodes), etc. The df command uses these internally.
