NVMe协议笔记分享

NVMe概述

NVMe是一个针对基于PCIe的固态硬盘的高性能的、可扩展的主机控制器接口。

NVMe的显著特征是提供多个队列来处理I/O命令。单个NVMe设备支持多达64K个I/O 队列,每个I/O队列可以管理多达64K个命令。

当主机发出一个I/O命令的时候,主机系统将命令放置到提交队列(SQ),然后使用门铃寄存器(DB)通知NVMe设备。

当NVMe设备处理完I/O命令之后,设备将处理结果写入到完成队列(CQ),并引发一个中断通知主机系统。

NVMe使用MSI/MSI-X和中断聚合来提高中断处理的性能。

NVMe驱动概述

NVMe驱动是一个C函数库,可直接链接到应用程序从而在应用与NVMe固态硬盘之间提供直接的、零拷贝的数据传输。这是完全被动的,意味着不会开启线程,只是执行来自应用程序本身的函数调用。这套库函数直接控制NVMe设备,通过将PCI BAR寄存器直接映射到本地进程中然后执行基于内存映射的I/O(MMIO)。I/O是通过队列对(QP)进行异步提交,其一般的执行流程跟Linux的libaio相比起来,并非完全不同。

NVM Express(NVMe)是一个寄存器级接口,允许带内主机软件与NVM子系统通信。NVMe管理界面(NVMe-MI)允许管理控制器通过一个或多个外部接口与NVMe NVM子系统进行带外通信。

NVMe是一种Host与SSD之间通讯的协议

图1:NVMe管理接口协议分层

NVMe-MI利用管理组件传输协议(MCTP)作为命令传输和利用现有的MCTP SMBus / I2C和PCIe绑定物理层。

NVMe是为SSD所生的。NVMe出现之前,SSD绝大多数走的是AHCI和SATA的协议,后者其实是为传统HDD服务的。与HDD相比,SSD具有更低的延时和更高的性能,AHCI已经不能跟上SSD性能发展的步伐了,已经成为制约SSD性能的瓶颈。

跟ATA spec中定义的命令相比,NVMe的命令个数少了很多,完全是为SSD量身定制的。

NVMe有三宝:Submission Queue (SQ),Completion Queue(CQ)和Doorbell Register (DB)。 SQ和CQ位于Host的内存中,DB则位于SSD的控制器内部。上图:

SQ和CQ在Host的memory中以及DB在SSD端,上图中的NVMe Subsystem一般就是SSD。
SQ位于Host内存中,Host要发送命令时,先把准备好的命令放在SQ中,然后通知SSD来取;CQ也是位于Host内存中,一个命令执行完成,成功或失败,SSD总会往CQ中写入命令完成状态。

DB又是干什么用的呢?Host发送命令时,不是直接往SSD中发送命令的,而是把命令准备好放在自己的内存中,那怎么通知SSD来获取命令执行呢?Host就是通过写SSD端的DB寄存器来告知SSD的。

1.4架构模型

图2:单端口PCIe SSD

图3:带SMBus / I2C的双端口PCIe SSD

NVMe管理界面用于发送命令消息,该命令消息由以NVM子系统内的控制器为目标的标准NVMe管理命令组成; 用于访问NVM子系统中控制器的PCI Express配置,I / O和存储空间的命令; 和管理接口特定命令,用于清点,配置和监视NVM子系统。

图4:与单端口PCIe SSD相关的NVM子系统

图5示出了与图3中所示的PCIe SSD相对应的示例NVM子系统。NVM子系统,包含一个与PCIe端口0相关联的控制器和两个与PCIe端口1相关联的控制器。存在与每个PCIe端口相关联的管理端点和SMBus / I2C端口。由于NVM子系统包含管理端点,因此所有控制器都具有关联的控制器管理接口。

图5:与带有SMBus / I2C的双端口PCIe SSD相关的NVM子系统

管理接口请求消息和响应消息作为MCTP消息传输,消息类型通过MCTP设置为NVM Express管理消息(请参阅MCTP ID和代码规范)。 所有命令消息都源自管理控制器,并从管理端点生成响应消息。

4消息处理模型

NVMe-MI****使用请求和响应处理模型。

图14:NVMe-MI MCTP消息分类

4.1 request消息

request消息是由管理控制器生成的NVMe-MI消息,用于发送给管理端点。

request消息指定管理端点要执行的操作。

4.2 reponse消息

reponse消息是管理端点完成时生成的NVMe-MI消息处理先前发出的request消息。

NVM Express基于配对的提交和完成队列机制

