一、KNI
KNI全称:Kernel NIC Interface,内核网卡接口,允许用户态程序访问linux控制平面。
在DPDK报文处理中,有些报文需要发送到内核协议栈进行处理,如GTP-C控制报文
如果报文数量较少,可以使用内核提供的TAP/TUN设备,但是鉴于这种设备使用的系统调用的方式,还涉及到copy_to_user()和copy_from_user()的开销,因此,提供了KNI接口用于改善用户态和内核态间报文的处理效率。
二、使用KNI的优势
比 Linux TUN/TAP interfaces的操作快(通过取消系统调用copy_to_user()/copy_from_user())。
可使用Linux标准网络工具ethtool, ifconfig和tcpdump管理DPDK端口。
允许端口使用内核网络协议栈。
kni的功能也是分为用户态KNI和内核态KNI两部分的
用户态的KNI代码在lib\librte_kni目录下
内核态的KNI代码在kernel/linux/kni目录下
三、用户态KNI处理
3、1 加载kni内核模块
在加载kni模块时,可以设置它的内核线程模式
insmod kmod/rte_kni.ko kthread_mode=single
insmod kmod/rte_kni.ko kthread_mode=multiple
single模式(默认):只在内核侧创建一个内核线程,来接收所有kni设备上的数据包,一个线程 vs 所有kni设备
multiple模式:每个kni接口创建一个内核线程,用来接收数据包,一个线程 vs 一个kni设备
dpdk在加载kni模块时,默认是采用的single模式,同时还可以为此内核线程设置cpu亲和性
小伙伴们可能疑问,这里的single和multiple模式是什么意思
参考官网链接:https://doc.dpdk.org/guides/prog_guide/kernel_nic_interface.html
例子程序位于examples/kni/main.c文件
3、2 初始化流程
main(int argc, char** argv)
{
/* eal初始化 */
ret = rte_eal_init(argc, argv);
/* 创建mbuf内存池*/
pktmbuf_pool = rte_pktmbuf_pool_create("mbuf_pool", NB_MBUF,
MEMPOOL_CACHE_SZ, 0, MBUF_DATA_SZ, rte_socket_id());
/* 初始化KNI子系统*/
init_kni();
/* 初始化端口port,并调用kni_alloc */
RTE_ETH_FOREACH_DEV(port) {
init_port(port);//初始化端口
kni_alloc(port);//kni申请资源
}
/* Launch per-lcore function on every lcore */
// 每个lcore逻辑核心上运行一个函数main_loop,CALL_MASTER表示在master core上运行
rte_eal_mp_remote_launch(main_loop, NULL, CALL_MASTER);
/* 释放资源 */
RTE_ETH_FOREACH_DEV(port) {
kni_free_kni(port);
}
return 0;
}
下面重点分析下init_kni,kni_alloc,kni_free_kni,main_loop这四个函数。
3、2、1 init_kni函数
init_kni函数中仅仅调用rte_kni_init。
rte_kni_init(unsigned int max_kni_ifaces __rte_unused)
{
/* Check FD and open */
if (kni_fd < 0) {
kni_fd = open("/dev/" KNI_DEVICE, O_RDWR);//打开/dev/kni设备
}
return 0;
}
DPDK在初始化阶段会调用rte_kni_init,打开kni设备。
3、2、2 kni_alloc函数
kni_alloc函数主要调用了rte_kni_alloc函数
问题1:rte_kni_alloc对应KNI内核态的什么代码呢?
答:kni_ioctl_create()函数,后面会分析
3、2、3 kni_free_kni函数
kni_free_kni主要是调用rte_kni_release函数
问题2:rte_kni_release对应KNI内核态的什么代码呢?
答:kni_ioctl_release()函数,后面会分析
3、2、4 主逻辑函数main_loop
定位到main_loop逻辑处理函数
static int
main_loop(__rte_unused void *arg)
{
uint16_t i;
int32_t f_stop;
const unsigned lcore_id = rte_lcore_id();
enum lcore_rxtx {
LCORE_NONE,
LCORE_RX,
LCORE_TX,
LCORE_MAX
};
enum lcore_rxtx flag = LCORE_NONE;
//遍历设备列表,判断当前的lcore逻辑核心是负责rx还是tx
RTE_ETH_FOREACH_DEV(i) {
if (kni_port_params_array[i]->lcore_rx == (uint8_t)lcore_id) {
flag = LCORE_RX;
break;
} else if (kni_port_params_array[i]->lcore_tx == lcore_id) {
flag = LCORE_TX;
break;
}
}
//如果是接收数据,则循环调用kni_ingress,直到f_stop被设置为true跳出循环
if (flag == LCORE_RX) {
while (1) {
kni_ingress(kni_port_params_array[i]);
}
}
//如果是发送数据,则循环调用kni_egress,直到f_stop被设置为true跳出循环
else if (flag == LCORE_TX) {
while (1) {
kni_egress(kni_port_params_array[i]);
}
}
return 0;
}
步骤如下:
获取配置,判断当前lcore是负责rx还是tx
如果lcore负责rx,则死循环调用接口kni_ingress进行报文的收取。
如果lcore负责tx,则死循环调用接口kni_egress进行报文