NVMe 协议学习笔记
NVMe 协议学习笔记
晨茗一、Linux NVMe 驱动中的限流:
为了防止 SQ 和 CQ 队列溢出,驱动中基于 tag 实现了一套 IO 限流策略:
- NVMe 驱动在初始化时会调用
blk_mq_init_queue创建用于容纳 request 的 queue,对于每个 queue Linux 驱动中可以为其指定一种用于调度 request 的 elevator 算法(通常包括 kyber、mq-deadline 以及 bfq),算法是通过blk_mq_init_sched初始化的,其中指定了 queue 中的nr_requests用于限制可容纳的最大请求。
 - 此后其会根据
nr_requests值去创建一系列blk_mq_tags以及request用于后续发送消息。 - 在 Linux Block 层创建新的 request 时会通过 blk_mq_get_tag 来判断软件队列 tag 是否有剩余,如果有剩余则下发 IO 到软件缓冲队列中,如果无剩余则循环 sleep 等待。

另外初始化完成后,调度算法会定期的去处理软件缓冲队列,将其中的 request 派发到对应的硬件队列中,具体是通过blk_mq_dispatch_rq_list进行派发的,此处通过__blk_mq_get_driver_tag判断硬件队列 tag 是否有剩余来进行限流。
 - 如果通过了限流,其就会调用
queue_rq将其放入到 nvme 的队列中,并写 sq tail doorbell 通知 NVMe 驱动。
可以参考:
https://docs.kernel.org/block/blk-mq.html
https://blog.csdn.net/hu1610552336/article/details/111464548
二、NVMe 中的 SQ 和 CQ:
他们队列是初始化在主机内存中的,因此控制器是只写的,而他们的寄存器则是初始化在控制器中的,因此主机是只读的。于是需要特殊的通信逻辑,对于 SQ 来讲,主机通过写 SQTD 寄存器来告知控制器有新的 SQE,但它不能读取 SQHD,因此 Head 是通过控制器写的每一个 CQE 中的 SQHP 字段来上报的。而对于 CQ 来讲,主机可以通过写 CQHD 来告知控制器消费到了哪里,而 Tail 则是由控制器发送 CQE 时的 Phase 字段来确定的,CQ 第一轮会被写 1,第二轮就被写 0,循环往复,主机通过检查 CQE 的 Phase 字段是否被翻转就可以知道 Tail 的位置了。

http://www.ssdfans.com/?p=8139
三、虚拟机重启时 NVMe 的逻辑:
流程:
- Linux 驱动下令删除所有的 IO Queue。
- Linux 驱动更新
cc.shn通知驱动 shutdown,SPDK 关闭所有剩余的 io queue,并调用disable_ctrlr。 - Qemu 通过 vfio_user 下发
VFIO_USER_DEVICE_RESET指令,SPDK 调用vnvmf_ctrlr_reset,清理SDBL,注意根据代码注释这里可能缺少一些逻辑。 - 分配一系列用于 dma 的内存。
- Qemu 再次下发
VFIO_USER_DEVICE_RESET指令。 - Qemu 清理
cc寄存器,获取vs、cap寄存器,设置aqa acq asq等,并触发 SPDK 调用enable_ctrlr。 - Qemu 创建 Admin Queue 和一个 IO Queue,之后销毁。
- Linux 进入启动阶段,驱动开始读取寄存器,获取
vs、cap。驱动清空cc寄存器,设置aqa acq asq等寄存器。 - 更新
cc寄存器,SPDK 调用enable_ctrlr。
四、PRP & SGL


https://blog.csdn.net/sinat_43629962/article/details/123991166
五、PersistentReservation
http://www.ssdfans.com/?p=97147
参考
https://github.com/andyBrake/andyBrake.github.io/blob/master/doc/nvme_express.md








%20%E6%9C%8D%E5%8A%A1.jpeg)

