参考文献:
《深入浅出DPDK》
DPDK官网
https://software.intel.com/en-us/articles/introduction-to-the-data-plane-development-kit-dpdk-packet-framework
...........................................................................................................
这一部分属于DPDK核心部分了, 对于一个报文的整个生命周期,如何从对接运营商的外部接口进入路由器,再连接计算机的网卡,发送的过程,或许这就是我们所疑惑的,也是此次学习的重点部分,只有弄清楚每个步骤每个环节,或许才能彻底弄懂网络报文处理,然后解决其中出现的问题
一. DPDK网络处理模块划分
网络报文的处理转发主要分为硬件部分和软件处理部分,由一下几个模块构成:
- packet input
- pre-processing:对报文进行粗粒度处理
- input classification:对报文进行细粒度分流
- ingress queuing:提供基于描述符的队列FIFO
- delivery/scheduling:根据队列优先级和CPU状态进行调度
- accelerator:提供加解密和压缩/解压缩等硬件功能
- egress queuing:在出口根据QoS等级进行调度
- post processing:后期报文处理释放缓存
- packet output:从硬件发送出去
图1
如图一所示,浅色和阴影部分都是对应模块和硬件相关的,所以提升这部分性能最佳的选择是尽量多的选择网卡上的或设备芯片提供的网络特定功能相关的卸载的特性,深色软件部分可以通过提高算法的效率和结合CPU相关的并行指令来提升网络性能,了解网络处理模块的基本组成部分后,我们再来看看不同的转发框架如何让这些模块协同工作完成网络包的处理的
二. 转发框架介绍
传统的Network Process (专用网络处理器)转发的模型可以分为run to comletion(运行至终结, RTC)模型和pipeline (流水线)模型
2.1 流水线模型(pipeline)
pipeline 借鉴与工业上的流水线模型,将整个功能拆分成多个独立的阶段,不同阶段通过队列传递产品。这样对于一些CPU密集和I/O密集的应用,将I/O密集的操作放在另一个微处理器引擎上执行。通过过滤器可以分为不同的操作分配不同的线程,通过队列匹配两个速度,达到最好的并发,
我们可以看到图中,TOP(Task Optimized Processor)单元,每个TOP单元都是对特定的事物进行优化处理的特殊微单元
2.2 run to completion 模型
这个模型是DPDK针对一般的程序的运行方法,一个程序分为几个不同的逻辑功能,几个逻辑功能会在一个CPU的核上运行,我们下面看下模型的视图
这个模型没有对报文特殊处理的的运算单元,只有两个NP核, 两个NP核利用已烧录的微码进行报文处理
2.3 转发模型对比
从run to completion的模型中,我们可以清楚地看出,每个IA的物理核都负责处理整个报文的生命周期从RX到TX,这点非常类似前面所提到的AMCC的nP核的作用。在pipeline模型中可以看出,报文的处理被划分成不同的逻辑功能单元A、B、C,一个报文需分别经历A、B、C三个阶段,这三个阶段的功能单元可以不止一个并且可以分布在不同的物理核上,不同的功能单元可以分布在相同的核上(也可以分布在不同的核上),从这一点可以看出,其对于模块的分类和调用比EZchip的硬件方案更加灵活。
两个的优缺点:
三. 转发算法
除了良好的转发框架之外,转发中很重要的一部分内容就是对于报文字段的匹配和识别,在DPDK
中主要用到了精确匹配(Exact Match
)算法和最长前缀匹配(Longest Prefix Matching
,LPM
)算法来进行报文的匹配从而获得相应的信息。精确匹配主要需要解决两个问题:进行数据的签名(哈希),解决哈希的冲突问题,DPDK
中主要支持CRC32
和J hash
。
最长前缀匹配(Longest Prefix Matching
,LPM
)算法是指在IP协议中被路由器用于在路由表中进行选择的一个算法。当前DPDK使用的LPM
算法就利用内存的消耗来换取LPM
查找的性能提升。当查找表条目的前缀长度小于24位时,只需要一次访存就能找到下一条,根据概率统计,这是占较大概率的,当前缀大于24位时,则需要两次访存,但是这种情况是小概率事件。
ACL
库利用N元组的匹配规则去进行类型匹配,提供以下基本操作:
Packet distributor
(报文分发)是DPDK提供给用户的一个用于包分发的API库,用于进行包分发。主要功能可以用下图进行描述:
四. 示例(参考英特尔网站:https://software.intel.com/en-us/articles/introduction-to-the-data-plane-development-kit-dpdk-packet-framework)
ip_pipline应用通过提供几个示例应用和配置文件,展示了流水线模块的使用方法
下图展示了三层转发配置应用
在这一示例中,它设置了简单的路由。
“core” 条目线程ID(socket ID,物理CPU ID和超线程ID)确定了用来运行流水线的CPU核。
“pktq_in” 和 “pktq_out”参数定义了数据包传输的接口,这里指接受和传送数据包。
可以将“encap”参数设置为“ethernet”, “qinq”或“mpls”,用来为所有出站包封装合适的包头。
最后一个参数”ip_hdr_offset”可用于设置DPDK数据包结构(mbuf)中ip-header的起始位置所需偏移字节
下图是ip_pipeline的脚本文件,该文件中的内容将作为路由表项加入到路由流水线的最长匹配表中:
p <pipeline_id> route add <ip_addr> <depth> port <port_id> ether <next hop mac_addr>
可使用以下命令来运行L3转发应用:
$./build/ip_pipeline -f l3fwd.cfg -p 0xf -s l3fwd.sh
有时,应用程序在不同的功能模块之间进行互联时具有复杂的拓扑结构, 一旦应用程序配置文件准备就绪,请运行以下命令生成拓扑图
$./diagram-generator.py -f <configuration file>
ip_pipeline示例程序生成的拓扑图:
各种使用此标准流水线模型构建的网络功能(流水线)都可作为DPDK ip_pipeline示例应用程序的一部分.
DPDK支持pipeline 的有以下几种:
1)Packet I/O
2)Flow classification
3)Firewall
4)Routing
5)Metering
6)Traffic Mgmt
下面以官l2转发实例来看下整体代码如何运行的:
源码下载:http://core.dpdk.org/download/
代码路径:dpdk/examples/l2fwd
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <string.h> 4 #include <stdint.h> 5 #include <inttypes.h> 6 #include <sys/types.h> 7 #include <sys/queue.h> 8 #include <netinet/in.h> 9 #include <setjmp.h> 10 #include <stdarg.h> 11 #include <ctype.h> 12 #include <errno.h> 13 #include <getopt.h> 14 #include <signal.h> 15 #include <stdbool.h> 16 17 #include <rte_common.h> 18 #include <rte_log.h> 19 #include <rte_malloc.h> 20 #include <rte_memory.h> 21 #include <rte_memcpy.h> 22 #include <rte_memzone.h> 23 #include <rte_eal.h> 24 #include <rte_per_lcore.h> 25 #include <rte_launch.h> 26 #include <rte_atomic.h> 27 #include <rte_cycles.h> 28 #include <rte_prefetch.h> 29 #include <rte_lcore.h> 30 #include <rte_per_lcore.h> 31 #include <rte_branch_prediction.h> 32 #include <rte_interrupts.h> 33 #include <rte_pci.h> 34 #include <rte_random.h> 35 #include <rte_debug.h> 36 #include <rte_ether.h> 37 #include <rte_ethdev.h> 38 #include <rte_mempool.h> 39 #include <rte_mbuf.h> 40 41 static volatile bool force_quit; 42 43 /* MAC updating enabled by default */ 44 static int mac_updating = 1; 45 46 #define RTE_LOGTYPE_L2FWD RTE_LOGTYPE_USER1 47 48 #define NB_MBUF 8192 49 50 #define MAX_PKT_BURST 32 51 #define BURST_TX_DRAIN_US 100 /* TX drain every ~100us */ 52 #define MEMPOOL_CACHE_SIZE 256 53 54 /* 55 * Configurable number of RX/TX ring descriptors 56 */ 57 #define RTE_TEST_RX_DESC_DEFAULT 128 58 #define RTE_TEST_TX_DESC_DEFAULT 512 59 static uint16_t nb_rxd = RTE_TEST_RX_DESC_DEFAULT; 60 static uint16_t nb_txd = RTE_TEST_TX_DESC_DEFAULT; 61 62 /* ethernet addresses of ports */ 63 //端口的以太网地址 64 static struct ether_addr l2fwd_ports_eth_addr[RTE_MAX_ETHPORTS]; 65 66 /* mask of enabled ports */ 67 //启用端口的mask 68 static uint32_t l2fwd_enabled_port_mask = 0; 69 70 /* list of enabled ports */ 71 //启用端口列表 72 static uint32_t l2fwd_dst_ports[RTE_MAX_ETHPORTS]; 73 74 static unsigned int l2fwd_rx_queue_per_lcore = 1; 75 76 #define MAX_RX_QUEUE_PER_LCORE 16 77 #define MAX_TX_QUEUE_PER_PORT 16 78 struct lcore_queue_conf { 79 unsigned n_rx_port; 80 unsigned rx_port_list[MAX_RX_QUEUE_PER_LCORE]; 81 } __rte_cache_aligned; 82 struct lcore_queue_conf lcore_queue_conf[RTE_MAX_LCORE]; 83 84 static struct rte_eth_dev_tx_buffer *tx_buffer[RTE_MAX_ETHPORTS]; 85 86 static const struct rte_eth_conf port_conf = { 87 .rxmode = { 88 .split_hdr_size = 0, 89 .header_split = 0, /**< Header Split disabled */ //报头分离 90 .hw_ip_checksum = 0, /**< IP checksum offload disabled */ //IP校验和卸载 91 .hw_vlan_filter = 0, /**< VLAN filtering disabled */ //vlan过滤 92 .jumbo_frame = 0, /**< Jumbo Frame Support disabled */ //巨星帧的支持 93 .hw_strip_crc = 0, /**< CRC stripped by hardware */ //使用硬件清除CRC 94 }, 95 .txmode = { 96 .mq_mode = ETH_MQ_TX_NONE, 97 }, 98 }; 99 100 struct rte_mempool * l2fwd_pktmbuf_pool = NULL; 101 102 /* Per-port statistics struct */ 103 struct l2fwd_port_statistics { 104 uint64_t tx; 105 uint64_t rx; 106 uint64_t dropped; 107 } __rte_cache_aligned; 108 struct l2fwd_port_statistics port_statistics[RTE_MAX_ETHPORTS]; 109 110 #define MAX_TIMER_PERIOD 86400 /* 1 day max */ 111 /* A tsc-based timer responsible for triggering statistics printout */ 112 static uint64_t timer_period = 10; /* default period is 10 seconds */ 113 114 /* Print out statistics on packets dropped */ 115 //打印的属性跳过。。 116 static void 117 print_stats(void) 118 { 119 uint64_t total_packets_dropped, total_packets_tx, total_packets_rx; 120 unsigned portid; 121 122 total_packets_dropped = 0; 123 total_packets_tx = 0; 124 total_packets_rx = 0; 125 126 const char clr[] = { 27, '[', '2', 'J', '