[OpenDataPlane 译] 3~7

3. 文档组织

本文档分成以下几个部分。

  • 第一部分介绍了ODP应用程序,ODP API组件区域及其相关抽象数据类型的高级概述。这一部分在概念层面介绍ODP API。

  • 第二部分提供了ODP支持的编程模型的教程,特别注意事件模型,因为它代表了大多数ODP应用程序的首选结构。这一部分基于第一部分介绍的概念,并展示了ODP应用程序的结构,以便最好地实现前面提到的三个ODP设计目标。

  • 第三部分提供了主要ODP API组件的更详细的概述,旨在作为每个API的完整参考规范的伴侣。 后者旨在由ODP应用程序员以及实现者使用,以了解每个API的精确语法和语义。

4. ODP 应用程序和报文流

数据面应用程序从根本上说涉及接收、检查、操作和传输数据包。 数据面的显著特点是这些应用程序主要关注ISO堆栈(L2和L3)的最底层,并且他们具有非常高的性能要求。 ODP旨在为这些应用程序提供便携式框架。

从最上层看,ODP应用程序是使用一个或多个ODP API的程序。由于ODP是一个编程框架,而不是编程环境, 所以,应用程序可以自由地使用其他非ODP API。

ODP应用程序的作用和运行方式各不相同,但总体来说具有如下特点:

  • 它们被组织成一个或多个并行执行的线程。
  • 这些线程使用各种同步机制来传达和协调其活动。
  • 它们从一个或多个数据包I/O接口接收数据包。
  • 他们检查,转换或以其他方式处理数据包。
  • 它们将数据包传输到一个或多个数据包I/O接口。

ODP应用程序的顶层视图如下所示:

[OpenDataPlane 译] 3~7_第1张图片
ODP Application Packet Flow Overview

数据包到达并从由PktIO抽象表示的网络接口接收(RX)。然后,它们直接转到由ODP线程轮询的队列,或者可以通过分类器Classification和排序,进入到代表各个流的队列。 接下来就可以通过调度器Scheduler将这些队列分派到应用程序线程。

线程,术语描述为可以调用各种ODP API来处理数据包内容。 对于输出处理,报文可以通过直接排队到PKTIO输出队列,或者可以在TX之前将其交给TM进行QoS处理。 注意,输出接口可以在loopback模式下工作,在这种情况下,发送给他们的数据包被重新回环到输入端进行“第二次”处理。例如,传入的IPSec报文在解密之前无法正确分类,因此必须回环回来进行第二次处理。一旦解密,其实际内容可见,则可以将其分配到对应flow上。

需要注意的是,上述图表中唯一需要操作的是黄色部分。 这里显示的其他内容都由ODP提供,可供任何ODP应用程序使用。 这代表了数据面应用程序的“机械”,并且被构造为允许写入ODP API的应用程序可以在提供ODP实现的每个平台上进行移植和优化,而无需额外的工作。

5. ODP API 组件

ODP程序围绕几个概念进行构建,每个开发者都应该熟悉这些概念。 主要的概念主要的是:线程、事件、队列、池、共享内存、缓冲区、数据包、PktIO、定时器和同步器。

5.1. 线程(Thread)

线程是ODP中的基本编程单元。ODP应用程序被组织成执行设计工作的线程集合。 ODP线程可能或者可能不会与其他线程共享内存,这取决于具体实现。线程有两种类型:控制线程和工作线程,他们由抽象类型 odp_thread_type_t 表示。

控制线程是组织工作线程工作的监督线程。 而工作线程则负责执行应用程序的主要逻辑,它采用的是RTC模型。特别的是,工作线程一般运行于专用的处理核心上,特别是在多核心的处理环境中。但是,如果需要,给定的实现可以在单核上运行(通常在较小和较低性能目标环境上)。

除了线程类型,线程还具有关联属性,例如,线程掩码和调度程序组,确定他们可以在哪里运行,以及他们可以处理的工作类型。

5.2. 事件(Event)

事件是线程执行工作的过程。事件可以表示新的工作,如需要处理的数据包到达,或者他们可以表示异步执行的请求完成。事件还可以表示通知时间,或应用程序感兴趣的各个组件状态的更改。 事件有一个描述代表它的事件类型。线程可以创建新事件,或消耗由他们处理的事件,或者可以对事件执行进一步处理,然后将事件传递给另一个组件以进行其他处理。对事件的引用是通过抽象类 odp_event_t 句柄实现的。 提供了专用函数将他们转换成由事件表示的适当类型的特定句柄。

5.3. 队列(Queue)

队列是保存事件的消息传递通道。事件可以通过入队操作添加到队列中,或者通过出队操作从队列中删除。队列的端点将根据使用方式而有所不同。队列有两种主要类型:轮询和调度,这将在引入事件模型时更详细的讨论。队列也可能具有关联的上下文,这表示所有使用它的事件的持久状态。这些状态是允许线程对事件进行有状态处理以及无状态处理。

