Understanding Block Device Drivers in Linux

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

  1. Access Granularity: Block devices operate on fixed-size blocks, while character devices use byte-level access.
  2. Random Access: Block devices employ buffers for random data retrieval; character devices are stream-oriented.
  3. Hardware Specifics: Block device drivers vary based on storage hardware (e.g., SD cards, EMMC).
  4. 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:

  1. Mapping Layer: Bridges file systems and storage devices, handling block size calculations and logical adddress resolution.
  2. Generic Block Layer: Manages I/O requests between file systems and physical disks via bio structures.
  3. 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).
  4. Block Device Driver: Directly interacts with hardware, managing request queues and I/O operations.

Core Data Structures

  1. block_device: Represents a block device (e.g., disk/partition), storing metadata like device numbers, open counts, and associated queues.
  2. gendisk: Describes a generic disk, including device numbers, name, operations, and request queue.
  3. block_device_operations: Defines driver functions (e.g., open, ioctl) for file system interaction.
  4. request_queue, request, bio: Manage I/O requests:
    • request_queue holds pending requests.
    • request encapsulates a single I/O operation.
    • bio describes data segments for transfer.

Implementing a Block Device Driver

Registration Steps:

  1. Allocate Device Numbers: Use register_blkdev().
  2. Initialize gendisk: Allocate with alloc_disk() and configure fields (major number, operations, queue).
  3. Set Up Request Queue: Create with blk_init_queue(), linking a request-handling function.
  4. 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 bio structures 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(), and unregister_blkdev().

Usage Example

  1. Check Device: Verify /dev/mydisk exists post-driver load.
  2. Format: Initialize with mkfs.vfat /dev/mydisk.
  3. Mount: Create a directory (e.g., temp) and mount the device (mount /dev/mydisk temp).
  4. Test: Write/read files in temp, then unmount (umount /dev/mydisk).

Tags: Linux kernel Drivers Block Devices Storage

Posted on Fri, 25 Sep 2026 16:46:09 +0000 by airric