dpdk之kni使用


难度 中等

什么是kni

Kni(Kernel NIC Interface)内核网卡接口,是DPDK允许用户态和内核态交换报文的解决方案,模拟了一个虚拟的网口,提供dpdk的应用程序和linux内核之间通讯。kni接口允许报文从用户态接收后转发到linu协议栈去。 为什么要弄一个kni接口,虽然dpdk的高速转发性能很出色,但是也有自己的一些缺点,比如没有协议栈就是其中一项缺陷,当然也可能当时设计时就将没有将协议栈考虑进去,毕竟协议栈需要将报文转发处理,可能会使处理报文的能力大大降低。

当kni向linux发送报文时通过调用netif_rx()将报文送入linux协议栈,这其中需要将dpdk的mbuf转换成skb_buf。

当linux向kni端口发送报文时,调用回调函数kni_net_tx(),然后报文经过转换之后发送到端口上。

kni优势

  • 相较现存的Linux TUN/TAP接口更快的速度(消除了系统调用以及copy_to_user()/copy_from_user()内存拷贝的消耗)
  • 允许标准Linux网络工具管理DPDK接口,如ethtool, ifconfig 和 tcpdump
  • 提供到内核协议栈接口

kni代码分析

和igb uio模块一样,kni模块分成内核以及用户态代码,内核模块在编译出来之后为rte_kni.ko,首先插入内核,dpdk提供了一个用户态的例子。首先看下kni内核模块代码,在kni_misc.c中,ko代码入口为

module_init(kni_init);

首先选择kni的线程模式,分为单线程还是多线程,所谓单线程是指所有的kni端口收发都由一个线程守护,多线程只是每一个kni端口分为由一个线程守护,这部分是在插入模块时带入参数选择。 接着调用注册函数misc_register,将kni注册为一个混杂设备。其中kni_misc结构体里面定义了该混杂设备的一些操作:

static struct miscdevice kni_misc = {
  .minor = MISC_DYNAMIC_MINOR,
  .name = KNI_DEVICE,
  .fops = &kni_fops,
};

static struct file_operations kni_fops = {
    .owner = THIS_MODULE,
    .open = kni_open,
    .release = kni_release,
    .unlocked_ioctl = (void *)kni_ioctl,
    .compat_ioctl = (void *)kni_compat_ioctl,
};

这里涉及的主要操作有kni_open,kni_release,以及kni_ioctl,分别对应几个函数。

  • kni_open

kni_open时如果是单线程模式则会创建一个内核线程,并打开dev/kni,这个时候在host的dev下能看到kni文件夹

  • kni_ioctl

kni_ioctl函数是与用户态通信的一个接口,主要是kni_ioctl_create函数。

ret = copy_from_user(&dev_info, (void *)ioctl_param, sizeof(dev_info));这条语句会拷贝从用户态传过来的消息,dev_info主要存放了虚拟kni网口的相关参数,接下来就会根据dev_info中的参数注册一个kni网口ret = register_netdev(net_dev);

这个函数完成创建,这样就虚拟出一个网口出来。其中165行是自己修改的,因为我发现按照文档提供的方法根本不能ping通报文,我将生成kni的mac地址修改成dpdk接管的网口mac即可贯通。原生态代码是随时生成一个mac。

  • 杂项设备(misc device)

杂项设备也是在嵌入式系统中用得比较多的一种设备驱动。在 Linux 内核的include/linux目录下有Miscdevice.h文件,要把自己定义的misc device从设备定义在这里。其实是因为这些字符设备不符合预先确定的字符设备范畴,所有这些设备采用主编号10 ,一起归于misc device,其实misc_register就是用主标号10调用register_chrdev()的。

也就是说,misc设备其实也就是特殊的字符设备,可自动生成设备节点。

dpdk mbuf与sk_buf转换

收包时需调用netif_receive_skb(skb)或netif_rx_ni(skb)通知内核处理包,发包时直接由内核调用ndo_start_xmit发出。

  • mbuf->sk_buf(收包)
/*
 * RX: normal working mode
 */
