当前位置: 首页 > news >正文

NVME驱动分析

一、NVME驱动介绍

  • 协议支持:NVMe(Non-Volatile Memory Express)是为PCIe SSD设计的高性能存储协议,提供低延迟、高吞吐(相比SATA SSD提升7倍以上)
  • 内核集成:自Linux 3.3版本起原生支持NVMe驱动,通过nvme.ko模块实现设备管理、队列调度和命令处理。
  • 核心组件:
    • Block层:对接文件系统,处理标准I/O请求(如读/写)。
    • NVMe核心层:管理控制器(nvme_ctrl)、命名空间(nvme_ns)和I/O队列。
    • PCIe驱动层:处理设备枚举、BAR空间映射和中断。

主机设备上电驱动加载简要流程(参考其他文章)

1、主机上电,开始扫描PCIE总线,深度优先搜索;

2、识别总线上的PCI设备;

3、为设备分配bar空间;

4、产生ACPI表,OS根据此表获取PCI设备;

5、OS启动,识别PCIE设备为NVME设备,probe驱动开始工作。

二、NVME驱动关键函数

(一)nvme_probe

功能模块

描述

技术细节

关键函数

设备识别与驱动绑定

PCIe总线检测到NVMe设备时,通过设备ID匹配驱动并触发nvme_probe入口函数。

检查PCI设备的Vendor ID/Device ID,匹配nvme_id_table,创建struct nvme_dev上下文结构体。

nvme_probe(struct pci_dev *pdev, const struct pci_device_id *id)

资源分配与初始化

为控制器分配内存资源,包括nvme_devqueue

通过kzalloc_node分配nvme_dev结构体,使用dma_alloc_coherent分配队列内存。

kcalloc_node

PCI配置与BAR空间映射

配置PCI设备的BAR空间,将其映射到内核虚拟地址以访问控制器寄存器。

调用pci_request_mem_regions申请内存区域,通过ioremapBAR0映射到dev->bar

nvme_dev_map(struct nvme_dev *dev)

pci_request_mem_regions nvme_remap_bar

初始化工作队列

初始化工作队列,包括nvme复位、移除流程

nvme_reset_work流程进一步说明

INIT_WORK(&dev->ctrl.reset_work, nvme_reset_work)

INIT_WORK(&dev->remove_work, nvme_remove_dead_ctrl_work);

初始化PRP内存池

通过预分配 PRP 池,驱动可避免每次 I/O 请求时动态申请内存,从而减少延迟和内存碎片

dma_pool_create() 创建多个不同大小的内存池(如 4KB8KB),适配不同大小的数据传输请求;池的大小通常基于 NVMe 控制器支持的最大物理页(Page Size)和队列深度(Queue Depth)动态计算

result = nvme_setup_prp_pools(dev);

iod_mempool初始化

主要用于管理 NVMe 设备进行 DMA 数据传输时所需的内存资源

计算单个 I/O 描述符(IOD)的内存分配大小;创建 NUMA 节点绑定的内存池

nvme_pci_iod_alloc_size

mempool_create_node

nvme ctrl初始化

初始化nvmectrl结构体;初始化工作队列scan nsasync eventfw actdelete nvme;分配nvme字符号,添加到系统;

nvme ctrl初始化关键函数

nvme_init_ctrl

nvme_async_probe函数提交到内核的异步任务队列

执行控制器复位、Admin队列配置、命名空间扫描等操作

执行工作队列内的函数

nvme_async_probe

错误处理与资源释放

在初始化失败时释放已分配的资源(如释放BAR映射、DMA内存等)。

调用pci_release_mem_regions释放PCI资源,kfree释放nvme_dev结构体。

mempool_destroy

nvme_release_prp_poolsnvme_dev_unmap

 表格记录probe的关键流程,主要在于:

1、分配资源 -相关结构体分配;

2、PCI bar空间资源与虚拟内存映射,方便主机访问;

3、初始化工作队列,主要是nvme_reset_work和nvme_scan_work

4、初始化PRP内存池,方便IO过程使用PRP地址;

