news 2026/4/16 17:54:12

教你如何玩转DPDK开发中的KNI与内核交互,让网络速度翻倍!

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
教你如何玩转DPDK开发中的KNI与内核交互,让网络速度翻倍!

DPDK,一个开源的、数据平面处理的高性能数据包处理库。核心思想是绕过操作系统内核,在用户态直接操作网络接口卡(NIC),实现对硬件的直接访问和控制。这种“用户态驱动”的模式,减少数据包在用户态和内核态之间切换的开销,避免不必要的内存拷贝,提升网络数据包的处理速度和吞吐量。

但是,DPDK带来的高性能也伴随着一个问题:DPDK接管了网卡,完全在用户态处理数据包,跟传统的操作系统内核网络栈之间形成了一道“鸿沟”。DPDK应用程序无法直接用内核提供的丰富网络服务,也无法和依赖内核网络栈的传统应用程序进行数据交换。为了解决这一用户态与内核态协同工作的问题,DPDK推出一个关键组件——Kernel Network Interface (KNI)。

KNI 弥合DPDK用户态数据平面和Linux内核网络栈之间的鸿沟。让DPDK应用程序在用户态和内核态之间交换数据包,实现用户态高性能网络应用与内核网络栈的互操作。通过KNI,DPDK应用程序可以有选择性的把部分数据包转发给内核处理,同时保留对其他数据包的高速处理能力。

比如,DPDK应用程序完全接管网卡后,所有进出网卡的数据包都会首先被DPDK处理。但真实的情况是只关注特定类型的数据包,而其他协议则希望由操作系统内核的通用协议栈来处理。在这种场景下,DPDK KNI模块就是一个智能过滤器:把不感兴趣的数据包发送到内核,让内核去处理这些通用网络流量;同时,DPDK自身则专注于对特定协议进行超高速处理。这种分工合作的模式,既利用了DPDK的极致性能,又保留了内核网络栈的灵活性和功能完整性。

注意:DPDK读取网卡数据包的机制是基于共享内存的。网卡通过DMA(直接内存访问)技术把数据包直接映射到主机内存中的特定区域,DPDK应用程序直接从这个共享内存区域读取数据,避免CPU参与数据拷贝的过程。同样,DPDK向内核发送数据以及从内核接收响应数据时,也是通过KNI模块在共享内存中进行操作。因此,rte_eth_rx_burst()rte_eth_tx_burst()这类函数执行的是纯粹的内存操作,而不是I/O操作。不涉及阻塞的概念:有数据时就返回数据,没有数据时也立即返回,保证了DPDK数据处理的非阻塞性和高效率。

二、搭建DPDK KNI开发环境

在进行DPDK KNI应用程序开发之前,要配置DPDK运行环境。

  1. 准备DPDK源码并进入目录:

    cd /path/to/your/dpdk/dpdk-stable-19.08.2/ # 如 dpdk/dpdk-stable-19.08.2/

  2. 切换到root权限(或使用sudo):DPDK的某些配置和驱动加载操作需要root权限。

    sudo su

  3. 导出DPDK环境变量:设置RTE_SDK指向DPDK的根目录,RTE_TARGET指定编译目标(x86_64-native-linux-gcc表示64位Linux系统上的GCC编译环境)。

    export RTE_SDK=/path/to/your/dpdk/dpdk-stable-19.08.2/ export RTE_TARGET=x86_64-native-linux-gcc

  4. 运行DPDK配置脚本:dpdk-setup.sh(或较新版本中的dpdk-setup.py),自动化配置和加载必要的模块。

    ./usertools/dpdk-setup.sh

    按照脚本提示,依次执行:

    • 43) Load DPDK UIO KERNEL MODULE: 加载用户态I/O(UIO)模块。UIO是DPDK早期版本常用的用户态驱动框架,让用户空间应用程序直接访问硬件设备。

    • 44) Load VFIO KERNEL MODULE: 加载虚拟功能I/O(VFIO)模块。VFIO是Linux内核提供的一种更安全、更现代的设备直通(PCI Passthrough)机制,推荐在支持的系统上使用,提供更好的隔离性和安全性。

    • 45) Load KNI KERNEL MODULE: 加载KNI内核模块。这是启用DPDK KNI功能的核心步骤,会在内核中创建虚拟网卡接口,用来DPDK与内核的数据交换。

    • 46) Setup 1GB Hugepages: 配置1GB大页内存。

    • 47) Setup 2MB Hugepages: 配置2MB大页内存。DPDK强烈推荐使用大页内存(Hugepages)。大页内存可以减少TLB(Translation Lookaside Buffer)未命中,减少页表查找的开销,提高内存访问效率,并避免频繁的页交换,提升数据包处理性能。可以配置一定数量的2MB和1GB大页。

    • 49) Bind NIC to DPDK driver: 物理网卡绑定到DPDK的用户态驱动。在执行此步骤之前,一定要先关闭(down)要绑定的网卡接口(sudo ifconfig eth0 down),确保其处于可用状态。提供网卡的PCI地址,通过lspci -nn命令查看。绑定成功后,该网卡不再被内核网络栈管理,而是完全由DPDK接管。

    • 60) Exit: 退出配置脚本。

