DPDK之eventdev_pipeline源码解析

本文详细解析了DPDKEventdev库的eventdev_pipeline实现原理,包括数据接收、发送、事件调度以及structrte_event结构。介绍了核心API和两种实现机制:generic和txenq,重点在于源码级别的操作和策略选择。

引言

DPDK Eventdev库是DPDK基于事件驱动的编程模型。其中eventdev_pipeline实现了对该模型的应用例子。

参考源码版本: dpdk-stable-22.11.1

1 实现原理

在这里插入图片描述
整个eventdev有两个关键的概念,即event queue与event port。event queue是存放事件的队列,当网卡或CPU产生一个事件最终会放到事件队列中待消费者取出来执行;此外,DPDK Eventdev架构不能直接对event queue进行enqueue或dequeue操作,只能通过event port放入事件和获取事件,然后eventdev内部实现从event port关联(或者说link)的event queue进行dequeue操作(即link确定了port可以取哪些queue的事件)。一般地,Queue与业务处理阶段有关;Port与处理核对应。

假设我们做基于UDP的一个网络回复程序:那么我们可以将业务处理阶段划分为如下几步:

  1. 读取以太网数据;
  2. 解析数据包;
  3. 根据请求内容,确定返回内容;
  4. 封装返回数据包;
  5. 发送以太网数据。

注1 : 当网络设备具备RTE_EVENT_ETH_RX_ADAPTER_CAP_INTERNAL_PORT能力,由网络硬件实现接受数据,CPU只需要通过rte_event_eth_rx_adapter_queue_add设置好接收到数据包需放入的工作队列ID;
注2 : 当网络设备具备RTE_EVENT_ETH_TX_ADAPTER_CAP_INTERNAL_PORT能力,由网络硬件实现发送数据的功能,CPU只需要设置struct rte_mbuf的dst_addr和hash.txadapter.txq两个字段即可

1.1 数据接收

当硬件不具备RTE_EVENT_ETH_RX_ADAPTER_CAP_INTERNAL_PORT能力的时候,由CPU调度:

  • 首先,通过rte_event_eth_rx_adapter_queue_add关联相应网卡号对应的接收队列收到数据后放入的工作队列ID;
  • 然后,通过rte_event_eth_rx_adapter_service_id_get获取rx_adapter_id的rxadptr_service_id;
  • 继续,通过rte_event_eth_rx_adapter_start启动rx_adapter_id;
  • 最后,通过rte_service_run_iter_on_app_lcore不断在数据接收核上调度rxadptr_service_id接收数据。

注:本质上,rx adapter内部会创建一个event port,接收到数据后会通过该port发送到第一步指定的业务队列

1.2 数据发送

当硬件不具备RTE_EVENT_ETH_TX_ADAPTER_CAP_INTERNAL_PORT能力的时候,由CPU调度:

  • 首先,通过rte_event_eth_tx_adapter_queue_add关联相应网卡号对应的发送队列;
  • 然后,通过rte_event_eth_tx_adapter_event_port_get获取tx_adapter_id的event port号tx_port_id;
  • 继续,通过rte_event_port_link将tx_port_id与业务创建的代表发送数据阶段的event queue关联起来;
  • 继续,通过rte_event_eth_tx_adapter_service_id_get获取tx_adapter_id的txadptr_service_id;
  • 继续,通过rte_event_eth_tx_adapter_start启动tx_adapter_id;
  • 最后,通过rte_service_run_iter_on_app_lcore不断在数据发送核上调度txadptr_service_id发送event queue中的数据。

注:本质上,tx adapter内部也会创建一个event port,通过该port去获取event queue中的数据

1.3 事件调度

DPDK Eventdev库从编程模型(或者说概念架构上)有event queue,但是底层实现中queue只是一个事件分发配置或者说策略(例如driver/event/sw中的实现)。它的事件是存储在event port对应的数据结构中,那么就需要由CPU或者硬件去实现将一个event port中的事件根据策略搬移到对应的event port中。

当硬件不具备RTE_EVENT_DEV_CAP_DISTRIBUTED_SCHED能力的时候,由CPU调度:

  • 首先,通过rte_event_dev_service_id_get获取event_dev_id的evdev_service_id;
  • 然后,通过rte_service_run_iter_on_app_lcore不断在调度核上调度evdev_service_id实现事件分发。

Eventdev库定义了三种事件调度的策略,即有序调度、原子调度和并行调度:

  • RTE_SCHED_TYPE_ORDERED
    有序调度允许同一个queue中、flow_id相同的事件,可以被多个port通过dequeue获取并处理;但在enqueue下个相同queue时,会维持之前的事件顺序;对该事件显示调用enqueue执行RTE_EVENT_OP_RELEASE可提前释放该事件在flow中的保序工作。
  • RTE_SCHED_TYPE_ATOMIC
    原子调度允许同一个queue中、flow_id相同的事件,只能被单个port通过dequeue获取;直到对该port再次调用dequeue,或对该事件显示调用enqueue执行RTE_EVENT_OP_RELEASE释放流上下文。
  • RTE_SCHED_TYPE_PARALLEL
    并行调度按照优先级、负载均衡策略去调度,由应用程序去保序。

