第18章 Linux块设备驱动开发深度解析

块设备是Linux系统中用于存储数据的重要组件,它以固定大小的块为单位进行数据读写操作。本章将全面分析块设备驱动的架构设计、I/O调度机制和性能优化技术,通过详细的代码实例展示如何开发高性能的块设备驱动程序。

18.1 块设备驱动基础概念

块设备与字符设备在Linux内核中有本质的区别,理解这些差异是开发块设备驱动的基础。

18.1.1 块设备特性分析

块设备具有以下关键特性:

  • 块状访问:数据以固定大小的块为单位进行读写
  • 缓冲机制:内核通过页面缓存对块设备数据进行缓存
  • 随机访问:支持任意位置的数据访问
  • I/O调度:内核对I/O请求进行重新排序以优化性能

常见的块设备包括:

  • 硬盘驱动器(HDD)
  • 固态硬盘(SSD)
  • USB存储设备
  • 虚拟块设备(ramdisk、loop设备)

与字符设备的对比:

// 字符设备操作
ssize_t char_read(struct file *file, char __user *buf, size_t count, loff_t *ppos);

// 块设备操作  
static struct block_device_operations blk_fops = {
    .owner = THIS_MODULE,
    .open = blk_open,
    .release = blk_release,
    .ioctl = blk_ioctl,
};

18.1.2 块设备驱动架构

块设备驱动的核心架构包含以下组件:

用户空间应用程序
    ↓ (系统调用)
虚拟文件系统(VFS)
    ↓
页面缓存(Page Cache)
    ↓
通用块层(Generic Block Layer)
    ↓
I/O调度器(I/O Scheduler)
    ↓
块设备驱动(Block Device Driver)
    ↓
物理存储设备

18.2 块设备驱动框架实现

块设备驱动的加载和卸载过程涉及多个关键数据结构的初始化和资源管理。

18.2.1 驱动加载过程详解

以下是一个完整的基本块设备驱动实现:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/blkdev.h>
#include <linux/genhd.h>
#include <linux/vmalloc.h>

#define DRIVER_NAME "virtual_blkdev"
#define DEVICE_NAME "vblk"
#define DISK_SECTOR_SIZE 512
#define DISK_SIZE_SECTORS 102400  // 50MB虚拟磁盘 (102400 * 512字节)

static struct gendisk *vdisk;
static struct request_queue *vqueue;
static unsigned char *vdata;

// 请求处理函数
static void virtual_request(struct request_queue *q)
{
    struct request *req;
    struct bio_vec bvec;
    struct req_iterator iter;
    sector_t sector;
    char *buffer;
    
    // 遍历请求队列中的所有请求
    while ((req = blk_fetch_request(q)) != NULL) {
        // 检查请求是否有效
        if (req == NULL || (req->cmd_type != REQ_TYPE_FS)) {
            printk(KERN_ERR "Invalid request\n");
            blk_end_request_all(req, -EIO);
            continue;
        }
        
        sector = blk_rq_pos(req);
        
        // 检查请求范围是否有效
        if ((sector + blk_rq_sectors(req)) > DISK_SIZE_SECTORS) {
            printk(KERN_ERR "Request beyond end of device\n");
            blk_end_request_all(req, -EIO);
            continue;
        }
        
        // 处理请求中的每个bio
        rq_for_each_segment(bvec, req, iter) {
            unsigned int bytes = bvec.bv_len;
            unsigned long offset = sector * DISK_SECTOR_SIZE;
            
            buffer = page_address(bvec.bv_page) + bvec.bv_offset;
            
            if (rq_data_dir(req) == WRITE) {
                // 写操作:从缓冲区复制数据到虚拟磁盘
                memcpy(vdata + offset, buffer, bytes);
                printk(KERN_DEBUG "Write: sector=%llu, bytes=%u\n", 
                       sector, bytes);
            } else {
                // 读操作:从虚拟磁盘复制数据到缓冲区
                memcpy(buffer, vdata + offset, bytes);
                printk(KERN_DEBUG "Read: sector=%llu, bytes=%u\n", 
                       sector, bytes);
            }
            
            sector += bytes / DISK_SECTOR_SIZE;
        }
        
        // 完成请求
        blk_end_request_all(req, 0);
    }
}

// 块设备操作函数集
static int virtual_open(struct block_device *bdev, fmode_t mode)
{
    printk(KERN_INFO "Virtual block device opened\n");
    return 0;
}

static void virtual_release(struct gendisk *disk, fmode_t mode)
{
    printk(KERN_INFO "Virtual block device released\n");
}

