零、前言
一些 DPDK 的基础内容(安装、配置、rte_flow等)可以参考前文:
一、哈希库rte_hash
DPDK 提供了一个标准的哈希表的实现,能用来根据键值快速进行索引。
a. API
// 初始化 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. 自定义哈希
#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. 快速获取五元组
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); }
二、高性能获取时间
可以通过获取处理器寄存器中的周期数来高性能的获取精准时间,但要保证处理器运行在一个稳定的频率上,这样时间才是准确的。
// 获取处理器自从开机以来的周期数 uint64_t rte_rdtsc() // 获取处理器一秒的周期数 rte_get_tsc_hz() time = rte_rdtsc() / rte_get_tsc_hz()
三、能耗管理库rte_power
由于获取时间需要稳定处理器频率,所以可以利用 DPDK 提供的 API 对处理器进行配置,稳定的频率也有利于保证业务的稳定,能去除尖刺。
// 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
使用
# 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)