dpdk kni学习
相关概念
Kernel NIC Interface (KNI) 是dpdk提供的允许用户面的应用报文访问内核协议栈接口库。
kni主要的特点:
- mbuf到skb转化,只需要一次内存拷贝,中间mbuf从用户态传到内核态,走的是内存零拷贝,中间没有系统调用和copy_to_user()/copy_from_user() 操作;
- 允许用户通过标准的linux net tools查看dpdk的报文
- 报文进正常的内核协议栈
初始化
- 需要安装dpdk提供的kni ko模块
- 初始化pmd的时候,需要初始化kni的ioctl, init_kni
- 调用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 */
};
这里以tx q为例,其他的fifo申请释放使用原理一样
给tx_q申请内存 kni->m_tx_q = rte_memzone_reserve(mz_name, KNI_FIFO_SIZE, SOCKET_ID_ANY, 0);
这里rte_memzone_reserve申请来的内存是从可以保证物理地址连续的,也就是说,这个物理地址,在内核态和用户态都是一样的,只需要在相应层面将其转换为自己空间可用的虚拟地址,就可以正常使用。
初始化tx q队列 kni_fifo_init(kni->tx_q, KNI_FIFO_COUNT_MAX);
把tx q的物理地址保存到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将上面tx q的物理地址转换为内核的虚拟地址,这样,txq这个队列,在用户态和内核在读写的话,就是完全一样的一块内存了,用户态往txq put数据,在内核在直接向txq get数据,就能拿到对应的数据了,当然,这里由于txq保存的是地址,所以内核态拿到地址后,还需要转换为虚拟地址才能正常使用,下面章节会具体将收发包的流程。
以上就是内存的申请,主要用到的四个队列:
rxq 用来从用户态收包后传给内核协议栈的,需要把mbuf拷贝到skb,如果是loopback模式,报文不送入协议栈,直接put 到txq
txq 用来内核网口发包kni_net_tx后,有skb拷贝到mbuf,然后内核把mbuf加入到txq,用户态收包报后从dpdk网口发送出去
alloc主要用途是内核态使用kni发包的时候,先从allocq预先拿到mbuf,然后把skb拷贝到mbuf里面,在加入txq,这样用户态就可以直接从txq拿到mbuf后直接发送出去了,这样减少用户态把skb数据转换为mbuf。
freeq的用途和alloc基本一样,配套使用的。
报文收发流程
- 用户态接收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);
- 协议栈主动发包流程
协议栈通过调用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到网口