1 命令由主机软件放入提交队列。完成被放入控制器关联的完成队列。多个提交队列可以使用相同的完成队列。提交和完成队列在内存中分配。    2 3 存在管理员提交和关联的完成队列以用于控制器管理和控制(例如,创建和删除I / O提交和完成队列,中止命令,等等)。只有属于管理员命令集的命令才可以提交给管理员提交队列。  4 5 I / O命令集与I / O队列对一起使用。该规范定义了一个I / O命令集,命名为NVM命令集。主机选择一个用于所有I / O队列的I / O命令集对。  6 7 主机软件创建队列,最高可达控制器支持的最大值。通常的数量创建的命令队列基于系统配置和预期的工作负载。例如, 在基于四核处理器的系统上,每个核心可能有一个队列对,以避免锁定和确保数据结构在适当的处理器核心缓存中创建。图1提供了图形队列对机制的表示,显示提交队列和。之间的11映射完成队列。图2显示了多个I / O提交队列使用相同的示例核心B上的I / O完成队列。图1和图2显示了之间始终存在11管理员提交队列和管理员完成队列。 

提交队列(SQ)是一个循环缓冲区,具有主机软件用于提交的固定插槽大小控制器执行的命令。主机软件更新相应的SQ Tail门铃当有一到n个新命令要执行时注册。之前的SQ Tail值被覆盖当有新的门铃寄存器写入时控制器。控制器按顺序提取SQ条目但是,提交队列可以按任何顺序执行这些命令。

1 4.1.1空队列  2 3当Head入口指针等于Tail入口指针时,队列为Empty。图8定义了Empty队列条件。  4 5                                  图8:空队列定义 

14.1.2满队列  2 3当Head等于尾部时,队列为Full。队列中的条目数full比队列大小少一个。 4 59定义了完整队列条件。注意:在确定队列是否为Full时,应考虑队列包装条件。  6 7                                  图9:完整队列定义 

7控制器架构

主机软件(Host)通过预先分配的提交队列向控制器(Controller)提交命令。通过SQ Tail Doorbell寄存器写入警告控制器新提交的命令。前一个门铃寄存器值和当前寄存器写入之间的差异表示已提交的命令数。

控制器从提交队列中提取命令并将它们发送到****NVM子系统进行处理。

命令处理

11.主机将一个或多个命令放置在位于内存中的提交队列(SQ)的下一个可用的槽位中执行。 2 32. Host用SQ尾部指针的新值去更新SQ的TailDB寄存器。这告诉了SSD控制器有一个新的命令被提交需要被处理。 4 53. SSD控制器将命令从SQ中转移到控制器中以供下一步执行。(从哪一个SQ中取出下一条候选命令去执行的仲裁方法,请参见4.11一节。) 6 74.控制器接下来执行下一条命令。命令的执行完成可能是乱序的(与提交或开始执行的时间点无关)8 95.在命令完成执行之后,SSD控制器将一个完成队列条目(CQE)放在相关的完成队列(CQ)的下一个空闲槽位中。作为CQE的一部分,SSD控制器通过修改完成条目的SQ头指针指示最新的SQE已经被消费了。每一个新的CQE都有一个从前一个条目中反转的相位标记(Phase Tag), 以向Host表明这个CQE是一个新条目。 10 116. SSD控制器给Host产生一个中断,以表明有一个新的CQE已经产生,可以被消费和处理了。在图中演示的是MSI-X中断,然而,中断也可以是基于PIN或者MSI的中断。注意:基于中断联合设置,可能或不能为每一个新的CQE产生一个中断。 12 137. Host消费和处理在CQ中放置的新的CQE。包括基于错误情况采取的任何操作。Host继续消耗和处理CQE,直到它遇到以前消费的一个条目的相位标签(Phase Tag)从当前完成队列条目(CQEs)的值中反转。 14 158. Host更新CQ的HeadDB寄存器,表明CQE已经被消费了。在更新相关联的CQ的HeadDB寄存器之前,Host可能消费了多个CQE16 17通俗易懂的话总结一下就是: 18 191. Host写命令到SQ 20 212. Host更新SQ的TailDB, 通知SSD取命令 22 233. SSD收到命令,于是从SQ中取出命令 24 254. SSD执行命令 26 275. 命令执行完成后,SSDCQ中写入命令执行结果,同时修改CQ的TailDB 28 296. SSD发短信通知Host命令已经执行完成 30 317. Host收到命令后,到CQ中查看命令完成状态 32 338. Host处理完CQ中的命令执行结果,更新CQ中的HeadDB, 回复SSD, "命令执行结果已经处理完毕,辛苦啦" 34 35NVMe over PCIe和RDMA本质上都是“玩队列”。 NVMe over PCIe有两条队列,一条提交队列(SQ)和一条完成队列(CQ);而RDMA有三条队列,一条发送队列(SQ),一条接收队列(RQ)和一条完成队列(CQ),而一个SQ和一个RQ被称之为一个QP(队列对)36 37对应nvme驱动代码位置:

nvme_pcie.c

