4. KNI 模块原理:KNI 内核模块架构、kni_dev 结构体、FIFO 队列与 mbuf 交互

KNI(Kernel NIC Interface)是 DPDK 提供的一种高效数据通道,允许用户态应用程序与 Linux 内核协议栈之间交换数据包。本章深入剖析 KNI 内核模块的架构设计、核心数据结构 kni_dev、FIFO 队列机制以及 mbuf 在内核与用户态之间的流转过程。

4.1 KNI 内核模块架构

KNI 内核模块位于 Linux 内核空间,作为用户态 DPDK 应用与内核网络协议栈之间的桥梁。其核心设计思想是:在用户态保留 DPDK 的高性能数据面,同时将需要内核协议栈处理的数据包(如 ARP、TCP 控制报文)通过 KNI 接口注入内核。

架构要点: KNI 模块通过创建虚拟网络设备(kni虚拟网卡)与用户态共享内存 FIFO 队列,实现零拷贝或极低拷贝的数据交换。
KNI 内核模块架构总览 用户态 (DPDK 应用) DPDK 主循环 / KNI 示例程序 rte_kni_rx_burst() rte_kni_tx_burst() mbuf 池 (mempool) 内核态 (KNI 模块) kni_dev 结构体实例 FIFO 队列 (rx_q / tx_q) kni_net_rx() / kni_net_tx() 虚拟网络设备 (ethX) 共享内存 FIFO 队列 (mmap) rx_q: 用户态 → 内核态 | tx_q: 内核态 → 用户态 内核协议栈

4.2 kni_dev 结构体

kni_dev 是 KNI 内核模块中每个虚拟网卡实例的核心数据结构,它串联了网络设备、FIFO 队列、内存映射等关键资源。每个通过 rte_kni_alloc() 创建的 KNI 接口,在内核中都对应一个 kni_dev 实例。

字段名 类型 描述
net_dev struct net_device * 关联的内核网络设备结构体
rx_q struct rte_kni_fifo * 接收 FIFO 队列(用户态→内核态)
tx_q struct rte_kni_fifo * 发送 FIFO 队列(内核态→用户态)
alloc_q struct rte_kni_fifo * mbuf 分配队列(内核态向用户态申请空 mbuf)
free_q struct rte_kni_fifo * mbuf 释放队列(用户态回收已处理 mbuf)
group_id uint16_t KNI 设备所属组 ID,用于多实例管理
mbuf_size unsigned int 每个 mbuf 的数据缓冲区大小
in_use uint8_t 设备使用状态标记
设计意图: kni_dev 通过 rx_q 和 tx_q 两个 FIFO 实现双向数据通道,而 alloc_q 和 free_q 则用于管理 mbuf 的生命周期,避免内核直接操作 DPDK 的 mempool。

4.3 FIFO 队列与 mbuf 交互

KNI 的核心数据交换机制基于无锁 FIFO 队列。这些队列位于通过 mmap 共享的内存区域中,用户态和内核态通过读写相同的 FIFO 来实现数据传递,无需系统调用。

4.3.1 FIFO 队列类型

  • rx_q(接收队列): 用户态 DPDK 应用将从网卡收到的数据包(mbuf 指针)写入此队列,KNI 内核模块从中读取并送入内核协议栈。
  • tx_q(发送队列): 内核协议栈发出的数据包由 KNI 模块写入此队列,用户态 DPDK 应用读取并发送到物理网卡。
  • alloc_q(分配队列): 当 KNI 内核模块需要发送数据时,从此队列获取预先分配的 mbuf,避免内核直接调用 DPDK 内存分配。
  • free_q(释放队列): 用户态 DPDK 应用将已经处理完毕的 mbuf 放回此队列,KNI 内核模块回收并归还给 mempool。

4.3.2 数据流交互过程

mbuf 在 FIFO 队列中的流转过程 用户态 DPDK 应用 ① 从网卡接收数据包 ② 判断需要内核处理 ③ 将 mbuf 指针写入 rx_q ④ 从 tx_q 读取内核发出的 mbuf 内核态 KNI 模块 ⑤ 从 rx_q 读取 mbuf 指针 ⑥ 构造 skb 并送入协议栈 ⑦ 协议栈发出数据包 ⑧ 将 mbuf 指针写入 tx_q 共享内存 FIFO 队列 rx_q: [mbuf_ptr_1] [mbuf_ptr_2] ... | tx_q: [mbuf_ptr_3] [mbuf_ptr_4] ... 关键点:FIFO 中传递的是 mbuf 指针(虚拟地址), 而非数据本身,实现零拷贝数据交换。

4.3.3 关键操作函数

函数 所在层 功能
rte_kni_rx_burst() 用户态 从 KNI 设备的 tx_q 中读取一批 mbuf 指针(接收内核发出的数据)
rte_kni_tx_burst() 用户态 将一批 mbuf 指针写入 KNI 设备的 rx_q(发送数据到内核)
kni_net_rx() 内核态 从 rx_q 读取 mbuf,转换为 skb 并调用 netif_rx() 送入协议栈
kni_net_tx() 内核态 将内核发出的 skb 转换为 mbuf,写入 tx_q

4.4 mbuf 生命周期管理

mbuf 的生命周期管理是 KNI 性能的关键。为了避免内核态直接操作 DPDK 的 mempool(这会导致锁竞争和内存管理复杂化),KNI 引入了 alloc_q 和 free_q 两个辅助队列。

生命周期流程:
  1. 预分配: 用户态在初始化时,预先分配一批 mbuf 放入 alloc_q。
  2. 内核使用: 当 KNI 内核模块需要发送数据时,从 alloc_q 取出 mbuf,填充数据后写入 tx_q。
  3. 用户态回收: 用户态从 tx_q 读取 mbuf,发送到物理网卡后,将 mbuf 放回 free_q。
  4. 内核回收: KNI 内核模块定期从 free_q 回收 mbuf,归还给 alloc_q 或直接释放。
注意事项: 如果 alloc_q 为空,KNI 内核模块将无法发送数据包,可能导致内核协议栈的发送队列阻塞。因此,用户态应用需要保证 alloc_q 中有足够的 mbuf 储备。

4.5 性能对比要点

对比维度 KNI 方式 传统方式(TUN/TAP)
数据拷贝次数 0~1 次(仅指针传递) 2~3 次(用户态↔内核态拷贝)
系统调用 无(基于共享内存) 每次读写均需系统调用
上下文切换 无 每次数据交换均发生
吞吐量 高(接近线速) 低(受限于系统调用开销)
适用场景 高性能数据面 + 控制面交互 通用虚拟网络设备

KNI 通过共享内存 FIFO 和 mbuf 指针传递,避免了传统虚拟网络设备中频繁的系统调用和数据拷贝,在需要与内核协议栈交互的场景下提供了接近 DPDK 原生性能的数据通道。