三、代码实现

理解了DPDK和KNI的基本概念,搭建好开发环境后,就可以着手编写代码了。主要目标是演示如何用KNI实现DPDK用户态应用与内核网络栈之间的数据交互:从网卡接收数据包,判断是否为UDP包;如果是,则由DPDK应用程序自行处理(并可选地发送回复);如果不是,则通过KNI转发给内核处理。

实现步骤:

  1. KNI 初始化与配置:在DPDK应用程序启动时,要初始化KNI子系统,创建和配置KNI设备。包括分配必要的内存资源、设置KNI设备的名称、MAC地址、MTU等参数,注册回调函数。这是DPDK应用程序与内核建立互操作通道的第一步。使用rte_kni_initrte_kni_alloc函数。

  2. 数据交换机制:KNI设备创建成功,DPDK应用程序就可以通过KNI设备与内核进行数据包交换。

    • DPDK到内核: DPDK从物理网卡接收到数据包,并决定将其转发给内核时,调用rte_kni_tx_burst()函数,数据包从DPDK的数据平面发送到KNI设备。KNI模块随后会把这些数据包注入到内核的网络协议栈中,如同从一个常规的虚拟网卡接口接收到数据一样。

    • 内核到DPDK: 类似,内核也可以通过KNI设备将数据包发送回DPDK。DPDK应用程序通过调用rte_kni_rx_burst()函数从KNI设备接收这些数据包。这些数据包是内核处理后需要发出的响应包,或者是由内核网络栈生成的包。接收到这些包后,DPDK应用程序可以决定如何处理它们,可以直接通过rte_eth_tx_burst()发送到物理网卡。

  3. 调度与同步:在多线程或高并发场景下,确保KNI设备访问的正确性很重要。要使用锁(如互斥锁)和信号量来保护对KNI资源的并发访问。还需要实现事件处理机制,处理内核与DPDK应用程序之间的异步交互。

  4. 错误处理:对DPDK和KNI相关的API调用进行充分的错误检查和处理是必不可少的。

dpdk_udp.c

展开

代码语言:C

自动换行

AI代码解释

