DPDK内核模块KNI


DPDK Kernel NIC Interface (KNI)接口允许DPDK用户程序访问Linux控制平面。

使用DPDK KNI的有点如下:

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

使用DPDK的内核KNI虚拟接口的应用程序组件如下图所示。

DPDK内核模块KNI_第1张图片

 

DPDK KNI内核模块


KNI内核可加载模块rte_kni为DPDK应用提供内核接口。

当rte_kni模块加载时,创建/dev/kni设备节点(rte_kni模块创建kni杂项设备,文件系统节点/dev/kni需要手动或者通过udev机制创建),藉此节点,DPDK KNI应用可控制和与内核rte_kni模块交互。

在内核模块rte_kni加载时,可指定一些可选的参数以控制其行为:

# modinfo rte_kni.ko
lo_mode:        KNI loopback mode (default=lo_mode_none):
                lo_mode_none        Kernel loopback disabled
                lo_mode_fifo        Enable kernel loopback with fifo
                lo_mode_fifo_skb    Enable kernel loopback with fifo and skb buffer

kthread_mode:   Kernel thread mode (default=single):
                single    Single kernel thread mode enabled.
                multiple  Multiple kernel thread mode enabled.

carrier:        Default carrier state for KNI interface (default=off):
                off   Interfaces will be created with carrier state set to off.
                on    Interfaces will be created with carrier state set to on.

 

典型的情况是,在加载rte_kni模块时不指定任何参数,DPDK应用可由内核网络协议栈获取和向其发送报文。不指定任何参数,意味着仅创建一个内核线程处理所有的KNI虚拟设备在内核侧的报文接收,并且禁用回环模式,KNI接口的默认链路状态为关闭off。

# insmod kmod/rte_kni.ko

 

回环模式


以测试为目的,在加载rte_kni模块式可指定lo_mode参数:

# insmod kmod/rte_kni.ko lo_mode=lo_mode_fifo
lo_mode_fifo回环模式将在内核空间中操作FIFO环队列,由函数kni_fifo_get(kni->rx_q,...)和kni_fifo_put(kni->tx_q,...)实现从rx_q接收队列读取报文,再写入发送队列tx_q来实现回环操作。


# insmod kmod/rte_kni.ko lo_mode=lo_mode_fifo_skb

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参数,回环模式将禁用。


内核线程模式


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


如下,使能单内核线程模式:

# insmod kmod/rte_kni.ko kthread_mode=single

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


为达到更高性能,内核线程绑定的核心应当与应用中DPDK的核心在同一个socket上。

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

# insmod kmod/rte_kni.ko kthread_mode=multiple

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


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

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


默认链路状态


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


如果指定了carrier=off,当接口管理使能时,内核模块将接口的链路状态设置为关闭。DPDK应用可通过函数rte_kni_update_link设置KNI虚拟接口的链路状态。这对于需要KNI虚拟接口状态与对应的物理接口实际状态一致的应用是有用的。


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


以下,设置默认的链路状态为启用:

# insmod kmod/rte_kni.ko carrier=on

以下,设置默认的链路状态为关闭:

# insmod kmod/rte_kni.ko carrier=off

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


KNI创建和删除


在任何KNI虚拟接口创建之前,rte_kni内核模块必须加装到内核,并且经由rte_kni_init函数配置(获取/dev/kni设备节点文件句柄)。

int 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);
        if (kni_fd < 0) {
            RTE_LOG(ERR, KNI,
                "Can not open /dev/%s\n", KNI_DEVICE);
            return -1;
        }
}

KNI虚拟接口由DPDK应用通过rte_kni_alloc函数动态的创建。


结构体rte_kni_conf包含的成员字段,允许用户指定KNI虚拟接口的名称、设置MTU值大小、设置明确的或者随机的MAC地址,以及控制内核接收线程的亲核性(单线程和多线程模式)。默认情况下,KNI示例程序由对应的物理接口获取MTU值,但是对于KNI PMD程序,其由mbuf的缓存长度决定MTU值。

struct rte_kni_conf {
    /*
     * KNI name which will be used in relevant network device. Let the name as short as possible, as it will be part of memzone name.
     */
    char name[RTE_KNI_NAMESIZE];
    uint32_t core_id;   /* Core ID to bind kernel thread on */
    uint16_t group_id;  /* Group ID */
    unsigned mbuf_size; /* mbuf size */
    struct rte_pci_addr addr;
    struct rte_pci_id id;

    __extension__
    uint8_t force_bind : 1; /* Flag to bind kernel thread */
    char mac_addr[ETHER_ADDR_LEN]; /* MAC address assigned to KNI */
    uint16_t mtu;
};


