Skip to content

块设备驱动最佳实践

1. 优先使用 blk-mq

旧式单队列 request_fn 接口已废弃,新驱动一律使用 blk-mq:

c
/* ✅ 现代方式:blk-mq */
static const struct blk_mq_ops my_mq_ops = {
    .queue_rq = my_queue_rq,
    .init_hctx = my_init_hctx,   /* 可选:初始化硬件队列 */
};

/* 多硬件队列(NVMe 风格) */
tag_set.nr_hw_queues = num_online_cpus();

2. 正确处理 blk_status_t

c
static blk_status_t 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);   /* 必须在处理前调用 */

    if (device_error())
        blk_mq_end_request(rq, BLK_STS_IOERR);
    else
        blk_mq_end_request(rq, BLK_STS_OK);

    return BLK_STS_OK;   /* queue_rq 本身的返回值 */
}

常用 blk_status_t 值:

含义
BLK_STS_OK成功
BLK_STS_IOERRI/O 错误
BLK_STS_TIMEOUT超时
BLK_STS_NOSPC空间不足(闪存写满)
BLK_STS_RESOURCE资源不足,稍后重试

3. 异步完成

真实硬件驱动中,I/O 完成通常在中断中通知:

c
/* 提交请求时不等待完成 */
static blk_status_t my_queue_rq(struct blk_mq_hw_ctx *hctx,
                                  const struct blk_mq_queue_data *bd)
{
    struct request *rq = bd->rq;
    struct my_cmd *cmd = blk_mq_rq_to_pdu(rq);

    blk_mq_start_request(rq);

    /* 将请求提交给硬件,不等待 */
    cmd->rq = rq;
    submit_to_hardware(cmd);

    return BLK_STS_OK;
}

/* 中断处理函数中完成请求 */
static irqreturn_t my_irq_handler(int irq, void *dev_id)
{
    struct my_priv *priv = dev_id;
    struct my_cmd *cmd = get_completed_cmd(priv);

    blk_mq_end_request(cmd->rq, BLK_STS_OK);
    return IRQ_HANDLED;
}

4. 每请求私有数据

c
/* 在 tag_set 中指定每请求数据大小 */
tag_set.cmd_size = sizeof(struct my_cmd);

/* 在 queue_rq 中获取 */
struct my_cmd *cmd = blk_mq_rq_to_pdu(rq);

5. 分区支持

c
/* 设置最大分区数 */
priv->disk->minors = 16;   /* 支持 15 个分区 */

/* 分区表由内核自动解析(GPT/MBR) */
/* 用户空间用 fdisk/parted 操作 */

6. 写缓存与 FUA

c
/* 声明设备支持写缓存 */
blk_queue_write_cache(disk->queue, true, true);

/* 处理 FUA(Force Unit Access)请求 */
if (req_op(rq) == REQ_OP_WRITE && (rq->cmd_flags & REQ_FUA)) {
    /* 写入后立即刷新到持久存储 */
    flush_to_storage();
}

褚成志的笔记