5、nvme ctrl初始化,主要初始化nvme_ctrl结构体,分配nvme节点,初始化字符设备并添加到字符设备中,方便上层应用访问;

6、执行nvme_aync_probe,即转向nvme_reset_work->nvme_scan_work

(二)nvme_reset_work

功能模块

描述

技术细节

关键函数

初始化PCI硬件资源

重新映射PCI BAR空间,配置DMA

使能PCI设备 DMA MSI-X等

nvme_pci_enable

admin queue配置

1. nvme_configure_admin_queue():设置 Admin 队列属性(大小、中断等)。

2. nvme_init_queue():初始化队列结构体。

3. nvme_alloc_admin_tags():分配 Admin 命令标签。

ASQ ACQ配置完成,主机可发admin命令

nvme_pci_configure_admin_queue

nvme_alloc_admin_tags

发生identity命令识别盘片信息

调用 nvme_init_identify读取控制器信息(如命名空间数量、特性支持等)

获取设备能力参数(如最大队列数、LBA 大小),以便于后续资源分配。

nvme_init_identify

IO队列创建

1、计算所需IO队列数;

2、向控制器申请queue资源;

3、创建并初始化IO队列

为每个CPU核心分配一个IO队列,提高并发性能

nvme_setup_io_queues

状态切换与启动

1. 调用 nvme_change_ctrl_state() 将状态切换为 NVME_CTRL_LIVE

2. 启动 I/O 队列。

标记ctrl运行状态,激活IO流程

nvme_start_queues

启动控制器任务

创建IO queue后,启动scan workasync event work

NS扫描流程和响应异步事件AER处理

nvme_start_ctrl

 表格记录nvme_reser_work的关键步骤:

1、初始化PCI硬件资源,使能PCI设备;

2、配置admin queue,如ACQ ASQ地址,初始化队列;

3、identitfy命令获取盘片能力以及支持特性;

4、IO SQ/CQ创建;

5、Controller状态切换,并转向nvme_scan_work;

(三) nvme_scan_work

功能模块

描述

技术细节

关键函数

发送identify命令获取NS数量

 控制器信息获取与命名空间数量解析

获取来自盘符的NS数量

nvme_identify_ctrl(ctrl, &id) le32_to_cpu(id->nn)

NS扫描

扫描NS list

遍历NS

nvme_scan_ns_sequential

上表描述扫盘过程,关键步骤如下:

1、identify命令获取NS相关信息;

2、扫描NS信息。

三、NVME IO执行流程

主机发送NVME命令,会经历如下流程:

1、调用系统调用SYSCALL_DEFINE3,转向VFS;

2、进入VFS文件系统,命令生成bio,转向mq_block层;

3、进入block层后,调用submit_io传输命令到mq list,开始多队列调度;

4、多队列调度分发到hw queue,使用blk_mq_run_hw_queue,最终调用nvme_queue_rq;

5、进入nvme驱动层,把提交的cmd填入SQ中,并更新doorbell tail指针;

6、SSD设备检测到doorbell tail不等于head,意味着主机有命令写入,开始从SQ中取指令执行。

简要流程如下图:

 

 四、控制器复位

 nvme中主要有两个常见的复位方式,其一是nvme subsystem reset,其二是nvme Controller reset,在驱动中如下:

static long nvme_dev_ioctl(struct file *file, unsigned int cmd,unsigned long arg)
{struct nvme_ctrl *ctrl = file->private_data;void __user *argp = (void __user *)arg;switch (cmd) {case NVME_IOCTL_ADMIN_CMD:return nvme_user_cmd(ctrl, NULL, argp);case NVME_IOCTL_IO_CMD:return nvme_dev_user_cmd(ctrl, argp);case NVME_IOCTL_RESET:dev_warn(ctrl->device, "resetting controller\n");return nvme_reset_ctrl_sync(ctrl);case NVME_IOCTL_SUBSYS_RESET:return nvme_reset_subsystem(ctrl);case NVME_IOCTL_RESCAN:nvme_queue_scan(ctrl);return 0;default:return -ENOTTY;}
}

