DPDK系列之三十七IO处理

一、介绍

如果一条通信链路要想达到最优的效果,一定是整体上每个环节都要有最佳的节奏协调控制而不一定是每个环节都是最优。这个在计算机的数据处理上就更是明显。一般来说,IO的速度是最低的,至少在可见的时光里要想超越CPU和内存还是很难的。
以网卡通信为例,网卡本身就IO传输,然后通过总线传输进入内存,内存进缓存,缓存到CPU,然后再原路一个反馈。学过计算机的一般都知道,这里面涉及到的几个部分,数据的处理速度那是有着量层级上的差异的。所以在处理问题上有三条思路:
一是把短板补齐,也就是那个慢把哪个速度提起来。这其中就包含着近些年来,不断提高的IO处理速度和总线处理速度(CPU的速度提高反而不太明显,主要是横向扩展);二是把不必要的中间环节去除,也就是本来是ABCD,是不是有可能把BC去除,直接让AD通信;三是通过软件优化,最大化的利用硬件资源(并行等)。
一般来说,这就是目前优化的三个主要的思路,在DPDK中,基本也是延着这两条思路前进的。不过,前者可能不是DPDK所能控制的,其更重要的是朝着后二种前进。

二、PCIe总线和DMA及缓存

PCI Express,Peripheral Component Interconnect Express(PCIe),是一种高速串联通信标准。目前的最新版本是7.0,但主流的标准应该3.0和4.0居多。PCIe主要用来连接显卡、固态硬盘以及采集卡和无线网卡等外设,用来为高速外设进行数据传输。
直接内存访问(DMA,Direct Memory Access),主要是绕过CPU,直接与IO进行数据通信。
缓存就更好理解了,Cache一般使用SRAM静态存储器,用来做为主存与CPU速度不匹配的一种缓冲存储(缓存的速度已经很接近CPU的处理速度),其重要指标就是命中率。

三、数据传输

这里不展开具体的PCIe及相关的DMA等的数据传输细节。一般来说,支持的PCIe的版本越高,其数据传输的速度越快,即使扣除相关的协议开销和控制指令等的开销后,其对数据传输的瓶颈解决越明显。但光其一个最优不行,中间的其它传输也要共同优化,节奏相同。
DMA是通过环形队列与CPU交互(利用DDIO技术减少对主存的访问次数),环形队列一般学过算法的都清楚怎么回事。而为了提高数据访问的速度,环形队列缓冲区的大小必须是网卡支持最大Cache line(128B)的整数倍,这样做的目的当然是为了方便内存对齐,即提高访问效率,效率高了自然速度就上来了。
正常的网卡驱动通信一般是以下几步:
1、将缓冲地址写到地址描述符
2、移动队列尾指针到指定位置
3、判断描述符中的状态位是否完成,如果是接收还要申请新的缓冲区;发送则需要释放已发送缓冲区;这些其实都是一些头尾的动作
4、一些处理操作,如描述符的内容更新和控制头的解析等

四、数据转发

刚刚提到了,某个局部最优并不一定是整体的最优解,所以要处理好数据传输,一个重要的问题是要处理好数据在各个环节的转发的问题。在网卡通信中,最重的是将CPU中的缓存与IO的通信整体协调好。
那么,首先要理顺一下,在这个通信过程中需要哪几个环节协调。首先,CPU面对的主存和外部寄存器。而对DMA来说就是主存和Cache。而它们之间的数据需要PCIe来传输,所以优化的手段就明晰了:
1、减少对外部寄存器MMIO的读取
2、提高PCIe的效率,即在固定的带宽下,如何最大程度的利用满
3、减少Cache的部分写
这都比较好理解,先从最后一个说起,Cache的内存未对齐,也就是部分写,会导致对缓存的二次操作,效率至少降低一半。而余下二者其实类似于批处理,把单次的操作改成多次合成,减少相关的控制和协议处理,自然可以提高数据的传输速度。
在DPDK中,使用Mbuf来处理网络数据帧。而在网络帧的处理上有两种情况,一种是将元数据和数据同时存放;另外一个是将二者分开存放。这样说有点不直白,其实就是空间利用率和时间利用率(效率)的二者的权衡。前者又更好的利用内存空间而后者则能够在浪费一部分内存的情况下更好的提高数据转发效率。DPDK当然选择了后者。
而为了更好的利用内存,将Mbuf形成一个内存池(双环形缓冲区),DPDK为应对多核的情况,允许每个CPU缓存一部分缓冲区。然后在写时进行CAS操作处理。当然,这样做的缺点和上面一样,也会浪费一部分缓存。

