本文主要介绍了NVMe中的SGL,通过本文您将了解到以下没用的知识:

  1. 什么是SGL?包括它的特点、结构、使用方法等;
  2. NVMe Linux驱动如何使用SGL?
  3. PCIe trace中的SGL。

什么是SGL?

在传输数据时,SSD需要知道数据在Host内存中的地址,否则它不知道数据应该从哪儿搬(写方向)以及搬到哪儿(读方向),也就是说,SSD需要知道数据在Host内存中的分布,而这个信息,是Host通过某种方式告诉SSD的,SGL就是“某种方式”之一。

类比“地理藏宝”

SGL是Scatter Gather List的简称,顾名思义,SGL是一条链子,将散落在不同地方的数据串在一起,有点像我们玩过的“地理藏宝”的游戏:

image.png

没喻硬比:

  • 游戏里的每一个地点相当于SGL里的SGL segment;
  • 游戏里每一个地点藏有下一个地点的信息(除了最后一个),SGL里每一个SGL Segment保存下一个SGL Segment的地址(除了最后一个);
  • 游戏里每一个地点藏有一个(大多数)或多个(少数)宝藏,而每一个SGL Segment包含一个或多个SGL descriptor;

SGL特点

SGL具有以下特点:

  • SGL是一个链表;
  • 链表的每一个节点是一个SGL Segment;
  • 一个SGL Segment包含一个或多个SGL Descriptor;
  • SGL Descriptor有多种类型,常用的有SGL data block descriptor,SGL Segment descriptor以及SGL Last Segment descriptor等;
  • 每一个SGL Descriptor指向了一段Memory buffer;
  • 一个SGL的第一个SGL Segment位于command内部(dword 6 - 9,16 Bytes),这个SGL Segment只包含一个SGL Descriptor。

下图描述了一种通用情况下,SGL是如何表达数据分布的:

image.png

SGL里各个概念之间的关系可以描述为:

image.png

SGL Descriptor

在SGL中,最基本的功能单位是SGL Descriptor,它就像一座建筑的板砖,构建了整个SGL的大厦。

SGL Descriptor的结构

SGL Descriptor的size是固定的16个字节,其中,

  • 第15个字节(0-based)的Bits[07:04]是SGL Descriptor type,表示该SGL Descriptor的类型;
  • 第15个字节(0-based)的Bits[03:00]是SGL Descriptor Sub Type,表示SGL Descriptor的子类型;
  • 其他的字段,根据Descriptor的类型而具有不同的定义。

image.png

SGL Descriptor的种类

SGL Descriptor的类型主要有以下几种:
image.png

其中,绿色的SGL Data Block descriptor, SGL Segment descriptor和SGL Last Segment descriptor是最常用的三种类型,而粉色的SGL Bit Bucket descriptor是次常用的,剩下的是最不常用的,我们主要关注有颜色的四种类型即可。

SGL Data Block descriptor

SGL Data Block descriptor是最常用的descriptor,它可以直接描述用户数据的排布。

BytesDescription
07:00Address(ADDR):
如果SGLDST=0h, 该字段表示存放用户数据的绝对地址;
否则,如果SGLDST=1h,该字段表示存放用户数据的相对地址,主要用于NVMe over fabric协议中命令和数据捆绑在一起的情况。
另外,如果SSD要求用户数据的地址需要dword对齐(在Identify controller数据中体现),那么该字段低2位比特需要清零。
11:08Length (LEN):
表示用户数据的长度,以字节为单位。
如果SSD要求用户数据的颗粒度需要dword对齐的话,该字段的低2位比特需要清零。
14:12预留
15SGL Identifier (SGLID):
Bits 07:04: SGL Descriptor Type (SGLDT): 0h, 表示是Data Block descriptor;
Bits 03:00: SGL Descriptor Sub Type (SGLDST): 如果是0h,ADDR=绝对地址;如果是1h,ADDR=相对地址

SGL Segment descriptor

