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 队列,实现零拷贝或极低拷贝的数据交换。
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 数据流交互过程
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 两个辅助队列。
生命周期流程:
- 预分配: 用户态在初始化时,预先分配一批 mbuf 放入
alloc_q。 - 内核使用: 当 KNI 内核模块需要发送数据时,从
alloc_q取出 mbuf,填充数据后写入tx_q。 - 用户态回收: 用户态从
tx_q读取 mbuf,发送到物理网卡后,将 mbuf 放回free_q。 - 内核回收: 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 原生性能的数据通道。