static int virtual_ioctl(struct block_device *bdev, fmode_t mode,
                        unsigned int cmd, unsigned long arg)
{
    printk(KERN_DEBUG "IOCTL called: cmd=0x%x\n", cmd);
    return -ENOTTY; // 大部分IOCTL不支持
}

static struct block_device_operations virtual_fops = {
    .owner = THIS_MODULE,
    .open = virtual_open,
    .release = virtual_release,
    .ioctl = virtual_ioctl,
};

static int __init virtual_blkdev_init(void)
{
    int ret;
    
    printk(KERN_INFO "Initializing virtual block device\n");
    
    // 分配虚拟磁盘内存
    vdata = vmalloc(DISK_SIZE_SECTORS * DISK_SECTOR_SIZE);
    if (!vdata) {
        printk(KERN_ERR "Failed to allocate device memory\n");
        return -ENOMEM;
    }
    
    // 初始化虚拟磁盘内容(可选)
    memset(vdata, 0, DISK_SIZE_SECTORS * DISK_SECTOR_SIZE);
    
    // 创建请求队列
    vqueue = blk_init_queue(virtual_request, NULL);
    if (!vqueue) {
        printk(KERN_ERR "Failed to initialize request queue\n");
        ret = -ENOMEM;
        goto err_init_queue;
    }
    
    // 设置队列参数
    blk_queue_logical_block_size(vqueue, DISK_SECTOR_SIZE);
    blk_queue_physical_block_size(vqueue, DISK_SECTOR_SIZE);
    
    // 分配gendisk结构
    vdisk = alloc_disk(1); // 次设备号数量
    if (!vdisk) {
        printk(KERN_ERR "Failed to allocate gendisk\n");
        ret = -ENOMEM;
        goto err_alloc_disk;
    }
    
    // 设置gendisk属性
    strcpy(vdisk->disk_name, DEVICE_NAME);
    vdisk->major = 0; // 动态分配主设备号
    vdisk->first_minor = 0;
    vdisk->fops = &virtual_fops;
    vdisk->queue = vqueue;
    vdisk->private_data = vdata;
    set_capacity(vdisk, DISK_SIZE_SECTORS);
    
    // 添加磁盘到系统
    add_disk(vdisk);
    
    printk(KERN_INFO "Virtual block device initialized: major=%d, capacity=%llu sectors\n",
           vdisk->major, (unsigned long long)DISK_SIZE_SECTORS);
    
    return 0;

err_alloc_disk:
    blk_cleanup_queue(vqueue);
err_init_queue:
    vfree(vdata);
    return ret;
}

static void __exit virtual_blkdev_exit(void)
{
    printk(KERN_INFO "Removing virtual block device\n");
    
    if (vdisk) {
        del_gendisk(vdisk);
        put_disk(vdisk);
    }
    
    if (vqueue) {
        blk_cleanup_queue(vqueue);
    }
    
    if (vdata) {
        vfree(vdata);
    }
    
    printk(KERN_INFO "Virtual block device removed\n");
}

module_init(virtual_blkdev_init);
module_exit(virtual_blkdev_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Block Device Example");
MODULE_DESCRIPTION("Virtual Block Device Driver");

编译此驱动需要以下Makefile:

obj-m += virtual_blkdev.o
KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)

all:
	$(MAKE) -C $(KDIR) M=$(PWD) modules

clean:
	$(MAKE) -C $(KDIR) M=$(PWD) clean

18.2.2 驱动卸载过程分析

驱动卸载时需要按正确顺序释放资源:

  1. 删除gendisk从系统
  2. 清理请求队列
  3. 释放设备内存
  4. 注销设备号

18.3 通用块层核心机制

通用块层是Linux块设备子系统的核心,它提供了块设备驱动的统一框架。

18.3.1 gendisk结构体详解

gendisk结构体代表一个完整的磁盘设备,包含以下重要字段:

struct gendisk {
    int major;                      // 主设备号
    int first_minor;                // 起始次设备号
    int minors;                     // 次设备号数量
    char disk_name[DISK_NAME_LEN];  // 设备名称
    struct block_device_operations *fops;  // 设备操作函数
    struct request_queue *queue;    // 请求队列
    void *private_data;            // 驱动私有数据
    sector_t capacity;             // 设备容量(扇区数)
    // ... 其他字段
};

18.3.2 请求队列管理

请求队列管理所有的I/O请求,支持多种调度算法:

// 创建请求队列的几种方式

