DPDK 进阶编程指南

零、前言

一些 DPDK 的基础内容(安装、配置、rte_flow等)可以参考前文:

一、哈希库rte_hash

DPDK 提供了一个标准的哈希表的实现,能用来根据键值快速进行索引。

a. API

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// 初始化
struct rte_hash_parameters flow_hash_table_parameter = {
.name = table_name, // 需保证唯一
.entries = MAX_HASH_ENTRIES,
.key_len = sizeof(union ipv4_5tuple_host),
.hash_func = ipv4_hash_crc, // 可以自行编写,可以使用自带的rte_hash_crc等
.hash_func_init_val = 0,
};
flow_hash_table = rte_hash_create(&flow_hash_table_parameter);

// 增加
// 对于重复的键值会直接进行覆盖,同时data的内存需要自行管理。
int rte_hash_add_key_data(const struct rte_hash *h, const void *key, void *data);

// 查找
// 对于存在的键值会返回一个非负的数,参数非法会返回-EINVAL,不存在则会返回-ENOENT。
int rte_hash_lookup_data(const struct rte_hash *h, const void *key, void **data);

// 删除
// 从哈希表中移除一个键值对,之后还需调用rte_hash_free_key_with_position清理键内存,以及使用rte_free释放值存储的内存。
int32_t rte_hash_del_key(const struct rte_hash *h, const void *key);

// 清理键内存
// 由于rte_hash会将键的内容复制一份进去,所以删除后需要对其存储该键的内存进行释放
int rte_hash_get_key_with_position(const struct rte_hash *h, const int32_t position,
void **key);

// 销毁
const void *key = 0;
void *data = 0;
uint32_t *next = 0;
uint32_t current = rte_hash_iterate(flow_hash_table, &key, &data, next);
for (; current != -ENOENT; current = rte_hash_iterate(flow_hash_table, &key, &data, next)) {
int32_t del_key = rte_hash_del_key(flow_hash_table, key);
rte_hash_free_key_with_position(flow_hash_table, del_key);
rte_free(data);
}
rte_hash_free(flow_hash_table);

b. 自定义哈希

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#if defined(RTE_ARCH_X86) || defined(__ARM_FEATURE_CRC32)
#define EM_HASH_CRC 1
#endif

#ifdef EM_HASH_CRC
#include <rte_hash_crc.h>
#define DEFAULT_HASH_FUNC rte_hash_crc
#else
#include <rte_jhash.h>
#define DEFAULT_HASH_FUNC rte_jhash
#endif

static inline uint32_t ipv4_hash_crc(const void *data, __rte_unused uint32_t data_len,
uint32_t init_val) {
const union ipv4_5tuple_host *k;
uint32_t t;
const uint32_t *p;

k = data;
t = k->proto;
p = (const uint32_t *) &k->port_src;

#ifdef EM_HASH_CRC
init_val = rte_hash_crc_4byte(t, init_val);
init_val = rte_hash_crc_4byte(k->ip_src, init_val);
init_val = rte_hash_crc_4byte(k->ip_dst, init_val);
init_val = rte_hash_crc_4byte(*p, init_val);
#else
init_val = rte_jhash_1word(t, init_val);
init_val = rte_jhash_1word(k->ip_src, init_val);
init_val = rte_jhash_1word(k->ip_dst, init_val);
init_val = rte_jhash_1word(*p, init_val);
#endif

return init_val;
}

c. 快速获取五元组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
union ipv4_5tuple_host key = {.xmm=0};
mask0 = (rte_xmm_t) {.u32 = {BIT_8_TO_15, ALL_32_BITS,
ALL_32_BITS, ALL_32_BITS}};

// x86-64 且支持 SSE
static __rte_always_inline void
get_ipv4_5tuple(struct rte_mbuf *m0, __m128i mask0, union ipv4_5tuple_host *key) {
__m128i tmpdata0 = _mm_loadu_si128(
rte_pktmbuf_mtod_offset(m0, __m128i *,
sizeof(struct rte_ether_hdr) +
offsetof(struct rte_ipv4_hdr, time_to_live)));

key->xmm = _mm_and_si128(tmpdata0, mask0);
}

// ARM
static inline void
get_ipv4_5tuple(struct rte_mbuf *m0, int32x4_t mask0,
union ipv4_5tuple_host *key)
{
int32x4_t tmpdata0 = vld1q_s32(rte_pktmbuf_mtod_offset(m0, int32_t *,
sizeof(struct rte_ether_hdr) +
offsetof(struct rte_ipv4_hdr, time_to_live)));

key->xmm = vandq_s32(tmpdata0, mask0);
}

二、高性能获取时间

可以通过获取处理器寄存器中的周期数来高性能的获取精准时间,但要保证处理器运行在一个稳定的频率上,这样时间才是准确的。

1
2
3
4
5
6
7
// 获取处理器自从开机以来的周期数
uint64_t rte_rdtsc()

// 获取处理器一秒的周期数
rte_get_tsc_hz()

time = rte_rdtsc() / rte_get_tsc_hz()

三、能耗管理库rte_power

由于获取时间需要稳定处理器频率,所以可以利用 DPDK 提供的 API 对处理器进行配置,稳定的频率也有利于保证业务的稳定,能去除尖刺。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// DPDK对处理器能耗进行接管
rte_power_init(lcore_id);

// 获取处理器可以运行的频率
uint32_t freqs[30];
rte_power_freqs(lcore_id, freqs, 30);
for (int i = 0; i < 30; ++i) {
printf("\t%u", freqs[i]);
}

// 设置处理器运行在哪个频率上
ret = rte_power_set_freq(lcore_id, 2);

// 停止接管
rte_power_exit(lcore_id);

四、搭配CMake使用

1
2
3
4
5
6
7
8
9
10
11
# import dpdk library
find_package(PkgConfig REQUIRED)
pkg_search_module(LIBDPDK REQUIRED libdpdk)

# import thread
find_package(Threads REQUIRED)

link_directories(${LIBDPDK_LIBRARY_DIRS})
include_directories(${LIBDPDK_INCLUDE_DIRS})

target_link_libraries(smart_offload ${LIBDPDK_LIBRARIES} Threads::Threads)