注:RTE_SCHED_TYPE_ORDERED与RTE_SCHED_TYPE_ATOMIC的区别:前者是可以被多个port在多核上并行执行的,一般应用场景是先用ORDERED并行处理、同时保证有序,最后用ATOMIC串行处理需要有序依次处理的部分。

1.4 struct rte_event

rte_event结构体记录着eventdev框架中流通的事件信息。

WORD0 核心成员如下:

  • event_type :事件类型。
  • flow_id :与事件分派保序有关;
  • queue_id :指定enqueue到哪个队列;
  • sched_type :调度类型/策略;
  • op :事件enqueue操作类型,可以是RTE_EVENT_OP_NEW、RTE_EVENT_OP_FORWARD和RTE_EVENT_OP_RELEASE;

注:RTE_EVENT_OP_RELEASE除了会将当前事件从保序上下文中释放,还会释放mbuf/vec字段。

WORD1 核心成员如下:

  • mbuf :事件中附带的单个收发数据包;
  • vec :事件中附带的单个或多个收发数据包;

注:当调用rte_event_eth_rx_adapter_queue_add时,设置struct rte_event_eth_rx_adapter_queue_conf.rx_queue_flags参数带有RTE_EVENT_ETH_RX_ADAPTER_QUEUE_EVENT_VECTOR标志,那么WORD1就是vec(此时WORD0.event_type带有标记位RTE_EVENT_TYPE_VECTOR);否则就是mbuf。

2 核心API

  • rte_event_dev_count / rte_event_dev_info_get / rte_event_dev_configure
    rte_event_dev_count用于获取事件设备数量;rte_event_dev_info_get用于获取事件设备信息;rte_event_dev_configure用于配置配置事件设备。
  • rte_event_queue_setup / rte_event_port_setup / rte_event_port_link
    rte_event_queue_setup 用于配置事件队列;rte_event_port_setup用于配置事件端口;rte_event_port_link用于将端口关联到队列;
  • rte_event_dev_service_id_get / rte_event_eth_rx_adapter_service_id_get / rte_event_eth_tx_adapter_service_id_get
    获取事件设备 / 接收适配器 / 发送适配器的服务ID
  • rte_event_dev_start / rte_event_dev_stop / rte_event_dev_close
    启动/停止/关闭事件设备;
  • rte_event_eth_rx_adapter_create / rte_event_eth_tx_adapter_create
    创建接收/发送适配器;
  • rte_event_eth_rx_adapter_queue_add / rte_event_eth_tx_adapter_queue_add
    将网卡设备端口的接收/发送队列配置到接收/发送适配器;
  • rte_event_eth_rx_adapter_event_port_get / rte_event_eth_tx_adapter_event_port_get
    获取软件实现的发送/接收数据的事件端口号;前者一般在应用层不常用;
  • rte_event_eth_rx_adapter_start / rte_event_eth_rx_adapter_stop
    用于启动/停止接收数据适配器;
  • rte_event_eth_tx_adapter_start / rte_event_eth_tx_adapter_stop
    用于启动/停止发送数据适配器;
  • rte_service_component_register / rte_service_component_runstate_set
    rte_service_component_register用于注册事件服务;rte_service_component_runstate_set用于设置服务组件状态为运行或停止状态;一般事件设备和适配器内部实现隐式调用了这两个接口。
  • rte_service_runstate_set / rte_service_set_runstate_mapped_check
    rte_service_runstate_set将服务应用状态设置为启动或停止;rte_service_set_runstate_mapped_check启动服务状态检测功能;两者会在启动相关句柄之前调用。
  • rte_service_run_iter_on_app_lcore
    当硬件不支持的情况下,用于在特定核上运行服务,例如收发数据服务、调度服务;
  • rte_event_dequeue_burst / rte_event_enqueue_burst / rte_event_eth_tx_adapter_enqueue
    rte_event_dequeue_burst从特定事件端口获取事件;rte_event_enqueue_burst将事件通过特定端口发送出去;rte_event_eth_tx_adapter_enqueue将事件通过指定的端口发送到硬件实现的发送端口绑定的队列上去。

3 源码解析

eventdev_pipeline将事件相关操作封装在fdata->cap变量中,这个结构体主要函数指针字段如下:

  • check_opt :用于运行前对事件设备和适配器的相关能力检测;
  • evdev_setup : 用于创建事件设备、事件端口、事件队列以及将队列与端口绑定;
  • adptr_setup :用于网络设备端口的初始化、然后根据网络设备创建收发数据的事件适配器;
  • scheduler :当硬件没有相关能力的时候,由软件去调度相关的服务(例如事件调度、收发数据);
  • worker :用于获取事件并处理,完毕后投送到下各队列。

本实例程序提供两套fdata->cap的实现机制,一套我们称为generic实现、一套称为tx enq实现。

3.1 generic实现

只要有一个网卡设备不具备RTE_EVENT_ETH_TX_ADAPTER_CAP_INTERNAL_PORT能力,那么就需要采用generic实现,该实现发送数据由上述的软件方式实现