五、源码分析

上面把相关的内容理顺了一下,这样再和源码匹配就好理解了。前面的队列和Mbuf等进行过分析,这里只讲一些整体上的源码流程:

//dpdk-stable-19.11.14\lib\librte_eal\linux\eal\eal_vfio.cstatic int
vfio_type1_dma_mem_map(int vfio_container_fd, uint64_t vaddr, uint64_t iova,uint64_t len, int do_map)
{struct vfio_iommu_type1_dma_map dma_map;struct vfio_iommu_type1_dma_unmap dma_unmap;int ret;if (do_map != 0) {memset(&dma_map, 0, sizeof(dma_map));dma_map.argsz = sizeof(struct vfio_iommu_type1_dma_map);dma_map.vaddr = vaddr;dma_map.size = len;dma_map.iova = iova;dma_map.flags = VFIO_DMA_MAP_FLAG_READ |VFIO_DMA_MAP_FLAG_WRITE;ret = ioctl(vfio_container_fd, VFIO_IOMMU_MAP_DMA, &dma_map);if (ret) {/*** In case the mapping was already done EEXIST will be* returned from kernel.*/if (errno == EEXIST) {RTE_LOG(DEBUG, EAL," Memory segment is already mapped,"" skipping");} else {RTE_LOG(ERR, EAL,"  cannot set up DMA remapping,"" error %i (%s)\n",errno, strerror(errno));return -1;}}} else {memset(&dma_unmap, 0, sizeof(dma_unmap));dma_unmap.argsz = sizeof(struct vfio_iommu_type1_dma_unmap);dma_unmap.size = len;dma_unmap.iova = iova;ret = ioctl(vfio_container_fd, VFIO_IOMMU_UNMAP_DMA,&dma_unmap);if (ret) {RTE_LOG(ERR, EAL, "  cannot clear DMA remapping, error %i (%s)\n",errno, strerror(errno));return -1;} else if (dma_unmap.size != len) {RTE_LOG(ERR, EAL, "  unexpected size %"PRIu64" of DMA ""remapping cleared instead of %"PRIu64"\n",(uint64_t)dma_unmap.size, len);rte_errno = EIO;return -1;}}return 0;
}static int
vfio_type1_dma_map(int vfio_container_fd)
{if (rte_eal_iova_mode() == RTE_IOVA_VA) {/* with IOVA as VA mode, we can get away with mapping contiguous* chunks rather than going page-by-page.*/int ret = rte_memseg_contig_walk(type1_map_contig,&vfio_container_fd);if (ret)return ret;/* we have to continue the walk because we've skipped the* external segments during the config walk.*/}return rte_memseg_walk(type1_map, &vfio_container_fd);
}

这段代码会在最初始的函数rte_eal_init这个函数中调用,这个函数不陌生吧,在最初的文章里就介绍过。它主要用来在初始时对固定内存的DMA地址映射,而若是对小的临时的地址映射可以使用下面的函数:

//dpdk-stable-19.11.14\lib\librte_eal\common\eal_common_dev.c
int
rte_dev_dma_map(struct rte_device *dev, void *addr, uint64_t iova,size_t len)
{if (dev->bus->dma_map == NULL || len == 0) {rte_errno = ENOTSUP;return -1;}/* Memory must be registered through rte_extmem_* APIs */if (rte_mem_virt2memseg_list(addr) == NULL) {rte_errno = EINVAL;return -1;}return dev->bus->dma_map(dev, addr, iova, len);
}int
rte_dev_dma_unmap(struct rte_device *dev, void *addr, uint64_t iova,size_t len)
{if (dev->bus->dma_unmap == NULL || len == 0) {rte_errno = ENOTSUP;return -1;}/* Memory must be registered through rte_extmem_* APIs */if (rte_mem_virt2memseg_list(addr) == NULL) {rte_errno = EINVAL;return -1;}return dev->bus->dma_unmap(dev, addr, iova, len);
}

这里不细节展开在使用虚地址和实地址以及自定义地址时,DMA对其的各自处理的细节,有兴趣可以自己查看源码。DMA中对数据的传输,其实就是对缓冲池和Mbuf的处理。在更高的版本中,DPDK在库单独对dmadev进行了封装。
DMA处理数据的过程将在后面的整体流程源码分析中,进行说明。
再看一下PCIe相关,首先是扫描(对于一些基础的PCIe相关的应用知识需要懂一些,否则代码看不看也没啥意义):