static void
kni_net_rx_normal(struct kni_dev *kni)
{
    unsigned ret;
    uint32_t len;
    unsigned i, num, num_rq, num_fq;
    struct rte_kni_mbuf *kva;
    struct rte_kni_mbuf *va[MBUF_BURST_SZ];
    void * data_kva;
 
    struct sk_buff *skb;
    struct net_device *dev = kni->net_dev;
 
    /* 每次收包的个数必须为rx_q和free_q的最小值且不超过MBUF_BURST_SZ */
 
    /* Get the number of entries in rx_q */
    num_rq = kni_fifo_count(kni->rx_q);
 
    /* Get the number of free entries in free_q */
    num_fq = kni_fifo_free_count(kni->free_q);
 
    /* Calculate the number of entries to dequeue in rx_q */
    num = min(num_rq, num_fq);
    num = min(num, (unsigned)MBUF_BURST_SZ);
 
    /* Return if no entry in rx_q and no free entry in free_q */
    if (num == 0)
        return;
 
    /* Burst dequeue from rx_q */
    ret = kni_fifo_get(kni->rx_q, (void **)va, num);
    if (ret == 0)
        return; /* Failing should not happen */
 
    /* mbuf转换为skb */
    /* Transfer received packets to netif */
    for (i = 0; i < num; i++) {
        /* mbuf kva */
        kva = (void *)va[i] - kni->mbuf_va + kni->mbuf_kva;
        len = kva->data_len;
        /* data kva */
        data_kva = kva->data - kni->mbuf_va + kni->mbuf_kva;
 
        skb = dev_alloc_skb(len + 2);
        if (!skb) {
            KNI_ERR("Out of mem, dropping pktsn");
            /* Update statistics */
            kni->stats.rx_dropped++;
        }
        else {
            /* Align IP on 16B boundary */
            skb_reserve(skb, 2);
            memcpy(skb_put(skb, len), data_kva, len);
            skb->dev = dev;
            skb->protocol = eth_type_trans(skb, dev);
            skb->ip_summed = CHECKSUM_UNNECESSARY;
 
            /* 发送skb到协议栈 */
            /* Call netif interface */
            netif_receive_skb(skb);
            /* Call netif interface */
        //netif_rx_ni(skb);//dpdk18.11
 
            /* Update statistics */
            kni->stats.rx_bytes += len;
            kni->stats.rx_packets++;
        }
    }
 
    /* 通知用户空间释放mbuf */
    /* Burst enqueue mbufs into free_q */
    ret = kni_fifo_put(kni->free_q, (void **)va, num);
    if (ret != num)
        /* Failing should not happen */
        KNI_ERR("Fail to enqueue entries into free_qn");
}
  • sk_buf->mbuf(发包)
static int
kni_net_tx(struct sk_buff *skb, struct net_device *dev)
{
    int len = 0;
    unsigned ret;
    struct kni_dev *kni = netdev_priv(dev);
    struct rte_kni_mbuf *pkt_kva = NULL;
    struct rte_kni_mbuf *pkt_va = NULL;
 
    dev->trans_start = jiffies; /* save the timestamp */
 
    /* Check if the length of skb is less than mbuf size */
    if (skb->len > kni->mbuf_size)
        goto drop;
 
    /**
     * Check if it has at least one free entry in tx_q and
     * one entry in alloc_q.
     */
    if (kni_fifo_free_count(kni->tx_q) == 0 ||
            kni_fifo_count(kni->alloc_q) == 0) {
        /**
         * If no free entry in tx_q or no entry in alloc_q,
         * drops skb and goes out.
         */
        goto drop;
    }
 
    /* skb转mbuf */
    /* dequeue a mbuf from alloc_q */
    ret = kni_fifo_get(kni->alloc_q, (void **)&pkt_va, 1);
    if (likely(ret == 1)) {
        void *data_kva;
 
        pkt_kva = (void *)pkt_va - kni->mbuf_va + kni->mbuf_kva;
        data_kva = pkt_kva->data - kni->mbuf_va + kni->mbuf_kva;
 
        len = skb->len;
        memcpy(data_kva, skb->data, len);
        if (unlikely(len < ETH_ZLEN)) {
            memset(data_kva + len, 0, ETH_ZLEN - len);
            len = ETH_ZLEN;
        }
        pkt_kva->pkt_len = len;
        pkt_kva->data_len = len;
 
        /* enqueue mbuf into tx_q */
        ret = kni_fifo_put(kni->tx_q, (void **)&pkt_va, 1);
        if (unlikely(ret != 1)) {
            /* Failing should not happen */
            KNI_ERR("Fail to enqueue mbuf into tx_qn");
            goto drop;
        }
    } else {
        /* Failing should not happen */
        KNI_ERR("Fail to dequeue mbuf from alloc_qn");
        goto drop;
    }
 
    /* Free skb and update statistics */
    dev_kfree_skb(skb);
    kni->stats.tx_bytes += len;
    kni->stats.tx_packets++;
 
    return NETDEV_TX_OK;
 
drop:
    /* Free skb and update statistics */
    dev_kfree_skb(skb);
    kni->stats.tx_dropped++;
 
    return NETDEV_TX_OK;
}