结构体rte_kni_ops包含的指针,执行处理来自rte_kni内核模块请求的函数。当KNI虚拟接口被一些控制命令(工具如ifconfig或者ethtool等)或者应用之外的函数操作时,这些rte_kni_ops指针函数允许DPDK应用执行一些注册好的操作。

struct rte_kni_ops {
    uint16_t port_id; /* Port ID */

    /* Pointer to function of changing MTU */
    int (*change_mtu)(uint16_t port_id, unsigned int new_mtu);

    /* Pointer to function of configuring network interface */
    int (*config_network_if)(uint16_t port_id, uint8_t if_up);

    /* Pointer to function of configuring mac address */
    int (*config_mac_address)(uint16_t port_id, uint8_t mac_addr[]);

    /* Pointer to function of configuring promiscuous mode */
    int (*config_promiscusity)(uint16_t port_id, uint8_t to_on);
};

 
例如,当DPDK应用想在用户通过ip link set [up|down] dev 命令使能或者禁用KNI虚拟接口时,同时使能或者禁止对应的物理网口。DPDK应用可注意一个config_network_if的回调函数,以便在接口管理状态改变时调用执行。


当前有4个回调可供用户注册应用函数:


config_network_if:

当KNI虚拟接口的管理状态改变时调用。例如,用户运行命令ip link set [up|down] dev


change_mtu:

当用户改变KNI虚拟接口的MTU值时调用。例如,当用户运行命令ip link set mtu dev 时。


config_mac_address:

当用户改变KNI虚拟接口的MAC地址时调用。例如,当用户执行命令ip link set address dev 时。当用户设置此回调指针为NULL空时,如果port_id不等于-1,rte_kni库中的默认回调函数kni_config_mac_address()将被调用,其使用指定的port_id参数调用rte_eth_dev_default_mac_addr_set()处理。


config_promiscusity:

当用户修改KNI虚拟接口的混杂模式时调用。例如,当用户运行命令ip link set promisc [on|off] dev 时。如果用户指定此回调函数为空,但是调用时指定的port_id不为空时,rte_kni库中的默认回调函数kni_config_promiscusity()将被调用,其使用指定的port_id参数调用函数rte_eth_promiscuous_()处理。


为了处理这些回调,DPDK应用必须周期性的运行rte_kni_handle_request()函数。由于注册的用户回调函数直接由rte_kni_handle_request()调用,所以必须注意避免死锁,以及避免阻塞DPDK快速路径的任务。典型的,使用这些回调的DPDK应用需要创建单独的线程或者子进程去定期调用rte_kni_handle_request()函数。


DPDK应用可使用函数rte_kni_release()删除KNI虚拟接口。所有没有被显示删除的kNI虚拟接口在/dev/kni节点关闭的时候一同被删除,可以显示的由rte_kni_close函数触发,或者当DPDK应用关闭时。

void rte_kni_close(void)
{
    if (kni_fd < 0)
        return;

    close(kni_fd);
    kni_fd = -1;
}


DPDK的mbuf流程

为最大限度的减小在内核空间运行的DPDK代码,mbuf内存池仅在用户空间管理。内核模块知道mbuf的存在,但是所有mbuf的分配和释放操作都仅由DPDK应用程序处理。


下图显示了典型的报文在两个方向的发送情况:

DPDK内核模块KNI_第2张图片


用例:输入


在DPDK接收侧,mbuf由PMD在接收线程上下文分配。此线程将mbuf加入对接收FIFO队列rx_q中。内核KNI线程poll轮询所有的活动KNI虚拟接口的接收队列rx_q。如果将mbuf移出队列,需要将其转换成sk_buff结构,通过netif_rx发送到Linux网络协议栈。出队列的mbuf必须被释放,所以其被发送回free_q释放队列的FIFO中,最终由DPDK应用释放。


DPDK接收线程中,位于相同的主循环中,轮询释放队列free_q FIFO,在mbuf移出队列后,进行释放。 

用例:输出


为实现报文发送,DPDK应用必须先行入队列一些mbuf,以便内核侧获得mbuf缓存。


通过调用kni_net_tx函数,内核将报文发送出去。mbuf由alloc_q队列移出(由于之前的mbuf缓存此处无需等待),填充上sk_buff中的数据。sk_buff结构随后被释放,而mbuf缓存通过tx_q表示的FIFO队列发送到DPDK应用。


DPDK发送线程将mbuf移出队列,通过rte_eth_tx_burst()函数发送给PMD。随后,将mbuf回送到alloc_q中,以供内核侧发送使用。

Ethtool


Ethtool是Linux特有的工具,需要内核中每个网络设备注册自身的回调以支持其相应的操作。当前的ethtool实现使用了修改过的igb/ixgbe内核驱动。i40e网卡和VM(VF或者EM设备)不支持Ethtool工具。

 

END

 

你可能感兴趣的:(DPDK)