vpp rte_mbuf和vlib_buffer_t的初始化以及数据如何在node之间传递

1.buffer pool的初始化

1.1函数调用关系

vlib_buffer_main_init->

        vlib_buffer_main_init_numa_node->

                vlib_buffer_pool_create

1.2创建vlib_buffer_pool的过程

每个numa节点都会创建一个pool,首先根据startup.conf文件中指定的num-mbufs值来确定要分配这些buffer,结合当前系统分配的巨页大小(1G或者2M)来计算需要用到几个巨页。但是实际分配的buffer数量不是由这个值指定,在代码中会计算这些巨页一共可以分配多少个buffer,这个值才是最终分配的buffer数量。

每个buffer元素的大小由alloc_size = data_size + sizeof (vlib_buffer_t) + bm->ext_hdr_size;来指定。

bm->ext_hdr_size是dpdk_plugin.so在early init的时候赋值,包含了rte_mbuf结构体的大小和私有数据的大小。data_size默认是2048,可以在vpp的配置文件startup.conf中指定。

n_alloc_per_page = (1ULL << m->log2_page_size) / alloc_size;这就是计算每个页最终分配了多少个buffer元素数量,通过前面计算出的需要用到几个巨页,来确定最终一共分配了多少个buffer元素数量。

这些buffer元素的索引值会保存在bp->buffers链表中。

1.3dpdk创建buffer pool的过程

函数入口是dpdk_buffer_pools_create。然后前面创建的pool作为入参,执行dpdk_buffer_pool_init函数。

首先会创建两个空的buffer pool,一个是 normal mempool ,另一个是non-cached mempool,具体的用法暂时还不熟悉。这两个pool是空的,也就是仅仅有pool的结构,并没有实际的元素填充在里面。后面会遍历bp->buffers链表,将这些buffer元素填入到这两个pool中。这两个pool的指针会分别填入dpdk_mempool_by_buffer_pool_index[]和dpdk_no_cache_mempool_by_buffer_pool_index[]数组中,后面dpdk在初始化接口时,在dpdk_device_setup函数中对收包队列进行配置,会把这个pool指针传进去,网卡收到报文,会在pool中申请内存,将报文写入这块内存,后续都是根据内存的索引值来获取报文,进行相应的操作。熟悉dpdk收包的朋友应该清楚,dpdk收包过程中有一个狸猫换太子的操作,通过重新申请一个buffer内存替换原来收包ring中的buffer内存实现零拷贝,在后续报文的处理中,就不会用额外的拷贝操作。

1.4rte_mbuf和vlib_buffer_t的关系

前面申请vlib buffer pool的时候,元素大小就已经包含了rte_mbuf和vlib_buffer_t的结构,vlib_buffer_t的首地址紧跟着rte_mbuf,也就是在headroom中。后续对rte_mbuf进行初始化,具体的函数是rte_mempool_obj_iter->rte_pktmbuf_init。这个init函数会填充部分rte_mbuf。

2.数据的起点

以dpdk为例,vpp的node调度框架首先会执行input节点。具体的函数调用流程是(这里以worker线程为例,main线程会有pre-input的节点调度,暂时不去分析)

vlib_main_loop->vlib_main_or_worker_loop->dispatch_node(入参的type指定为VLIB_NODE_TYPE_INPUT)-> node->function,到这里就进入到了dpdk_input_node的入口函数里。这里简单的分析下dpdk_input_node的入口函数都做了哪些操作

2.1dpdk-input的处理

针对当前线程需要处理的队列,执行dpdk_device_input函数,在这个函数里,会调用dpdk的收包接口rte_eth_rx_burst。随后初始化vlib_buffer_t bt,这个bt是一个模板,后续实际报文的vlib_buffer_t结构都要从这个模板中拷贝部分数据。

后面会调用dpdk_process_rx_burst函数,这个函数会将dpdk收到的所有的报文结构体struct rte_mbuf转为vpp的报文结构体vlib_buffer_t,同时更新vlib_buffer_t结构体中的current_data指针,将current_data指针指向报文的开头位置。

接下来就是将报文结构放入下一个节点的收包结构体中。具体的函数是vlib_get_buffer_indices_with_offset。在执行vlib_get_buffer_indices_with_offset这个函数之前,会根据vpp初始化接口层时为接口指定的下一跳获取下一跳节点的收包起始位置,具体的函数是vlib_get_new_next_frame。得到to_next指针,也可以看做一个数组,这个指针指向的是下一跳节点收包位置的索引值。然后调用vlib_get_buffer_indices_with_offset函数,填充to_next数组,这样下一跳节点就可以获取到报文数据的索引值,也就可以获取到报文数据的指针了。

后续的一些操作就是一些辅助性质的,填充接口号等。最后调用vlib_put_next_frame来结束本节点的处理流程,将下一节点的索引放入全局变量nm->pending_frames中,在主循环里会继续dispatch pending frames,继续后续的处理。

3.业务节点处理逻辑(以ethernet_input_node为例)

3.1数据获取

vlib_frame_vector_args这个函数就会获取到本节点收包位置的起始位置。这里得到的是一个u32的值,获取到的是索引值。随后就可以对这个数据进行进一步的分析处理。节点函数最后要调用vlib_put_next_frame,这样下一个节点才会得到调度。

4.一些函数的作用

vlib_buffer_enqueue_to_next  将报文内存的索引传递给下一个节点

vlib_set_next_frame_buffer,调用vlib_set_next_frame,继续调用vlib_get_next_frame,得到下一跳节点的收包索引数组的空闲位置,然后将待传入下一节点的报文索引填入这个数组中,然后调用           vlib_put_next_frame,使下一个节点得到调度。

vlib_validate_buffer_enqueue_x1,vlib_validate_buffer_enqueue_x2,vlib_validate_buffer_enqueue_x4 检验下一跳节点是否为入参中的下一跳,如果不是,则调用vlib_set_next_frame_buffer。

vlib_get_next_frame,得到下一个节点的收包索引数组的起始位置以及空闲总数。

vlib_put_next_frame,新增一个pending frame  p结构,将下一个节点的frame,node_runtime_index填入p,然后将p加入nm->pending_frames数组中,调度中心在调度完input节点之后,会根据nm->pending_frames对业务处理节点进行调度。

 

vpp的节点处理实现假定所有报文的下一跳节点都是上一次记录下来的节点,如果后续操作重新指定了和上一次记录下来的节点不一样的节点,就要调用vlib_validate_buffer_enqueue_x1等函数进行修正。如果是vlib_validate_buffer_enqueue_x1函数,会直接将上次记录的节点替换成本次实际转发给下一跳的节点,如果是vlib_validate_buffer_enqueue_x2或者vlib_validate_buffer_enqueue_x4函数,会修改为多数报文转发到下一跳的节点的index。

  • 0
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值