kni运行

编译

  • x86
make config T=x86_64-native-linuxapp-gcc EXTRA_CFLAGS='-g -Ofast -fPIC -ftls-model=local-dynamic'
make T=x86_64-native-linuxapp-gcc  CONFIG_RTE_KNI_KMOD=y CONFIG_RTE_EAL_IGB_UIO=y EXTRA_CFLAGS='-g -Ofast -fPIC -ftls-model=local-dynamic' install -j 8
make examples T=x86_64-native-linuxapp-gcc O=x86_64-native-linuxapp-gcc -j16
  • dpaa
export CROSS=/opt/cross/sysroots/x86_64-fslsdk-linux/usr/bin/aarch64-fsl-linux/aarch64-fsl-linux-
export RTE_KERNELDIR=/home/duanyingshou/linux
make config T=arm64-dpaa2-linuxapp-gcc CROSS=aarch64-fsl-linux- CROSS_COMPILE="aarch64-fsl-linux-"
CC="/opt/cross/sysroots/x86_64-fslsdk-linux/usr/bin/aarch64-fsl-linux/aarch64-fsl-linux-gcc --sysroot=/opt/cross/sysroots/aarch64-fsl-linux -fPIC -g"  EXTRA_CFLAGS='-g -Ofast -fPIC -ftls-model=local-dynamic'
make examples T=arm64-dpaa2-linuxapp-gcc CC="/opt/cross/sysroots/x86_64-fslsdk-linux/usr/bin/aarch64-fsl-linux/aarch64-fsl-linux-gcc --sysroot=/opt/cross/sysroots/aarch64-fsl-linux -fPIC" EXTRA_CFLAGS='-g -Ofast -fPIC -ftls-model=local-dynamic -I/opt/cross/sysroots/aarch64-fsl-linux/usr/include' CONFIG_RTE_KNI_KMOD=y CONFIG_RTE_EAL_IGB_UIO=n install -j 32

执行

kni [EAL options] -- -p PORTMASK --config="(port,lcore_rx,lcore_tx[,lcore_kthread,...])[,(port,lcore_rx,lcore_tx[,lcore_kthread,...])]" [-P] [-m]

-p PORTMASK:
十六进制接口掩码
--config="(port,lcore_rx,lcore_tx[,lcore_kthread,...])[,(port,lcore_rx,lcore_tx[,lcore_kthread,...])]":
指定对于每个物理网口,接收和发送DPDK线程绑定的核心,以及KNI内核线程绑定的核心
-P:
可选标志,设置的话意味着混杂模式,以便不区分以太网目的MAC地址,接收所有报文。不设置此选项,仅目的MAC地址等于接口MAC地址的报文被接收
-m:
可选标志。使能监控模式并更新以太网链路状态。此选项需要启动一个DPDK线程定期检查物理接口链路状态,同步相应的KNI虚拟网口状态。意味着当以太网链路down的时候,KNI虚拟接口将自动禁用,反之,自动启用。
# rmmod rte_kni
# insmod kmod/rte_kni.ko
# sudo ./kni -l 0-3 -n 4 -- -P -p 0x3 -m --config="(0,0,1),(1,2,3)"
# -l 0-3 使用0-3核
# -n 4   使用4个核
# -p 0x3 (11)使用两个网口,port 0和port 1
# --config="(0,0,1),(1,2,3)" 
# (0,0,1) port 0 使用0核rx,1核tx
# (1,2,3) port 1 使用2核rx,3核tx
./kni -l 0-1 -n 2 -- -p 0x1 -P --config="(0,0,1)"

ifconfig vEth0 121.168.1.12/24

定位追踪

在kni_net.c的kni_rx_normal中的382行添加如下代码:

pr_info("kni recv a packetn");

使用dmesg观察输出。

RTE_LOG(INFO, APP, "kni send %lu packetsn",kni_stats[port_id].tx_packets);

测试