static int
pci_scan_one(const char *dirname, const struct rte_pci_addr *addr)
{char filename[PATH_MAX];unsigned long tmp;struct rte_pci_device *dev;char driver[PATH_MAX];int ret;dev = malloc(sizeof(*dev));if (dev == NULL)return -1;memset(dev, 0, sizeof(*dev));dev->device.bus = &rte_pci_bus.bus;dev->addr = *addr;/* get vendor id */snprintf(filename, sizeof(filename), "%s/vendor", dirname);if (eal_parse_sysfs_value(filename, &tmp) < 0) {free(dev);return -1;}dev->id.vendor_id = (uint16_t)tmp;/* get device id */snprintf(filename, sizeof(filename), "%s/device", dirname);if (eal_parse_sysfs_value(filename, &tmp) < 0) {free(dev);return -1;}dev->id.device_id = (uint16_t)tmp;/* get subsystem_vendor id */snprintf(filename, sizeof(filename), "%s/subsystem_vendor",dirname);if (eal_parse_sysfs_value(filename, &tmp) < 0) {free(dev);return -1;}dev->id.subsystem_vendor_id = (uint16_t)tmp;/* get subsystem_device id */snprintf(filename, sizeof(filename), "%s/subsystem_device",dirname);if (eal_parse_sysfs_value(filename, &tmp) < 0) {free(dev);return -1;}dev->id.subsystem_device_id = (uint16_t)tmp;/* get class_id */snprintf(filename, sizeof(filename), "%s/class",dirname);if (eal_parse_sysfs_value(filename, &tmp) < 0) {free(dev);return -1;}/* the least 24 bits are valid: class, subclass, program interface */dev->id.class_id = (uint32_t)tmp & RTE_CLASS_ANY_ID;/* get max_vfs */dev->max_vfs = 0;snprintf(filename, sizeof(filename), "%s/max_vfs", dirname);if (!access(filename, F_OK) &&eal_parse_sysfs_value(filename, &tmp) == 0)dev->max_vfs = (uint16_t)tmp;else {/* for non igb_uio driver, need kernel version >= 3.8 */snprintf(filename, sizeof(filename),"%s/sriov_numvfs", dirname);if (!access(filename, F_OK) &&eal_parse_sysfs_value(filename, &tmp) == 0)dev->max_vfs = (uint16_t)tmp;}/* get numa node, default to 0 if not present */snprintf(filename, sizeof(filename), "%s/numa_node",dirname);if (access(filename, F_OK) != -1) {if (eal_parse_sysfs_value(filename, &tmp) == 0)dev->device.numa_node = tmp;elsedev->device.numa_node = -1;} else {dev->device.numa_node = 0;}pci_name_set(dev);/* parse resources */snprintf(filename, sizeof(filename), "%s/resource", dirname);if (pci_parse_sysfs_resource(filename, dev) < 0) {RTE_LOG(ERR, EAL, "%s(): cannot parse resource\n", __func__);free(dev);return -1;}/* parse driver */snprintf(filename, sizeof(filename), "%s/driver", dirname);ret = pci_get_kernel_driver_by_path(filename, driver, sizeof(driver));if (ret < 0) {RTE_LOG(ERR, EAL, "Fail to get kernel driver\n");free(dev);return -1;}if (!ret) {if (!strcmp(driver, "vfio-pci"))dev->kdrv = RTE_KDRV_VFIO;else if (!strcmp(driver, "igb_uio"))dev->kdrv = RTE_KDRV_IGB_UIO;else if (!strcmp(driver, "uio_pci_generic"))dev->kdrv = RTE_KDRV_UIO_GENERIC;elsedev->kdrv = RTE_KDRV_UNKNOWN;} elsedev->kdrv = RTE_KDRV_NONE;/* device is valid, add in list (sorted) */if (TAILQ_EMPTY(&rte_pci_bus.device_list)) {rte_pci_add_device(dev);} else {struct rte_pci_device *dev2;int ret;TAILQ_FOREACH(dev2, &rte_pci_bus.device_list, next) {ret = rte_pci_addr_cmp(&dev->addr, &dev2->addr);if (ret > 0)continue;if (ret < 0) {rte_pci_insert_device(dev2, dev);} else { /* already registered */if (!rte_dev_is_probed(&dev2->device)) {dev2->kdrv = dev->kdrv;dev2->max_vfs = dev->max_vfs;pci_name_set(dev2);memmove(dev2->mem_resource,dev->mem_resource,sizeof(dev->mem_resource));} else {/*** If device is plugged and driver is* probed already, (This happens when* we call rte_dev_probe which will* scan all device on the bus) we don't* need to do anything here unless...**/if (dev2->kdrv != dev->kdrv ||dev2->max_vfs != dev->max_vfs)/** This should not happens.* But it is still possible if* we unbind a device from* vfio or uio before hotplug* remove and rebind it with* a different configure.* So we just print out the* error as an alarm.*/RTE_LOG(ERR, EAL, "Unexpected device scan at %s!\n",filename);else if (dev2->device.devargs !=dev->device.devargs) {rte_devargs_remove(dev2->device.devargs);pci_name_set(dev2);}}free(dev);}return 0;}rte_pci_add_device(dev);}return 0;
}

