DPDK Ring

news/2024/11/16 4:50:28/

无锁环ring是DPDK提供的一种较为基础的数据结构,其支持多生产者和多消费者同时访问。
经过我的经验,无锁结构的实现主要依靠两方面:

  1. 最终的数据交换一定要是原子级的操作,最常用到的自然就是比较后交换(Compare And Swap,CAS)
  2. 数据的实际写入与原子级的交换操作相分离

只是这么说可能不是很好理解,我们以我目前知道的两种实现方式,即RCU和DPDK ring的实现来举例子就好理解了。

CAS

首先我们需要之后CAS的原理,CAS共有三个输入,分别是将要修改的地址reg,reg中的原数据oldvalue,以及想要更新的新数据newvalue。

在CAS的执行流程如下:

  1. 取出reg中的当前数据regvalue
  2. 将regvalue与oldvalue进行比较
  3. 如果二者相等说明在当前核读取数据到写入数据的这一段时间reg中的值没有被修改过,可以直接写入。CAS函数在写入后返回成功信号
  4. 如果二者不等说明该数据已经被修改过了,当前核使用的数据有可能是错的,因此不会执行写入操作,并返回一个错误信号

上述的所有步骤均由一个原子级的CPU指令完成,在CAS失败时,通常需要用户重新读取新数据执行对应操作,再尝试执行一次CAS,直到写入成功为止。

RCU和无锁Ring

RCU和无锁Ring都是无锁通信的实现,二者在原理上有上述的类似却又不同,适用场景也不同。
首先,既然是CPU之间的通信,那么就一定会有数据被修改,加锁也只是为了保证写核在修改数据时不会被其他读核读取到数据。
而CAS恰恰就是一个原子级的操作,也就是说通过CAS在修改数据的时候,别的核是无法读取到对应数据的,也就是相当于自带加锁了。
那既然有了CAS为什么不能所有数据都这样写呢?因为种种原因(例如CPU指令集限制,以及需要比较的字节数等等),CAS只支持基本数据类型(int, bool)维度的操作。
那假如我要修改一个很长的结构体对象该怎么办呢?

RCU

原理

很自然的可以想到,我们可以先创建一个新的结构体对象,这个对象不会被任何其他核访问到,无论花多少时间都可以。
在对象创建好之后,我们取出他的指针,由于指针是基本数据类型,我们可以使用CAS完成一次狸猫换太子,将指向原来对象的指针替换成新对象的指针,这样之后的读核就会无感的访问到新对象了。
在完成指针更新之后,为了防止内存泄露,我们需要将原来的对象释放掉。
这里就需要注意一个问题,原来的数据结构可能还在被其他核所读取,冒然释放可能引发系统错误,因此我们要等所有读核都不在使用该地址再进行释放。
也就是说这块内存会额外存活一段时间,这段时间在RCU中就被成为宽限区,宽限区从写核修改指针开始,到所有读核读取结束为止。
为了让写核能够感知到释放内存的时间,需要为每一个对象内存添加一个引用计数。读核每次开始读取时引用计数加1,结束读取时引用计数减1,当引用计数为0就说明可以释放了。
至此我们已经掌握了RCU的基本原理。

特点

优点:对于读核来说无需进行任何加锁操作,只需要在开始读取和结束读取时修改一个引用计数,这一操作同样可以使用CAS实现。
缺点:拖慢写核的工作进程,同时需要额外的内存地址,无法做到原址修改。
因此RCU适合读多写少,并且不需要原址修改的场景中。

无锁Ring

原理

假如我们想要实现一个存储对象的循环队列,队列的底层是一个数组,自然需要在原址修改,此时RCU就不适用了,我们需要实现新的无锁方案。
为了利用CAS,我们需要找到能够代表一个对象的基本数据类型,那自然是对象在数组中的索引了。
在写入时,我们可以先修改队尾的下标地址,相当于在实际写入数据之前通告其他写核,这块地址已经被我占据了,你们不要再往里面写了,由于交换的只是一个索引,因此可以使用CAS。
在新下标确定之后,写核会对数据进行实际的写入,这里有一个问题,由于我们已经修改了队尾指针,那么虽然其他写核访问不到这片内存,但是现在其他读核可以了啊。
解决方法也很简单粗暴,我们再加一个索引就好了。新加的索引在没有写操作进行时指向与队尾相同的位置,在开始写入时不变,写入结束时再与队尾同步,只要让读核所看到的队尾一直是该索引即可。
当然,上述讨论的只是多生产者单消费者的情况,如果是多生产者多消费者,那么我们需要为队头也创建一个额外的索引,其余原理其实都相同。
至此,我们就掌握了无锁循环队列的基本原理。

DPDK实现

下图展示的是DPDK中无锁Ring的基本结构,其中prod_head和prod_tail分别上述的队尾指针和队尾额外指针,同样cons_head和cons_tail也是队头指针和队头额外指针。
对于生产者来说,它在任意时刻看到的队列实际是prod_head到cons_tail,而对消费者来说,其看到的队列始终是prod_tail和cons_head。