3.2 tx enq实现

当所有的网卡具备RTE_EVENT_ETH_TX_ADAPTER_CAP_INTERNAL_PORT能力,那么采用tx enq实现。该实现发送数据采用硬件实现。
tx enq的worker实现分为原子和非原子调度。原子调度是每个网络设备对应一个事件队列,各个网络设备不共享事件队列,每次递增struct rte_event.sub_event_type;非原子调度是每个网络设备对应cdata.num_stages + 1个事件队列,各个网络设备不共享事件队列,每次调度递增struct rte_event.queue_id。
当设备不具备接收数据能力的时候,它与generic实现一样采用软件实现,同时它与generic实现不同的是:tx enq实现是每个网络设备(或者说端口)创建一个对应的接收适配器并与之关联。
另外,tx enq实现中没有发现调用scheduler回调的地方。

### DPDK 中的 `rte_mbuf` 结构解析DPDK(Data Plane Development Kit)中,`rte_mbuf` 是一个核心数据结构,用于管理网络数据包的元信息和数据缓冲区。该结构的设计旨在提供高性能的数据包处理能力,同时确保内存使用的高效性和灵活性。 #### `rte_mbuf` 的基本组成 `rte_mbuf` 结构体包含多个字段,每个字段都有特定的功能。以下是其主要组成部分: 1. **缓冲区指针 (`buf_addr`)** 该字段指向实际存储数据的内存区域。通常情况下,`rte_mbuf` 会与 `rte_mempool` 配合使用,以实现高效的内存分配和回收机制[^1]。 2. **数据长度 (`data_len`)** 表示当前 `rte_mbuf` 所持有的数据长度。这个值可以用来判断数据包的实际大小,而不包括其他元数据或预留的空间。 3. **总长度 (`pkt_len`)** 表示整个数据包的总长度。如果数据包被分片,则 `pkt_len` 是所有分片数据长度的总和。 4. **段数 (`nb_segs`)** 如果数据包被分割成多个段(segments),`nb_segs` 字段记录了这些段的数量。这对于支持大容量数据包的处理非常有用。 5. **标志位 (`ol_flags`)** 这个字段用于存储各种标志位,表示数据包的状态或特性,例如是否进行了校验和计算、是否启用了 VLAN 标签等。 6. **端口信息 (`port`)** 记录数据包来自哪个物理或虚拟端口。这对于后续的数据包处理和转发决策非常重要。 7. **时间戳 (`timestamp`)** 可选字段,用于记录数据包的时间戳信息。这在某些应用场景中(如流量分析)非常有用。 8. **用户定义字段 (`userdata`)** 提供了一个通用的指针,允许应用程序在 `rte_mbuf` 中附加自定义的数据。 9. **链表指针 (`next`)** 当数据包被分割为多个段时,`next` 指针用于链接各个段,形成一个链表结构。 10. **引用计数 (`refcnt`)** 用于跟踪 `rte_mbuf` 的引用次数。当引用计数归零时,表示该 `rte_mbuf` 可以被释放回内存池。 #### 内存布局与性能优化 `rte_mbuf` 的设计充分考虑了内存对齐和缓存友好的特性。为了提高性能,`rte_mbuf` 通常会被分配在一个连续的内存区域中,并且其大小可以通过配置进行调整。这种设计使得 CPU 缓存能够更好地利用 `rte_mbuf` 的数据,从而减少缓存未命中带来的性能损失。 此外,`rte_mbuf` 支持多种类型的缓冲区管理方式,包括直接缓冲区和间接缓冲区。直接缓冲区适用于较小的数据包,而间接缓冲区则适用于需要多次复制或分片的大数据包。 #### 示例代码:创建和操作 `rte_mbuf` 以下是一个简单的示例代码,展示了如何在 DPDK 应用程序中创建和操作 `rte_mbuf`: ```c #include <rte_mbuf.h> #include <rte_ethdev.h> // 创建一个新的 rte_mbuf struct rte_mbuf *mbuf = rte_pktmbuf_alloc(mempool); if (mbuf == NULL) { // 处理内存分配失败的情况 return -ENOMEM; } // 初始化 mbuf 的数据长度和总长度 mbuf->data_len = 64; mbuf->pkt_len = 64; // 设置端口号 mbuf->port = port_id; // 发送数据包 int ret = rte_eth_tx_burst(port_id, queue_id, &mbuf, 1); if (ret < 0) { // 处理发送失败的情况 rte_pktmbuf_free(mbuf); } ``` 在这个示例中,首先通过 `rte_pktmbuf_alloc` 函数从内存池中分配一个 `rte_mbuf` 实例。接着,设置数据长度、总长度和端口号。最后,调用 `rte_eth_tx_burst` 函数将数据包发送出去。 #### 总结 `rte_mbuf` 是 DPDK 中用于管理数据包的核心结构之一。它的设计不仅提供了丰富的功能,还通过内存对齐和缓存友好的方式提高了数据包处理的性能。理解 `rte_mbuf` 的结构和使用方法对于开发高性能网络应用至关重要。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值