扫描并增加相关设备后,就可以将其位于“/sys/bus/pci/devices/0000:xxxx:xx/resouce”的每个PCI设备对应的文件中保存的pci设备bar寄存器的地址映射信息在这个设备启动加载时将映射的物理内存地址信息保存在bar寄存器中同时写入到此文件。而UIO就是通过这个文件来使用mmap对其的物理地址进行映射,然后就可以在应用层对此设备进行访问了。
cache等的代码在前面分析过,这里就不再重复,如果需要可以翻翻前面的文章。

六、总结

其实数据IO的处理,比之内存等内的操作更好理解,因为它更接近于现实世界的处理流程。形象化的描述比之抽象化的东西更容易为人所理解。就如让人理解IO数据流不好理解,但去理解水管阀门调节水流容易理解一样。其实二者本质是相同的,原理是相通的。
把原理吃透,多看手册,对比源码,秘密全无。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/171007.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Scope 模块

Scope 模块可以连接任何类型的实数信号线 (不支持复数)。 波形显示界面主要包括两个部分: Scope 独有的工具栏、波形显示区域。 波形显示界面默认是黑色背景, 当有单个信号输入时, 信号线是黄色的。 Scope 模块也有菜单栏, 只不过默认将其句柄和显示都隐藏起来, 可以通过下面…

技术类知识汇总(二)

在自己日常学习javaweb的过程中&#xff0c;做的一些笔记和总结&#xff0c;汇总如下&#xff1a; Springboot项目的静态资源(html&#xff0c;css&#xff0c;js等前端资源)默认存放目录为&#xff1a;classpath:/static classpath:/public classpath:/resources"三层架…

java-hprof 文件是什么

一、是什么 hprof 文件是 Java进程所使用的内存情况在某一时间的一次快照&#xff08;Heap Profile 的缩写&#xff09;&#xff0c;格式为java_pidxxxxx*.hprof 二、文件里面有什么 1、所有的对象信息 对象的类信息、字段信息、原生值(int, long等)及引用值 2、所有的类信…

一键下载Python各版本中的最新版

一、背景需求 下载Python的安装包非常简单&#xff0c;只需要去官网就可以了&#xff1a;https://www.python.org/downloads/windows/ 但是有时候你可能需要环境测试&#xff0c;需要安装很多版本的Python。 一个一个找倒是也可以&#xff0c;但是我做了个自动筛选的脚本&am…

Kafka-Consumer

Kafka消费者 消费者 与生产者对应的是消费者&#xff0c;应用程序可以通过KafkaConsumer来订阅主题&#xff0c;并从订阅的主题中拉取消息。 消费者与消费者组 Kafka的消费者&#xff08;Consumer&#xff09;负责订阅Kafka中的主题&#xff08;Topic&#xff09;&#xff…

Web 安全之证书透明(Certificate Transparency)详解

目录 证书透明性的概念 数字证书和颁发机构 证书透明的起源 证书透明的工作原理 证书透明的实现方法 证书透明的优点 浏览器和客户端对证书透明的支持情况 小结 证书透明&#xff08;Certificate Transparency, CT&#xff09;是网络安全领域中的一个重要概念&#xff…

车载以太网-数据链路层-MAC

文章目录 车载以太网MAC(Media Access Control)车载以太网MAC帧格式以太网MAC帧报文示例车载以太网MAC层测试内容车载以太网MAC(Media Access Control) 车载以太网MAC(Media Access Control)是一种用于车载通信系统的以太网硬件地址,用于在物理层上识别和管理数据包的传…

5 个适用于 Windows 的顶级免费数据恢复软件

对于计算机来说&#xff0c;最重要的是用户数据。除了您的数据之外&#xff0c;有关计算机的其他所有内容都是可替换的。这三个是数据丢失的最常见原因&#xff1a; 文件/文件夹删除丢失分区分区损坏 文件/文件夹删除 文件/文件夹删除是最常见的数据丢失类型。大多数时候&am…

