NVMe系统内存结构 - SGL

本文详细介绍了NVMe系统中的ScatterGatherList(SGL)数据结构,包括SGLSegment、SGLDescriptor的不同类型及其在数据传输中的作用,以及PRP与SGL的区别。通过实例展示了SGL在数据读取请求中的应用,强调了SGL在灵活性和地址描述方面的优势。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


本文属于《 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描述了一个数据块,定义如下表:

BytesDescription
7:0Address:指定了数据块的64位起始内存地址。
11:8Length:指定了数据块的字节长度。
若设置为00000000h,表示没有数据传输。指定没有数据传输的SGL Data Block descriptor是一个有效的descriptor。
如果Address字段的值,加上Length字段的值,大于1_00000000_00000000h,则应当作SGL Data Block descriptor发生错误来处理。
14:12保留
15SGL 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从控制器读取数据时有效),定义如下表:

BytesDescription
7:0保留
11:8Length
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保留
15SGL 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,定义如下表:

BytesDescription
7:0Address:指定了下一个SGL Segment的起始64位内存地址,这是一个SGL Segment。
11:8Length:指定了下一个SGL Segment的长度,单位为字节。Length字段,应为非零值,且为16的倍数。
如果Address字段的值,加上Length字段的值,大于1_00000000_00000000h,则应当作SGL Segment descriptor发生错误来处理。
14:12保留
15SGL 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,定义如下表:

BytesDescription
7:0Address:指定了下一个和最后一个SGL Segment的起始64位内存地址,这是一个SGL Segment。
11:8Length:指定了下一个和最后一个SGL Segment的长度,单位为字节。Length字段,应为非零值,且为16的倍数。
如果Address字段的值,加上Length字段的值,大于1_00000000_00000000h,则应当作SGL Last Segment descriptor发生错误来处理。
14:12保留
15SGL 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。

参考文档:

<think>好的,用户现在想了解Dify节点的相关信息或配置,特别是节点的配置和使用方法。让我先回顾一下之前的对话历史和提供的参考资料。用户之前询问了如何下载Dify源码,现在转向节点的具体配置和使用。 首先,用户提供的引用中提到了零基础AI开发系列教程中的Dify节点详细介绍,分为上、中、下三部分,这可能涉及节点的类型、功能以及具体操作步骤。另外,引用[2]提到了HTTP请求节点的认证方式,引用[3]则讨论了变量聚合节点的作用。这些信息都是关键点,需要整合到回答中。 用户的具体问题是如何配置和使用Dify节点,因此需要分步骤解释,包括节点类型、配置步骤、常见节点的操作示例,以及注意事项。可能需要结合之前的回答中提到的源码结构和相关模块,如蓝图控制器,来增强回答的专业性。 需要注意的是,用户可能对Dify的工作流设计不太熟悉,因此需要从基础开始解释,例如节点的概念、如何添加到工作流、配置参数等。同时,结合引用中的HTTP节点和变量聚合节点作为例子,能够具体说明配置过程,帮助用户理解。 另外,用户之前的问题涉及源码下载和依赖项,因此在回答节点配置时,可能需要提到环境准备,比如确保Docker和相关服务已正确安装,这部分在之前的回答中已经提到过,可以引用之前的建议作为参考。 还需要考虑用户可能的后续问题,比如遇到配置错误如何处理,或如何调试节点,所以应在注意事项中涵盖这些方面,或者生成相关问题引导用户进一步探索。 最后,确保回答结构清晰,分点说明,并正确引用提供的资料,比如引用[1]的教程系列,引用[2]的HTTP节点认证,引用[3]的变量聚合节点功能。同时,使用中文,符合格式要求,行内公式用$...$,独立公式用$$...$$,但可能这里公式较少,重点在配置步骤和代码示例。</think>### Dify 节点配置与使用详解 Dify 的节点是工作流设计的核心组件,用于实现数据处理、逻辑控制和结果聚合等功能。以下是节点配置与使用的关键要点及操作示例: --- #### 一、节点基础配置 1. **节点类型与功能** - **输入节点**:接收外部数据(如用户输入、API请求) - **处理节点**:执行逻辑操作(如LLM调用、条件分支、变量聚合[^3]) - **输出节点**:返回最终结果(如API响应、文件生成) 2. **通用配置步骤** ```bash # 示例:通过Dify界面配置节点的基本流程 1. 创建工作流 -> 拖拽节点至画布 -> 连接节点 2. 双击节点 -> 填写参数 -> 保存配置 ``` - **必填字段**:节点名称、输入/输出变量映射 - **动态参数**:支持通过 `{{variable}}` 语法引用上下文变量 --- #### 二、常见节点操作示例 1. **HTTP请求节点** *(适用于调用外部API)* - **认证配置**: 使用Bearer Token认证时,需在请求头添加: ```http Authorization: Bearer {{your_api_key}}[^2] ``` - **参数映射**: 将上游节点的输出变量(如 `{{query_result}}`)映射到请求体参数 2. **变量聚合节点** *(整合多分支结果)* ```yaml # 配置示例:聚合分类结果 branches: - path: A output_vars: {category: "科技"} - path: B output_vars: {category: "生活"} aggregated_var: unified_category[^3] ``` - **作用**:将不同分支的 `category` 变量统一映射为 `unified_category` 3. **LLM调用节点** *(核心AI处理单元)* - **模型选择**:支持本地部署模型(如DeepSeek)或云端API - **提示词模板**: 使用动态变量增强灵活性: ```python "请根据{{user_input}}生成摘要,限制{{max_length}}字" ``` --- #### 三、高级配置技巧 1. **条件分支控制** ```python # 通过表达式实现动态路由 if {{input.score}} > 0.7: route_to = "positive_feedback" else: route_to = "default_process" ``` 2. **错误处理机制** - **重试策略**:配置HTTP节点的超时时间与重试次数 - **异常捕获**:添加备用处理节点应对API调用失败 3. **性能优化** - **并行执行**:对无依赖关系的节点启用并行处理 - **缓存设置**:为LLM节点配置结果缓存(需启动Redis服务[^1]) --- #### 四、注意事项 1. **环境依赖** 确保已部署 PostgreSQL(数据库)、Redis(缓存)、Weaviate(向量搜索)等基础设施[^1] 2. **调试工具** 使用工作流的 **"测试运行"** 功能实时观察变量传递状态 3. **版本兼容** 升级Dify版本时需检查节点配置变更(参考《Dify升级指南》[^1])
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

百里杨

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值