1.Int nvme_pcie_qpair_submit_request()

TAILQ_INSERT_TAIL(&pqpair->outstanding_tr, tr, tq_list);

(对应app代码位置:bdev_nvme_submit_request( ) )??是吗,只是名称相近?

2. static void nvme_pcie_qpair_complete_tracker()

TAILQ_INSERT_HEAD(&pqpair->free_tr, tr, tq_list);

bdev_nvme.c

static const struct spdk_bdev_fn_table nvmelib_fn_table = {     (device function table)

.destruct                           = bdev_nvme_destruct,

.submit_request             = bdev_nvme_submit_request,

.io_type_supported       = bdev_nvme_io_type_supported,

.get_io_channel                             = bdev_nvme_get_io_channel,

.dump_info_json            = bdev_nvme_dump_info_json,

.write_config_json          = bdev_nvme_write_config_json,

.get_spin_time                = bdev_nvme_get_spin_time,

};

static struct spdk_bdev_module nvme_if = {

.name = "nvme",

.module_init = bdev_nvme_library_init,

.module_fini = bdev_nvme_library_fini,

.config_text = bdev_nvme_get_spdk_running_config,

.config_json = bdev_nvme_config_json,

.get_ctx_size = bdev_nvme_get_ctx_size,

};

1关于MSI-X,在igb_uio.c里igbuio_msix_mask_irq( ) 2 3浅谈NVMe与MSI-X

https://blog.csdn.net/wangpeng22/article/details/78390694?locationNum=2&fps=1
https://blog.csdn.net/weijitao/article/details/46566789
http://blog.sina.com.cn/s/blog_6472c4cc0102dskj.html

1 NVMe制定了Host与SSD之间通讯的命令,以及命令如何执行的。 2 3NVMe有两种命令, 4 5一种叫Admin Command,用以Host管理和控制SSD6 7另外一种就是I/O Command,用以Host和SSD之间数据的传输。下面是NVMe1.2支持的命令列表: 8 9NVMe支持的Admin Command:

NVMe支持的I/O Command:

1lib/bdev/nvme/bdev_nvme.c 2 3_bdev_nvme_submit_request( )IO操作的处理, 4 5nvme_ctrlr_ut.c 6 7test_nvme_ctrlr_init_en_1_rdy_0 8 9test_nvme_ctrlr_init_en_1_rdy_1 10 11test_nvme_ctrlr_init_en_0_rdy_0 12 13test_nvme_ctrlr_init_en_0_rdy_1 14 15test_nvme_ctrlr_init_en_0_rdy_0_ams_rr 16 17test_nvme_ctrlr_init_en_0_rdy_0_ams_wrr 18 19test_nvme_ctrlr_init_en_0_rdy_0_ams_vs 20 21test_alloc_io_qpair_rr_1 22 23test_ctrlr_get_default_ctrlr_opts 24 25test_ctrlr_get_default_io_qpair_opts 26 27test_alloc_io_qpair_wrr_1 28 29test_alloc_io_qpair_wrr_2 30 31test_spdk_nvme_ctrlr_update_firmware 32 33test_nvme_ctrlr_fail 34 35test_nvme_ctrlr_construct_intel_support_log_page_list 36 37test_nvme_ctrlr_set_supported_features 38 39test_spdk_nvme_ctrlr_doorbell_buffer_config----5 Admin Command Set 40 41test_nvme_ctrlr_test_active_ns 42 43nvme_ctrlr_cmd_ut.c  -----  AdminCommand的功能测试集 44 45test_get_log_pages----5 Admin Command Set 46 47test_set_feature_cmd----5 Admin Command Set 48 49test_set_feature_ns_cmd----5 Admin Command Set 50 51test_get_feature_cmd----5 Admin Command Set 52 53test_get_feature_ns_cmd----5 Admin Command Set 54 55test_abort_cmd----5 Admin Command Set 56 57test_io_raw_cmd 58 59test_io_raw_cmd_with_md 60 61test_namespace_attach----5 Admin Command Set 62 63test_namespace_detach----5 Admin Command Set 64 65test_namespace_create----5 Admin Command Set 66 67test_namespace_delete----5 Admin Command Set 68 69test_format_nvme 70 71test_fw_commit----5 Admin Command Set 72 73test_fw_image_download----5 Admin Command Set
点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

一篇文章带你了解JavaScript日期

日期对象允许您使用日期(年、月、日、小时、分钟、秒和毫秒)。一、JavaScript的日期格式一个JavaScript日期可以写为一个字符串:ThuFeb02201909:59:51GMT0800(中国标准时间)或者是一个数字:1486000791164写数字的日期,指定的毫秒数自1970年1月1日00:00:00到现在。1\.显示日期使用

2020年前端实用代码段,为你的工作保驾护航

有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )