Learn the Block Driver Model in Linux with a clear, beginner-friendly explanation covering architecture, request flow, kernel components, and real-world use cases.
If you’re diving into Linux kernel development or embedded systems, one of the crucial areas you’ll encounter is block device drivers. Whether it’s a hard disk, SSD, or flash memory, understanding the Block Driver Model in Linux is key to interacting with storage devices efficiently. In this article, we’ll cover everything from the basics of block devices to writing a simple Linux block driver, all in a beginner-friendly way.
Understanding the Block Driver Model
The Block Driver Model in Linux provides a structured framework for managing devices that transfer data in fixed-size blocks. Unlike character devices, which allow byte-by-byte access, block devices work with blocks of data, making them ideal for storage devices.
At its core, the block driver model allows the Linux kernel to handle block devices consistently, abstracting hardware-specific details. It enables features like:
- Buffered I/O: Efficient reading/writing using kernel buffers.
- Request Queues: Optimized scheduling of read/write requests.
- Device Abstraction: Treating different storage hardware in a unified way.
Common block devices in Linux include:
- HDDs and SSDs
- Flash drives
- Virtual devices like loop devices
Block Device vs Character Device
Before going deeper, it’s important to distinguish block devices from character devices:
| Feature | Block Device | Character Device |
|---|---|---|
| Data Access | Fixed-size blocks | Stream of bytes |
| I/O | Buffered I/O | Unbuffered |
| Examples | HDD, SSD, USB drives | Keyboards, mice, serial ports |
| Kernel API | struct block_device_operations | struct file_operations |
This distinction is crucial because Linux provides different kernel structures and APIs to interact with these device types.
Components of the Block Driver Model
The Linux block driver model has several key components:
1. Block Device Structure (gendisk)
The gendisk structure represents a block device in the kernel. It holds information about:
- Device name
- Major/minor numbers
- Device capacity (number of sectors)
- Operations supported by the device
Example:
struct gendisk {
int major;
int first_minor;
const char *disk_name;
struct block_device_operations *fops;
struct request_queue *queue;
};
2. Request Queue (request_queue)
The request queue is the heart of the Linux block layer. It stores pending read/write requests and schedules them efficiently using different I/O schedulers like CFQ or NOOP.
3. Block Device Operations (block_device_operations)
This structure defines the operations a block device can perform, such as open, release, and ioctl. It is analogous to file_operations for character devices.
struct block_device_operations {
int (*open)(struct block_device *, fmode_t);
void (*release)(struct gendisk *, fmode_t);
int (*ioctl)(struct block_device *, fmode_t, unsigned, unsigned long);
};
How the Linux Kernel Handles Block I/O
When an application writes or reads data from a block device:
- System Call: Application calls
read()orwrite(). - VFS Layer: Virtual File System passes the request to the block layer.
- Request Queue: Block layer queues the request in
request_queue. - I/O Scheduler: Scheduler orders the requests for optimal performance.
- Device Driver: The driver performs hardware-specific I/O.
- Completion: The kernel signals completion to the application.
This layered approach ensures efficient I/O while hiding hardware complexity.
Writing a Simple Block Driver in Linux
Let’s look at a step-by-step process to implement a basic Linux block driver.
Step 1: Include Kernel Headers
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/blkdev.h>
#include <linux/genhd.h>
#include <linux/fs.h>
#include <linux/vmalloc.h>
Step 2: Define Device Parameters
#define DEVICE_NAME "myblock"
#define DEVICE_MINOR 0
#define DEVICE_SECTORS 1024
#define SECTOR_SIZE 512
static int major_num;
static struct gendisk *my_gendisk;
static struct request_queue *my_queue;
static u8 *device_data;
Step 3: Implement Request Handler
static void my_request(struct request_queue *q)
{
struct request *req;
while ((req = blk_fetch_request(q)) != NULL) {
struct bio_vec bv;
struct req_iterator iter;
rq_for_each_segment(bv, req, iter) {
u8 *buffer = kmap(bv.bv_page) + bv.bv_offset;
// Perform read/write operation here
kunmap(bv.bv_page);
}
__blk_end_request_all(req, 0); // Complete request
}
}
Step 4: Initialize Block Device
static int __init my_block_init(void)
{
device_data = vmalloc(DEVICE_SECTORS * SECTOR_SIZE);
major_num = register_blkdev(0, DEVICE_NAME);
my_queue = blk_init_queue(my_request, NULL);
my_gendisk = alloc_disk(1);
my_gendisk->major = major_num;
my_gendisk->first_minor = DEVICE_MINOR;
my_gendisk->fops = NULL; // Add your operations
my_gendisk->queue = my_queue;
snprintf(my_gendisk->disk_name, 32, DEVICE_NAME);
set_capacity(my_gendisk, DEVICE_SECTORS);
add_disk(my_gendisk);
return 0;
}
Step 5: Cleanup
static void __exit my_block_exit(void)
{
del_gendisk(my_gendisk);
put_disk(my_gendisk);
blk_cleanup_queue(my_queue);
unregister_blkdev(major_num, DEVICE_NAME);
vfree(device_data);
}
module_init(my_block_init);
module_exit(my_block_exit);
MODULE_LICENSE("GPL");
With this, you have a minimal block device driver ready to be compiled and tested on a Linux system.
Testing Your Block Driver
- Compile the module:
make -C /lib/modules/$(uname -r)/build M=$PWD modules - Insert the driver:
sudo insmod my_block.ko - Check device creation:
ls /dev/myblock* - Test I/O operations:
sudo dd if=/dev/zero of=/dev/myblock bs=512 count=10 sudo dd if=/dev/myblock of=/tmp/test bs=512 count=10
Key Concepts to Remember
- Request Queues: Central to performance optimization.
- Buffered vs Direct I/O: Buffered I/O is handled by kernel caches.
- Major & Minor Numbers: Identify devices uniquely.
- Synchronous vs Asynchronous I/O: Blocks until operation finishes vs queued operations.
Common Use Cases
- Embedded systems requiring custom storage drivers
- Virtual devices like RAM disks or loop devices
- Filesystem development on block devices
- SSD/HDD performance tuning via custom drivers
Advantages of the Block Driver Model
- Standardized interface across different storage hardware
- Efficient I/O scheduling
- Support for advanced features like partitioning, snapshots
- Easy integration with filesystems
Step-by-Step Implementation of a Linux Block Driver
We’ll create a simple RAM-backed block device driver. This is safe because it doesn’t touch real disks, making it ideal for testing.
Step 1: Setup Your Environment
Make sure you have:
- A Linux system (Ubuntu/Debian preferred for beginners)
- Kernel headers installed:
sudo apt-get install build-essential linux-headers-$(uname -r) - Root access (needed to insert kernel modules)
- A folder for your driver code:
mkdir ~/block_driver cd ~/block_driver
Step 2: Create the Block Driver Source File
Create a file named my_block.c:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/blkdev.h>
#include <linux/genhd.h>
#include <linux/vmalloc.h>
#include <linux/hdreg.h>
#define DEVICE_NAME "myblock"
#define DEVICE_MINOR 0
#define NSECTORS 1024 // Total number of sectors
#define SECTOR_SIZE 512 // 512 bytes per sector
static int major_num = 0;
static struct gendisk *my_disk;
static struct request_queue *my_queue;
static u8 *device_data;
// Request handler
static void my_request(struct request_queue *q)
{
struct request *req;
while ((req = blk_fetch_request(q)) != NULL) {
struct bio_vec bv;
struct req_iterator iter;
rq_for_each_segment(bv, req, iter) {
u8 *buffer = kmap(bv.bv_page) + bv.bv_offset;
if (rq_data_dir(req) == READ) {
memcpy(buffer, device_data + (blk_rq_pos(req) * SECTOR_SIZE), bv.bv_len);
} else if (rq_data_dir(req) == WRITE) {
memcpy(device_data + (blk_rq_pos(req) * SECTOR_SIZE), buffer, bv.bv_len);
}
kunmap(bv.bv_page);
}
__blk_end_request_all(req, 0);
}
}
// Module initialization
static int __init my_block_init(void)
{
device_data = vmalloc(NSECTORS * SECTOR_SIZE);
if (!device_data) {
printk(KERN_ERR "vmalloc failed\n");
return -ENOMEM;
}
major_num = register_blkdev(0, DEVICE_NAME);
if (major_num <= 0) {
printk(KERN_ERR "Failed to register block device\n");
vfree(device_data);
return -EBUSY;
}
my_queue = blk_init_queue(my_request, NULL);
if (!my_queue) {
printk(KERN_ERR "Failed to initialize request queue\n");
unregister_blkdev(major_num, DEVICE_NAME);
vfree(device_data);
return -ENOMEM;
}
my_disk = alloc_disk(1);
if (!my_disk) {
printk(KERN_ERR "Failed to allocate gendisk\n");
blk_cleanup_queue(my_queue);
unregister_blkdev(major_num, DEVICE_NAME);
vfree(device_data);
return -ENOMEM;
}
my_disk->major = major_num;
my_disk->first_minor = DEVICE_MINOR;
my_disk->fops = NULL; // Can define block_device_operations if needed
my_disk->queue = my_queue;
snprintf(my_disk->disk_name, 32, DEVICE_NAME);
set_capacity(my_disk, NSECTORS);
add_disk(my_disk);
printk(KERN_INFO "Block driver initialized: major=%d\n", major_num);
return 0;
}
// Module cleanup
static void __exit my_block_exit(void)
{
del_gendisk(my_disk);
put_disk(my_disk);
blk_cleanup_queue(my_queue);
unregister_blkdev(major_num, DEVICE_NAME);
vfree(device_data);
printk(KERN_INFO "Block driver removed\n");
}
module_init(my_block_init);
module_exit(my_block_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Rak");
MODULE_DESCRIPTION("Simple RAM-backed Block Driver");
Step 3: Create the Makefile
Create a Makefile in the same directory:
obj-m += my_block.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
Step 4: Compile the Driver
Run:
make
You should see my_block.ko generated. This is your kernel module.
Step 5: Insert the Module into the Kernel
sudo insmod my_block.ko
dmesg | tail
You should see:
Block driver initialized: major=XXXX
Step 6: Create the Device Node
- Check the major number:
cat /proc/devices | grep myblockSuppose it returns250. - Create a block device node:
sudo mknod /dev/myblock b 250 0 sudo chmod 666 /dev/myblock - Verify the device:
ls -l /dev/myblock
Step 7: Test the Block Device
Write Data
echo "Hello Block Device" | dd of=/dev/myblock bs=512 count=1
Read Data Back
dd if=/dev/myblock bs=512 count=1
You should see the data you wrote.
Step 8: Remove the Module
sudo rmmod my_block
dmesg | tail
You should see:
Block driver removed
Remove the device node:
sudo rm /dev/myblock
Step 9: Optional – Test with Filesystem
You can even format your block device with a filesystem and mount it:
sudo mkfs.ext4 /dev/myblock
sudo mkdir /mnt/myblock
sudo mount /dev/myblock /mnt/myblock
ls /mnt/myblock
Now your RAM-backed block device acts like a real disk!
Step 10: Debugging Tips
- Use
dmesgfor kernel logs. - Check device nodes in
/dev. - If
insmodfails, ensure the kernel headers match your running kernel. - Use
blkid /dev/myblockto check the device.
Block Driver Model in Linux : Interview Questions & Answers
Round 1 – Basic / Fundamentals
These questions focus on understanding concepts, architecture, and terminology.
1. What is a block device in Linux?
Answer:
A block device is a type of device that transfers data in fixed-size blocks, unlike character devices which handle streams of bytes. Examples include HDDs, SSDs, USB drives, and RAM disks. Block devices support random access, and Linux uses the Block Driver Model to manage them efficiently.
2. How does a block device differ from a character device?
Answer:
| Feature | Block Device | Character Device |
|---|---|---|
| Data Access | Fixed-size blocks | Byte-by-byte stream |
| I/O Mode | Buffered I/O | Unbuffered I/O |
| Examples | HDD, SSD, USB | Keyboard, Mouse |
| API | block_device_operations | file_operations |
3. What is the Block Driver Model in Linux?
Answer:
It’s a framework in the Linux kernel that manages block devices. It abstracts hardware-specific details and provides features like request queues, buffered I/O, scheduling, and device abstraction, so multiple block devices can be handled uniformly.
4. What is gendisk in Linux block drivers?
Answer:gendisk is a kernel structure representing a block device. It contains:
- Device name (
disk_name) - Major and minor numbers
- Capacity (number of sectors)
- Operations supported (
fops) - Request queue pointer
It connects the hardware-level driver to the block subsystem in Linux.
5. What is a request queue in block devices?
Answer:
The request queue holds pending read/write requests for a block device. The Linux kernel schedules these requests efficiently using I/O schedulers like CFQ, NOOP, or Deadline. This optimizes performance and ensures correct sequencing.
6. What is block_device_operations?
Answer:
It’s a structure in Linux defining operations a block device can perform:
struct block_device_operations {
int (*open)(struct block_device *, fmode_t);
void (*release)(struct gendisk *, fmode_t);
int (*ioctl)(struct block_device *, fmode_t, unsigned, unsigned long);
};
It is similar to file_operations for character devices.
7. What are major and minor numbers?
Answer:
- Major number: Identifies the driver type in Linux.
- Minor number: Identifies specific devices handled by the driver.
For example, multiple partitions of a disk share the same major number, but each partition has a different minor number.
8. What is buffered vs direct I/O?
Answer:
- Buffered I/O: Data passes through kernel caches. It’s safer and improves performance.
- Direct I/O: Bypasses kernel caches, accessing hardware directly. Useful for high-performance storage systems.
9. Name common I/O schedulers in Linux block subsystem.
Answer:
- CFQ (Completely Fair Queuing)
- NOOP (simple FIFO queue)
- Deadline (prioritizes request deadlines)
- BFQ (Budget Fair Queuing)
10. Can a block driver support multiple partitions?
Answer:
Yes, Linux supports partitioning. One block driver can handle multiple logical partitions, each represented as a separate minor number.
11. What is the role of vmalloc in block drivers?
Answer:vmalloc is used to allocate contiguous virtual memory in the kernel, typically for storing RAM-backed block devices.
12. How does Linux handle read/write operations in block drivers?
Answer:
- Application calls
read()orwrite(). - VFS layer forwards it to block subsystem.
- Request is queued in
request_queue. - I/O scheduler orders the request.
- Device driver performs hardware-specific I/O.
- Completion is signaled back to the application.
13. What is the role of blk_fetch_request?
Answer:
It fetches pending I/O requests from the block device’s request queue. The driver processes each request sequentially or using optimized scheduling.
14. Why do we use add_disk()?
Answer:add_disk() makes the device available to the kernel and user space, registering it under /dev and linking it with the block subsystem.
15. What is the use of set_capacity()?
Answer:set_capacity() sets the total number of sectors for the block device. This defines its size to the kernel and filesystem.
Round 2 – Advanced / Practical Implementation
These questions focus on driver development, implementation, debugging, and performance tuning.
1. How do you create a device node for a block driver?
Answer:
- Check major number:
cat /proc/devices | grep myblock - Create device node:
sudo mknod /dev/myblock b <major> <minor> sudo chmod 666 /dev/myblock - Verify:
ls -l /dev/myblock
2. How do you test a block device driver?
Answer:
- Use
ddcommand:sudo dd if=/dev/zero of=/dev/myblock bs=512 count=10 sudo dd if=/dev/myblock of=/tmp/test bs=512 count=10 - Check logs:
dmesg | tail - Optional: Format with filesystem and mount:
sudo mkfs.ext4 /dev/myblock sudo mount /dev/myblock /mnt
3. How do you handle memory allocation in block drivers?
Answer:
vmalloc()– allocates virtual contiguous memory for the device buffer.kmalloc()– allocates kernel memory for small buffers.biostructures are used for I/O mapping.
4. How do you implement a request handler?
Answer:
- Use
blk_fetch_request()in a loop to fetch pending requests. - Map each segment using
kmap()for virtual memory access. - Perform read/write using
memcpy(). - Complete the request using
__blk_end_request_all().
5. How do you debug a block driver?
Answer:
- Use
printk(KERN_INFO)for logs. - Check
/var/log/kern.logordmesg. - Verify
/devdevice node and major/minor numbers. - Use
blkidorlsblkto inspect the device.
6. What is the difference between bio and request?
Answer:
- bio: Represents I/O segments in memory.
- request: High-level structure representing a complete I/O operation, may contain multiple bios.
7. Can block drivers support DMA?
Answer:
Yes, professional block drivers often use DMA (Direct Memory Access) to improve performance. Linux provides DMA APIs for mapping buffers and handling transfers.
8. What is the difference between blk_init_queue and blk_cleanup_queue?
Answer:
blk_init_queue()initializes the request queue for the block device.blk_cleanup_queue()frees and removes the request queue during cleanup.
9. How do you handle multiple concurrent I/O requests?
Answer:
- Requests are queued in
request_queue. - The I/O scheduler (CFQ, Deadline, NOOP) optimizes the order of requests.
- Request handler processes requests in sequence or using hardware features like parallel DMA channels.
10. How do you remove/unload a block driver?
Answer:
- Delete disk:
del_gendisk(my_disk) - Put disk:
put_disk(my_disk) - Cleanup queue:
blk_cleanup_queue(my_queue) - Unregister device:
unregister_blkdev(major_num, DEVICE_NAME) - Free memory:
vfree(device_data) - Remove module:
rmmod my_block
11. How do you implement partitions for your block driver?
Answer:
- Use
add_disk()multiple times with different minor numbers. - Each minor number represents a logical partition.
- Optionally, use
partitionsarray to define sector offsets.
12. How to ensure thread safety in block drivers?
Answer:
- Use
spinlocksormutexeswhen accessing shared buffers or request queues. - The kernel handles most queue serialization, but hardware access may require locking.
13. How do you integrate your block driver with a filesystem?
Answer:
- Create the device node (
/dev/myblock). - Format with filesystem:
mkfs.ext4 /dev/myblock - Mount it:
mount /dev/myblock /mnt - Perform normal filesystem operations (
touch,cp,ls).
14. How can you measure block device performance?
Answer:
- Use
ddfor raw I/O:dd if=/dev/myblock of=/dev/null bs=1M count=100 - Use
iostatorblktracefor detailed metrics. - Compare buffered vs direct I/O performance.
15. Common mistakes beginners make in block driver development
- Forgetting
set_capacity(). - Not creating device node in
/dev. - Ignoring
request_queueinitialization. - Not freeing allocated memory (
vmalloc) during cleanup. - Using
memcpywithout properkmap/kunmap.
FAQs About Block Driver Model in Linux
1. What is a block device in Linux?
A block device is a storage device that allows reading/writing data in fixed-size blocks, like HDDs, SSDs, or USB drives.
2. How does a block driver differ from a character driver?
Block drivers use buffered I/O and work with data blocks, while character drivers handle byte streams and unbuffered I/O.
3. What is the role of request_queue?
The request_queue stores pending I/O requests and optimizes their execution using the Linux I/O scheduler.
4. Can I write a block driver without understanding gendisk?
Not really. gendisk is essential as it represents the device to the kernel and connects your driver with the block subsystem.
5. Are block devices slower than character devices?
Not inherently. Block devices can be more efficient for large sequential I/O due to buffering and request queuing.
6. What I/O schedulers does Linux support?
Common schedulers include CFQ, NOOP, Deadline, and BFQ, each optimized for different workloads.
7. How do I test a block driver safely?
Use a virtual device first (like a RAM disk) to avoid corrupting real storage.
8. Do I need root permissions to insert a block driver module?
Yes, inserting kernel modules requires root privileges.
9. What is the difference between blk_fetch_request and bio?blk_fetch_request fetches requests from the queue; bio structures describe individual I/O segments.
10. Can block drivers support multiple partitions?
Yes, Linux block subsystem supports partitioning, allowing one driver to handle multiple logical partitions.
Conclusion
The Block Driver Model in Linux is a powerful framework that abstracts hardware details and allows developers to handle storage devices efficiently. From understanding gendisk to managing request queues and writing your own driver, this model is fundamental for kernel developers, embedded engineers, and anyone working with Linux storage systems.
With hands-on experimentation, you can master block driver development, optimize I/O performance, and even contribute to Linux kernel storage subsystems.
Read More about Process : What is is Process
Read More about System Call in Linux : What is System call
Read More about IPC : What is IPC
Mr. Raj Kumar is a highly experienced Technical Content Engineer with 7 years of dedicated expertise in the intricate field of embedded systems. At Embedded Prep, Raj is at the forefront of creating and curating high-quality technical content designed to educate and empower aspiring and seasoned professionals in the embedded domain.
Throughout his career, Raj has honed a unique skill set that bridges the gap between deep technical understanding and effective communication. His work encompasses a wide range of educational materials, including in-depth tutorials, practical guides, course modules, and insightful articles focused on embedded hardware and software solutions. He possesses a strong grasp of embedded architectures, microcontrollers, real-time operating systems (RTOS), firmware development, and various communication protocols relevant to the embedded industry.
Raj is adept at collaborating closely with subject matter experts, engineers, and instructional designers to ensure the accuracy, completeness, and pedagogical effectiveness of the content. His meticulous attention to detail and commitment to clarity are instrumental in transforming complex embedded concepts into easily digestible and engaging learning experiences. At Embedded Prep, he plays a crucial role in building a robust knowledge base that helps learners master the complexities of embedded technologies.