// 1. 传统请求队列(带请求处理函数)
struct request_queue *q = blk_init_queue(request_fn, lock);

// 2. 制造请求队列(驱动自己处理bio)
struct request_queue *q = blk_alloc_queue(GFP_KERNEL);
blk_queue_make_request(q, make_request_fn);

// 3. 设置队列参数
blk_queue_logical_block_size(queue, 512);    // 逻辑块大小
blk_queue_physical_block_size(queue, 512);   // 物理块大小
blk_queue_max_hw_sectors(queue, 255);        // 最大扇区数

18.4 无请求队列块设备驱动

对于简单的块设备,可以绕过传统的请求队列机制,直接处理bio请求。

18.4.1 制造请求驱动架构

以下是不使用请求队列的块设备驱动实现:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/blkdev.h>
#include <linux/vmalloc.h>

#define DRIVER_NAME "simple_blkdev"
#define DEVICE_NAME "sblk"
#define SECTOR_SIZE 512
#define NSECTORS 2048  // 1MB虚拟磁盘

static struct gendisk *sdisk;
static struct request_queue *squeue;
static unsigned char *sdata;

// 制造请求函数
static int simple_make_request(struct request_queue *q, struct bio *bio)
{
    struct bio_vec bvec;
    struct bvec_iter iter;
    sector_t sector;
    char *buffer;
    int dir;
    
    // 检查bio是否有效
    if (!bio) {
        printk(KERN_ERR "Invalid bio\n");
        return -EIO;
    }
    
    sector = bio->bi_iter.bi_sector;
    dir = bio_data_dir(bio);
    
    // 检查请求范围
    if ((sector + (bio->bi_iter.bi_size / SECTOR_SIZE)) > NSECTORS) {
        printk(KERN_ERR "Bio beyond end of device\n");
        bio_io_error(bio);
        return -EIO;
    }
    
    // 处理bio中的每个段
    bio_for_each_segment(bvec, bio, iter) {
        unsigned long offset = sector * SECTOR_SIZE;
        unsigned int bytes = bvec.bv_len;
        
        buffer = page_address(bvec.bv_page) + bvec.bv_offset;
        
        if (dir == WRITE) {
            // 写操作
            memcpy(sdata + offset, buffer, bytes);
        } else {
            // 读操作
            memcpy(buffer, sdata + offset, bytes);
        }
        
        sector += bytes / SECTOR_SIZE;
    }
    
    // 完成bio
    bio_endio(bio);
    return 0;
}

static struct block_device_operations simple_fops = {
    .owner = THIS_MODULE,
};

static int __init simple_blkdev_init(void)
{
    int ret;
    
    printk(KERN_INFO "Initializing simple block device\n");
    
    // 分配设备内存
    sdata = vmalloc(NSECTORS * SECTOR_SIZE);
    if (!sdata) {
        printk(KERN_ERR "Failed to allocate device memory\n");
        return -ENOMEM;
    }
    memset(sdata, 0, NSECTORS * SECTOR_SIZE);
    
    // 创建制造请求队列
    squeue = blk_alloc_queue(GFP_KERNEL);
    if (!squeue) {
        printk(KERN_ERR "Failed to allocate request queue\n");
        ret = -ENOMEM;
        goto err_alloc_queue;
    }
    
    // 设置制造请求函数
    blk_queue_make_request(squeue, simple_make_request);
    
    // 设置队列参数
    blk_queue_logical_block_size(squeue, SECTOR_SIZE);
    blk_queue_physical_block_size(squeue, SECTOR_SIZE);
    
    // 分配gendisk
    sdisk = alloc_disk(1);
    if (!sdisk) {
        printk(KERN_ERR "Failed to allocate gendisk\n");
        ret = -ENOMEM;
        goto err_alloc_disk;
    }
    
    // 配置gendisk
    strcpy(sdisk->disk_name, DEVICE_NAME);
    sdisk->major = 0;
    sdisk->first_minor = 0;
    sdisk->fops = &simple_fops;
    sdisk->queue = squeue;
    sdisk->private_data = sdata;
    set_capacity(sdisk, NSECTORS);
    
    // 添加磁盘
    add_disk(sdisk);
    
    printk(KERN_INFO "Simple block device initialized: major=%d\n", sdisk->major);
    return 0;

err_alloc_disk:
    blk_cleanup_queue(squeue);
err_alloc_queue:
    vfree(sdata);
    return ret;
}