《微信小程序开发从入门到实战》学习三十一

3.4 开发参与投票页面 3.4.9 显示投票结果 在实际使用中&#xff0c;一个用户不能对同一个投票进行重复提交&#xff0c;因此需要向服务器端提交投票结果和提交用户ID。另外页面&#xff0c;需要完善。用户提交完投票后 &#xff0c;还需要显示投票目前的结果&#xff0c;提交…

C语言进阶之笔试题详解(1)

引言&#xff1a; 对指针知识进行简单的回顾&#xff0c;然后再完成笔试题。 ✨ 猪巴戒&#xff1a;个人主页✨ 所属专栏&#xff1a;《C语言进阶》 &#x1f388;跟着猪巴戒&#xff0c;一起学习C语言&#x1f388; 目录 引言&#xff1a; 知识简单回顾 指针是什么 指针变…

1 时间序列模型入门: LSTM

0 前言 循环神经网络&#xff08;Recurrent Neural Network&#xff0c;RNN&#xff09;是一种用于处理序列数据的神经网络。相比一般的神经网络来说&#xff0c;他能够处理序列变化的数据。比如某个单词的意思会因为上文提到的内容不同而有不同的含义&#xff0c;RNN就能够很好…

2023-3年CSDN创作纪念日

机缘 今天开开心心出门去上班&#xff0c;就收到了一个csdn私信&#xff0c;打开一看说是给我惊喜来着&#xff0c;我心想csdn还能给惊喜&#xff1f;以为是有什么奖品或者周边之类的&#xff0c;结果什么也没有&#xff0c;打开就是一份信&#x1f602;。 也挺不错的&#xf…

Python基础入门例程67-NP67 遍历字典(字典)

最近的博文: Python基础入门例程66-NP66 增加元组的长度(元组)-CSDN博客 Python基础入门例程65-NP65 名单中出现过的人(元组)-CSDN博客 Python基础入门例程64-NP64 输出前三同学的成绩(元组)-CSDN博客 目录 最近的博文: 描述

1.6 C语言之数组概述

1.6 C语言之数组概述 一、数组二、练习 一、数组 所谓数组&#xff0c;就是内存中一片连续的空间&#xff0c;可以用来存储一组同类型的数据 数组有下标&#xff0c;从0开始&#xff0c;可以理解为是给数组中的元素编号&#xff0c;便于后续寻址访问 我们来编写一个程序&…

SparkSQL之Optimized LogicalPlan生成过程

经过Analyzer的处理&#xff0c;Unresolved LogicalPlan已经解析成为Analyzed LogicalPlan。Analyzed LogicalPlan中自底向上节点分别对应Relation、Subquery、Filter和Project算子。   Analyzed LogicalPlan基本上是根据Unresolved LogicalPlan一对一转换过来的&#xff0c;…

量子计算的世界:探索叠加态与Python编程

1.量子计算概述 量子计算是一种利用量子力学的原理来进行信息处理的技术。它与传统的计算机科学有着根本的不同&#xff0c;主要体现在以下几个方面&#xff1a; 1.基本原理 量子比特&#xff08;Qubit&#xff09;&#xff1a; 传统计算机使用比特作为信息的基本单位&#x…

针对哈希冲突的解决方法

了解哈希表和哈希冲突是什么 哈希表&#xff1a;是一种实现关联数组抽象数据类型的数据结构&#xff0c;这种结构可以将关键码映射到给定值。简单来说哈希表&#xff08;key-value&#xff09;之间存在一个映射关系&#xff0c;是键值对的关系&#xff0c;一个键对应一个值。 …

foobar2000 突然无法正常输出DSD信号

之前一直在用foobar2000加外置dac听音乐&#xff0c;有一天突然发现听dsd的时候&#xff0c;dac面板显示输出的是PCM格式信号&#xff0c;而不是DSD信号&#xff0c;这让我觉得很奇怪&#xff0c;反复折腾了几次&#xff0c;卸载安装驱动什么的&#xff0c;依然如此&#xff0c…

java协同过滤算法 springboot+vue游戏推荐系统

随着人们生活质量的不断提高以及个人电脑和网络的普及&#xff0c;人们的业余生活质量要求也在不断提高&#xff0c;选择一款好玩&#xff0c;精美&#xff0c;画面和音质&#xff0c;品质优良的休闲游戏已经成为一种流行的休闲方式。可以说在人们的日常生活中&#xff0c;除了…