在这里插入图片描述

DPDK Ring单生产者入队流程

下面是单生产者的入队流程,我们已经介绍过了
需要注意的是,local variables中的prod_head核prod_next分别是CAS操作中的oldvalue和newvalue,而队列中的prod_head则是reg。
第一步:读取原始prod_head,根据入队数量计算新的prod_head,即prod_next
在这里插入图片描述

第二步:通过CAS修改prod_head,占据内存
在这里插入图片描述
第三步:实际写入数据,并使用CAS修改prod_tail
在这里插入图片描述

DPDK Ring多生产者入队流程

在这里插入图片描述
第二步:先提交的核完成CAS占据内存,后提交的CAS失败,重新读取prod_head并计算prod_next
在这里插入图片描述
第三步:core1开始写入数据,core2重新CAS成功
在这里插入图片描述
第四步:core2写入数据,core1通过CAS修改prod_tail
在这里插入图片描述
第五步:core2修改prod_tail,至此多生产者入队结束。
在这里插入图片描述
由于消费者出队的操作与生产者入队几乎完全相同,在此不做赘述,可以参考https://dpdk-docs.readthedocs.io/en/latest/prog_guide/ring_lib.html

优点:可以原址修改,支持批量入队和出队,也就是可以通过一次CAS占据大片内存,这都是RCU做不到的。如果使用CAS直接操作内存地址队列,也做不到批量操作。
缺点:内存空间需要预先分配,不像RCU使用是才申请,比较费内存。

进程间通信

通过Ring和mempool的结合使用还可以实现进程间的通信,原理与共享内存类似,DPDK在创建Primary Process时会将使用的大页内存的详细信息记录到文件中,在启动Secondary Process时会读取这些信息,这样就能实现类似共享内存的效果。
在两个进程内都需要调用rte_eal_init初始化DPDK环境。
在一个进程内创建Ring和Mempool,在另一个进程内调用rte_ring_lookup和rte_mempool_lookup函数获取对应的Ring和Mempool。
通信时,一个进程从Mempool中取一个元素,放入Ring中,另一个进程从Ring中取出,处理完之后再放回Mempool即可。


http://www.ppmy.cn/news/687273.html

相关文章

DPDK系列之二DPDK编译

一、DPDK下载 前面把环境基本搭好,今天开始编译DPDK。首先是下载DPDK的源码,地址是: https://core.dpdk.org/download/ 也可以从官网https://www.dpdk.org/跳过来。然后就可以看到排下载的DPDK的版本: 这里下载的是DPDK 19.11.14…

如何在 Tkinter 中检测画布上的前一个项目?

在使用 Python 的 Tkinter 模块创建图形用户界面(GUI)时,有时我们需要检测画布上的前一个项目。这可以通过跟踪鼠标事件和使用适当的方法来实现。下面将介绍如何在 Tkinter 中检测画布上的前一个项目。 首先,让我们了解一下 Tkin…

DPDK概览

背景描述 DPDK的引入 对网卡频繁中断问题,提出DMA方式和设备轮询机制,其中NAPI采用“中断加轮询”的方式工作;网卡驱动在中断量较大(设定阈值)时会关闭中断,并自动采用轮询方式手法报文。 针对网络协议栈…

什么是DPDK

DPDK官网:http://www.dpdk.org/ What it is DPDK is a set of libraries and drivers for fast packet processing. It was designed to run on any processors. The first supported CPU was Intel x86 and it is now extended to IBM Power 8, EZchip TILE-Gx and…

DPDK相关介绍

读前须知 阅读本文前建议了解: 计算机网络基础知识; Linux用户态及内核态的简单介绍; 服务器 Numa 节点相关知识; 简述 概念 DPDK(Data Plane Development Kit,数据平面开发套件)&#xf…

DPDK简介

DPDK是什么 Intel DPDK全称Intel Data Plane Development Kit,是intel提供的数据平面开发工具集,为Intel architecture(IA)处理器架构下用户空间高效的数据包处理提供库函数和驱动的支持,它不同于Linux系统以通用性设…

DPDK介绍

目录 什么是dpdk 内核协议栈 vs dpdk 内核收包的两种方式 中断模式 轮询模式 内核协议栈收包流程 ​ DPDK收包 1、UIO框架 2、用户态驱动pmd轮询与uio中断的关系 3、 mellanox dpdk 混和中断轮询模式 DPDK核心部件库 DPDK 内存结构 hugetlb TLB 为什么要…

Delphi 制作自定义数据感知控件并装入包(dpk文件)中(与DBText类似的数据感知控件)

一、基础知识简介: 1、包的命名介绍: 包的命名没有规则,但是建议遵守包的命名约定:包的命名与包的版本相关,包的名称前面几个字符通常表示作者或公司名,也可以是控件的一个描述词,后面紧跟的St…