static void __exit simple_blkdev_exit(void)
{
    printk(KERN_INFO "Removing simple block device\n");
    
    if (sdisk) {
        del_gendisk(sdisk);
        put_disk(sdisk);
    }
    
    if (squeue) {
        blk_cleanup_queue(squeue);
    }
    
    if (sdata) {
        vfree(sdata);
    }
}

module_init(simple_blkdev_init);
module_exit(simple_blkdev_exit);

MODULE_LICENSE("GPL");

18.4.2 驱动测试方法

编译并加载驱动后,可以使用以下命令测试:

# 加载驱动
sudo insmod virtual_blkdev.ko

# 查看分配的设备号
dmesg | tail -10

# 创建设备节点(假设主设备号为250)
sudo mknod /dev/vblk0 b 250 0

# 测试设备
sudo fdisk -l /dev/vblk0
sudo mkfs.ext4 /dev/vblk0
sudo mount /dev/vblk0 /mnt/tmp
sudo dd if=/dev/zero of=/mnt/tmp/testfile bs=1M count=10
sudo umount /mnt/tmp

18.5 I/O调度器工作机制

I/O调度器是块设备子系统的关键组件,它负责优化I/O请求的执行顺序。

18.5.1 块I/O请求处理流程

数据从内存到磁盘的完整流程:

  1. 页面缓存:用户数据首先写入页面缓存
  2. bio生成:文件系统将写操作转换为bio结构
  3. I/O调度:调度器对bio进行排序和合并
  4. 请求生成:调度器将bio合并为request
  5. 驱动处理:驱动执行实际的I/O操作

18.5.2 bio结构分析

bio结构体代表一个块I/O操作:

struct bio {
    struct bio *bi_next;           // 请求队列中的下一个bio
    struct block_device *bi_bdev;  // 目标块设备
    unsigned long bi_flags;        // 状态标志
    unsigned long bi_rw;           // 读写标志
    struct bvec_iter bi_iter;      // I/O迭代器
    unsigned short bi_vcnt;        // bio_vec数量
    unsigned short bi_max_vecs;    // bio_vec最大数量
    atomic_t bi_cnt;               // 引用计数
    struct bio_vec *bi_io_vec;     // bio_vec数组
    bio_end_io_t *bi_end_io;       // I/O完成回调
    void *bi_private;              // 私有数据
};

18.5.3 四种I/O调度算法

Linux内核支持多种I/O调度算法:

  1. CFQ(完全公平队列):为每个进程维护独立的I/O队列
  2. Deadline:确保请求在截止时间内得到服务
  3. NOOP:简单的FIFO队列,不进行排序
  4. Kyber:基于延迟的现代调度器

18.6 自定义I/O调度配置

驱动可以根据设备特性选择合适的I/O调度器。

18.6.1 指定NOOP调度器

对于基于内存的虚拟设备,NOOP调度器通常是最佳选择:

static int __init custom_blkdev_init(void)
{
    struct request_queue *queue;
    struct elevator_type *elevator;
    
    // 创建请求队列
    queue = blk_init_queue(virtual_request, NULL);
    
    // 切换到NOOP调度器
    elevator = elevator_get(q, "noop", false);
    if (elevator) {
        elevator_change(q, elevator);
        elevator_put(elevator);
    }
    
    // 其余初始化代码...
}

18.6.2 改进的虚拟块设备驱动

以下是一个支持多种调度器的改进版本:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/blkdev.h>
#include <linux/elevator.h>
#include <linux/vmalloc.h>

#define DRIVER_NAME "advanced_blkdev"
#define DEVICE_NAME "ablk"
#define SECTOR_SIZE 512
#define DISK_SIZE 2097152  // 1GB (2097152个扇区)

static struct gendisk *adisk;
static struct request_queue *aqueue;
static unsigned char *adata;