#include <rte_eal.h> // DPDK Environment Abstraction Layer #include <rte_ethdev.h> // Ethernet Device API #include <rte_mbuf.h> // Memory Buffer API #include <rte_kni.h> // Kernel Network Interface API #include <rte_ether.h> // Ethernet header definitions #include <rte_ip.h> // IP header definitions #include <rte_udp.h> // UDP header definitions #include <stdio.h> #include <arpa/inet.h> #include <string.h> // 定义常量 #define MBUF_NUMBER 8196 // Mempool中mbuf的数量 #define MBUF_SIZE 32 // 单次burst操作中mbuf的最大数量 #define ENABLE_SEND 1 // 是否启用发送回复数据包的功能 #define ENABLE_KNI_APP 1 // 是否启用KNI功能 #define ENABLE_PROMISCUOUS 0 // 是否启用混杂模式(接收所有数据包) // 全局变量 int gDpdkPortId = 0; // DPDK端口ID,默认为0 #if ENABLE_KNI_APP struct rte_kni *global_kni = NULL; // KNI实例指针 #endif #if ENABLE_SEND // 用于构建回复包的MAC/IP/Port信息 static uint8_t gSrcMac[RTE_ETHER_ADDR_LEN]; static uint8_t gDstMac[RTE_ETHER_ADDR_LEN]; static uint32_t gSrcIp; static uint32_t gDstIp; static uint16_t gSrcPort; static uint16_t gDstPort; #endif #if ENABLE_KNI_APP /** * @brief KNI回调函数:配置网络接口状态 (up/down) * @param port_id DPDK端口ID * @param if_up 接口是否应设置为UP * @return 0 on success, negative on error */ static int g_config_network_if(uint16_t port_id, uint8_t if_up) { if (!rte_eth_dev_is_valid_port(port_id)) { printf("Invalid port ID: %d\n", port_id); return -EINVAL; } int ret = 0; if (if_up) { // 停止并重新启动端口,以应用配置 rte_eth_dev_stop(port_id); ret = rte_eth_dev_start(port_id); printf("Port %d started.\n", port_id); } else { rte_eth_dev_stop(port_id); printf("Port %d stopped.\n", port_id); } if (ret < 0) { printf("Failed to %s port %d: %s\n", if_up ? "start" : "stop", port_id, rte_strerror(-ret)); } return ret; } #endif #if ENABLE_SEND /** * @brief 分配并构造一个UDP数据包 (用于发送回复) * @param pool Mempool用于分配mbuf * @param data UDP payload数据 * @param length UDP payload长度 * @return 构造好的mbuf指针 */ static struct rte_mbuf *alloc_udp_pkt(struct rte_mempool *pool, uint8_t *data, uint16_t length) { // 从mempool中分配一个mbuf struct rte_mbuf *mbuf = rte_pktmbuf_alloc(pool); if (!mbuf) { rte_exit(EXIT_FAILURE, "Failed to allocate mbuf for UDP packet\n"); } // 计算数据包总长度 (以太网头 + IP头 + UDP头 + Payload) uint16_t total_len = length + sizeof(struct rte_ipv4_hdr) + sizeof(struct rte_ether_hdr); mbuf->pkt_len = total_len; mbuf->data_len = total_len; // 获取mbuf的数据指针 uint8_t *msg = rte_pktmbuf_mtod(mbuf, uint8_t*); // 1. 构造以太网头 struct rte_ether_hdr *eth = (struct rte_ether_hdr *)msg; rte_memcpy(eth->s_addr.addr_bytes, gSrcMac, RTE_ETHER_ADDR_LEN); // 源MAC rte_memcpy(eth->d_addr.addr_bytes, gDstMac, RTE_ETHER_ADDR_LEN); // 目的MAC eth->ether_type = htons(RTE_ETHER_TYPE_IPV4); // 以太网类型:IPv4 // 2. 构造IPv4头 struct rte_ipv4_hdr *ip = (struct rte_ipv4_hdr *)(msg + sizeof(struct rte_ether_hdr)); ip->version_ihl = 0x45; // Version (4) and IHL (5 words) ip->type_of_service = 0; ip->total_length = htons(length + sizeof(struct rte_ipv4_hdr)); // IP头 + UDP头 + Payload ip->packet_id = 0; // 可以随机生成 ip->fragment_offset = 0; ip->time_to_live = 64; // TTL ip->next_proto_id = IPPROTO_UDP; // 下层协议:UDP ip->src_addr = gSrcIp; // 源IP ip->dst_addr = gDstIp; // 目的IP ip->hdr_checksum = 0; // 计算校验和前置0 ip->hdr_checksum = rte_ipv4_cksum(ip); // 计算IP头校验和 // 3. 构造UDP头 struct rte_udp_hdr *udp = (struct rte_udp_hdr *)(msg + sizeof(struct rte_ether_hdr) + sizeof(struct rte_ipv4_hdr)); udp->src_port = gSrcPort; // 源端口 udp->dst_port = gDstPort; // 目的端口 udp->dgram_len = htons(length); // UDP数据报长度 (UDP头 + Payload) // 4. 拷贝UDP payload数据 rte_memcpy((uint8_t*)(udp + 1), data, length - sizeof(struct rte_udp_hdr)); // 5. 计算UDP校验和 udp->dgram_cksum = 0; // 计算校验和前置0 udp->dgram_cksum = rte_ipv4_udptcp_cksum(ip, udp); // 计算UDP校验和 return mbuf; } #endif /** * @brief 主函数 */ int main(int argc, char *argv[]) { // 1. EAL (Environment Abstraction Layer) 初始化 // 负责DPDK的初始化,包括解析命令行参数、设置大页内存、绑定CPU核等 if (rte_eal_init(argc, argv) < 0) { rte_exit(EXIT_FAILURE, "Error: EAL initialization failed\n"); } printf("DPDK EAL initialized successfully.\n"); // 2. 创建Mempool // Mempool是DPDK中用于管理mbuf(内存缓冲区)的内存池。 // Mbuf是DPDK数据包处理的基本单元。 struct rte_mempool *mbuf_pool = rte_pktmbuf_pool_create("mbufpool", // Mempool名称 MBUF_NUMBER, // mbuf数量 0, // 每个mbuf额外空间 0, // 每个mbuf私有数据大小 RTE_MBUF_DEFAULT_BUF_SIZE, // mbuf数据区大小 rte_socket_id()); // CPU socket ID if (!mbuf_pool) { rte_exit(EXIT_FAILURE, "Error: Failed to create mbuf pool\n"); } printf("Mbuf pool created successfully.\n"); #if ENABLE_KNI_APP // 3. KNI 子系统初始化 // 在分配KNI设备之前,需要先初始化KNI子系统。 if (rte_kni_init(gDpdkPortId) < 0) { // gDpdkPortId在这里作为KNI的唯一标识 rte_exit(EXIT_FAILURE, "Error: KNI subsystem initialization failed\n"); } printf("KNI subsystem initialized successfully.\n"); #endif // 4. 配置以太网设备 uint16_t nb_rx_queues = 1; // 接收队列数量 #if ENABLE_SEND uint16_t nb_tx_queues = 1; // 发送队列数量 #else uint16_t nb_tx_queues = 0; #endif const struct rte_eth_conf port_conf_default = { .rxmode = {.max_rx_pkt_len = RTE_ETHER_MAX_LEN } // 最大接收数据包长度 }; // 配置DPDK端口,包括接收/发送队列数量和基本模式 int ret = rte_eth_dev_configure(gDpdkPortId, nb_rx_queues, nb_tx_queues, &port_conf_default); if (ret < 0) { rte_exit(EXIT_FAILURE, "Error: Failed to configure Ethernet device: %s\n", rte_strerror(-ret)); } printf("Ethernet device %d configured.\n", gDpdkPortId); // 5. 设置接收队列 // 设置端口的接收队列,指定mbuf_pool用于接收数据包。 ret = rte_eth_rx_queue_setup(gDpdkPortId, // 端口ID 0, // 队列ID 128, // 接收描述符数量 rte_eth_dev_socket_id(gDpdkPortId), // CPU socket ID NULL, // 额外配置 (NULL表示默认) mbuf_pool); // Mempool if (ret < 0) { rte_exit(EXIT_FAILURE, "Error: Failed to setup RX queue: %s\n", rte_strerror(-ret)); } printf("RX queue for port %d setup.\n", gDpdkPortId); #if ENABLE_SEND // 6. 设置发送队列 // 设置端口的发送队列。 ret = rte_eth_tx_queue_setup(gDpdkPortId, // 端口ID 0, // 队列ID 1024, // 发送描述符数量 rte_eth_dev_socket_id(gDpdkPortId), // CPU socket ID NULL); // 额外配置 (NULL表示默认) if (ret < 0) { rte_exit(EXIT_FAILURE, "Error: Failed to setup TX queue: %s\n", rte_strerror(-ret)); } printf("TX queue for port %d setup.\n", gDpdkPortId); #endif // 7. 启动以太网设备 ret = rte_eth_dev_start(gDpdkPortId); if (ret < 0) { rte_exit(EXIT_FAILURE, "Error: Failed to start Ethernet device: %s\n", rte_strerror(-ret)); } printf("Ethernet device %d started.\n", gDpdkPortId); #if ENABLE_PROMISCUOUS // 8. 启用混杂模式 (可选) // 混杂模式下,网卡会接收所有经过的数据包,无论目的MAC地址是否是自己。 // 在调试或特殊应用场景下可能有用。 rte_eth_promiscuous_enable(gDpdkPortId); printf("Promiscuous mode enabled for port %d.\n", gDpdkPortId); #endif #if ENABLE_KNI_APP // 9. 配置和分配KNI设备 struct rte_kni_conf conf; memset(&conf, 0, sizeof(conf)); // 设置KNI设备名称,通常是vEthX snprintf(conf.name, RTE_KNI_NAMESIZE, "vEth%d", gDpdkPortId); conf.group_id = gDpdkPortId; // KNI组ID conf.mbuf_size = RTE_MBUF_DEFAULT_BUF_SIZE; // KNI内部mbuf大小 // 获取DPDK端口的MAC地址和MTU,用于KNI设备的配置 struct rte_ether_addr eth_addr; rte_eth_macaddr_get(gDpdkPortId, &eth_addr); rte_memcpy(conf.mac_addr, eth_addr.addr_bytes, RTE_ETHER_ADDR_LEN); uint16_t mtu; rte_eth_dev_get_mtu(gDpdkPortId, &mtu); conf.mtu = mtu; // 设置KNI操作回调函数 struct rte_kni_ops ops; memset(&ops, 0, sizeof(ops)); ops.port_id = gDpdkPortId; // 注册KNI接口的up/down回调函数,当内核控制KNI接口状态时会调用 ops.config_network_if = g_config_network_if; // 分配KNI设备 global_kni = rte_kni_alloc(mbuf_pool, &conf, &ops); if (!global_kni) { rte_exit(EXIT_FAILURE, "Error: Failed to allocate KNI device\n"); } printf("KNI device '%s' allocated successfully.\n", conf.name); #endif printf("Entering main loop...\n"); // 10. 主循环:数据包处理 while (1) { unsigned num_recvd = 0; unsigned i = 0; #if ENABLE_KNI_APP // 从KNI接收数据包 (即从内核接收数据包) struct rte_mbuf *kni_burst[MBUF_SIZE]; num_recvd = rte_kni_rx_burst(global_kni, kni_burst, MBUF_SIZE); if (num_recvd > 0) { // 将从KNI接收到的数据包发送到物理网卡 unsigned nb_tx = rte_eth_tx_burst(gDpdkPortId, 0, kni_burst, num_recvd); if (nb_tx < num_recvd) { // 如果未能全部发送,释放剩余的mbuf printf("Warning: Only %u/%u KNI packets sent to NIC. Freeing remaining.\n", nb_tx, num_recvd); for (i = nb_tx; i < num_recvd; i++) { rte_pktmbuf_free(kni_burst[i]); } } } #endif // 从物理网卡接收数据包 struct rte_mbuf *mbufs[MBUF_SIZE]; num_recvd = rte_eth_rx_burst(gDpdkPortId, 0, mbufs, MBUF_SIZE); if (num_recvd == 0) { // 没有收到数据包,继续循环 // 小心,如果循环太快会占用大量CPU,实际应用中会加入延时或条件等待 // rte_delay_us_block(100); // 示例:可以加入短暂延时 goto handle_kni_requests; // 跳到处理KNI请求 } // 遍历接收到的数据包 for (i = 0; i < num_recvd; i++) { struct rte_mbuf *current_mbuf = mbufs[i]; struct rte_ether_hdr *ehdr = rte_pktmbuf_mtod(current_mbuf, struct rte_ether_hdr *); // 检查以太网类型是否为IPv4 if (ehdr->ether_type != rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4)) { #if ENABLE_KNI_APP // 非IPv4数据包,转发给KNI (即转发给内核) rte_kni_tx_burst(global_kni, &current_mbuf, 1); // 注意:这里只发送一个包,如果需要发送多个,需要调整数组和计数 #else // 如果没有启用KNI,则直接释放非IPv4数据包 rte_pktmbuf_free(current_mbuf); #endif continue; // 处理下一个数据包 } // 解析IPv4头 struct rte_ipv4_hdr *iphdr = rte_pktmbuf_mtod_offset(current_mbuf, struct rte_ipv4_hdr *, sizeof(struct rte_ether_hdr)); // 检查IP协议类型是否为UDP if (iphdr->next_proto_id == IPPROTO_UDP) { // 是UDP数据包,由DPDK应用程序处理 struct rte_udp_hdr *udphdr = (struct rte_udp_hdr *)(iphdr + 1); // 数据过滤:只处理目的端口为8888的UDP包 (用于调试) if (ntohs(udphdr->dst_port) != 8888) { rte_pktmbuf_free(current_mbuf); // 释放不感兴趣的UDP包 continue; } // 打印UDP数据包信息 uint16_t payload_len = ntohs(udphdr->dgram_len) - sizeof(struct rte_udp_hdr); // 确保payload以null结尾,以便printf打印字符串 // 注意:这里直接修改mbuf内容,可能影响后续处理,仅为示例 if (payload_len > 0 && (char*)(udphdr+1) + payload_len < (char*)rte_pktmbuf_mtod(current_mbuf, char*) + current_mbuf->data_len) { *((char*) udphdr + ntohs(udphdr->dgram_len)) = '\0'; } else { // Payload长度异常或太短,无法安全添加null终止符 payload_len = 0; // 避免访问越界 } struct in_addr addr; addr.s_addr = iphdr->src_addr; printf("RX UDP: src: %s:%d, ", inet_ntoa(addr), ntohs(udphdr->src_port)); addr.s_addr = iphdr->dst_addr; printf("dst: %s:%d, Payload: '%s'\n", inet_ntoa(addr), ntohs(udphdr->dst_port), (char *)(udphdr + 1)); #if ENABLE_SEND // 构造并发送一个UDP回复包 // 交换源MAC/IP/Port和目的MAC/IP/Port rte_memcpy(gSrcMac, ehdr->d_addr.addr_bytes, RTE_ETHER_ADDR_LEN); rte_memcpy(gDstMac, ehdr->s_addr.addr_bytes, RTE_ETHER_ADDR_LEN); gSrcIp = iphdr->dst_addr; gDstIp = iphdr->src_addr; gSrcPort = udphdr->dst_port; gDstPort = udphdr->src_port; // 构造回复消息,例如 "Hello from DPDK!" char reply_msg[] = "Hello from DPDK!"; struct rte_mbuf *reply_mbuf = alloc_udp_pkt(mbuf_pool, (uint8_t*)reply_msg, sizeof(reply_msg)); // 发送回复包 unsigned nb_tx = rte_eth_tx_burst(gDpdkPortId, 0, &reply_mbuf, 1); if (nb_tx == 1) { printf("TX UDP: Sent reply to %s:%d\n", inet_ntoa(addr), ntohs(udphdr->src_port)); } else { printf("Failed to send UDP reply.\n"); rte_pktmbuf_free(reply_mbuf); // 发送失败,释放mbuf } #endif rte_pktmbuf_free(current_mbuf); // 处理完毕,释放原始数据包 } else { // 非UDP的IPv4数据包 (如TCP, ICMP等),转发给KNI (即转发给内核) #if ENABLE_KNI_APP rte_kni_tx_burst(global_kni, &current_mbuf, 1); #else // 如果没有启用KNI,则直接释放这些数据包 rte_pktmbuf_free(current_mbuf); #endif } } handle_kni_requests: #if ENABLE_KNI_APP // 处理KNI控制请求 // KNI模块会通过此函数处理来自内核的控制请求,如接口状态改变等。 rte_kni_handle_request(global_kni); #endif } // 程序退出前清理资源 (通常不会到达这里,因为是无限循环) // rte_kni_release(global_kni); // rte_eth_dev_stop(gDpdkPortId); // rte_eth_dev_close(gDpdkPortId); // rte_eal_cleanup(); return 0; }

