【Linux驱动开发】Linux块设备驱动开发详解

Linux块设备驱动开发详解

概述

Linux块设备驱动是Linux内核中最复杂和重要的驱动类型之一。块设备以固定大小的块为单位进行数据读写,与字符设备相比,它们具有缓冲、缓存和复杂的I/O调度机制。

1. 块设备基础概念

1.1 块设备vs字符设备

块设备特点:

  • 以固定大小的块为单位进行数据传输(通常为512字节或4KB)
  • 支持随机访问,可以寻址到任意块
  • 具有缓冲和缓存机制
  • 支持复杂的I/O调度和重排序
  • 典型例子:硬盘、SSD、USB存储设备、RAM磁盘

字符设备特点:

  • 以字节流形式进行数据传输
  • 通常不支持随机访问
  • 无缓冲机制(除非应用层实现)
  • 典型例子:串口、键盘、鼠标

1.2 块设备核心数据结构

1.2.1 gendisk结构体
c 复制代码
struct gendisk {
    int major;                  /* 主设备号 */
    int first_minor;           /* 第一个次设备号 */
    int minors;                /* 次设备号数量 */
    char disk_name[32];        /* 设备名称 */
    struct disk_part_tbl *part_tbl; /* 分区表 */
    struct hd_struct part0;    /* 第0个分区 */
    struct block_device_operations *fops; /* 块设备操作函数 */
    struct request_queue *queue; /* 请求队列 */
    void *private_data;        /* 私有数据 */
    int flags;                 /* 设备标志 */
    // ... 其他字段
};
1.2.2 block_device_operations结构体
c 复制代码
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);
    int (*compat_ioctl) (struct block_device *, fmode_t, unsigned, unsigned long);
    int (*direct_access) (struct block_device *, sector_t, void __pmem **, unsigned long *);
    unsigned int (*check_events) (struct gendisk *disk, unsigned int clearing);
    // ... 其他函数指针
};
1.2.3 request结构体
c 复制代码
struct request {
    struct request_queue *q;     /* 所属的请求队列 */
    sector_t sector;            /* 起始扇区号 */
    unsigned int nr_sectors;    /* 扇区数量 */
    unsigned int current_nr_sectors; /* 当前扇区数量 */
    struct bio *bio;            /* 关联的bio */
    struct bio *biotail;        /* bio链表尾部 */
    // ... 其他字段
};
1.2.4 bio结构体
c 复制代码
struct bio {
    sector_t bi_sector;        /* 起始扇区号 */
    struct bio *bi_next;       /* 下一个bio */
    struct block_device *bi_bdev; /* 块设备 */
    unsigned long bi_flags;    /* bio标志 */
    unsigned long bi_rw;       /* 读写标志 */
    unsigned short bi_vcnt;    /* bio_vec数量 */
    unsigned short bi_idx;     /* 当前bio_vec索引 */
    unsigned short bi_phys_segments; /* 物理段数量 */
    unsigned short bi_hw_segments;   /* 硬件段数量 */
    unsigned int bi_size;      /* 数据大小(字节) */
    unsigned int bi_hw_front_size;  /* 硬件前填充大小 */
    unsigned int bi_hw_back_size;   /* 硬件后填充大小 */
    unsigned int bi_max_vecs;  /* 最大bio_vec数量 */
    struct bio_vec *bi_io_vec; /* bio_vec数组 */
    // ... 其他字段
};

2. 块设备驱动架构

2.1 整体架构

Linux块设备驱动采用分层架构:

复制代码
用户空间应用程序
        ↓
VFS(虚拟文件系统)
        ↓
块设备层(Block Layer)
        ↓
I/O调度器
        ↓
块设备驱动
        ↓
硬件设备

2.2 关键组件

2.2.1 请求队列(Request Queue)
  • 管理待处理的I/O请求
  • 实现I/O调度算法
  • 处理请求合并和重排序