// 请求处理函数
static void advanced_request(struct request_queue *q)
{
    struct request *req;
    sector_t start_sector;
    unsigned int nr_sectors;
    char *buffer;
    
    while ((req = blk_fetch_request(q)) != NULL) {
        if (req == NULL || (req->cmd_type != REQ_TYPE_FS)) {
            blk_end_request_all(req, -EIO);
            continue;
        }
        
        start_sector = blk_rq_pos(req);
        nr_sectors = blk_rq_sectors(req);
        
        // 验证请求范围
        if ((start_sector + nr_sectors) > DISK_SIZE) {
            printk(KERN_ERR "Request out of bounds: %llu + %u > %u\n",
                   start_sector, nr_sectors, DISK_SIZE);
            blk_end_request_all(req, -EIO);
            continue;
        }
        
        buffer = adata + (start_sector * SECTOR_SIZE);
        
        if (rq_data_dir(req) == WRITE) {
            // 处理写请求
            unsigned int bytes = blk_rq_bytes(req);
            if (bytes > (nr_sectors * SECTOR_SIZE)) {
                blk_end_request_all(req, -EIO);
                continue;
            }
            
            // 将数据从请求复制到虚拟磁盘
            if (blk_rq_map_kern(q, req, buffer, bytes, GFP_KERNEL)) {
                blk_end_request_all(req, -EIO);
                continue;
            }
        } else {
            // 处理读请求
            unsigned int bytes = blk_rq_bytes(req);
            if (bytes > (nr_sectors * SECTOR_SIZE)) {
                blk_end_request_all(req, -EIO);
                continue;
            }
            
            // 将数据从虚拟磁盘复制到请求
            if (blk_rq_map_kern(q, req, buffer, bytes, GFP_KERNEL)) {
                blk_end_request_all(req, -EIO);
                continue;
            }
        }
        
        blk_end_request_all(req, 0);
    }
}

// 设置I/O调度器
static int set_io_scheduler(const char *scheduler_name)
{
    struct elevator_type *elevator;
    int ret;
    
    elevator = elevator_get(aqueue, scheduler_name, false);
    if (!elevator) {
        printk(KERN_ERR "Scheduler %s not found\n", scheduler_name);
        return -EINVAL;
    }
    
    ret = elevator_change(aqueue, elevator);
    elevator_put(elevator);
    
    if (ret) {
        printk(KERN_ERR "Failed to change scheduler: %d\n", ret);
        return ret;
    }
    
    printk(KERN_INFO "I/O scheduler set to: %s\n", scheduler_name);
    return 0;
}

static struct block_device_operations advanced_fops = {
    .owner = THIS_MODULE,
    .open = virtual_open,
    .release = virtual_release,
    .ioctl = virtual_ioctl,
};

static int __init advanced_blkdev_init(void)
{
    int ret;
    
    printk(KERN_INFO "Initializing advanced block device\n");
    
    // 分配设备内存
    adata = vmalloc(DISK_SIZE * SECTOR_SIZE);
    if (!adata) {
        printk(KERN_ERR "Failed to allocate device memory\n");
        return -ENOMEM;
    }
    memset(adata, 0, DISK_SIZE * SECTOR_SIZE);
    
    // 创建请求队列
    aqueue = blk_init_queue(advanced_request, NULL);
    if (!aqueue) {
        printk(KERN_ERR "Failed to create request queue\n");
        ret = -ENOMEM;
        goto err_init_queue;
    }
    
    // 设置队列参数
    blk_queue_logical_block_size(aqueue, SECTOR_SIZE);
    blk_queue_physical_block_size(aqueue, SECTOR_SIZE);
    blk_queue_max_hw_sectors(aqueue, 255);
    
    // 设置I/O调度器(默认为当前系统调度器,可手动更改)
    printk(KERN_INFO "Current I/O scheduler: %s\n", 
           aqueue->elevator->elevator_type->elevator_name);
    
    // 分配和配置gendisk
    adisk = alloc_disk(1);
    if (!adisk) {
        printk(KERN_ERR "Failed to allocate gendisk\n");
        ret = -ENOMEM;
        goto err_alloc_disk;
    }
    
    strcpy(adisk->disk_name, DEVICE_NAME);
    adisk->major = 0;
    adisk->first_minor = 0;
    adisk->fops = &advanced_fops;
    adisk->queue = aqueue;
    adisk->private_data = adata;
    set_capacity(adisk, DISK_SIZE);
    
    add_disk(adisk);
    
    printk(KERN_INFO "Advanced block device initialized: major=%d\n", adisk->major);
    return 0;

err_alloc_disk:
    blk_cleanup_queue(aqueue);
err_init_queue:
    vfree(adata);
    return ret;
}

static void __exit advanced_blkdev_exit(void)
{
    printk(KERN_INFO "Removing advanced block device\n");
    
    if (adisk) {
        del_gendisk(adisk);
        put_disk(adisk);
    }
    
    if (aqueue) {
        blk_cleanup_queue(aqueue);
    }
    
    if (adata) {
        vfree(adata);
    }
}

module_init(advanced_blkdev_init);
module_exit(advanced_blkdev_exit);

MODULE_LICENSE("GPL");

18.7 直接bio处理机制

对于高性能需求,驱动可以绕过I/O调度器直接处理bio请求。

18.7.1 脱离I/O调度器的驱动实现