Makefile支持两种编译方式:用pkg-config(推荐,更现代)或用RTE_SDK环境变量。生成共享库版本和静态库版本的可执行文件。

展开

代码语言:Bash

自动换行

AI代码解释

# binary name APP = dpdk_udp # all source are stored in SRCS-y SRCS-y := dpdk_udp.c # Build using pkg-config variables if possible # 检查系统是否安装并配置了libdpdk的pkg-config文件 ifeq ($(shell pkg-config --exists libdpdk && echo 0),0) # 如果pkg-config可用,则使用其提供的编译和链接标志 all: shared # 默认构建共享库版本 .PHONY: shared static shared: build/$(APP)-shared # 目标:构建共享库版本 ln -sf $(APP)-shared build/$(APP) # 创建软链接,方便执行 static: build/$(APP)-static # 目标:构建静态库版本 ln -sf $(APP)-static build/$(APP) # 创建软链接 # 获取pkg-config文件的路径 PKGCONF=pkg-config --define-prefix PC_FILE := $(shell $(PKGCONF) --path libdpdk) # 从pkg-config获取C编译标志和链接标志 CFLAGS += -O3 $(shell $(PKGCONF) --cflags libdpdk) LDFLAGS_SHARED = $(shell $(PKGCONF) --libs libdpdk) LDFLAGS_STATIC = -Wl,-Bstatic $(shell $(PKGCONF) --static --libs libdpdk) # 编译共享库版本 build/$(APP)-shared: $(SRCS-y) Makefile $(PC_FILE) | build $(CC) $(CFLAGS) $(SRCS-y) -o $@ $(LDFLAGS) $(LDFLAGS_SHARED) # 编译静态库版本 build/$(APP)-static: $(SRCS-y) Makefile $(PC_FILE) | build $(CC) $(CFLAGS) $(SRCS-y) -o $@ $(LDFLAGS) $(LDFLAGS_STATIC) # 创建build目录 build: @mkdir -p $@ .PHONY: clean clean: rm -f build/$(APP) build/$(APP)-static build/$(APP)-shared test -d build && rmdir -p build || true # 删除build目录,如果为空 else # 如果pkg-config不可用,退回到使用RTE_SDK环境变量 ifeq ($(RTE_SDK),) $(error "Please define RTE_SDK environment variable") # 提示用户设置RTE_SDK endif # 尝试从RTE_SDK路径推断RTE_TARGET RTE_TARGET ?= $(notdir $(abspath $(dir $(firstword $(wildcard $(RTE_SDK)/*/.config))))) # 包含DPDK的通用Makefile变量和规则 include $(RTE_SDK)/mk/rte.vars.mk # 添加优化标志 CFLAGS += -O3 CFLAGS += $(WERROR_FLAGS) # 启用警告作为错误(可选) # 包含DPDK外部应用程序的Makefile规则 include $(RTE_SDK)/mk/rte.extapp.mk endif