一端使用pktgen回放报文到kni口,分别使用大包和小包。大包使用GTPU报文或者其他TCP/IP报文,小包使用arp报文。

  • 大包(包长1460)
./app/x86_64-native-linuxapp-gcc/pktgen -c 0xe0000 --socket-mem 1024 -n 2 -- -P -m [18:19].0 -s 0:gtpu.pcap -T --crc-strip
  • 小包()
./app/x86_64-native-linuxapp-gcc/pktgen -c 0xe0000 --socket-mem 1024 -n 2 -- -P -m [18:19].0 -s 0:arp_request.pcap -T --crc-strip

kni端的ip需要根据抓包报文进行修改。

10G网口大包大致可以跑到4Gbps线速,小包10G网口大致可以跑到50Mbps线速。

reference

  1. https://www.cnblogs.com/kb342/p/6033139.html
  2. https://blog.csdn.net/u013216061/article/details/72511653
  3. https://www.freesion.com/article/148821991/
  4. https://blog.csdn.net/whenloce/article/details/94591357
  5. https://blog.csdn.net/nanhangfengshuai/article/details/50533230

文章作者: growdu
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 growdu !
  目录
分类导航
随笔3 AI29 计算机基础13 算法1 博客搭建7 ChatGPT2 集群63 计算机通信1 数据库59 数据库深入93 数据库93 Docker11 DPDK26 编辑工具4 Elasticsearch4 FAQ1 Go Web1 hometown2 编程语言16 人生11 网络9 Linux38 OPC1 openGauss4 页面12 PostgreSQL54 程序员自我修养1 协议11 stock1 存储5 成长之路1 工具20 视频作品1 VPP18 Vue13 Web1 代码示例11 BenchmarkSQL1 PostgreSQL 源码修炼之路14
最热文章
1
PostgreSQL 逻辑复制全链路:以一个真实事务为例,从 `INSERT` 到 `apply` 的源码级旅程
数据库🔥 2162
2
PostgreSQL 逻辑复制 Stream 执行逻辑源码级拆解:从 reorderbuffer 触发到订阅端落盘回放
数据库🔥 1765
3
PostgreSQL 行锁机制源码级深度拆解:4 种锁模式 + xmax + MultiXact 的完整链路
数据库🔥 1656
4
PostgreSQL reorderbuffer.c 函数级源码全集:5615 行 / 95 个函数的逐函数解析
数据库深入🔥 1650
5
PostgreSQL 目录快照源码级深度拆解:系统表读取的 MVCC 真相与失效机制
数据库🔥 1604
6
13 逻辑复制深入
数据库深入🔥 1570
7
0 Postgresql存储、索引及系统优化、主备切换
PostgreSQL🔥 1495
8
PostgreSQL 读一行数据源码级深度拆解:MVCC 在每个环节的落点
数据库🔥 1464
9
PostgreSQL WAL 记录类型完全指南:21 个 RMGR × 80+ 种记录的源码级拆解
数据库🔥 1431
10
一文读懂openguass dcf网络模块
集群🔥 1420
11
PostgreSQL 元数据存储机制:从磁盘文件到内存缓存,`pg_class` 撑起的整个系统表体系
数据库🔥 1411
12
openGauss 并行解码设计原理深度拆解:reader + 多 worker + CSN 三层架构的源码级剖析
数据库深入🔥 1344
13
逻辑复制源码分析
数据库深入🔥 1327
14
PostgreSQL 逻辑复制:catalog 改动、可见性判定与 xmin/xmax 推进全景
数据库深入🔥 1317
15
自托管服务全家桶:从原文方案到可复制实操手册
人生🔥 1278
16
PostgreSQL CREATE_REPLICATION_SLOT 端到端源码分析:从 SQL 到 snapbuild 状态机的完整链路(每个函数每个参数)
数据库深入🔥 1277
17
PostgreSQL syscache 模块源码深度拆解:CatCache 数据结构、LRU 哈希表、cache invalidation 全链路
数据库深入🔥 1216
18
PostgreSQL 18 内核数据结构全景:RangeVar 与 20+ 个核心数据结构源码级拆解
数据库🔥 1158
19
PostgreSQL 逻辑复制里 `RUNNING_XACTS` 写入机制与快照 `xmin / xmax / xip` 的演化:源码级深度拆解
数据库🔥 1119
20
PostgreSQL 分区表:从一行 `PARTITION BY` 到路由热路径的全链路拆解
数据库🔥 1094