首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

DPDK-KNI内核网卡接口介绍

1 前言

Kni(Kernel NIC Interface)内核网卡接口,是DPDK允许用户态和内核态交换报文的解决方案,模拟了一个虚拟的网口,提供dpdk的应用程序和linux内核之间通讯。kni接口允许报文从用户态接收后转发到linux协议栈去。

为什么要弄一个kni接口,虽然dpdk的高速转发性能很出色,但是也有自己的一些缺点,比如没有协议栈就是其中一项缺陷,当然也可能当时设计时就将没有将协议栈考虑进去,毕竟协议栈需要将报文转发处理,可能会使性能大大降低了呢。

Kni应用组件如下图:

思考一下在实际工作场景中, kni帮助我们解决了哪些事?

2 Kni 内核模块介绍

在21.11版本的DPDK里,kni是默认打开编译的,如果想打开/关闭编译可以:

需要注意的是在insmod编译出来的kni模块时如果没有携带任何参数,默认carrier状态是关闭的。

编译出来的rte_kni.ko包含一些设置参数:

2.1 Loopback 回环模式

以测试为目的的话,在insmod模块时可以指定为lo_mod模式

lo_mode_fifo回环模式会在内核空间中操作FIFO环队列,kni_fifo_get(kni->rx_q,...)和kni_fifo_put(kni->tx_q,...)实现从rx_q接收队列读取报文,再写入发送队列tx_q来实现回环操作。

lo_mode_fifo_skb回环模式在以上lo_mode_fifo的基础之上,增加了sk_buff缓存的相关拷贝操作。具体包括将rx_q接收队列的数据拷贝到分配的接收skb缓存中。以及分配发送skb缓存,将之前由rx_q队列接收数据拷贝到发送skb缓存中,使用函数kni_net_tx(skb, dev)发送skb缓存数据。将数据报文拷贝到mbuf结构中,使用kni_fifo_put函数加入到tx_q发送队列,更加接近真实的使用场景。

如果没有指定lo_mode参数,回环模式将禁用。

2.2 内核线程模式

为了提供性能的灵活性,内核模块rte_kni在加载时刻指定kthread_mode参数。rte_kni模块支持两个选项:单线程内核模式和多核线程内核模式。

此模式为所有的KNI虚拟接口在内核侧接收数据创建唯一的内核线程。默认情况下,此内核线程不绑定在特定的core上,但是,用户可在创建第一个KNI虚拟接口时通过指定结构体struct rte_kni_conf的core_id和force_bind成员参数,设置此线程的亲核性。

为了提升性能,内核线程应该被绑定在与DPDK线程同一个socket的core上。

KNI内核模块也可配置成为每个DPDK应用创建的KNI虚拟接口启动一个单独的内核线程。以下,使能多线程内核模式:

此模式为每个KNI虚拟接口在内核侧接收数据创建一个单独的内核线程。内核线程的亲核性通过每个KNI虚拟接口创建时的结构体rte_kni_conf成员core_id和force_bind变量参数指定。

如果系统中由足够的未使用core,多线程内核模式可提供具有扩展性的高性能。

如果kthread_mode参数未指定,使用单内核线程模式。

2.3 默认链路状态

内核模块rte_kni创建的KNI虚拟接口的链路状态,可通过模块加装时的carrier选项控制。

如果指定了carrier=off,当接口管理使能时,内核模块将接口的链路状态设置为关闭。DPDK应用通过函数rte_kni_update_link设置KNI虚拟接口的链路状态。这对于需要KNI虚拟接口状态与对应的物理接口实际状态一致的应用是有用的。如果指定了carrier=on,当接口管理使能时,内核模块将自动设置接口的链路状态为启用。这对于仅将KNI接口作为纯虚拟接口,而不对应任何物理硬件;或者并不想通过rte_kni_update_link函数显示设置接口链路状态的DPDK应用。对于物理口为连接任何链路而进行的回环模式测试也适用。

设置默认的链路状态为启用/关闭:

carrier参数没有指定,KNI虚拟接口的默认链路状态为关闭。

2.4 分叉设备支持

用户回调在内核模块持有 rtnl 锁时执行,当回调在另一个 Linux 内核网络接口上运行控制命令时,这会导致死锁。

分叉设备具有内核网络驱动程序部分,为了防止死锁,使用 enable_bifurcated。

要启用分叉设备支持:

启用分叉设备支持在调用回调之前释放 rtnl 锁,并在回调之后将其锁定。还启用异步请求以支持需要 rtnl 锁才能工作的回调(接口关闭)。

2.5 KNI Kthread调度

min_scheduling_interval 和 max_scheduling_interval 参数控制 KNI kthreads 的重新调度间隔。