四、编译与执行

  1. 编译应用程序:执行make命令。

    make

  2. DPDK环境配置完成后(特别是网卡已绑定到DPDK驱动,且大页内存已分配),以root权限运行编译好的程序。

    sudo ./build/dpdk_udp

  3. 启动KNI虚拟网卡接口:

    sudo ifconfig vEth0 192.168.7.26 up

  4. KNI接口的载波状态默认可能为DOWN。要手动将其设置为UP,以便内核网络栈认为该接口已连接并可以传输数据。

    sudo sh -c 'echo 1 > /sys/devices/virtual/net/vEth0/carrier'

  5. 从另一台机器或同一台机器上的其他终端pingvEth0配置的IP地址,或者向IP地址的8888端口发送UDP数据包。

    ping 192.168.7.26

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/15 10:01:56

C语言实现幂级数(附带源码)

一、项目背景详细介绍幂级数&#xff08;Power Series&#xff09;是数学分析中一种极为重要的函数表达方式&#xff0c;通过将函数展开为一系列以某点为中心的无限项多项式&#xff0c;可以用来近似计算、求导、积分、解析表达、数值计算等。大量经典函数&#xff0c;如指数函…

作者头像 李华
网站建设 2026/4/16 9:24:38

去哪儿网航班数据采集:API接口分析与加密参数解密实战