2.2.2 I/O调度器
  • noop:最简单的调度器,只做基本合并
  • deadline:防止请求饥饿,保证服务质量
  • cfq(Completely Fair Queueing):完全公平队列调度
  • mq-deadline:多队列版本的deadline调度器
2.2.3 bio(Block I/O)
  • 描述块I/O操作的数据结构
  • 支持分散-聚集(scatter-gather)I/O
  • 可以链接多个bio形成bio链表

3. 块设备注册和初始化

3.1 基本步骤

  1. 分配gendisk结构体
  2. 设置设备参数
  3. 初始化请求队列
  4. 注册块设备
  5. 添加磁盘设备

3.2 代码示例

c 复制代码
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/genhd.h>
#include <linux/blkdev.h>
#include <linux/bio.h>

#define DEVICE_NAME "myblock"
#define SECTOR_SIZE 512
#define DEVICE_SIZE (16 * 1024 * 1024) /* 16MB */

static int major;
static struct gendisk *disk;
static struct request_queue *queue;
static spinlock_t lock;
static unsigned char *device_data;

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

static void myblock_release(struct gendisk *gd, fmode_t mode)
{
    printk(KERN_INFO "myblock: device released\n");
}

static const struct block_device_operations myblock_fops = {
    .owner = THIS_MODULE,
    .open = myblock_open,
    .release = myblock_release,
};

/* 请求处理函数 */
static void myblock_request(struct request_queue *q)
{
    struct request *req;
    
    while ((req = blk_fetch_request(q)) != NULL) {
        struct bio_vec bvec;
        struct req_iterator iter;
        sector_t sector = blk_rq_pos(req);
        unsigned long offset;
        void *buffer;
        
        /* 检查请求有效性 */
        if (blk_rq_sectors(req) * SECTOR_SIZE + sector * SECTOR_SIZE > DEVICE_SIZE) {
            printk(KERN_ERR "myblock: request beyond device size\n");
            __blk_end_request_all(req, -EIO);
            continue;
        }
        
        /* 处理请求 */
        rq_for_each_segment(bvec, req, iter) {
            offset = sector * SECTOR_SIZE + iter.iter.bi_sector * SECTOR_SIZE;
            buffer = page_address(bvec.bv_page) + bvec.bv_offset;
            
            if (rq_data_dir(req) == READ) {
                memcpy(buffer, device_data + offset, bvec.bv_len);
            } else {
                memcpy(device_data + offset, buffer, bvec.bv_len);
            }
        }
        
        __blk_end_request_all(req, 0);
    }
}

static int __init myblock_init(void)
{
    /* 1. 注册块设备 */
    major = register_blkdev(0, DEVICE_NAME);
    if (major < 0) {
        printk(KERN_ERR "myblock: failed to register block device\n");
        return major;
    }
    
    /* 2. 分配设备内存 */
    device_data = vmalloc(DEVICE_SIZE);
    if (!device_data) {
        unregister_blkdev(major, DEVICE_NAME);
        return -ENOMEM;
    }
    
    /* 3. 初始化自旋锁 */
    spin_lock_init(&lock);
    
    /* 4. 创建请求队列 */
    queue = blk_init_queue(myblock_request, &lock);
    if (!queue) {
        vfree(device_data);
        unregister_blkdev(major, DEVICE_NAME);
        return -ENOMEM;
    }
    
    /* 5. 设置队列参数 */
    blk_queue_logical_block_size(queue, SECTOR_SIZE);
    blk_queue_physical_block_size(queue, SECTOR_SIZE);
    
    /* 6. 分配gendisk结构体 */
    disk = alloc_disk(1);
    if (!disk) {
        blk_cleanup_queue(queue);
        vfree(device_data);
        unregister_blkdev(major, DEVICE_NAME);
        return -ENOMEM;
    }
    
    /* 7. 设置gendisk参数 */
    disk->major = major;
    disk->first_minor = 0;
    disk->minors = 1;
    strcpy(disk->disk_name, DEVICE_NAME);
    disk->fops = &myblock_fops;
    disk->queue = queue;
    set_capacity(disk, DEVICE_SIZE / SECTOR_SIZE);
    
    /* 8. 添加磁盘设备 */
    add_disk(disk);
    
    printk(KERN_INFO "myblock: block device initialized successfully\n");
    return 0;
}

