2025/12/25 16:30:00
网站建设
项目流程
开发网站的财务分析,菏泽炫佑网站建设,多说wordpress,建设公司网站的会计分录有了KNI模块#xff0c;DPDK应用程序就可以实现#xff1a;选择性处理#xff1a; DPDK专注处理关注的高性能数据路径流量#xff0c;把自己不想要的协议、控制平面流量或要内核处理的包转发给内核协议栈。直接用内核已有的网络功能#xff0c;不用在用户空间重新实现这些…有了KNI模块DPDK应用程序就可以实现选择性处理 DPDK专注处理关注的高性能数据路径流量把自己不想要的协议、控制平面流量或要内核处理的包转发给内核协议栈。直接用内核已有的网络功能不用在用户空间重新实现这些复杂逻辑。DPDK应用能够更好的跟现有Linux网络工具和基础设施集成。简单的说DPDK KNI模块让DPDK再是一个纯粹的数据包转发引擎更是一个智能的数据包分发器和过滤器实现了高性能数据平面与通用内核网络栈的协同工作。二、KNI实现原理ifreqDPDK KNI模块的核心实现依靠Linux内核的ifreq结构体和TUN/TAP虚拟网络设备。2.1、ifreq简介ifreqinterface request是Linux内核获取或设置网络接口信息的一个标准C结构体。提供统一的方式来跟网络接口进行交互获取接口的IP地址、MAC地址、MTU、接口状态UP/DOWN等或进行相应的配置。ifreq结构体的定义展开代码语言C自动换行AI代码解释struct ifreq { char ifr_name[IFNAMSIZ]; /* Interface name, e.g., eth0, lo */ union { struct sockaddr ifr_addr; /* IP address */ struct sockaddr ifr_dstaddr; /* Destination IP address (for point-to-point) */ struct sockaddr ifr_broadaddr; /* Broadcast IP address */ struct sockaddr ifr_netmask; /* Netmask */ struct sockaddr ifr_hwaddr; /* Hardware (MAC) address */ short ifr_flags; /* Interface flags (e.g., IFF_UP, IFF_BROADCAST) */ int ifr_ifindex; /* Interface index */ int ifr_metric; /* Routing metric */ int ifr_mtu; /* Maximum Transmission Unit */ struct ifmap ifr_map; /* Device mapping */ char ifr_slave[IFNAMSIZ]; /* Slave interface name (for bonding) */ char ifr_newname[IFNAMSIZ]; /* New interface name */ char *ifr_data; /* Pointer to extra data (for private ioctls) */ }; };ifr_name成员指定要操作的网络接口名称其后的联合体则包含了各种接口属性ifr_addr接口IP地址、ifr_netmask子网掩码、ifr_hwaddrMAC地址等。ifreq结构体一般会跟ioctl系统调用结合使用通过不同的ioctl命令字SIOCGIFADDR获取IP地址SIOCSIFADDR设置IP地址来执行特定的网络接口操作。示例用ifreq获取指定接口的IP地址。展开代码语言C自动换行AI代码解释#include stdio.h #include string.h #include unistd.h #include sys/socket.h #include sys/ioctl.h #include net/if.h #include arpa/inet.h #include errno.h int main() { struct ifreq ifr; int sockfd; struct sockaddr_in *addr; // 创建一个UDP套接字用于ioctl操作 sockfd socket(AF_INET, SOCK_DGRAM, IPPROTO_IP); if (sockfd 0) { perror(socket creation failed); return 1; } // 指定要查询的接口名称 strcpy(ifr.ifr_name, eth0); // 或者你的网卡名称如ens33 // 使用SIOCGIFADDR命令获取接口的IP地址 if (ioctl(sockfd, SIOCGIFADDR, ifr) 0) { perror(ioctl SIOCGIFADDR failed); close(sockfd); return 1; } // 将通用地址结构体转换为IPv4地址结构体 addr (struct sockaddr_in*)ifr.ifr_addr; printf(Interface %s IP Address: %s\n, ifr.ifr_name, inet_ntoa(addr-sin_addr)); close(sockfd); return 0; }2.2、ifreq在KNI中的应用KNI模块的实现特别是在内核的虚拟接口创建主要用Linux内核提供的/dev/net/tun设备文件。这个设备文件可以在用户空间程序创建虚拟网络接口这些接口可以模拟物理网卡让数据包在用户空间和内核空间之间进行交换。TUN设备工作在网络层IP层它处理IP数据包。用户空间程序向TUN设备写入一个IP包时该包会被注入到内核的网络协议栈进行处理反之内核要把一个IP包发送到TUN设备时该包会出现在用户空间程序可读的设备文件中。TAP设备工作在数据链路层以太网层处理以太网帧。跟TUN类似TAP设备可以在用户空间程序直接读写以太网帧包括MAC头、IP头等完整帧结构。KNI一般都是使用TAP设备因为DPDK应用可以在以太网层面上和内核进行数据包交换和DPDK直接操作以太网帧的特性更加匹配。示例用ifreq和/dev/net/tun创建虚拟网卡。结合open(/dev/net/tun)和ioctl来创建一个TAP虚拟网络接口展开代码语言C自动换行AI代码解释#include stdio.h #include string.h #include unistd.h #include fcntl.h #include net/if.h #include linux/if_tun.h // 包含TUN/TAP设备相关的定义 #include errno.h #include sys/ioctl.h #include stdlib.h // For EXIT_FAILURE /** * brief 分配并配置一个TUN/TAP设备 * param dev 期望创建的设备名称如 MyDev * return 成功返回文件描述符失败返回 -1 */ int tun_alloc(char *dev) { struct ifreq ifr; int fd; int err; // 打开TUN/TAP设备文件 fd open(/dev/net/tun, O_RDWR); if (fd 0) { perror(Failed to open /dev/net/tun); return -1; } // 清零ifreq结构体 memset(ifr, 0, sizeof(ifr)); // 设置接口标志 // IFF_TAP: 创建一个TAP设备以太网层 // IFF_NO_PI: 不包含包信息Packet Information头即只传输原始以太网帧数据 ifr.ifr_flags IFF_TAP | IFF_NO_PI; // 拷贝期望的设备名称 // 注意ifr.ifr_name 不能为空且不能包含空格 if (strlen(dev) IFNAMSIZ) { fprintf(stderr, Device name %s is too long.\n, dev); close(fd); return -1; } memcpy(ifr.ifr_name, dev, strlen(dev)); ifr.ifr_name[strlen(dev)] \0; // 确保字符串以null结尾 printf(Attempting to create TAP device: %s\n, ifr.ifr_name); // 使用TUNSETIFF ioctl命令设置TUN/TAP接口 if ((err ioctl(fd, TUNSETIFF, (char *)ifr)) 0) { perror(ioctl TUNSETIFF failed); close(fd); return err; // 返回ioctl的错误码 } printf(Successfully created TAP device: %s (fd %d)\n, ifr.ifr_name, fd); return fd; } int main() { char dev_name[] MyDev; // 虚拟设备名称 int tun_fd tun_alloc(dev_name); if (tun_fd 0) { fprintf(stderr, Failed to allocate TUN/TAP device.\n); return EXIT_FAILURE; } printf(Virtual device %s created. Press Enter to exit...\n, dev_name); getchar(); // 保持程序运行以便查看新创建的设备 close(tun_fd); // 关闭文件描述符设备通常会自动消失 return 0; }编译与运行代码语言Bash自动换行AI代码解释gcc -o ifr ifr.c sudo ./ifr # 需要root权限来创建网络接口程序运行后打开另一个终端用ifconfig -a或ip link show命令可以看到一个名为MyDev的新网络接口被创建展开代码语言Bash自动换行AI代码解释MyDev Link encap:以太网 硬件地址 c2:44:70:f2:79:f9 BROADCAST MULTICAST MTU:1500 跃点数:1 接收数据包:0 错误:0 丢弃:0 过载:0 帧数:0 发送数据包:0 错误:0 丢弃:0 过载:0 载波:0 碰撞:0 发送队列长度:1000 接收字节:0 (0.0 B) 发送字节:0 (0.0 B) # ... 其他网络接口信息 ...这个MyDev接口就是KNI模块在内核中创建的虚拟网络接口的抽象。KNI的实现原理打开/dev/net/tun设备文件用ioctl()系统调用进行配置和控制。ioctl()调用在内核中创建一个实际的虚拟网络接口该设备能够跟内核网络协议栈进行数据包交换。三、DPDK KNI模块的代码实现与API使用DPDK KNI模块提供有一整套API用来在DPDK应用程序集成KNI功能。全局KNI变量定义是struct rte_kni *指针管理KNI实例。KNI系统初始化DPDK应用程序初始化rte_eal_init之后要调用rte_kni_init()初始化KNI子系统。配置KNI接口参数struct rte_kni_confrte_kni_conf结构体可以定义KNI接口的各种属性这些属性用来在内核创建对应的虚拟网卡。name: KNI接口的名称。group_id: KNI接口所属的组ID跟DPDK端口ID关联。mbuf_size: KNI内部用来数据包传输的mbuf大小。mac_addr: KNI接口的MAC地址跟DPDK物理端口的MAC地址相同。mtu: KNI接口的MTU最大传输单元。定义KNI操作回调函数struct rte_kni_ops。rte_kni_ops结构体包含一系列回调函数指针这些函数在内核对KNI接口进行操作时会被DPDK KNI层调用DPDK应用程序响应这些内核事件。port_id: 关联的DPDK端口ID。config_network_if: 最重要的回调函数之一内核尝试启动或停止KNI接口时ifconfig vEth0 up/down此函数会被调用。DPDK应用程序要在此函数中实现对应的DPDK端口的启动或停止逻辑。其他回调函数还包括配置MAC地址、MTU、链路状态等。实现网络接口配置回调函数config_network_if。static int gconfig_network_if(uint16_t port_id, uint8_t if_up) { if (!rte_eth_dev_is_valid_port(port_id)) { return -EINVAL; // 无效端口 } int ret 0; if (if_up) { // 如果内核请求接口UP rte_eth_dev_stop(port_id); // 先停止确保干净启动 ret rte_eth_dev_start(port_id); // 启动DPDK端口 } else { // 如果内核请求接口DOWN rte_eth_dev_stop(port_id); // 停止DPDK端口 } if (ret 0) { printf(Failed to %s port %d\n, if_up ? start : stop, port_id); } return 0; }分配KNI实例rte_kni_alloc()调用rte_kni_alloc(mbuf_pool, conf, ops)创建并初始化一个KNI实例。函数会根据conf中的参数在内核中创建对应的虚拟网络接口并注册ops中定义的回调函数。成功后会返回一个rte_kni *指针用在后续的操作。数据包收发与KNI交互rte_kni_tx_burst()和rte_kni_rx_burst()。从DPDK转发到内核rte_kni_tx_burst(kni, mbufs, num_pkts)。DPDK应用程序接收到它不希望处理或需要内核处理的数据包时可以把这些rte_mbuf数据包通过rte_kni_tx_burst函数发送给KNI接口。KNI会把这些数据包注入到内核对应的虚拟网卡然后由内核协议栈进行后续处理。从内核接收到DPDKrte_kni_rx_burst(kni, mbufs, num_pkts)如果内核有数据包需要发送到KNI接口DPDK应用程序可以通过rte_kni_rx_burst函数从KNI接口接收这些数据包然后进行用户空间的DPDK处理。开启混杂模式rte_eth_promiscuous_enable()想要确保DPDK端口能够接收所有到达的数据包包括那些不以本网卡MAC地址为目的地址的包就要调用rte_eth_promiscuous_enable(port_id)来开启网卡的混杂模式。示例代码dpdk_udp.c。展开代码语言C自动换行AI代码解释#include rte_eal.h #include rte_ethdev.h #include rte_mbuf.h #include rte_kni.h // KNI模块头文件 #include rte_ether.h #include rte_ip.h #include rte_udp.h #include rte_cycles.h #include stdio.h #include arpa/inet.h #include string.h // For memset // 定义宏控制功能 #define ENABLE_SEND 1 // 是否启用UDP回显功能 #define ENABLE_KNI 1 // 是否启用KNI功能 #define NUM_MBUFS (8191) // Mbuf池大小通常是2的幂减1 #define MBUF_CACHE_SIZE 250 // Mbuf缓存大小 #define BURST_SIZE 32 // 每次从网卡接收或发送的包数量 int gDpdkPortId 0; // DPDK端口ID static const struct rte_eth_conf port_conf_default { .rxmode { .max_rx_pkt_len RTE_ETHER_MAX_LEN, // 最大接收包长 .mq_mode RTE_ETH_MQ_RX_NONE, }, .txmode { .mq_mode RTE_ETH_MQ_TX_NONE, }, }; #if ENABLE_KNI struct rte_kni *global_kni NULL; // 全局KNI实例指针 #endif #if ENABLE_SEND // 用于UDP回显的源/目的信息 static uint32_t gSrcIp; static uint32_t gDstIp; static uint16_t gSrcPort; static uint16_t gDstPort; // Changed from uint32_t to uint16_t for UDP port static uint8_t gSrcMac[RTE_ETHER_ADDR_LEN]; static uint8_t gDstMac[RTE_ETHER_ADDR_LEN]; #endif // 初始化DPDK端口 static void ng_init_port(struct rte_mempool *mbuf_pool) { uint16_t nb_sys_ports rte_eth_dev_count_avail(); if (nb_sys_ports 0) { rte_exit(EXIT_FAILURE, No Supported eth found\n); } struct rte_eth_dev_info dev_info; rte_eth_dev_info_get(gDpdkPortId, dev_info); const int num_rx_queues 1; const int num_tx_queues 1; struct rte_eth_conf port_conf port_conf_default; // 配置以太网设备 if (rte_eth_dev_configure(gDpdkPortId, num_rx_queues, num_tx_queues, port_conf) 0) { rte_exit(EXIT_FAILURE, Could not configure ethdev\n); } // 设置RX队列 if (rte_eth_rx_queue_setup(gDpdkPortId, 0, 1024, rte_eth_dev_socket_id(gDpdkPortId), NULL, mbuf_pool) 0) { rte_exit(EXIT_FAILURE, Could not setup RX queue\n); } #if ENABLE_SEND // 设置TX队列 struct rte_eth_txconf txq_conf dev_info.default_txconf; txq_conf.offloads port_conf.txmode.offloads; // Use txmode.offloads if (rte_eth_tx_queue_setup(gDpdkPortId, 0, 1024, rte_eth_dev_socket_id(gDpdkPortId), txq_conf) 0) { rte_exit(EXIT_FAILURE, Could not setup TX queue\n); } #endif // 启动以太网设备 if (rte_eth_dev_start(gDpdkPortId) 0) { rte_exit(EXIT_FAILURE, Could not start ethdev\n); } // 开启混杂模式确保能收到所有发往KNI接口的包 rte_eth_promiscuous_enable(gDpdkPortId); printf(DPDK Port %d started in promiscuous mode.\n, gDpdkPortId); } #if ENABLE_SEND // 编码UDP回显数据包 static int ng_encode_udp_pkt(uint8_t *msg, unsigned char *data, uint16_t total_len) { // 1. 以太网头 struct rte_ether_hdr *eth (struct rte_ether_hdr *)msg; rte_memcpy(eth-s_addr.addr_bytes, gSrcMac, RTE_ETHER_ADDR_LEN); rte_memcpy(eth-d_addr.addr_bytes, gDstMac, RTE_ETHER_ADDR_LEN); eth-ether_type htons(RTE_ETHER_TYPE_IPV4); // 2. IP头 struct rte_ipv4_hdr *ip (struct rte_ipv4_hdr *)(msg sizeof(struct rte_ether_hdr)); ip-version_ihl 0x45; // IPv4, IHL5 (no options) ip-type_of_service 0; ip-total_length htons(total_len - sizeof(struct rte_ether_hdr)); ip-packet_id 0; ip-fragment_offset 0; ip-time_to_live 64; // TTL ip-next_proto_id IPPROTO_UDP; ip-src_addr gSrcIp; ip-dst_addr gDstIp; ip-hdr_checksum 0; ip-hdr_checksum rte_ipv4_cksum(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; uint16_t udplen total_len - sizeof(struct rte_ether_hdr) - sizeof(struct rte_ipv4_hdr); udp-dgram_len htons(udplen); // 拷贝UDP数据 rte_memcpy((uint8_t*)(udp 1), data, udplen - sizeof(struct rte_udp_hdr)); // udplen is total UDP length including header udp-dgram_cksum 0; // UDP checksum calculation requires pseudo-header checksum udp-dgram_cksum rte_ipv4_udptcp_cksum(ip, udp); struct in_addr addr; addr.s_addr gSrcIp; printf( -- src: %s:%d, , inet_ntoa(addr), ntohs(gSrcPort)); addr.s_addr gDstIp; printf(dst: %s:%d\n, inet_ntoa(addr), ntohs(gDstPort)); return 0; } // 构造并发送一个mbuf static struct rte_mbuf *ng_send(struct rte_mempool *mbuf_pool, uint8_t *data, uint16_t length) { const unsigned total_len length sizeof(struct rte_ether_hdr) sizeof(struct rte_ipv4_hdr) sizeof(struct rte_udp_hdr); struct rte_mbuf *mbuf rte_pktmbuf_alloc(mbuf_pool); if (!mbuf) { rte_exit(EXIT_FAILURE, rte_pktmbuf_alloc failed\n); } mbuf-pkt_len total_len; mbuf-data_len total_len; uint8_t *pktdata rte_pktmbuf_mtod(mbuf, uint8_t*); ng_encode_udp_pkt(pktdata, data, total_len); return mbuf; } #endif #if ENABLE_KNI // KNI回调函数配置网络接口的UP/DOWN状态 static int gconfig_network_if(uint16_t port_id, uint8_t if_up) { if (!rte_eth_dev_is_valid_port(port_id)) { return -EINVAL; } int ret 0; if (if_up) { // 内核请求接口UP printf(KNI: Bringing DPDK port %d UP.\n, port_id); rte_eth_dev_stop(port_id); // 先停止确保干净启动 ret rte_eth_dev_start(port_id); } else { // 内核请求接口DOWN printf(KNI: Bringing DPDK port %d DOWN.\n, port_id); rte_eth_dev_stop(port_id); } if (ret 0) { printf(Failed to %s DPDK port %d\n, if_up ? start : stop, port_id); } return 0; } #endif int main(int argc, char *argv[]) { // 1. EAL初始化 if (rte_eal_init(argc, argv) 0) { rte_exit(EXIT_FAILURE, Error with EAL init\n); } // 2. 创建mbuf内存池 struct rte_mempool *mbuf_pool rte_pktmbuf_pool_create(mbuf pool, NUM_MBUFS, MBUF_CACHE_SIZE, 0, RTE_MBUF_DEFAULT_BUF_SIZE, rte_socket_id()); if (mbuf_pool NULL) { rte_exit(EXIT_FAILURE, Could not create mbuf pool\n); } #if ENABLE_KNI // 3. KNI子系统初始化 rte_kni_init(gDpdkPortId); #endif // 4. 初始化DPDK端口 ng_init_port(mbuf_pool); #if ENABLE_KNI // 5. 配置KNI接口参数 struct rte_kni_conf conf; memset(conf, 0, sizeof(conf)); snprintf(conf.name, RTE_KNI_NAMESIZE, vEth%d, gDpdkPortId); // KNI接口名称 conf.group_id gDpdkPortId; conf.mbuf_size RTE_MBUF_DEFAULT_BUF_SIZE; // 获取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; // 6. 定义KNI操作回调函数 struct rte_kni_ops ops; memset(ops, 0, sizeof(ops)); ops.port_id gDpdkPortId; ops.config_network_if gconfig_network_if; // 注册网络接口UP/DOWN回调 // 7. 分配KNI实例 global_kni rte_kni_alloc(mbuf_pool, conf, ops); if (global_kni NULL) { rte_exit(EXIT_FAILURE, Failed to allocate KNI device\n); } printf(KNI device %s allocated.\n, conf.name); #endif printf(Entering main loop...\n); while (1) { struct rte_mbuf *mbufs[BURST_SIZE]; // 8. 从DPDK端口接收数据包 unsigned num_recvd rte_eth_rx_burst(gDpdkPortId, 0, mbufs, BURST_SIZE); if (num_recvd 0) { #if ENABLE_KNI // 如果没有从DPDK端口收到包检查KNI接口是否有来自内核的包 rte_kni_rx_burst(global_kni, mbufs, BURST_SIZE); // 这里可以处理从内核收到的包例如转发到DPDK端口或进行其他处理 // 本示例中未实现从KNI接收并转发的逻辑仅作为示例 // 如果有从KNI收到的包可以继续处理例如 rte_eth_tx_burst(gDpdkPortId, 0, mbufs, num_recvd_from_kni); #endif continue; // 没有收到包继续循环 } unsigned i; for (i 0; i num_recvd; i) { struct rte_ether_hdr *ehdr rte_pktmbuf_mtod(mbufs[i], struct rte_ether_hdr*); // 检查是否是IPv4包 if (ehdr-ether_type rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4)) { struct rte_ipv4_hdr *iphdr rte_pktmbuf_mtod_offset(mbufs[i], struct rte_ipv4_hdr *, sizeof(struct rte_ether_hdr)); // 检查是否是UDP包 if (iphdr-next_proto_id IPPROTO_UDP) { struct rte_udp_hdr *udphdr (struct rte_udp_hdr *)(iphdr 1); #if ENABLE_SEND // 如果启用UDP回显 // 交换MAC、IP、端口信息准备回显 rte_memcpy(gDstMac, ehdr-s_addr.addr_bytes, RTE_ETHER_ADDR_LEN); rte_memcpy(gSrcMac, ehdr-d_addr.addr_bytes, RTE_ETHER_ADDR_LEN); rte_memcpy(gSrcIp, iphdr-dst_addr, sizeof(uint32_t)); rte_memcpy(gDstIp, iphdr-src_addr, sizeof(uint32_t)); rte_memcpy(gSrcPort, udphdr-dst_port, sizeof(uint16_t)); rte_memcpy(gDstPort, udphdr-src_port, sizeof(uint16_t)); #endif // 打印接收到的UDP包信息 uint16_t length ntohs(udphdr-dgram_len); // UDP数据报总长度包括UDP头 // 确保字符串终止符避免打印越界 if (length sizeof(struct rte_udp_hdr)) { char *udp_payload (char *)(udphdr 1); // 确保不会越界访问mbuf数据 if (rte_pktmbuf_data_len(mbufs[i]) (sizeof(struct rte_ether_hdr) sizeof(struct rte_ipv4_hdr) length)) { udp_payload[length - sizeof(struct rte_udp_hdr)] \0; // 终止字符串 } else { // 数据长度不足可能截断或不打印payload udp_payload[0] \0; // 确保安全 } struct in_addr addr; addr.s_addr iphdr-src_addr; printf(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), udp_payload); } else { struct in_addr addr; addr.s_addr iphdr-src_addr; printf(src: %s:%d, , inet_ntoa(addr), ntohs(udphdr-src_port)); addr.s_addr iphdr-dst_addr; printf(dst: %s:%d, (no payload)\n, inet_ntoa(addr), ntohs(udphdr-dst_port)); } #if ENABLE_SEND // 构造并发送回显包 struct rte_mbuf *txbuf ng_send(mbuf_pool, (unsigned char*)(udphdr 1), length - sizeof(struct rte_udp_hdr)); // 仅传递payload长度 if (txbuf) { rte_eth_tx_burst(gDpdkPortId, 0, txbuf, 1); // 发送回显包 } else { printf(Failed to create TX mbuf for echo.\n); } #endif rte_pktmbuf_free(mbufs[i]); // 释放已处理的UDP包 } else { #if ENABLE_KNI // 如果不是UDP包或者不是我们感兴趣的协议则通过KNI转发给内核 // 9. 将数据包发送到KNI由内核处理 rte_kni_tx_burst(global_kni, mbufs[i], 1); // 注意rte_kni_tx_burst 会接管mbuf的所有权无需手动释放 #else rte_pktmbuf_free(mbufs[i]); // 如果KNI未启用则直接释放 #endif } } else { #if ENABLE_KNI // 如果不是IPv4包则通过KNI转发给内核 rte_kni_tx_burst(global_kni, mbufs[i], 1); #else rte_pktmbuf_free(mbufs[i]); // 如果KNI未启用则直接释放 #endif } } } // 资源清理 (实际应用中可能不会到达这里) rte_eal_cleanup(); return 0; }Makefile展开代码语言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 ifeq ($(shell pkg-config --exists libdpdk echo 0),0) 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) PKGCONFpkg-config --define-prefix PC_FILE : $(shell $(PKGCONF) --path libdpdk) 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: mkdir -p $ .PHONY: clean clean: rm -f build/$(APP) build/$(APP)-static build/$(APP)-shared test -d build rmdir -p build || true else # Fallback if pkg-config is not available ifeq ($(RTE_SDK),) $(error Please define RTE_SDK environment variable) endif # Default target, detect a build directory, by looking for a path with a .config RTE_TARGET ? $(notdir $(abspath $(dir $(firstword $(wildcard $(RTE_SDK)/*/.config))))) include $(RTE_SDK)/mk/rte.vars.mk endif四、总结DPDK KNI模块是实现高性能用户空间数据平面与传统内核网络栈无缝集成的关键技术。DPDK应用程序在处理高性能流量的同时控制平面流量、非关键流量或要内核复杂处理的流量卸载到内核充分利用Linux内核的丰富网络功能。开发和部署DPDK KNI模块要注意的几个点DPDK环境配置安装必要的库、设置大页内存hugepages、加载igb_uio或vfio-pci驱动等。理解KNI工作原理 KNI通过在用户空间和内核空间之间共享一个虚拟TAP接口来实现数据包的交换。要理解ifreq结构体、/dev/net/tun设备以及KNI内部的数据包队列机制。KNI API的正确使用初始化rte_kni_init()是KNI子系统启动的第一步。配置与分配rte_kni_conf配置KNI接口的名称、MAC、MTU等并通过rte_kni_ops注册回调函数特别是config_network_if这是内核控制KNI接口UP/DOWN的关键。最后用rte_kni_alloc()创建KNI实例。数据包交换 用rte_kni_tx_burst()把DPDK处理后的数据包发送到内核用rte_kni_rx_burst()从内核接收数据包到DPDK。