目录 引言 项目目标 效果展示 网站抓包分析过程 抓包分析:探索去哪儿网API的加密机制 第一步:打开网络监控,观察数据流动 第二步:分析请求参数,理解数据交换规则 第三步:解密认证机制,掌握访问控制策略 第四步:理解双重加密机制,掌握解密流程 第五步:解析响…

作者头像 李华
网站建设 2026/4/16 9:25:23

League Akari:英雄联盟智能自动化助手的五大核心功能详解

League Akari&#xff1a;英雄联盟智能自动化助手的五大核心功能详解 【免费下载链接】LeagueAkari ✨兴趣使然的&#xff0c;功能全面的英雄联盟工具集。支持战绩查询、自动秒选等功能。基于 LCU API。 项目地址: https://gitcode.com/gh_mirrors/le/LeagueAkari Leagu…

作者头像 李华
网站建设 2026/4/16 9:25:25

理工科论文模板推荐:8大平台+免费下载工具

理工科论文模板推荐&#xff1a;8大平台免费下载工具 8大理工科论文工具速览 工具名称 核心功能 适用场景 免费程度 aibiye AI论文生成与优化 初稿创作、结构优化 部分免费 aicheck 论文查重与降重 查重、降重、AIGC检测 按字数收费 askpaper 文献智能阅读 文献综…

作者头像 李华
网站建设 2026/4/15 16:33:56

论文查重率排名:10大平台+不同阶段标准

论文查重率排名&#xff1a;10大平台不同阶段标准论文查重率排名&#xff1a;10大平台不同阶段标准查重工具核心对比速览工具名称查重准确率数据库规模特色功能适用阶段AI论文及时雨★★★★☆千万级同步降AIGC率初稿生成后学术GPT★★★☆☆百万级语法优化查重写作过程中aiche…

作者头像 李华