《Linux设备驱动开发详解(第3版)》 第12章 Linux块设备驱动

12.1 块设备驱动概述

块设备以块为单位进行数据传输,通常支持随机访问,如硬盘、U盘等。块设备驱动负责管理这些设备的I/O操作,为内核和用户空间提供统一的接口。这部分通常以理论讲解为主,代码示例较少。

12.2 块设备的I/O栈

块设备的I/O栈涉及多个层次,从用户空间的系统调用开始,经过VFS(虚拟文件系统)、通用块层,最终到达块设备驱动层。这部分也是理论性较强,直接的代码示例较少。

12.3 通用块层

通用块层是Linux内核中位于VFS和块设备驱动之间的一层,它负责管理块设备的请求队列,对I/O请求进行合并、排序等优化操作,以提高I/O性能。这部分同样以理论为主,直接代码示例较少。

12.4 块设备驱动的注册与注销

在Linux内核中,块设备驱动需要先注册才能被系统识别和使用,使用完毕后需要注销以释放资源。

#include 
#include 
#include 
#include 

// 定义设备号
dev_t dev_num;
// 定义块设备结构体
struct gendisk *my_disk;

// 模块初始化函数,用于注册块设备
static int __init block_dev_init(void) {
    // 分配设备号
    // 第一个参数是指向dev_t类型变量的指针,用于存储分配的设备号
    // 第二个参数是起始设备号,这里设为0
    // 第三个参数是要分配的设备号数量,这里设为1
    // 第四个参数是设备名称,用于在sysfs中显示
    int ret = alloc_chrdev_region(&dev_num, 0, 1, "my_block_dev");
    if (ret) {
        // 如果分配设备号失败,打印错误信息
        printk(KERN_ERR "Failed to allocate device number\n");
        return ret;
    }

    // 创建一个gendisk结构体实例
    // 第一个参数是设备名称
    // 第二个参数是该设备包含的分区数量,这里设为0
    my_disk = alloc_disk(0);
    if (!my_disk) {
        // 如果创建gendisk失败,释放已分配的设备号
        unregister_chrdev_region(dev_num, 1);
        printk(KERN_ERR "Failed to allocate gendisk\n");
        return -ENOMEM;
    }

    // 设置gendisk的主设备号和次设备号
    my_disk->major = MAJOR(dev_num);
    my_disk->first_minor = MINOR(dev_num);
    // 设置gendisk的名称
    sprintf(my_disk->disk_name, "my_disk");
    // 设置gendisk的所有者为当前模块
    my_disk->owner = THIS_MODULE;
    // 设置gendisk的容量,这里假设为1024个扇区,每个扇区512字节
    my_disk->capacity = 1024 * 512;

    // 注册gendisk
    add_disk(my_disk);

    printk(KERN_INFO "Block device registered successfully\n");
    return 0;
}

// 模块退出函数,用于注销块设备
static void __exit block_dev_exit(void) {
    // 从系统移除gendisk
    del_gendisk(my_disk);
    // 释放gendisk结构体占用的内存
    put_disk(my_disk);
    // 释放设备号
    unregister_chrdev_region(dev_num, 1);
    printk(KERN_INFO "Block device unregistered successfully\n");
}

module_init(block_dev_init);
module_exit(block_dev_exit);
MODULE_LICENSE("GPL");

12.5 request_queue与bio

request_queue是块设备请求队列,用于管理I/O请求。bio(Block I/O)结构体表示一个块设备I/O请求,包含了请求的目标设备、数据缓冲区、操作类型等信息。

#include 
#include 
#include 
#include 
#include 
#include 

// 定义设备号
dev_t dev_num;
// 定义块设备结构体
struct gendisk *my_disk;
// 定义请求队列
struct request_queue *my_queue;

// 块设备的请求处理函数
static void my_request_fn(struct request_queue *q) {
    struct request *req;
    // 循环处理请求队列中的请求
    while ((req = elv_next_request(q))!= NULL) {
        struct bio *bio;
        // 遍历请求中的所有bio
        for_each_bio(bio, req) {
            // 获取bio的扇区偏移
            sector_t sector = bio->bi_sector;
            // 获取bio的数据缓冲区
            char *buffer = bio->bi_io_vec[0].bv_data;
            // 获取bio的长度
            unsigned int len = bio->bi_io_vec[0].bv_len;
            // 这里可以进行实际的I/O操作,例如模拟读取扇区数据
            for (unsigned int i = 0; i < len; i++) {
                buffer[i] = sector + i;
            }
            // 标记bio完成
            bio_endio(bio, 0);
        }
        // 标记请求完成
        end_request(req, 0);
    }
}