static void __exit myblock_exit(void)
{
    /* 1. 删除磁盘设备 */
    del_gendisk(disk);
    
    /* 2. 清理gendisk结构体 */
    put_disk(disk);
    
    /* 3. 清理请求队列 */
    blk_cleanup_queue(queue);
    
    /* 4. 释放设备内存 */
    vfree(device_data);
    
    /* 5. 注销块设备 */
    unregister_blkdev(major, DEVICE_NAME);
    
    printk(KERN_INFO "myblock: block device removed\n");
}

module_init(myblock_init);
module_exit(myblock_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Linux Driver Developer");
MODULE_DESCRIPTION("A simple block device driver");

4. 请求处理机制

4.1 请求处理流程

  1. 请求接收 :内核通过make_request_fn接收bio请求
  2. 请求合并:尝试将新请求与队列中的现有请求合并
  3. 请求调度:根据I/O调度算法对请求进行排序和调度
  4. 请求派发:将请求派发给驱动的请求处理函数
  5. 请求完成:驱动处理完成后通知内核

4.2 bio处理

现代块设备驱动通常直接使用bio而不是传统的request:

c 复制代码
static void myblock_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;
    unsigned long offset;
    void *buffer;
    
    /* 检查bio有效性 */
    if (bio_sectors(bio) * SECTOR_SIZE + sector * SECTOR_SIZE > DEVICE_SIZE) {
        printk(KERN_ERR "myblock: bio beyond device size\n");
        bio_io_error(bio);
        return;
    }
    
    /* 处理bio */
    bio_for_each_segment(bvec, bio, iter) {
        offset = sector * SECTOR_SIZE + iter.bi_sector * SECTOR_SIZE;
        buffer = kmap(bvec.bv_page) + bvec.bv_offset;
        
        if (bio_data_dir(bio) == READ) {
            memcpy(buffer, device_data + offset, bvec.bv_len);
        } else {
            memcpy(device_data + offset, buffer, bvec.bv_len);
        }
        
        kunmap(bvec.bv_page);
    }
    
    bio_endio(bio);
}

5. 高级特性

5.1 多队列支持

现代存储设备支持多队列,提高并发性能:

c 复制代码
static int myblock_init(void)
{
    /* 创建多队列 */
    queue = blk_mq_init_sq_queue(&myblock_tag_set, &myblock_mq_ops, 128, 
                                  BLK_MQ_F_SHOULD_MERGE);
    if (!queue) {
        return -ENOMEM;
    }
    
    /* 设置队列参数 */
    blk_queue_logical_block_size(queue, SECTOR_SIZE);
    blk_queue_physical_block_size(queue, SECTOR_SIZE);
    blk_queue_max_hw_sectors(queue, 256);
    
    /* 其他初始化代码 */
    // ...
}

5.2 多队列操作函数

c 复制代码
static const struct blk_mq_ops myblock_mq_ops = {
    .queue_rq = myblock_queue_rq,
    .map_queue = blk_mq_map_queue,
};

static int myblock_queue_rq(struct blk_mq_hw_ctx *hctx, 
                           const struct blk_mq_queue_data *bd)
{
    struct request *req = bd->rq;
    struct myblock_dev *dev = hctx->queue->queuedata;
    
    /* 处理请求 */
    if (blk_rq_is_passthrough(req)) {
        return BLK_MQ_RQ_QUEUE_ERROR;
    }
    
    /* 执行实际的I/O操作 */
    myblock_execute_rq(dev, req);
    
    return BLK_MQ_RQ_QUEUE_OK;
}

5.3 电源管理

c 复制代码
static int myblock_suspend(struct device *dev)
{
    struct myblock_dev *mydev = dev_get_drvdata(dev);
    
    /* 停止请求队列 */
    blk_mq_stop_hw_queues(mydev->queue);
    
    /* 保存设备状态 */
    myblock_save_device_state(mydev);
    
    return 0;
}

static int myblock_resume(struct device *dev)
{
    struct myblock_dev *mydev = dev_get_drvdata(dev);
    
    /* 恢复设备状态 */
    myblock_restore_device_state(mydev);
    
    /* 重启请求队列 */
    blk_mq_start_stopped_hw_queues(mydev->queue, true);
    
    return 0;
}

static const struct dev_pm_ops myblock_pm_ops = {
    .suspend = myblock_suspend,
    .resume = myblock_resume,
};

6. 错误处理和调试

6.1 错误处理

c 复制代码
static void myblock_handle_error(struct myblock_dev *dev, int error)
{
    switch (error) {
    case -EIO:
        dev->stats.io_errors++;
        break;
    case -ENOMEM:
        dev->stats.memory_errors++;
        break;
    case -ETIME:
        dev->stats.timeout_errors++;
        break;
    default:
        dev->stats.other_errors++;
        break;
    }
    
    /* 记录错误信息 */
    if (dev->stats.total_errors++ > MAX_ALLOWED_ERRORS) {
        dev->state = DEVICE_STATE_ERROR;
        /* 停止设备 */
        blk_mq_stop_hw_queues(dev->queue);
    }
}

6.2 调试技巧

  1. 使用内核调试工具

    • blktrace:跟踪块设备I/O
    • blkparse:解析blktrace输出
    • btt:分析I/O性能
  2. 添加调试信息

c 复制代码
#define MYBLOCK_DEBUG

