块设备驱动

一、块设备基础

二、关键数据结构

1. bio(Block I/O)

struct bio {
    sector_t bi_sector;       // 起始扇区
    struct bio_vec *bi_io_vec; // 段数组
    unsigned int bi_vcnt;     // 段数
    unsigned int bi_idx;      // 当前段
    unsigned int bi_size;
    struct block_device *bi_bdev;
    // ...
};

struct bio_vec {
    struct page *bv_page;
    unsigned int bv_offset;
    unsigned int bv_len;
};

2. request / request_queue

struct request {
    struct list_head queuelist;
    sector_t sector;
    unsigned long nr_sectors;
    struct bio *bio;
    // ...
};

struct request_queue {
    struct elevator_queue *elevator;
    // ...
};

3. 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;
    // ...
};

三、传统方式(blk_init_queue)

static struct request_queue *queue;
static struct gendisk *gd;
static DEFINE_SPINLOCK(lock);

static void my_request(struct request_queue *q)
{
    struct request *rq;
    while ((rq = blk_fetch_request(q)) != NULL) {
        struct bio *bio;
        sector_t sector = blk_rq_pos(rq);
        unsigned int nr = blk_rq_cur_sectors(rq);
        // 遍历 bio
        __rq_for_each_bio(bio, rq) {
            // 处理 bio(map、transfer、copy)
        }
        if (!__blk_end_request_cur(rq, 0))
            continue;
    }
}

static int my_init(void)
{
    queue = blk_init_queue(my_request, &lock);
    blk_queue_logical_block_size(queue, 512);
    gd = alloc_disk(1);
    gd->major = MY_MAJOR;
    gd->first_minor = 0;
    gd->fops = &my_fops;
    gd->queue = queue;
    snprintf(gd->disk_name, 32, "myblk");
    set_capacity(gd, NR_SECTORS);
    add_disk(gd);
    return 0;
}

四、现代方式(blk-mq,推荐)

static int my_queue_rq(struct blk_mq_hw_ctx *hctx,
                       const struct blk_mq_queue_data *bd)
{
    struct request *rq = bd->rq;
    blk_mq_start_request(rq);
    // 异步处理
    // 完成时:
    blk_mq_end_request(rq, 0);
    return BLK_MQ_RQ_QUEUE_OK;
}

static struct blk_mq_ops my_mq_ops = {
    .queue_rq = my_queue_rq,
};

static const struct block_device_operations my_fops = {
    .owner   = THIS_MODULE,
    .open    = my_open,
    .release = my_release,
};

static int my_init(void)
{
    struct blk_mq_tag_set tag_set = {
        .ops        = &my_mq_ops,
        .nr_hw_queues = 1,
        .nr_maps      = 1,
        .queue_depth  = 128,
    };
    blk_mq_alloc_tag_set(&tag_set);

    queue = blk_mq_init_queue(&tag_set);
    blk_queue_logical_block_size(queue, 512);

    gd = blk_mq_alloc_disk(&tag_set, NULL);
    gd->fops = &my_fops;
    set_capacity(gd, NR_SECTORS);
    add_disk(gd);
    return 0;
}

五、提交 bio(绕过 IO 调度)

// 直接提交 bio(如 NVMe 驱动)
void submit_bio(struct bio *bio);

六、bio 处理

static int my_xfer_bio(struct bio *bio)
{
    struct bio_vec bvec;
    struct bvec_iter iter;
    bio_for_each_segment(bvec, bio, iter) {
        void *buf = kmap(bvec.bv_page) + bvec.bv_offset;
        sector_t sector = iter.bi_sector;
        unsigned int len = bvec.bv_len;
        // 读 / 写 buf
        kunmap(bvec.bv_page);
    }
    return 0;
}

七、块设备操作

static const struct block_device_operations my_fops = {
    .owner          = THIS_MODULE,
    .open           = my_open,
    .release        = my_release,
    .ioctl          = my_ioctl,
    .getgeo         = my_getgeo,
    .revalidate_disk = my_revalidate,
};

八、嵌入式应用

嵌入式开发一般不直接写块设备驱动,但理解原理能帮调试。

九、调试

lsblk
cat /proc/partitions
cat /sys/block/sda/queue/scheduler     # io scheduler
iostat -x 1
cat /sys/block/sda/stat
cat /proc/diskstats
blktrace / blkparse