The Problem with Streaming Data
Imagine an app needs to read a single megabyte near the end of a 4GB video file. If the system treats data as a continuous stream, it must read every single byte from the beginning just to reach the end. Character devices operate this way, streaming data sequentially like water through a pipe. This streaming approach works perfectly for a mouse or a keyboard, but it destroys performance for high-capacity storage. Wasting time and memory to stream gigabytes of irrelevant data would drain a mobile battery in minutes. Android devices need a way to jump instantly to any exact address on a storage chip.
The Block Driver Abstraction
Android solves this random access requirement using block drivers. These drivers represent storage hardware that reads and writes data in fixed-size chunks, usually 4KB at a time. This block structure gives the operating system true random access capabilities. An app can request block 50,000 instantly without touching the surrounding data. The hardware controller simply jumps directly to that memory address and returns the payload. Such precise targeting makes it possible to store the operating system, databases, and large media files on a single flash chip without sacrificing performance.
The I/O Scheduler Architecture
Even with random access, flash storage is thousands of times slower than the CPU. If twenty background apps sync data simultaneously, sending their requests directly to the flash chip creates a chaotic traffic jam. The Linux kernel solves this by placing an abstraction layer between the file system and the block driver. This traffic cop is called the I/O Scheduler.
This diagram shows how storage requests travel from user space down to the physical chip. It illustrates why the I/O Scheduler is necessary to prevent the CPU from stalling while storage hardware works. Notice how the scheduler intercepts and merges multiple app requests before they ever reach the physical hardware.
Whenever an app requests a file, you can see the request enter the Virtual FS before hitting the I/O Scheduler. The scheduler analyzes this queue and sorts the requests by their block addresses. It merges adjacent reads into a single larger command. Only after this optimization does the scheduler pass the commands down to the actual block driver. This batching process drastically reduces the physical wear on the flash chip. It also prevents a single heavy background download from freezing the foreground app.
Partitions and Block Devices
A physical UFS chip holds everything from the protected bootloader to the user's downloaded photos. The operating system needs a way to isolate these distinct regions to prevent a rogue app from overwriting the kernel. During startup, the bootloader divides the physical chip into logical partitions based on a GUID Partition Table. The kernel then exposes each of these partitions as a separate block device.
The primary drive appears in the device tree as something like sda. Individual partitions appear as numbered sub-devices like sda1 and sda2. You can inspect this layout directly from the shell using the cat command. This helps you identify the exact partition layout of your physical storage chip. Expect to see a list of device nodes with sizes and their assigned major or minor numbers.
cat /proc/partitions
Common Mistake: Engineers often assume the major number will be consistent across different Android devices. The kernel assigns the major device number for a block device dynamically during boot. You must always read the partition table rather than hardcoding major numbers in your shell scripts.
Block drivers provide the foundation for true random access, allowing the system to jump to any memory address instantly. The kernel I/O Scheduler then steps in to merge concurrent requests so the physical flash chip never stalls. Finally, dividing the physical chip into logical partitions allows Android to apply strict mount permissions to critical system data. Application developers remain entirely shielded from this complexity.
With the block layer handling the physical hardware, apps can request data without knowing its exact location on the chip. But raw blocks are just an unstructured ocean of bytes. The system now needs a way to organize those blocks into a meaningful hierarchy.