#ifdef MYBLOCK_DEBUG
#define myblock_dbg(fmt, args...) \
    printk(KERN_DEBUG "myblock: " fmt "\n", ##args)
#else
#define myblock_dbg(fmt, args...)
#endif
  1. 使用动态调试
c 复制代码
/* 在代码中添加动态调试点 */
pr_debug("myblock: processing request sector=%llu nr_sectors=%u\n",
         (unsigned long long)sector, nr_sectors);

7. 性能优化

7.1 I/O合并优化

c 复制代码
static void myblock_merge_bvec(struct request_queue *q,
                              struct bvec_merge_data *bmd,
                              struct bio_vec *bvec)
{
    struct myblock_dev *dev = q->queuedata;
    sector_t sector = bmd->bi_sector;
    unsigned short max_sectors;
    
    /* 计算最大合并扇区数 */
    max_sectors = min_t(unsigned short, dev->max_hw_sectors,
                       (dev->cache_size - (sector & (dev->cache_sectors - 1))));
    
    if (bvec->bv_len > (max_sectors << 9)) {
        bvec->bv_len = max_sectors << 9;
    }
}

7.2 预读优化

c 复制代码
static void myblock_end_io(struct bio *bio)
{
    struct myblock_dev *dev = bio->bi_private;
    
    if (bio_data_dir(bio) == READ && bio->bi_iter.bi_size > 0) {
        /* 触发预读 */
        myblock_trigger_readahead(dev, bio->bi_iter.bi_sector + 
                                      bio_sectors(bio));
    }
    
    bio_endio(bio);
}

7.3 多队列负载均衡

c 复制代码
static int myblock_map_queues(struct blk_mq_tag_set *set)
{
    struct myblock_dev *dev = set->driver_data;
    int i;
    
    for (i = 0; i < set->nr_maps; i++) {
        struct blk_mq_queue_map *map = &set->map[i];
        
        /* 根据CPU拓扑映射队列 */
        map->nr_queues = dev->nr_queues;
        map->queue_offset = i * dev->nr_queues;
        
        /* 设置CPU亲和性 */
        cpumask_setall(&map->mq_map[0]);
    }
    
    return 0;
}

8. 最佳实践

8.1 内存管理

  1. 使用适当的内存分配函数

    • kmalloc():小内存分配
    • vmalloc():大内存分配
    • dma_alloc_coherent():DMA缓冲区
  2. 避免内存泄漏

c 复制代码
static int myblock_init_memory(struct myblock_dev *dev)
{
    dev->data_buf = kmalloc(DATA_BUFFER_SIZE, GFP_KERNEL);
    if (!dev->data_buf)
        return -ENOMEM;
    
    dev->meta_buf = kmalloc(META_BUFFER_SIZE, GFP_KERNEL);
    if (!dev->meta_buf) {
        kfree(dev->data_buf);
        return -ENOMEM;
    }
    
    return 0;
}

static void myblock_cleanup_memory(struct myblock_dev *dev)
{
    kfree(dev->meta_buf);
    kfree(dev->data_buf);
}

8.2 并发控制

  1. 使用适当的锁机制

    • 自旋锁:短时间锁定
    • 互斥锁:长时间锁定
    • RCU:读多写少场景
  2. 避免死锁

c 复制代码
static void myblock_lock_queues(struct myblock_dev *dev)
{
    int i;
    
    /* 按固定顺序获取锁,避免死锁 */
    for (i = 0; i < dev->nr_queues; i++) {
        spin_lock(&dev->queues[i].lock);
    }
}

static void myblock_unlock_queues(struct myblock_dev *dev)
{
    int i;
    
    /* 按相反顺序释放锁 */
    for (i = dev->nr_queues - 1; i >= 0; i--) {
        spin_unlock(&dev->queues[i].lock);
    }
}

8.3 错误恢复

  1. 实现超时机制
c 复制代码
static void myblock_timeout_handler(unsigned long data)
{
    struct myblock_dev *dev = (struct myblock_dev *)data;
    
    if (dev->state == DEVICE_STATE_BUSY) {
        dev->timeout_count++;
        if (dev->timeout_count > MAX_TIMEOUT_COUNT) {
            dev->state = DEVICE_STATE_ERROR;
            /* 重置设备 */
            myblock_reset_device(dev);
        }
    }
}
  1. 实现重试机制
c 复制代码
static int myblock_retry_request(struct myblock_dev *dev, struct request *req)
{
    int ret;
    int retries = 0;
    
    do {
        ret = myblock_execute_request(dev, req);
        if (ret == 0)
            return 0;
            
        retries++;
        msleep(RETRY_DELAY_MS);
        
    } while (retries < MAX_RETRIES);
    
    return ret;
}

9. 总结

Linux块设备驱动开发涉及内核多个子系统,需要深入理解:

  1. 内核块设备子系统架构
  2. 请求队列和I/O调度机制
  3. bio和request的处理
  4. 多队列和电源管理
  5. 错误处理和性能优化

开发高质量的块设备驱动需要:

  • 扎实的内核编程基础
  • 对硬件特性的深入理解
  • 充分的测试和调试
  • 持续的性能优化

通过遵循最佳实践和不断学习,可以开发出稳定、高效的块设备驱动程序。

相关推荐
qq_401700412 小时前
Linux 信号机制
linux·运维·服务器
!chen2 小时前
Zabbix 配置中文界面、监控告警以及Windows、Linux主/被监控模板
linux·windows·zabbix
_Stellar2 小时前
Linux 服务器配置 rootless docker Quick Start
linux·服务器·docker
石像鬼₧魂石3 小时前
Kali Linux 中对某(靶机)监控设备进行漏洞验证的完整流程(卧室监控学习)
linux·运维·学习
“愿你如星辰如月”3 小时前
Linux:进程间通信
linux·运维·服务器·c++·操作系统
Code Warrior5 小时前
【Linux】应用层协议HTTP
linux·网络·网络协议·http
Hello,C++!6 小时前
linux下libcurl的https简单例子
linux·数据库·https
qq_401700416 小时前
Linux 磁盘挂载管理
linux·运维·服务器
q***31837 小时前
在Linux系统上使用nmcli命令配置各种网络(有线、无线、vlan、vxlan、路由、网桥等)
linux·服务器·网络