SGL Segment descriptor是用于描述SGL Segment的描述符,通过它,SSD可以知道下一个SGL Segment的位置以及该SGL Segment包含多少个SGL descriptor。

BytesDescription
07:00Address(ADDR):
如果SGLDST=0h, 该字段表示存放SGL Segment的绝对地址;
否则,如果SGLDST=1h,该字段表示存放SGL Segment的相对地址,主要用于NVMe over fabric协议中命令和SGL Segment捆绑在一起的情况.
11:08Length (LEN):
表示用户数据的长度,以字节为单位,并且是16的整数倍,因为一个SGL descriptor的size是16个字节。
该SGL Segment descriptor指向的SGL Segment内含有的descriptor的数量为:LEN/16.
14:12预留
15SGL Identifier (SGLID):
Bits 07:04: SGL Descriptor Type (SGLDT): 2h, 表示是SGL Segment descriptor;
Bits 03:00: SGL Descriptor Sub Type (SGLDST): 如果是0h,ADDR=绝对地址;如果是1h,ADDR=相对地址

SGL Last Segment descriptor

SGL Last Segment descriptor跟SGL Segment descriptor是一样的,唯一的区别是,它指向的是最后一个SGL Segment。

BytesDescription
07:00Address(ADDR):
如果SGLDST=0h, 该字段表示存放SGL Last Segment的绝对地址;
否则,如果SGLDST=1h,该字段表示存放SGL Last Segment的相对地址,主要用于NVMe over fabric协议中命令和SGL Last Segment捆绑在一起的情况.
11:08Length (LEN):
表示用户数据的长度,以字节为单位,并且是16的整数倍,因为一个SGL descriptor的size是16个字节。
该SGL Segment descriptor指向的SGL Last Segment内含有的descriptor的数量为:LEN/16.
14:12预留
15SGL Identifier (SGLID):
Bits 07:04: SGL Descriptor Type (SGLDT): 3h, 表示是SGL Last Segment descriptor;
Bits 03:00: SGL Descriptor Sub Type (SGLDST): 如果是0h,ADDR=绝对地址;如果是1h,ADDR=相对地址

SGL Bit Bucket descriptor

SGL Bit Bucket descriptor是用于描述在read时(数据从SSD到Host)需要跳过或者忽略的数据size.

BytesDescription
07:00预留
11:08Length (LEN):
以字节为单位,表示需要跳过或者忽略传输的数据size。
14:12预留
15SGL Identifier (SGLID):
Bits 07:04: SGL Descriptor Type (SGLDT): 1h, 表示是SGL Bit Bucket descriptor;
Bits 03:00: SGL Descriptor Sub Type (SGLDST): 0h

其他类型的SGL descriptor请参考NVMe Base Spec。

An Example

下面是一个肤浅的例子,讲述的是Host读取LBA x到LBA x+25的数据,其中有2KiB的数据需要忽略:

image.png

除了SGL,还有谁?

SGL并不是唯一一种描述数据分布的方式,甚至在NVMe over PCIe中,SGL只是可选的,而PRP(下一篇文章我们将介绍PRP,欢迎关注)才是必须支持的;相反,在NVMe over fabric中,SGL是必须的:

Data Pointer LayoutNVMe over PCIeNVMe over Fabric
PRPMust Support, admin must use PRPNot Supported
SGLOptional, only I/O can use itMust Support, used by both admin and I/O

绝大部分普通消费者所持有的NVMe SSD,都是NVMe over PCIe。

Host如何知道SSD是否支持SGL?

Host是通过Identify Controller数据中的Byte [539:536]4个字节来获知SSD是否支持SGL的。

image.png

SSD怎么知道用SGL还是PRP?

在每一个Host生成的IO command里,都有一个字段来表示该IO command使用PRP还是SGL的方式做数据传输,所以理论上SGL和PRP是可以混合使用的,部分IO使用PRP,部分IO使用SGL。
该字段位于dword 0的bit [15:14]:

image.png

  • 如果是00b, 表示使用PRP传输;
  • 如果是01b,同时,LBA格式带有Meta data(比如512+8,4KiB+64等),那么user data使用SGL传输,而Meta data使用连续的buffer;
  • 如果是10b,表示user data和meta data都是用SGL传输。

Linux NVMe驱动如何决定是否使用SGL?

static inline bool nvme_pci_use_sgls(struct nvme_dev *dev, struct request *req,
                     int nseg)
{
    struct nvme_queue *nvmeq = req->mq_hctx->driver_data;
    unsigned int avg_seg_size;

    avg_seg_size = DIV_ROUND_UP(blk_rq_payload_bytes(req), nseg);

    if (!nvme_ctrl_sgl_supported(&dev->ctrl))
        return false;
    if (!nvmeq->qid)
        return false;
    if (nvme_pci_metadata_use_sgls(dev, req))
        return true;
    if (!sgl_threshold || avg_seg_size < sgl_threshold)
        return nvme_req(req)->flags & NVME_REQ_USERCMD;
    return true;
}

通过以上代码可以知道,如果NVMe controller支持SGL,并且是非admin command,并且平均的Segment size小于threshold(默认是32K),那么驱动将会使用SGL。
所以,如果要使用默认驱动测试SGL,可以把sgl_threshold设置到最小(1),这样就很容易满足使用SGL的条件。

一个栗子,PCIe Trace里的SGL

下面是一个炒熟的栗子,包含了一个使用SGL做数据传输的Read command:

image.png

我们稍微解释一下这个trace,另外,在上图和下文中,我用不同的颜色来标识不同的字段,方便您对应和理解,供参考。
其中,完整的Read command (16个dword)是:

03E54002 00000001 00000000 00000000 00000000 00000000 1A911000 00000004
00000020 30000000 00000208 00000000 340000FF 00000000 00000208 00000000

可以看到:

  1. DW 0是03E54002h, 其中bit 15:14(PSDT)为01b, 根据前面的章节“1.3.2 SGL Descriptor”, 01b表示使用SGL做数据传输;
  2. DW 6-9是1A911000 00000004 00000020 30000000,这是一个SGL descriptor,根据前面的章节“1.3.2 SGL Descriptor”,第15个字节的bit 7:4(SGL descriptor type)是3,3表示该descriptor是SGL Last Segment descriptor,其中address是04_1A911000h, 而Length是20h,也就是说,在04_1A911000h存放着一个SGL Last Segment,其中包含20h / 16 = 2个SGL descriptor;
  3. DW 12是340000FFh,其中bit 15:00 (Number of Logical Blocks)是FFh,表示256个LBA,这条trace是在一个LBAF=512的SSD上抓的,因此数据量是256*512=131072字节,也就是128KiB;

下面我们看一下具体数据传输的过程:

  1. 首先,SSD从地址04_1A911000h地址拿到2个descriptor,分别为:365BE000 00000004 00010000 00000000365CE000 00000004 00010000 00000000
  2. 其中,365BE000 00000004 00010000 00000000的SGL descriptor type是0,表示该descriptor是一个SGL Data Block descriptor,其Address=04_365BE000h, Length=00010000h, 也就是说,在地址04_365BE000h存放着64KiB的数据;
  3. 同理,365CE000 00000004 00010000 00000000也是一个SGL Data Block descriptor,表示在地址04_365CE000h存放着128KiB中剩下的64KiB的数据;
  4. 现在所有数据的排布都已知晓,因此,下一步是分别将128KiB的数据DMA到对应的地址;
  5. 最后,发送CQ post和Interrupt,告诉Host命令执行完毕。

文章版本

  • 2025-02-07: v0.1,初始版本
  • 2025-02-12:v0.2,去掉了文章原始的标题序号

最后

🤝如需转载,请全文转载并注明出处和原始链接
😄欢迎关注我的公众号:

🙃欢迎来我的菜园子逛逛闷仙的菜园子 记录和分享,学习与进步
🧡如果,这篇文章帮助到了您,欢迎投喂