NVMe系统内存结构 - SGL
- 1 SGL简介
- 2 SGL Segment
- 3 SGL Descriptor
- 3.1 SGL Data Block descriptor
- 3.2 SGL Bit Bucket descriptor
- 3.3 SGL Segment descriptor
- 3.4 SGL Last Segment descriptor
- 4 SGL组织结构
- 4.1 SGL1为SGL Data Block descriptor
- 4.2 SGL1为SGL Last Segment descriptor
- 4.3 SGL1为SGL Segment descriptor
- 5 PRP与SGL的区别
- 6 SGL数据读取请求示例
本文属于《 NVMe协议基础系列教程》之一,欢迎查看其它文章。
1 SGL简介
Scatter Gather List(SGL)是一个数据结构,用以描述一段数据空间,这个空间可以是数据源所在的空间,也可以是数据目标空间。
SGL首先是个List,是个链表,由一个或者多个SGL Segment组成,而每个SGL Segment又由一个或者多个SGL Descriptor组成。
2 SGL Segment
SGL Segment是物理内存中一个连续区域中的Qword对齐数据结构,它描述了数据缓冲区的全部、部分或没有数据缓冲区,以及指向下一个SGL Segment(如果有的话)。
一个SGL Segment由一个或多个SGL descriptor组成。只有SGL Segment中的最后一个描述符,可以是SGL Segment descriptor或SGL Last Segment descriptor。
3 SGL Descriptor
SGL Descriptor是SGL最基本的单元,它描述了一段连续的物理内存空间:起始地址+空间大小。每个SGL Descriptor大小是16字节,其通用格式定义,如下图所示:
- 第14:00字节:与Descriptor类型相关,不同的类型,有不同的定义。
- 第15字节:SGL Identifier(SGL标识符)。
Descriptor Type Specific(03:00)- 具体定义,与Descriptor类型相关;
SGL Descriptor Type(07:04)- 表示Descriptor类型。
SGL Descriptor Type,有如下这些,具体类型值:
接下来,详细介绍,每一种具体Descriptor的定义。
3.1 SGL Data Block descriptor
SGL Data Block descriptor描述了一个数据块,定义如下表:
Bytes | Description |
---|---|
7:0 | Address:指定了数据块的64位起始内存地址。 |
11:8 | Length:指定了数据块的字节长度。 若设置为00000000h,表示没有数据传输。指定没有数据传输的SGL Data Block descriptor是一个有效的descriptor。 如果Address字段的值,加上Length字段的值,大于1_00000000_00000000h,则应当作SGL Data Block descriptor发生错误来处理。 |
14:12 | 保留 |
15 | SGL Identifier:该字段的定义如下所示。 Zero(03:00)- 值为0h,如果为非0值,则视为发生错误。 SGL Descriptor Type(07:04)- 值为0h,表示SGL Data Block descriptor类型。 |
一个全零的SGL descriptor,是一个Address字段为00000000_00000000h,Length字段为00000000h的SGL Data Block descriptor,可以用作空descriptor。
3.2 SGL Bit Bucket descriptor
SGL Bit Bucket descriptor用于忽略一部分源数据(仅Host从控制器读取数据时有效),定义如下表:
Bytes | Description |
---|---|
7:0 | 保留 |
11:8 | Length: a)如果SGL描述了一个目标数据缓冲区,那么Length字段指定了,不传输源数据的字节数(即丢弃的字节数)。当Length字段设置为00000000h时,表示源数据不被丢弃。指定不丢弃源数据的SGL Bit Bucket descriptor是有效的descriptor。 b)如果SGL描述了一个源数据缓冲区(即,从主机内存向控制器的写操作),那么Length字段将被忽略,并且源数据或目标数据中不会丢弃任何数据。指定不丢弃数据的SGL Bit Bucket descriptor不应被视为发生错误。 c)如果支持SGL Bit Bucket descriptor,则其在目标数据缓冲区中的长度,需要被包含在NVM命令集数据传输命令中,指定的NLB (Number of Logical Blocks)参数中。它们在源数据缓冲区中的长度不被包括在NLB参数中。 |
14:12 | 保留 |
15 | SGL Identifier:该字段的定义如下所示。 Zero(03:00)- 值为0h,如果为非0值,则视为发生错误。 SGL Descriptor Type(07:04)- 值为1h,表示SGL Bit Bucket descriptor类型。 |
3.3 SGL Segment descriptor
SGL Segment descriptor用于描述下一个SGL Segment,而不是最后一个SGL segment,定义如下表:
Bytes | Description |
---|---|
7:0 | Address:指定了下一个SGL Segment的起始64位内存地址,这是一个SGL Segment。 |
11:8 | Length:指定了下一个SGL Segment的长度,单位为字节。Length字段,应为非零值,且为16的倍数。 如果Address字段的值,加上Length字段的值,大于1_00000000_00000000h,则应当作SGL Segment descriptor发生错误来处理。 |
14:12 | 保留 |
15 | SGL Identifier:该字段的定义如下所示。 Zero(03:00)- 值为0h,如果为非0值,则视为发生错误。 SGL Descriptor Type(07:04)- 值为2h,表示SGL Segment descriptor类型。 |
3.4 SGL Last Segment descriptor
SGL Last Segment descriptor,也叫SGL末段描述符。
SGL Last Segment descriptor描述了下一个和最后一个SGL Segment,定义如下表:
Bytes | Description |
---|---|
7:0 | Address:指定了下一个和最后一个SGL Segment的起始64位内存地址,这是一个SGL Segment。 |
11:8 | Length:指定了下一个和最后一个SGL Segment的长度,单位为字节。Length字段,应为非零值,且为16的倍数。 如果Address字段的值,加上Length字段的值,大于1_00000000_00000000h,则应当作SGL Last Segment descriptor发生错误来处理。 |
14:12 | 保留 |
15 | SGL Identifier:该字段的定义如下所示。 Zero(03:00)- 值为0h,如果为非0值,则视为发生错误。 SGL Descriptor Type(07:04)- 值为3h,表示SGL Last Segment descriptor类型。 |
SGL Last Segment descriptor与SGL Segment descriptor,除了SGL Descriptor Type字段值不一样,其他字段完全没有区别。
SGL Last Segment descriptor,通常作为倒数第二个SGL段描述符使用。
为什么需要把倒数第二个SGL段描述符,单独的定义成一种类型呢?
我认为是,让SSD在解析SGL的时候,碰到SGL末段描述符,就知道链表快到头了,后面只有一个段了。
4 SGL组织结构
上面讲了很多,关于SGL Segment和SGL Descriptor定义,可能大家比较迷惑,SGL结构究竟是如何组织的,接下来,我们一探究竟。
对Admin命令来说,它只能用PRP告诉SSD内存物理地址;对I/O命令(NVM Command Set)来说,除了用PRP,Host还可以用SGL的方式,来告诉SSD数据在内存中写入或者读取的物理地址。
Host在命令中会告诉SSD采用何种方式。具体来说,如果命令当中DW0[15]是0,就是PRP方式,否则就是SGL方式。
在“Command Format — NVM Command Set”命令格式定义中,[39:24]SGL Entry 1(SGL1)字段,就是该命令的第一个SGL Segment(链表的头部),如下图所示:
SGL1可能为三种情况:SGL Data Block descriptor、SGL Last Segment descriptor、SGL Segment descriptor,接下来,我们详细介绍。
4.1 SGL1为SGL Data Block descriptor
SGL1也就是下图中的“First SGL Segment in SQ Entry”,如下所示:
SGL1是一个SGL Data Block descriptor,该descriptor直接指向内存数据块,它描述了需要传输的整个数据,因此没有其他SGL Segment了。
在本例中,该命令,通过SGL1表示了1块数据。
这也是最简单的情况。
4.2 SGL1为SGL Last Segment descriptor
SGL1是一个SGL Last Segment descriptor,因此它是倒数第二个SGL Segment,那么它指向的SGL Segment,即为最后一个SGL Segment(图中Last SGL Segment所示)。
最后一个SGL Segment,有4个SGL Data Block descriptor,均指向数据块。
在本例中,该命令,通过SGL1表示了4块数据。
4.3 SGL1为SGL Segment descriptor
按上图描述,可得出如下信息:
- SGL1是一个SGL Segment descriptor,指向第2个SGL Segment;该descriptor中Length字段,应该为6个Descriptor长度(6*16=96字节)。
- 在第2个SGL Segment中,有5个SGL Data Block descriptor与1个SGL Last Segment descriptor,前者指向数据块,后者再指向第3个SGL Segment(最后一个SGL Segment)。
- 在第3个SGL Segment中,有4个SGL Data Block descriptor,也是指向数据块。
在本例中,该命令,通过SGL1表示了5+4=9块数据。
如果要继续往下链接SGL Segment的话,只需要把上例中,第2个SGL Segment中最后一个descriptor换成SGL Segment descriptor类型,并保证倒数第二个SGL Segment的最后一个descriptor为SGL Last Segment descriptor类型,就可以了。
通过上述的三个例子,想必大家,已经掌握了SGL的组织结构。
5 PRP与SGL的区别
无论是PRP还是SGL,本质都是描述内存中的一段数据空间,这段数据空间在物理上,可能是连续的,也可能是不连续的。主机在命令中设置好PRP或者SGL,告诉SSD数据源在内存的什么位置,或者从闪存上读取的数据,应该放到内存的什么位置。
大家也许跟我有同样的疑问,那就是,既然有PRP,为什么还需要SGL?
事实上,NVMe1.0时候的确只有PRP,SGL是NVMe1.1之后引入的。
SGL和PRP本质区别在哪?
下图道出了真相:一段数据空间,对PRP来说,它只能映射到一个个物理页;而对SGL来说,它可以映射到,任意大小的连续物理空间。
SGL和PRP本质区别就是:
PRP只能描述定长的内存空间(Address),而SGL可以描述任意大小的内存空间(Address + Length),SGL更加灵活。
在支持上的区别:
- 对NVMe over PCIe(我们目前讲的都是NVMe跑在PCIe上),Admin命令只支持PRP,I/O命令可以支持PRP或者SGL;
- 对NVMe over Fabrics,所有命令只支持SGL。
6 SGL数据读取请求示例
这个例子中,假设Host需要从SSD中读取13KB的数据,其中真正只需要11KB数据,这11KB的数据需要放到3个大小不同的内存中,分别是:3KB,4KB和4KB。
有三个SGL Segment描述了,逻辑块数据在主机内存中传输的位置。
- 这三个SGL Segment,总共包含三个Data Block descriptor,长度分别为3kb、4kb和4kb。
- Destination SGL Segment 1,包含一个长度为2kb的Bit Bucket descriptor,它表示不传输(即忽略)来自设备的2kb的逻辑块数据,也就是读取的时候跳过这2kb数据。
- Destination SGL Segment 1,还包含一个Last Segment descriptor,表示该描述符所指向的Segment是最后一个SGL Segment。
参考文档:
- 蛋蛋读NVMe之三
- 《深入浅出SSD-固态存储核心技术原理与实战》
- 初探 NVMe
- NVMe系列专题之四:寻址模型PRP和SGL解析
- NVMe 1.1a 协议规范