以下实现直接处理bio,不经过传统的请求队列:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/blkdev.h>
#include <linux/vmalloc.h>

#define DRIVER_NAME "direct_blkdev"
#define DEVICE_NAME "dblk"
#define SECTOR_SIZE 512
#define TOTAL_SECTORS 1048576  // 512MB

static struct gendisk *ddisk;
static struct request_queue *dqueue;
static unsigned char *ddata;

// 直接bio处理函数
static blk_qc_t direct_make_request(struct request_queue *q, struct bio *bio)
{
    struct bio_vec bvec;
    struct bvec_iter iter;
    sector_t sector = bio->bi_iter.bi_sector;
    int dir = bio_data_dir(bio);
    int err = 0;
    
    // 验证bio范围
    if ((sector + bio_sectors(bio)) > TOTAL_SECTORS) {
        err = -EIO;
        goto out;
    }
    
    // 处理bio中的每个段
    bio_for_each_segment(bvec, bio, iter) {
        unsigned long offset = sector * SECTOR_SIZE;
        unsigned int bytes = bvec.bv_len;
        char *buffer = page_address(bvec.bv_page) + bvec.bv_offset;
        
        if (dir == WRITE) {
            memcpy(ddata + offset, buffer, bytes);
        } else {
            memcpy(buffer, ddata + offset, bytes);
        }
        
        sector += bytes / SECTOR_SIZE;
    }

out:
    bio_endio(bio);
    return BLK_QC_T_NONE;
}

static int __init direct_blkdev_init(void)
{
    int ret;
    
    printk(KERN_INFO "Initializing direct bio block device\n");
    
    // 分配设备内存
    ddata = vmalloc(TOTAL_SECTORS * SECTOR_SIZE);
    if (!ddata) {
        printk(KERN_ERR "Failed to allocate device memory\n");
        return -ENOMEM;
    }
    memset(ddata, 0, TOTAL_SECTORS * SECTOR_SIZE);
    
    // 创建制造请求队列
    dqueue = blk_alloc_queue(GFP_KERNEL);
    if (!dqueue) {
        printk(KERN_ERR "Failed to allocate request queue\n");
        ret = -ENOMEM;
        goto err_alloc_queue;
    }
    
    // 设置直接bio处理
    blk_queue_make_request(dqueue, direct_make_request);
    
    // 配置队列参数
    blk_queue_logical_block_size(dqueue, SECTOR_SIZE);
    blk_queue_physical_block_size(dqueue, SECTOR_SIZE);
    blk_queue_max_hw_sectors(dqueue, 256);
    
    // 分配gendisk
    ddisk = alloc_disk(1);
    if (!ddisk) {
        printk(KERN_ERR "Failed to allocate gendisk\n");
        ret = -ENOMEM;
        goto err_alloc_disk;
    }
    
    // 配置gendisk
    strcpy(ddisk->disk_name, DEVICE_NAME);
    ddisk->major = 0;
    ddisk->first_minor = 0;
    ddisk->fops = &simple_fops;
    ddisk->queue = dqueue;
    ddisk->private_data = ddata;
    set_capacity(ddisk, TOTAL_SECTORS);
    
    add_disk(ddisk);
    
    printk(KERN_INFO "Direct bio block device initialized: major=%d\n", ddisk->major);
    return 0;

err_alloc_disk:
    blk_cleanup_queue(dqueue);
err_alloc_queue:
    vfree(ddata);
    return ret;
}

static void __exit direct_blkdev_exit(void)
{
    printk(KERN_INFO "Removing direct bio block device\n");
    
    if (ddisk) {
        del_gendisk(ddisk);
        put_disk(ddisk);
    }
    
    if (dqueue) {
        blk_cleanup_queue(dqueue);
    }
    
    if (ddata) {
        vfree(ddata);
    }
}

module_init(direct_blkdev_init);
module_exit(direct_blkdev_exit);

MODULE_LICENSE("GPL");

18.8 块设备分区支持

完整的块设备驱动应该支持分区功能。

18.8.1 分区支持实现

以下驱动支持多个分区:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/blkdev.h>
#include <linux/vmalloc.h>

#define DRIVER_NAME "partition_blkdev"
#define DEVICE_NAME "pblk"
#define SECTOR_SIZE 512
#define TOTAL_SECTORS 2097152  // 1GB
#define MINOR_COUNT 16         // 支持16个分区

static struct gendisk *pdisk;
static struct request_queue *pqueue;
static unsigned char *pdata;

