DPDK 源码分析之 l2fwd
什么是 L2 转发
2 层转发,即对应 OSI 模型中的数据链路层,该层以 Mac 帧进行传输,运行在 2 层的比较有代表性的设备就是交换机了。
当交换机收到数据时,它会检查它的目的 MAC 地址,然后把数据从目的主机所在的接口转发出去。
交换机之所以能实现这一功能,是因为交换机内部有一个 MAC 地址表,MAC 地址表记录了网络中所有 MAC 地址与该交换机各端口的对应信息。某一数据帧需要转发时,交换机根据该数据帧的目的 MAC 地址来查找 MAC 地址表,从而得到该地址对应的端口,即知道具有该 MAC 地址的设备是连接在交换机的哪个端口上,然后交换机把数据帧从该端口转发出去。
1.交换机根据收到数据帧中的源 MAC 地址建立该地址同交换机端口的映射,并将其写入 MAC 地址表中。2.交换机将数据帧中的目的 MAC 地址同已建立的 MAC 地址表进行比较,以决定由哪个端口进行转发。3.如数据帧中的目的 MAC 地址不在 MAC 地址表中,则向所有端口转发。这一过程称为泛洪(flood)。4.接到广播帧或组播帧的时候,它立即转发到除接收端口之外的所有其他端口。
DPDK-l2fwd 做了什么
该实例中代码写死的网卡为 promiscuous 混杂模式,我的虚拟机的两个网卡是直连的,拓扑如下:
因此,dpdk-l2fwd 中,port 0 收到包会转发给 port 1,port 1 收到包也会转发给相邻端口 port 0,下图 port 0 混杂模式收到 29694508 个包然后会把这些包都 sent 给 port 1,port 1 同样收到其他包后也会转发给 port 0。
因此,dpdk l2 fwd 这个例子展示了两个网卡在 mac 层成功的转发了数据包,后续我们会阅读源码并调试程序来看,dpdk 是如何实现这一功能的。
Pktgen 安装
pktgen-dpdk 是用于对 DPDK 进行高速数据包测试的工具
使用的命令行参数如下:
由于 pktgen 是基于 dpdk 进行开发的,因此选取的 pktgen 的版本和 dpdk 的版本一定是相互兼容的,我这边版本如下:
安装过程严格按照 install.md 即可:
这样就可以通过参数配置,指定 port 去发包了,下面我通过 port0 发包 10000pkts/s,由于 port0 和 port1 直连,可以看到 port 1 收包 10000pkts/s。
源码阅读
无情 GDB
gdb 并打印一些关键信息,加深 l2fwd 源码理解
l2fwd_parse_args
解析 l2fwd 转发的一些命令行配置,我这边配置的是 set args -- -q 1 -p 0x3 即:
l2fwd_rx_queue_per_lcore:每个逻辑核负责处理一个 rx 队列,后续可以看到网卡配置时一个网卡配置一个 rx 队列和 tx 队列,因此这个参数可以理解为一个逻辑核负责处理一个网卡。
l2fwd_enabled_port_mask:可用的的网卡 port 的掩码
timer_period:多长时间将统计信息输出到 stdout 中,缺省为 10s
转发端口配置
两两一组互为转发,因为我这边就两个 port0 和 port1:
port 0 转给 port 1, port 1 转给 port 0
这里面用到了 rte_eth_devices[portid]->data.owner.id 与 RTE_ETH_DEV_NO_OWNER 进行比较,代表该接管的网卡还没有被使用。
网卡接管
我这边是虚拟机网卡 E1000,因此是在 eal 初始化时调用的是 eth_em_devinit,内容很多后面用到哪个在详细看一下,这边就是 dpdk 通过 igb_uio 用户态驱动接管网卡,并对其进行一些参数的初始化。
为逻辑核分配 port
根据之前配置的 core 最大可以处理几个网卡 port,我这边是 1 所以,每个 core 赋值一个网卡,可以看到 n_rx_port 都是 1
rte_pktmbuf_pool_create
这个 mbuf pool 主要是给网卡接收数据包提供 mbuf 的,换句话说网卡通过 DMA 收到数据需要把数据包通过 DMA 传送到一块内存,正是这个 mbuf pool 中的内存。这里会创建一个 mbuf 的内存池,每个 muf 的大小为( sizeof(struct rte_mbuf) + priv_size + data_room_size ,总共有 nb_mbufs 个 mbuf。
pktbuf pool 创建使用了下面几个函数,我们逐一 debug:
rte_mempool_create_empty
->rte_mempool_populate_default
->rte_mempool_obj_iter
rte_mempool_create_empty
在 memzone 申请内存,这块内存包含 sizeof(struct rte_mempool),每个逻辑核的 cache size 大小,以及私有数据的大小。然后会创建一个空的 mempool 头指针指向这块内存,并挂载到 rte_mempool_tailq 中,这个头包含 sizeof(struct rte_mempool)以及每个逻辑核的 cache 大小,然后会在 memzone 申请所有 pool 内元素所需要的内存,这个内存地址赋给 mp 指针。总结如下:
debug 如下:
rte_mempool_populate_default
ring 队列默认为多生产者多消费者模式,eal 初始化时会生成一个 ring 队列 table,用于规定每种 ring 队列的一些函数操作,包括元素入队,出队,遍历等。
为 mp 创建一个 ring 队列,后续会存 mbuf 的指针。mp->flags |= MEMPOOL_F_POOL_CREATED
为 mp 每一个元素分配空间,这里面会做一个 page-aligned address 的操作如下:
然后寻找最大的连续页面进行分配元素,将这些元素指针加入到 mp->elmlist 中,分配的内存块信息记录在 mp->memlist 中,并把这些 mbuf 指针入队 ring,然后会继续寻找连续页面分配元素直到 elt_size。总结如下:
debug 如下:
rte_mempool_obj_iter
初始化 mbuf 信息,包括所属内存池、缓存起始地址等。
网卡启动
rte_eth_dev_info_get 获取对应 port 的网卡信息,包括网卡驱动,发送和接收队列个数,mtu 值以及 rx 和 tx 的 hw descriptor(后序给 dma 用的, 里面包括了 内存搬运的起始地址 结束地址 什么的,dma 分析这个结构体完成数据传输。)
然后会通过 eth_dev_rx_queue_config 为 dev->data->tx_queues 和 dev->data->rx_queues 分配指向队列的指针,这里面代码写死了,1 个接收队列 1 个发送队列,然后会调用 setup 函数对收发队列进行初始化。
收队列初始化:
网卡驱动的 rx_queue_setup 函数,由于我是虚拟网卡 e1000,所以这边调用的是 eth_igb_rx_queuesetup,这个函数主要分配了 2 个队列,sw_ring 和 rx_ring,以及网卡相关的寄存器设置。
rx_ring 包含了 E1000_MAX_RING_DESC 个网卡描述符,里面含有 dma 传输的报文数据地址以及报文头部地址,rss hash 值以及报文的校验和,长度等等。
sw_ring 包含了 nb_desc 个 struct igb_rx_entry,也就是 mbuf 地址。
rx_ring 主要存储报文数据的物理地址,物理地址供网卡 DMA 使用,也称为 DMA 地址(硬件使用物理地址,将报文 copy 到报文物理位置上)。sw_ring 主要存储报文数据的虚拟地址,虚拟地址供应用使用(软件使用虚拟地址,读取报文)。
发队列初始化:
和接收队列类似,调用 eth_em_tx_queue_setup。
启动网卡前:
设置报文发送失败的回调函数,这个实例是失败计数并 free 的操作,rte_eth_tx_buffer_count_callback.
启动网卡:
我这边调用的是 e1000 的 pmd 驱动,eth_em_start,函数极其复杂,原谅太菜的我过早的遇到了它,不过这边有个函数比较关键 em_alloc_rx_queue_mbufs。它将 sw_ring 队列和用户的 mbuf pool 内存池关联,并设置 rx_ring 的 dma 地址。总结如下:
debug 如下:
lcore_worker 启动
主核负责统计各个网卡 port 收发包的数量
其他子核负责读取对应的网卡 port 的 rx queue,然后如果有数据包的话就,就循环转发到配置的另一个网卡上。
代码极其简单,关键收发包 api:
我这边就一个核,所以它接收后会自己发出去。
Reference
理解物理网卡、网卡接口、内核、IP等属性的关系-CSDN博客
在CentOS中升级gcc4.8到gcc5并修改默认设置 - it610.com
dpdk环境搭建+创建dpdk项目,并连接dpdk库_linggang_123的博客-CSDN博客
dpdk 网卡队列初始化 + 收发包 - tycoon3 - 博客园 (cnblogs.com)
DPDK数据包与内存专题-mempool内存池 - AISEED - 博客园 (cnblogs.com)
dpdk mbuf之概念理解_ych的专栏-CSDN博客_mbuf
DPDK 网卡收包流程_RToax-CSDN博客_dpdk多队列收包
版权声明: 本文为 InfoQ 作者【于顾而言】的原创文章。
原文链接:【http://xie.infoq.cn/article/8e1bfc67d96effd5331fa7643】。文章转载请联系作者。
评论