如果我们有需要改进控制平面流量的延迟或性能的用例,这可能很有用。

该实现由 Linux 高精度计时器支持,并使用 usleep_range。因此,它将具有与Linux 子系统相同的约束粒度。

将 min_scheduling_interval 设置为 100 微秒的值:

将 max_scheduling_interval 设置为 200 微秒的值:

如果未指定 min_scheduling_interval 和 max_scheduling_interval 参数,则默认间隔限制将分别设置为 100 和 200。

3 KNI源码分析

3.1 mbuf管理

先来看双向发送数据包的典型场景,值得注意的是,为了尽量减少在内核空间中运行的 DPDK 代码量,mbuf 内存池仅在用户空间中进行管理。所有的mbuf分配释放都由DPDK应用程序处理。

3.2 DPDK用户态代码

先来看DPDK中KNI 部分的代码,咱们以示例代码中的程序代码为例来看一下KNI的使用:

1.初始化

其实就是open了 kni设备,如果此时安装了kni内核模块,初始化即成功。

2.创建

KNI接口由DPDK应用程序通过rte_kni_alloc()函数动态创建,rte_kni_alloc做了以下几个事。

先来看struct rte_kni这个结构:

所以,就是初始化对应的内容,这个结构中保存着kni 设备需要在数据传输中使用的各种 queue,比如tx_q,rx_q,free_q啥的,每个queue都有对应DPDK的memzone,最后再用struct rte_kni_device_info作为参数调用kni设备的ioctl create

完成之后在allocte mbufs并把它关联到kni结构上

3.发送rte_kni_tx_burst

DPDK侧的主要发送函数为rte_kni_tx_burst

通过调用kni_fifo_put把mbuf物理地址写入fifo中

rte_knit_rx_burst则是调用kni_fifo_get从fifo中拿到mbuf

3.3 内核态代码

对于内核模块如何编写木木这里就不介绍了,不熟悉的建议先去恶补一下,实在读起来吃力的就跳过这个介绍分析章节。

那么开始吧,那就从内核模块加载开始吧,执行insmod的时候,动态加载kni模块

我们来看看kni_init这里面做什么了?上面已经介绍了几种模式,那么kni_parse_kthread_mode()获取是支持单线程内核模式还是多线程内核模式。

kni_parse_carrier_state获取默认carrier状态参数

kni_parse_bifurcated_support获取分叉设备支持参数

让后通过register_pernet_subsys或者register_pernet_gen_subsys注册kni_net_ops, 注册为misc设备后,其工作机制由注册的miscdevice决定

然后将获取到的参数配置信息配置进去kni_net_config_lo_mode

现在来看kni_net_netdev_ops结构包含了哪些接口:

首先来看open接口,DPDK程序会调用rte_kni_init打开kni设备。

再来看内核kni提供了怎样的使用方法, ioctl的支持

一共两个有效的opt,RTE_KNI_IOCTL_CREATE和RTE_KNI_IOCTL_RELEASE分别对应的DPDK用户态的rte_kni_alloc和rte_kni_release,即申请kni interface和释放kni interface。

在rte_kni_alloc中关键的部分kni_reserve_mz申请连续的物理内存給各个ring

而在内核部分的kni_ioctl_create部分在没有支持iova时则将ring的物理地址转换成虚拟地址,这样就能保证kni用户态和内核态一致了

然后就是注册启动内核线程了

来到kni_run_thread中看

如果KNI模块的参数指定了多线程模式,每创建一个kni设备,就创建一个内核线程。如果为单线程模式,则检查是否已经启动了kni_thread。没有的话,创建唯一的kni内核thread kni_single

在持有读锁的情况下,遍历所有的kni设备,执行接收动作,根据rte_kni.ko加载时的模块参数lo_mode的值不同,执行不同的动作。看rx_normal_mode:

循环处理收到的kni 数据,将内容复制到申请的skb中

再通过netif_rx_ni将skb传给内核协议栈处理,对于KNI驱动来说,即kni_net_tx。

对于kni_net_tx来说,则是从从alloc_q取一个内存块,将其转换为虚拟地址,然后将skb的数据复制过去,最后将其追加到发送队列tx_q中。

接收报文时,从kni->tx_q直接取走所有报文。前面内核用KNI发送报文时,填充的就是这个fifo。当取走了报文后,DPDK应用层的调用kni_allocate_mbufs,负责给tx_q填充空闲mbuf,供内核使用。

而rte_kni_tx_burst,先将要发送给KNI的报文地址转换为物理地址,然后enqueue到kni->rx_q中(内核的KNI实现也是从这个fifo中读取报文),最后调用kni_free_mbufs释放掉内核处理完的mbuf报文。

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20230519A0A3P400?refer=cp_1026
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券