// 请求处理函数(支持分区)
static void partition_request(struct request_queue *q)
{
    struct request *req;
    
    while ((req = blk_fetch_request(q)) != NULL) {
        struct block_device *bdev = req->rq_disk->part0;
        sector_t start_sector;
        
        if (req == NULL || (req->cmd_type != REQ_TYPE_FS)) {
            blk_end_request_all(req, -EIO);
            continue;
        }
        
        // 计算物理扇区地址(考虑分区偏移)
        start_sector = blk_rq_pos(req);
        if (req->rq_disk->part0 != bdev) {
            // 分区设备:调整扇区地址
            start_sector += get_start_sect(bdev);
        }
        
        // 验证范围
        if ((start_sector + blk_rq_sectors(req)) > TOTAL_SECTORS) {
            blk_end_request_all(req, -EIO);
            continue;
        }
        
        // 处理请求(简化处理)
        if (rq_data_dir(req) == WRITE) {
            // 写操作处理
            printk(KERN_DEBUG "Write to sector %llu\n", start_sector);
        } else {
            // 读操作处理
            printk(KERN_DEBUG "Read from sector %llu\n", start_sector);
        }
        
        blk_end_request_all(req, 0);
    }
}

static int __init partition_blkdev_init(void)
{
    int ret;
    
    printk(KERN_INFO "Initializing partition-aware block device\n");
    
    // 分配设备内存
    pdata = vmalloc(TOTAL_SECTORS * SECTOR_SIZE);
    if (!pdata) {
        printk(KERN_ERR "Failed to allocate device memory\n");
        return -ENOMEM;
    }
    memset(pdata, 0, TOTAL_SECTORS * SECTOR_SIZE);
    
    // 创建请求队列
    pqueue = blk_init_queue(partition_request, NULL);
    if (!pqueue) {
        printk(KERN_ERR "Failed to create request queue\n");
        ret = -ENOMEM;
        goto err_init_queue;
    }
    
    // 设置队列参数
    blk_queue_logical_block_size(pqueue, SECTOR_SIZE);
    blk_queue_physical_block_size(pqueue, SECTOR_SIZE);
    
    // 分配支持多个分区的gendisk
    pdisk = alloc_disk(MINOR_COUNT);
    if (!pdisk) {
        printk(KERN_ERR "Failed to allocate gendisk\n");
        ret = -ENOMEM;
        goto err_alloc_disk;
    }
    
    // 配置gendisk
    strcpy(pdisk->disk_name, DEVICE_NAME);
    pdisk->major = 0;
    pdisk->first_minor = 0;
    pdisk->minors = MINOR_COUNT;
    pdisk->fops = &virtual_fops;
    pdisk->queue = pqueue;
    pdisk->private_data = pdata;
    set_capacity(pdisk, TOTAL_SECTORS);
    
    // 添加磁盘(这会自动扫描分区)
    add_disk(pdisk);
    
    printk(KERN_INFO "Partition-aware block device initialized: major=%d\n", 
           pdisk->major);
    
    // 打印分区信息
    printk(KERN_INFO "Device capacity: %llu sectors (%llu MB)\n",
           (unsigned long long)TOTAL_SECTORS,
           (unsigned long long)(TOTAL_SECTORS * SECTOR_SIZE) / (1024 * 1024));
    
    return 0;

err_alloc_disk:
    blk_cleanup_queue(pqueue);
err_init_queue:
    vfree(pdata);
    return ret;
}

static void __exit partition_blkdev_exit(void)
{
    printk(KERN_INFO "Removing partition-aware block device\n");
    
    if (pdisk) {
        del_gendisk(pdisk);
        put_disk(pdisk);
    }
    
    if (pqueue) {
        blk_cleanup_queue(pqueue);
    }
    
    if (pdata) {
        vfree(pdata);
    }
}

module_init(partition_blkdev_init);
module_exit(partition_blkdev_exit);

MODULE_LICENSE("GPL");

18.8.2 分区测试方法

加载支持分区的驱动后,可以进行分区操作:

# 加载驱动
sudo insmod partition_blkdev.ko

# 查看设备号
dmesg | tail -5

# 创建设备节点(假设主设备号为251)
sudo mknod /dev/pblk b 251 0

# 创建分区表
sudo fdisk /dev/pblk
# 在fdisk中:n(新建分区),p(主分区),1(分区号),默认起始扇区,+500M(大小)
# 重复创建多个分区,然后w保存

# 格式化分区
sudo mkfs.ext4 /dev/pblk1
sudo mkfs.ext4 /dev/pblk2

