在进行正式的收发包之前,DPDK需要做一些初始化操作,包括:
- 初始化一个或多个mbuf_pool,用来存储从网卡中接受的数据包
- 修改网卡配置,指定其接受队列的个数(通常每个转发核一个),长度(也就是能存储的接收描述符的最大个数),以及接受队列的选择方法(通常根据数据包头中的关键字进行哈希)等等。
- 接受队列中实际存储的是一个个的接收描述符,接下来为每个接受描述符初始化一个mbuf pool中的地址。
由于在初始化阶段,DPDK会为每lcore在每个设备,也就是port的实体上创建一个发送队列和接收队列,以此来尽最大可能避免冲突。
因此本质上DPDK的收包流程和发包流程都是一个单生产者单消费者的问题。
在DPDK的一次完整收发包中,数据包的整个接受流程如下:
- 网卡收到数据包,根据预定义的算法选择一个接受队列。
- 在接收队列中找到可用描述符(dd位为0)
- 根据接收描述符的mbuf地址,通知对应DMA将数据包拷贝到mbuf中,同时将接受描述符的dd位置1,表明该描述符可以被读取。
- 用户进程循环访问接收队列,当发现有描述符dd位=1时,表面有数据包可以处理,并从描述符中获取对应的mbuf地址。
- 从mbuf pool中申请一块新的地址,并将描述符的地址指针指向新的地址,再将dd位置0,表示此描述符可以被网卡驱动重新写入。
- 将原mbuf对应的地址返回给用户,用户便可以访问到数据包中的数据了。
发包流程与收包流程在很多地方都很类似,发送流程如下:
- 用户将想要发送的mbuf地址传给发送函数,同时指定想要从哪个设备发出,以及对应的tx_queue_id
- 发送函数根据port_id和queue_id在对应的发送队列中寻找空闲(dd位为0)的发送描述符。
- 如果空闲描述符上已经绑定了mbuf,需要将对应的mbuf释放回对应的mbuf pool
- 为发送描述符绑定待发送的mbuf地址,并将描述符的dd位置1,发送函数返回
- 网卡驱动异步遍历发送队列,从中选取dd=1的描述符。驱动DMA从描述符绑定的mbuf中将数据拷贝至自身缓存,并执行发包操作。发包完成后dd位置0。
发包流程与收包流程类似,在此就不专门再画流程图了。需要注意的一点是,用户调用的发包函数只是负责将待发送的mbuf地址与空闲的发送描述相绑定,并不会真正等待发送结束再返回。
而网卡在发送完成之后是没有办法主动释放内存中的mbuf的。
因此引入了步骤3,在绑定之前查看空闲描述符是否还绑定着mbuf地址,如果有说明此mbuf已经被成功发送过了,可以释放回内存池。