队列由抽象类型 odp_event_t 表示。

5.4. 池(Pool)

池是元素存储的共享内存区域。Pool代表了事件及其他东西的后备存储。 池通常在应用程序初始化和终止期间内创建和销毁,在程序处理过程中被调用。池可以由专门的ODP组件或专门的应用程序使用,或者两者共享使用。池具有描述他们包含的元素的关联类型。两个最重要的池类型是缓冲区(buffer)和数据包(packet)。

池由抽象类型 odp_pool_t 表示。

5.5. 共享内存(Shared Memory)

共享内存表示在线程之间共享的原始存储块。 他们是池的构建块,但是如果需要,可以直接由ODP应用程序使用。

共享内存由抽象数据 odp_shm_t 表示。

5.6. 缓冲区(Buffer)
Buffer是ODP组件和应用程序用于实现其功能的固定大小的共享存储块。Buffer包含0个或多个字节的应用程序数据以及提供有关Buffer信息的系统维护Metadata(例如buffer大小,从哪个pool分配)。 Metadata是一个重要的ODP概念,因为他允许任意数量的辅助信息与ODP对象相关联。大多数ODP对象都有相关联的元数据,并且该元数据通过访问器函数进行操作,该函数用作此数据信息的getter和setter。 Getter操作允许应用程序读取Metadata,而Setter操作允许应用程序写入Metadata。请注意,一些Meatdata本质上是只读的,因此没有提供Setter操作。 当对象具有多个Metadata时,每个Metadata都有自己关联的Getter和Setter访问操作。

Buffer由抽象数据类型 odp_buffer_t 表示。

5.7. 数据包(Packet)

Packet是指通过IO接口接收和发送,并表示数据面应用程序操作的基本数据。 Packet来自 ODP_POOL_PACKET 类型的Pool。 与简单的Buffer不同,ODPPacket具有丰富的语义,允许以复杂的方式进行检查和操作,这些将在后面描述。 Packet还支持丰富的Metadata以及User Metadata。 User Metadaa允许应用程序将确定的副信息量与每个Packet相关联以供自己使用。

Packet由抽象类型 odp_packet_t 表示。

5.8. 报文接口(PktIO)

PktIO是ODP表示IO接口的方式。 PktIO对象是能够接收(RX)和发送(TX)报文的逻辑端口。这可以由基础平台作为集成功能直接支持,或者可以表示通过PCIE或其他总线连接的设备。

PktIO由抽象类型 odp_pktio_t 表示。

5.9. 时间(Time)

时间API用于测量应用程序的时间间隔和跟踪事件流程,并提供了访问时间源的便利方式。 事件API由两个主要部分组成:Local time API和Global time API。

5.9.1. 局部时间(Local time)

Local time API被设计为在一个线程内使用,并且可以比Global time API更快。 由于时间一致性不能保证,Local time API不能在线程间使用。本地时间戳是调用线程本地的,不能与其他线程共享。 当前本地时间可以通过接口 odp_time_local() 获取。

5.9.2. 全局时间(Global time)

Global time API被设计为用于跟踪线程之间的时间。 所以,全局时间戳可以在线程之间共享。 当前全局时间可以通过接口 odp_time_global() 获取。

时间API包括随时间运行的功能,例如 odp_time_diff() odp_time_sum() 和 odp_time_cmp() 。 时间转换函数 odp_time_to_ns() , odp_time_local_from_ns() , odp_time_global_from_ns() 。 要获取时间源使用接口 odp_time_local_res() , odp_time_global_res() 。 要等待,使用接口 odp_time_wait_ns() 和 odp_time_wait_until() 在线程繁忙期间循环等待 。

odp_time_t 类型表示本地或全局时间戳。

5.10. 定时器(Timer)

Timer指应用程序如何衡量和相应时间的流逝。 Timer从具有自己的抽象类型 odp_timer_pool_t 的专用池(定时器池)中申请出来。应用程序可能同时具有许多定时器活动,并可将其设置为使用相对或绝对时间。 当定时器到期时,他们会创建类型为 odp_timeout_t 的事件,这些事件作为定时器到期的通知。

5.11. 同步器(Synchronizer)

并行运行的多个线程通常需要各种同步服务,使得他们以可靠和协调的方式运行。ODP提供了一组丰富的locks、barries,和类似的同步原语,以及用于表示各种类型的原子变量的抽象类型。ODP事件模型还使用队列来避免在许多情况下显式加锁的需要。这些内容将在下一节讨论。

6. ODP 组件

基于ODP概念,ODP提供了与ODP应用程序的工作流程相关的多个组件。 这些组件包括分类器,调度程序和流量管理器等。这些组件与数据包处理的三个主要阶段有关:接收,处理和发送。

6.1. 分类器(Classifier)

分类器提供了一套控制数据包接收(RX)处理的API。

[OpenDataPlane 译] 3~7_第2张图片
ODP Receive Processing with Classifier

