NVMe IO数据传输如何选择PRP or SGL?

本文探讨了在NVMe IO数据传输中,如何根据Linux内核中的条件选择使用PRP或SGL来描述Host内存地址。讲解了PRP和SGL的区别,以及内核如何决定使用哪种方式,涉及avg_seg_size和sgl_threshold的计算。此外,还提到了NVMe Controller的标识页面信息对SGL支持的查询以及在NVME over Fabrics场景中的应用。

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

在Host与Controller之间有数据交互时,Controller会多次访问Host内存。比如执行NVMe Read/Write:

  • 当Host下发NVMe Write命令时,Host会先放数据放在Host内存中,然后通知Controller过来取数据。Controller接到信息后,会通过PCIe Memory Read TLP读取相应的数据,接着Host返回的PCIe Completion报文中会携带数据给Controller,最后再写入NAND中。

  • 当Host下发NVMe Read命令时,Controller先从NAND中读出相应数据,然后通过PCIe Memory Write TLP将数据写入Host内存中。

上述Read/Write均有访问Host内存进行数据交互,那么,问题来了:

  • NVMe Write时,Controller怎么知道数据在Host内存的具体位置?

  • NVMe Read时,Controller怎么知道要把数据写到Host内存中哪个位置?

不怕,NVMe给Host配备了两大"法宝":PRPSGL。这两个模型均可以帮助Host告知Controller数据在Host内存中的具体地址。

PRP/SGL的背景知识,这里就暂时略过了,感兴趣的同学可以参考之前发布的文章。这里简单提下。存储随笔《NVMe专题》大合集及PDF版正式发布!

PRP和SGL是描述Host内存物理空间的两种方式,本质的不同是:PRP必须是物理页对齐的,而SGL则可以表述任意的物理空间。如下图。

在Linux内核中,Block层下发的IO请求以BIO表示。我们需要通过DMA发送这些数据,Command使用dma_alloc_coherent分配DMA地址,但是BIO是存放在普通的内核线程空间的(线程的虚拟空间不能直接作为DMA地址)。

Linux函数nvme_map_data能够将虚拟空间地址(BIO数据存放地址)转换成DMA可用地址,并且多个IO请求的DMA地址可以通过scatterlist来表示。有了DMA地址就可以把BIO封装成NVMe Command发送出去。

所以,linux驱动中nvme_map_data中针对NVME Command的IO传输格式就有了很重要的设定。比如,函数nvme_map_data中有iod->use_sgl的结果可以觉得IO传输过程中是使用SGL还是PRP。

而iod->use_sgl的返回结果依赖函数nvme_pci_use_sgls的判断,主要有两种情况:

  • 当SGL不支持的时,iod->use_sgl返回false,对应的IO数据传输就采用PRP了。

  • 当平均请求大小avg_seg_size值小于SGL阈值sgl_threshold时,也返回false,需要采用PRP。也就是说,使用SGL情况,必须要要求avg_seg_size大于等于sgl_threshold。SGL比较适合大块数据的传输。

<think>好的,我现在需要帮助用户解析NVMe设备驱动的工作原理。根据用户提供的引用资料,我得先整理这些信息,然后按照系统级指令的要求来组织回答。 首先,用户引用了五篇参考资料,内容涉及NVMe驱动的匹配机制、初始化流程、PCIe设备枚举、以及相关函数和结构体。我需要将这些信息整合成一个逻辑清晰的解析,分步骤说明驱动的工作原理。 第一步,NVMe驱动如何识别设备。根据引用[1]和[4],PCI总线通过设备ID和厂商ID匹配驱动,驱动中的nvme_id_table存储了支持的设备ID列表。这部分需要详细说明pci_driver结构体和匹配过程。 第二步,驱动初始化流程。引用[3]提到了nvme_probe函数,这是初始化的入口点。需要描述nvme_probe如何调用nvme_init_ctrl和nvme_reset_work,进而启用控制器和管理队列。同时,引用[2]提到启动两个内核线程,这部分也要涵盖。 第三步,设备操作接口。初始化完成后,字符设备和块设备的注册是关键。引用[2]指出用户通过/dev/nvmeX和块设备文件进行操作,这里需要解释两种设备的作用,比如字符设备用于管理命令,块设备用于数据传输。 第四步,核心命令处理。引用[5]提到了Identify、Read、Write命令,以及IOCTL的自定义实现。这部分要说明驱动如何处理这些基本命令,以及如何与硬件交互。 在组织内容时,要确保所有数学公式和独立公式使用正确的LaTeX格式。例如,PCI配置空间的寄存器地址可能需要用$符号包裹。同时,生成相关问题时要参考用户提供的示例,确保问题相关且有深度。 检查是否满足所有系统级指令:中文回答、正确LaTeX格式、相关问题的生成,以及引用标识的正确添加。确保每个引用在回答中自然出现,比如在说明设备匹配时引用[1][4],初始化流程引用[3][2],命令处理引用[5]。 最后,确保回答结构清晰,分步骤解析,语言通俗易懂,符合用户可见层的要求。避免使用过于专业的术语,必要时进行解释,帮助用户逐步理解NVMe驱动的工作原理。</think>### NVMe设备驱动工作原理解析 NVMe设备驱动通过PCIe总线与硬件交互,核心流程可分为四个阶段: #### 一、设备识别与驱动匹配 1. PCIe总线枚举设备时,通过**设备ID/厂商ID**匹配驱动模块中的`nvme_id_table`[^1] 2. 驱动通过`struct pci_driver`结构体声明支持设备列表,包含`.id_table`和`.probe`函数指针[^4] $$ \text{struct pci\_driver} \{ \quad const struct pci\_device\_id *id\_table; \\ \quad int (*probe)(struct pci\_dev *dev, const struct pci\_device\_id *id); \}; $$ #### 二、驱动初始化流程 1. 触发`nvme_probe()`入口函数[^3] 2. 创建控制器对象`nvme_init_ctrl()` 3. 启动内核工作线程: - **controller reset**:`nvme_reset_work()`处理控制器初始化 - **namespace scan**:扫描存储命名空间[^2] 4. 配置管理队列: ```c nvme_pci_configure_admin_queue() // 建立Admin SQ/CQ队列 ``` #### 三、设备操作接口 完成初始化后注册两类设备: 1. **字符设备**`/dev/nvmeX`:用于发送管理命令(如Identify) 2. **块设备**`/dev/nvmeXnY`:处理IO请求(如Read/Write)[^2] #### 四、核心命令处理 驱动需实现三大基础命令: 1. `Identify`:获取设备特性(最大队列数、LBA格式等) 2. `Read/Write`:数据传输通过PRP/SGL描述符实现[^5] 3. 自定义`IOCTL`:支持P2P访问等高级功能 ```mermaid graph TD A[PCIe设备枚举] --> B[ID匹配] B --> C[nvme_probe] C --> D[初始化控制器] D --> E[创建管理队列] E --> F[注册字符设备] E --> G[注册块设备] ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

古猫先生

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

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

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

打赏作者

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

抵扣说明:

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

余额充值