块设备驱动最佳实践
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_IOERR | I/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();
}