针对subsystem reset复位方式如下:

static inline int nvme_reset_subsystem(struct nvme_ctrl *ctrl)
{if (!ctrl->subsystem)return -ENOTTY;return ctrl->ops->reg_write32(ctrl, NVME_REG_NSSR, 0x4E564D65);
}

nvme协议中有描述:

 

Controller reset:

 

int nvme_reset_ctrl(struct nvme_ctrl *ctrl)
{if (!nvme_change_ctrl_state(ctrl, NVME_CTRL_RESETTING))return -EBUSY;if (!queue_work(nvme_reset_wq, &ctrl->reset_work))return -EBUSY;return 0;
}
EXPORT_SYMBOL_GPL(nvme_reset_ctrl);int nvme_reset_ctrl_sync(struct nvme_ctrl *ctrl)
{int ret;ret = nvme_reset_ctrl(ctrl);if (!ret) {flush_work(&ctrl->reset_work);if (ctrl->state != NVME_CTRL_LIVE &&ctrl->state != NVME_CTRL_ADMIN_ONLY)ret = -ENETRESET;}return ret;
}
EXPORT_SYMBOL_GPL(nvme_reset_ctrl_sync);

 最后执行的是nvme_reset_work

五、常见nvme驱动问题

nvme驱动相关问题主要集中在nvme timeout函数中,函数如下:

static enum blk_eh_timer_return nvme_timeout(struct request *req, bool reserved)
{struct nvme_iod *iod = blk_mq_rq_to_pdu(req);struct nvme_queue *nvmeq = iod->nvmeq;struct nvme_dev *dev = nvmeq->dev;struct request *abort_req;struct nvme_command cmd;u32 csts = readl(dev->bar + NVME_REG_CSTS);/* If PCI error recovery process is happening, we cannot reset or* the recovery mechanism will surely fail.pcie链路发生异常*/mb();if (pci_channel_offline(to_pci_dev(dev->dev)))return BLK_EH_RESET_TIMER;/** Reset immediately if the controller is failed, 需要重启controller*/if (nvme_should_reset(dev, csts)) {nvme_warn_reset(dev, csts);nvme_dev_disable(dev, false);nvme_reset_ctrl(&dev->ctrl);return BLK_EH_DONE;}/** Did we miss an interrupt?*/if (__nvme_poll(nvmeq, req->tag)) {dev_warn(dev->ctrl.device,"I/O %d QID %d timeout, completion polled\n",req->tag, nvmeq->qid);return BLK_EH_DONE;}/** Shutdown immediately if controller times out while starting. The* reset work will see the pci device disabled when it gets the forced* cancellation error. All outstanding requests are completed on* shutdown, so we return BLK_EH_DONE.*///如果控制器在启动时超时,请立即关闭。复位将看到pci设备在出现强制取消错误时被禁用。所有未完成的请求都在关机时完成,因此我们返回BLK_EH_DONE。switch (dev->ctrl.state) {case NVME_CTRL_CONNECTING:case NVME_CTRL_RESETTING:dev_warn_ratelimited(dev->ctrl.device,"I/O %d QID %d timeout, disable controller\n",req->tag, nvmeq->qid);nvme_dev_disable(dev, false);nvme_req(req)->flags |= NVME_REQ_CANCELLED;return BLK_EH_DONE;default:break;}/** Shutdown the controller immediately and schedule a reset if the* command was already aborted once before and still hasn't been* returned to the driver, or if this is the admin queue.*///如果命令之前已经被abort中止过一次,但仍未返回给驱动程序,或者这是管理队列(qid不为0),请立即关闭控制器并安排重置。if (!nvmeq->qid || iod->aborted) {dev_warn(dev->ctrl.device,"I/O %d QID %d timeout, reset controller\n",req->tag, nvmeq->qid);nvme_dev_disable(dev, false);nvme_reset_ctrl(&dev->ctrl);nvme_req(req)->flags |= NVME_REQ_CANCELLED;return BLK_EH_DONE;}if (atomic_dec_return(&dev->ctrl.abort_limit) < 0) {atomic_inc(&dev->ctrl.abort_limit);return BLK_EH_RESET_TIMER;}iod->aborted = 1;构造abort命令,发送设备以终止命令执行memset(&cmd, 0, sizeof(cmd));cmd.abort.opcode = nvme_admin_abort_cmd;cmd.abort.cid = req->tag;cmd.abort.sqid = cpu_to_le16(nvmeq->qid);dev_warn(nvmeq->dev->ctrl.device,"I/O %d QID %d timeout, aborting\n",req->tag, nvmeq->qid);abort_req = nvme_alloc_request(dev->ctrl.admin_q, &cmd,BLK_MQ_REQ_NOWAIT, NVME_QID_ANY);if (IS_ERR(abort_req)) {atomic_inc(&dev->ctrl.abort_limit);return BLK_EH_RESET_TIMER;}abort_req->timeout = ADMIN_TIMEOUT;abort_req->end_io_data = NULL;blk_execute_rq_nowait(abort_req->q, NULL, abort_req, 0, abort_endio);/** The aborted req will be completed on receiving the abort req.* We enable the timer again. If hit twice, it'll cause a device reset,* as the device then is in a faulty state.*/return BLK_EH_RESET_TIMER;
}