Classfier提供两个逻辑相关的服务:

  • Packet parsing : 检查并从接收的数据包中提取结构信息
  • Packet classification : 将模式匹配规则PMR应用于解析结果,以将传入的数据包分配给服务等级CoS

综合起来,这些允许进入的数据包被分类成流,这些流是具有共同处理要求的逻辑上相关的数据包系列。虽然许多数据面应用程序执行无状态报文处理(如简单转发),但是也有很多数据面程序执行有状态报文处理。 流的状态信息与这些报文组的状态相关。

CoS确定属于某条流的数据包的两个属性:

  • 收到报文时存储的池
  • 报文将被添加进去的处理队列

ODP支持的PRMs允许基于报文字段值组合(tuples)的流分类。分类的主要优点是在许多平台上,这些功能是以硬件方式执行的, 这意味着当数据包正在被接收,而ODP应用程序没有任何明确处理的情况下,分类是以线速发生的。

Note: Classfier的使用是可选的。如果选择使用,应用程序可以通过直接轮询的方式直接从对应PktIO输入队列接收数据包。

6.2. 调度器(Scheduler)

Scheduler提供了一套控制可扩展事件处理的API。

[OpenDataPlane 译] 3~7_第3张图片
ODP Scheduler and Event Processing

Scheduler负责选择和调度一个或多个事件到请求的线程。 事件选择基于几个因素,涉及包含可调度事件的队列和进行 odp_schedule() 或 odp_schedule_multi() 调用的线程。

ODP队列具有调度优先级(scheduling priority),可以确定应相对于其他队列上的事件,当前队列上的紧急事件如何处理。队列还具有与他们相关联的调度程序组的ID,他们必须与调用程序的线程的关联调度程序组线程掩码想匹配。 这允许事件被分类处理,并且具有专门用于处理某一类事件的线程。线程可以动态地加入或离开调度程序组,从而允许应用程序的相应更快地满足需求。

当线程从shceduler接收到事件时,它又可以通过异步操作的ODP API(如加密处理)来调用其他处理引擎。当这样的处理完成时,完成的事件被添加回可调度队列中,这个事件可以被再次调度回线程,以使用异步操作的结果继续处理。

线程本身可以将事件排入队列,以供其他线程进行下游处理,从而允许应用程序自身结构最大化并发处理。

6.3. 流量管理(Traffic Manager)

Traffic Manager提供了一整套API用于控制数据包输出的流量整形和服务质量QoS处理。

[OpenDataPlane 译] 3~7_第4张图片
ODP Transmit processing with Traffic Manager

报文处理的最后阶段是发包。此时,应用程序有几个选择。 与RX处理一样,应用程序可以将数据包直接发送到PktIO TX队列进行直接传输。但是,通常情况下,应用陈旭需要对包括作为发送处理的一部分流的分组执行流量整形和相关服务质量QoS处理。为了满足这一需求,ODP提供了一套流量管理API,允许编码建立仲裁器,整形器等,以控制输出数据包处理实现所需要的QoS目标。而且,这样做的优点是在许多平台上,流量管理功能都是硬件实现的,允许透明的卸载这个功能。

7. ODP 应用程序编码结构

7.1. 头文件结构

应用程序编程时仅需要包含 include/odp_api.h 文件,这个文件包含了 platform//include/odp/api 文件,以在该平台上提供API的完整定义。 定义API行为的doxygen文档都包含在公共APi文件中,接口实现在每个平台目录中。 如果 #define 不直接对用户可见的话,通常可以适当的访问函数来替代。

Users include structure

./
├── include/
│   ├── odp/
│   │   └── api/
│   │       └── spec/
│   │           └── The Public API and the documentation.
│   │
│   │
│   ├── odp_api.h   This file should be the only file included by the
│   │               application.

7.2. Initialization

ODP应用程序需要优雅退出,所有程序必须在关闭ingress,情况所有队列等情况下才能执行终结函数退出程序。

7.3. Startup

ODP应用程序必须调用的第一个API是 odp_init_global() 。这需要两个指针。第一个是 odp_init_t ,包含平台独立且可移植的ODP初始化数据,第二个是 odp_platform_init_t ,用于平台特定的数据。

调用 odp_init_global() 建立ODP API框架,这个操作需要在调用其他API之前执行。 每个应用程序只调用一次。全局初始化完成之后,每个线程依次调用odp_init_local() 。这为该线程建立本地ODP线程上下文,这个操作必须在该线程调用其他ODP API前执行。线程类型是 ODP_THREAD_WORKER 或 ODP_THREAD_CONTROL 。

7.4. Shutdown

关闭操作是初始化过程的反响逻辑,在调用 odp_term_global() 之前,每个线程调用 odp_term_local 来终止ODP。

7.5. 应用程序初始化和终止时序

ODP应用程序遵循以下一般结构流程:

[OpenDataPlane 译] 3~7_第5张图片
ODP Application Structure Flow Diagram

你可能感兴趣的:([OpenDataPlane 译] 3~7)