dpdk kni学习

相关概念

Kernel NIC Interface (KNI) 是dpdk提供的允许用户面的应用报文访问内核协议栈接口库。
kni主要的特点:

  • mbuf到skb转化,只需要一次内存拷贝,中间mbuf从用户态传到内核态,走的是内存零拷贝,中间没有系统调用和copy_to_user()/copy_from_user() 操作;
  • 允许用户通过标准的linux net tools查看dpdk的报文
  • 报文进正常的内核协议栈

初始化

  1. 需要安装dpdk提供的kni ko模块
  2. 初始化pmd的时候,需要初始化kni的ioctl, init_kni
  3. 调用kni_alloc初始化内核网口设备驱动信息,主要包含:
    网口设备驱动信息,比如pci地址,mac和mtu,网口名字等,主要结构体是rte_kni_conf
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_alloc里面,需要申请网口的tx队列、rx队列、alloc队列、free队列内存
主要结构体如下

struct rte_kni {
	char name[RTE_KNI_NAMESIZE];        /**< KNI interface name */
	uint16_t group_id;                  /**< Group ID of KNI devices */
	uint32_t slot_id;                   /**< KNI pool slot ID */
	struct rte_mempool *pktmbuf_pool;   /**< pkt mbuf mempool */
	unsigned mbuf_size;                 /**< mbuf size */

	const struct rte_memzone *m_tx_q;   /**< TX queue memzone */
	const struct rte_memzone *m_rx_q;   /**< RX queue memzone */
	const struct rte_memzone *m_alloc_q;/**< Alloc queue memzone */
	const struct rte_memzone *m_free_q; /**< Free queue memzone */

	struct rte_kni_fifo *tx_q;          /**< TX queue */
	struct rte_kni_fifo *rx_q;          /**< RX queue */
	struct rte_kni_fifo *alloc_q;       /**< Allocated mbufs queue */
	struct rte_kni_fifo *free_q;        /**< To be freed mbufs queue */

	const struct rte_memzone *m_req_q;  /**< Request queue memzone */
	const struct rte_memzone *m_resp_q; /**< Response queue memzone */
	const struct rte_memzone *m_sync_addr;/**< Sync addr memzone */

	/* For request & response */
	struct rte_kni_fifo *req_q;         /**< Request queue */
	struct rte_kni_fifo *resp_q;        /**< Response queue */
	void * sync_addr;                   /**< Req/Resp Mem address */

	struct rte_kni_ops ops;             /**< operations for request */
};

这里以txq为例,其他的fifo申请释放使用原理一样
给txq申请内存 kni->m_tx_q = rte_memzone_reserve(mz_name, KNI_FIFO_SIZE, SOCKET_ID_ANY, 0);
这里rte_memzone_reserve申请来的内存是从可以保证物理地址连续的,也就是说,这个物理地址,在内核态和用户态都是一样的,只需要在相应层面将其转换为自己空间可用的虚拟地址,就可以正常使用。
初始化txq队列 kni_fifo_init(kni->tx_q, KNI_FIFO_COUNT_MAX);
把txq的物理地址保存到dev_info.tx_phys
dev_info.tx_phys = kni->m_tx_q->phys_addr;
填充好dev_info后,走ioctl RTE_KNI_IOCTL_CREATE发送到内核。
内核调用ioctl这个kni create的钩子

	case _IOC_NR(RTE_KNI_IOCTL_CREATE):
		ret = kni_ioctl_create(net, ioctl_num, ioctl_param);
		break;

static int
kni_ioctl_create(struct net *net, uint32_t ioctl_num,
		unsigned long ioctl_param)
	kni->tx_q = phys_to_virt(dev_info.tx_phys);
	kni->rx_q = phys_to_virt(dev_info.rx_phys);
	kni->alloc_q = phys_to_virt(dev_info.alloc_phys);
	kni->free_q = phys_to_virt(dev_info.free_phys);

这里使用phys_to_virt将上面txq的物理地址转换为内核的虚拟地址,这样,txq这个队列,在用户态或者内核操作的是完全一样的一块内存,用户态往txq put数据,在内核在直接向txq get数据,就能拿到对应的数据了,当然,这里由于txq保存的是地址,所以内核态拿到地址后,还需要转换为虚拟地址才能正常使用,下面章节会具体将收发包的流程。

以上就是内存的申请,主要用到的四个队列:

  1. rxq 用在从用户态收包后传给内核协议栈使用,内核协议栈拿到buf后,需要把mbuf拷贝到skb,如果是loopback模式,报文不送入协议栈,直接put 到txq送回用户态处理;
  2. txq 用在内核网口使用kni_net_tx发包的用到,kni需要把skb拷贝到mbuf(从alloc队列拿到可用的mbuf空闲内存),然后内核把mbuf加入到txq,用户态收包报后从dpdk网口发送出去;
  3. alloc主要用途是内核态使用kni发包的时候,先从allocq预先拿到mbuf,然后把skb拷贝到mbuf里面,在加入txq,这样用户态就可以直接从txq拿到mbuf后直接发送出去了,这样减少用户态把skb数据转换为mbuf的麻烦操作。
  4. freeq的用途和alloc基本一样,配套使用的。

报文收发流程

  1. 用户态接收mbuf后送入内核协议栈流程
    rte_eth_rx_burst 拿到mbuf后,加入rte_kni_tx_burst
用户态put mbuf到rxq

    rte_kni_tx_burst
    	kni_fifo_put

内核态任务循环get rxq,如果有数据则进行处理:

 	kni_thread_single  处理用户态报文的任务,循环收rxq的mbuf
 	     kni_net_rx(dev)
 	         kni_net_rx_normal	    
					num_rx = kni_fifo_get(kni->rx_q, kni->pa, num_rx);   拿到mbuf
							kva = pa2kva(kni->pa[i]);                     转换为内核虚拟地址
							len = kva->pkt_len;
							data_kva = kva2data_kva(kva);
							kni->va[i] = pa2va(kni->pa[i], kva);
                            skb = dev_alloc_skb(len + 2);                 申请skb
                            memcpy(skb_put(skb, len), data_kva, len);       拷贝mbuf数据到skb
                            netif_rx_ni(skb);                                                 把skb送入协议栈处理
                  ret = kni_fifo_put(kni->free_q, kni->va, num_rx);         再把mbuf送到freeq,让用户态去释放mbuf

用户态释放已经拷贝过的mbuf内存

kni_free_mbufs(kni);        
  1. 协议栈主动发包流程
    协议栈通过调用kni_net_tx,把已经构造好的skb发送给用户态
kni_net_tx
    kni_fifo_get(kni->alloc_q, &pkt_pa, 1);   从allocq申请一块mbuf
		pkt_kva = pa2kva(pkt_pa);                 把mbuf转换为内核可以访问的虚拟地址
		data_kva = kva2data_kva(pkt_kva);
		pkt_va = pa2va(pkt_pa, pkt_kva);
		memcpy(data_kva, skb->data, len);   拷贝skb数据到mbuf
    kni_fifo_put(kni->tx_q, &pkt_va, 1);       把mbuf put到txq

用户态需要循环判断txq,如果有数据,直接发送

main_loop
    kni_egress(struct kni_port_params *p)
        rte_kni_rx_burst                                                   
            kni_fifo_get(kni->tx_q, (void **)mbufs, num)   从txq拿数据
            kni_allocate_mbufs(kni)     向allocq补充mbuf
       rte_eth_tx_burst  发送mbuf到网口

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/yaochuh/article/details/88555397
kni