根据nvme timout函数描述,可能会发生的情况:

1、上层应用执行IO超时,进入nvme_timeout,发送abort命令终止IO,并成功执行;

2、上层应用执行IO超时,进入nvme_timeout,发送abort命令终止IO,并成功执行,IO继续执行,开始复位Controller;

3、上层应用执行IO超时,进入nvme_timeout,发送abort命令终止IO,并成功执行,IO继续执行,开始复位Controller,执行复位流程中超时,禁用Controller,主机开始不识别盘符;

4、正常复位过程超时,进入nvme_timeout开始重新复位。

5、由于链路异常,Controller状态改变,进入timeout直接退出。

http://www.lqws.cn/news/511039.html

相关文章:

  • 2025湖北省职业院校技能大赛信息安全管理与评估样题
  • Unity3D仿星露谷物语开发70之背景音乐
  • 深度解析:2D写实数字人交互场景的创新与应用
  • 华为云Flexus+DeepSeek征文 | 基于ModelArts Studio、DeepSeek大模型和Dify搭建智能聊天助手
  • PostgreSQL(二十八)执行计划与单表查询成本估算
  • Git提交失败?commit hook:lint-staged
  • Handle本地部署
  • JVM的内存模型和内存结构
  • 模块化桌面机器人概念设计​​ - ModBot
  • 七天学会SpringCloud分布式微服务——01
  • Vue的学习内容和目标
  • 10-C#的dataGridView1和datatable的使用
  • vue 3 计算器
  • 用 Python 打造立体数据世界:3D 堆叠条形图绘制全解析
  • STM32学习笔记——中断控制
  • 利用大型语言模型增强边缘云 AI 系统安全性
  • wordpress无法将上传图片的原因和解决方法
  • windows系统中docker数据迁移出系统盘
  • uniapp/Vue/微信小程序瀑布流,小红书瀑布流,豆瓣瀑布流,淘宝瀑布流布局
  • IoTDB的基本概念及常用命令
  • RabbitMQ是什么?以及优缺点
  • Unity2D 街机风太空射击游戏 学习记录 #13 射击频率道具 最高分
  • 【JavaScript-Day 48】告别 Ajax,拥抱现代网络请求:Fetch API 完全指南
  • C++【生存游戏】开发:荒岛往事 第一期
  • CDN+OSS边缘加速实践:动态压缩+智能路由降低30%视频流量成本(含带宽峰值监控与告警配置)
  • 抖音视频怎么去掉抖音号水印保存
  • Unity_导航操作(鼠标控制人物移动)_运动动画
  • 性能测试-jmeter实战4
  • 【Spring底层分析】AOP的cligb代理和jdk代理
  • go语言 *和