// 模块初始化函数,用于注册块设备
static int __init block_dev_init(void) {
    int ret;

    // 分配设备号
    ret = alloc_chrdev_region(&dev_num, 0, 1, "my_block_dev");
    if (ret) {
        printk(KERN_ERR "Failed to allocate device number\n");
        return ret;
    }

    // 创建请求队列
    // 第一个参数是请求处理函数
    // 第二个参数是当前模块指针
    my_queue = blk_init_queue(my_request_fn, THIS_MODULE);
    if (!my_queue) {
        unregister_chrdev_region(dev_num, 1);
        printk(KERN_ERR "Failed to create request queue\n");
        return -ENOMEM;
    }

    // 创建一个gendisk结构体实例
    my_disk = alloc_disk(0);
    if (!my_disk) {
        blk_cleanup_queue(my_queue);
        unregister_chrdev_region(dev_num, 1);
        printk(KERN_ERR "Failed to allocate gendisk\n");
        return -ENOMEM;
    }

    my_disk->major = MAJOR(dev_num);
    my_disk->first_minor = MINOR(dev_num);
    sprintf(my_disk->disk_name, "my_disk");
    my_disk->owner = THIS_MODULE;
    my_disk->capacity = 1024 * 512;
    // 设置gendisk的请求队列
    my_disk->queue = my_queue;

    add_disk(my_disk);

    printk(KERN_INFO "Block device registered successfully\n");
    return 0;
}

// 模块退出函数,用于注销块设备
static void __exit block_dev_exit(void) {
    del_gendisk(my_disk);
    put_disk(my_disk);
    blk_cleanup_queue(my_queue);
    unregister_chrdev_region(dev_num, 1);
    printk(KERN_INFO "Block device unregistered successfully\n");
}

module_init(block_dev_init);
module_exit(block_dev_exit);
MODULE_LICENSE("GPL");

12.6 块设备驱动实例

下面是一个完整的块设备驱动实例,结合了前面的注册、注销以及请求处理的内容。

#include 
#include 
#include 
#include 
#include 
#include 

// 定义设备号
dev_t dev_num;
// 定义块设备结构体
struct gendisk *my_disk;
// 定义请求队列
struct request_queue *my_queue;

// 块设备的请求处理函数
static void my_request_fn(struct request_queue *q) {
    struct request *req;
    while ((req = elv_next_request(q))!= NULL) {
        struct bio *bio;
        for_each_bio(bio, req) {
            sector_t sector = bio->bi_sector;
            char *buffer = bio->bi_io_vec[0].bv_data;
            unsigned int len = bio->bi_io_vec[0].bv_len;
            // 这里可以进行实际的I/O操作,例如模拟读取扇区数据
            for (unsigned int i = 0; i < len; i++) {
                buffer[i] = sector + i;
            }
            bio_endio(bio, 0);
        }
        end_request(req, 0);
    }
}

// 模块初始化函数,用于注册块设备
static int __init block_dev_init(void) {
    int ret;

    // 分配设备号
    ret = alloc_chrdev_region(&dev_num, 0, 1, "my_block_dev");
    if (ret) {
        printk(KERN_ERR "Failed to allocate device number\n");
        return ret;
    }

    // 创建请求队列
    my_queue = blk_init_queue(my_request_fn, THIS_MODULE);
    if (!my_queue) {
        unregister_chrdev_region(dev_num, 1);
        printk(KERN_ERR "Failed to create request queue\n");
        return -ENOMEM;
    }

    my_disk = alloc_disk(0);
    if (!my_disk) {
        blk_cleanup_queue(my_queue);
        unregister_chrdev_region(dev_num, 1);
        printk(KERN_ERR "Failed to allocate gendisk\n");
        return -ENOMEM;
    }

    my_disk->major = MAJOR(dev_num);
    my_disk->first_minor = MINOR(dev_num);
    sprintf(my_disk->disk_name, "my_disk");
    my_disk->owner = THIS_MODULE;
    my_disk->capacity = 1024 * 512;
    my_disk->queue = my_queue;

    add_disk(my_disk);

    printk(KERN_INFO "Block device registered successfully\n");
    return 0;
}

// 模块退出函数,用于注销块设备
static void __exit block_dev_exit(void) {
    del_gendisk(my_disk);
    put_disk(my_disk);
    blk_cleanup_queue(my_queue);
    unregister_chrdev_region(dev_num, 1);
    printk(KERN_INFO "Block device unregistered successfully\n");
}

module_init(block_dev_init);
module_exit(block_dev_exit);
MODULE_LICENSE("GPL");

这个实例展示了一个基本的块设备驱动的完整实现,包括设备的注册与注销、请求队列的创建和请求处理函数的实现。实际应用中,需要根据具体设备的特性对请求处理函数进行更详细的定制,以实现真实的块设备I/O操作。

你可能感兴趣的:(嵌入式,linux,驱动开发,运维)