# 挂载测试
sudo mkdir -p /mnt/part1 /mnt/part2
sudo mount /dev/pblk1 /mnt/part1
sudo mount /dev/pblk2 /mnt/part2

# 测试读写
sudo dd if=/dev/zero of=/mnt/part1/test1 bs=1M count=10
sudo dd if=/dev/zero of=/mnt/part2/test2 bs=1M count=10

# 卸载
sudo umount /mnt/part1 /mnt/part2

18.9 用户空间测试工具

以下是一个用户空间测试程序,用于验证块设备驱动的功能:

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/fs.h>
#include <string.h>
#include <errno.h>
#include <time.h>

void test_basic_operations(const char *device_path) {
    int fd;
    char buffer[512];
    char readback[512];
    int ret;
    
    printf("=== Testing Basic Block Device Operations ===\n");
    
    // 打开设备
    fd = open(device_path, O_RDWR);
    if (fd < 0) {
        perror("Failed to open device");
        return;
    }
    
    // 获取设备大小
    unsigned long long size;
    ret = ioctl(fd, BLKGETSIZE64, &size);
    if (ret == 0) {
        printf("Device size: %llu bytes (%llu MB)\n", 
               size, size / (1024 * 1024));
    }
    
    // 测试写操作
    memset(buffer, 0xAA, sizeof(buffer));
    ret = write(fd, buffer, sizeof(buffer));
    if (ret != sizeof(buffer)) {
        perror("Write failed");
    } else {
        printf("Write test passed: wrote %d bytes\n", ret);
    }
    
    // 测试读操作
    lseek(fd, 0, SEEK_SET);
    ret = read(fd, readback, sizeof(readback));
    if (ret != sizeof(readback)) {
        perror("Read failed");
    } else {
        if (memcmp(buffer, readback, sizeof(buffer)) == 0) {
            printf("Read test passed: data verified correctly\n");
        } else {
            printf("Read test failed: data mismatch\n");
        }
    }
    
    // 性能测试
    printf("=== Performance Test ===\n");
    struct timespec start, end;
    const int test_size = 1024 * 1024; // 1MB
    char *test_data = malloc(test_size);
    memset(test_data, 0x55, test_size);
    
    clock_gettime(CLOCK_MONOTONIC, &start);
    ret = write(fd, test_data, test_size);
    clock_gettime(CLOCK_MONOTONIC, &end);
    
    if (ret == test_size) {
        double elapsed = (end.tv_sec - start.tv_sec) + 
                        (end.tv_nsec - start.tv_nsec) / 1e9;
        double speed = (test_size / (1024.0 * 1024.0)) / elapsed;
        printf("Write performance: %.2f MB/s\n", speed);
    }
    
    free(test_data);
    close(fd);
}

void test_partition_scan(const char *device_path) {
    printf("=== Testing Partition Scan ===\n");
    
    char command[256];
    snprintf(command, sizeof(command), "fdisk -l %s", device_path);
    printf("Executing: %s\n", command);
    system(command);
}

int main(int argc, char *argv[]) {
    if (argc != 2) {
        printf("Usage: %s /dev/device_name\n", argv[0]);
        printf("Example: %s /dev/vblk0\n", argv[0]);
        return 1;
    }
    
    const char *device_path = argv[1];
    
    printf("Block Device Test Utility\n");
    printf("Testing device: %s\n", device_path);
    
    test_basic_operations(device_path);
    printf("\n");
    test_partition_scan(device_path);
    
    return 0;
}

编译和运行:

gcc -o blk_test blk_test.c -lrt
./blk_test /dev/vblk0

18.10 本章总结

本章详细介绍了Linux块设备驱动的开发技术,涵盖了从基础概念到高级特性的完整知识体系。通过分析块设备驱动的架构、I/O调度机制、性能优化和分区支持,读者可以掌握:

  1. 块设备基础:理解块设备与字符设备的本质区别
  2. 驱动架构:掌握gendisk、request_queue等核心数据结构
  3. I/O处理:了解请求队列、bio直接处理等不同I/O处理模式
  4. 性能优化:学习I/O调度器选择和调优技术
  5. 高级特性:实现分区支持、性能监控等高级功能

块设备驱动开发是Linux内核编程中的重要领域,通过本章提供的完整代码实例和实践方法,读者可以快速上手各种块设备驱动的开发工作,为存储系统开发奠定坚实基础。在实际项目中,需要根据具体硬件特性和性能需求选择合适的架构和优化策略。

更多推荐