Block devices are one of the three primary driver types in Linux, handling data access in fixed-size blocks (typically 512KB). Unlike character devices, block devices support file system mounting, enabling applications to interact via file systems rather than direct device access.
Key Differences Between Block and Character Devices
- Access Granularity: Block devices operate on fixed-size blocks, while character devices use byte-level access.
- Random Access: Block devices employ buffers for random data retrieval; character devices are stream-oriented.
- Hardware Specifics: Block device drivers vary based on storage hardware (e.g., SD cards, EMMC).
- Kernel Structures: Block devices rely less on critical kernel data structures compared to character devices.
Disk Fundamentals
- Sector: Smallest physical storage unit (typically 512 bytes).
- Track: Concentric data rings on a disk platter, subdivided into sectors.
- Cylinder: Vertical stack of tracks across multiple platters; data is read cylinder-by-cylinder for efficiency.
Block Device Driver Architecture
Layers:
- Mapping Layer: Bridges file systems and storage devices, handling block size calculations and logical adddress resolution.
- Generic Block Layer: Manages I/O requests between file systems and physical disks via
biostructures. - I/O Scheduler: Optimizes request execution order using algorithms like:
- CFQ: Fairly allocates disk bandwidth.
- Deadline: Prioritizes requests based on deadlines.
- NOOP: Simple FIFO scheduling for self-optimizing devices (e.g., SSDs).
- Block Device Driver: Directly interacts with hardware, managing request queues and I/O operations.
Core Data Structures
block_device: Represents a block device (e.g., disk/partition), storing metadata like device numbers, open counts, and associated queues.gendisk: Describes a generic disk, including device numbers, name, operations, and request queue.block_device_operations: Defines driver functions (e.g.,open,ioctl) for file system interaction.request_queue,request,bio: Manage I/O requests:request_queueholds pending requests.requestencapsulates a single I/O operation.biodescribes data segments for transfer.
Implementing a Block Device Driver
Registration Steps:
- Allocate Device Numbers: Use
register_blkdev(). - Initialize
gendisk: Allocate withalloc_disk()and configure fields (major number, operations, queue). - Set Up Request Queue: Create with
blk_init_queue(), linking a request-handling function. - Activate Disk: Add to the kernel via
add_disk().
Example Initialization:
static int __init mydisk_init(void) {
// Allocate buffer
dev_data = vmalloc(dev_size);
// Register block device
major = register_blkdev(0, "mydisk");
// Initialize gendisk
mydisk = alloc_disk(1);
mydisk->major = major;
mydisk->fops = &my_fops;
// Set up request queue
spin_lock_init(&lock);
queue = blk_init_queue(my_request, &lock);
mydisk->queue = queue;
// Add disk
add_disk(mydisk);
return 0;
}
Handling Requests:
- Fetch Requests: Use
blk_fetch_request()to retrieve pending I/O. - Process BIOs: Iterate through
biostructures in each request (rq_for_each_bio), copying data between device and user buffers. - Complete Requests: Notify the kernel with
__blk_end_request_all().
Example Request Handler:
static void my_request(struct request_queue *q) {
struct request *req;
while ((req = blk_fetch_request(q)) != NULL) {
struct bio *bio;
rq_for_each_bio(bio, req) {
void *buffer = bio_data(bio);
sector_t sector = bio->bi_iter.bi_sector;
// ... handle read/write ...
}
__blk_end_request_all(req, 0);
}
}
Cleanup:
- Remove the disk with
del_gendisk(). - Release resources using
put_disk(),blk_cleanup_queue(), andunregister_blkdev().
Usage Example
- Check Device: Verify
/dev/mydiskexists post-driver load. - Format: Initialize with
mkfs.vfat /dev/mydisk. - Mount: Create a directory (e.g.,
temp) and mount the device (mount /dev/mydisk temp). - Test: Write/read files in
temp, then unmount (umount /dev/mydisk).