总体流程如图所示,初始化时配置设备和队列,运行时通过rte_eth_tx_burst和rte_eth_rx_burst来收发包。
收包内存池创建
rte_pktmbuf_pool_create(pool_name, num, cache_size, priv_size, data_room_size, socket_id);
-
num是pool里block个数
-
cache_size是一些lcore的指针,指向本mempool里的buffer,注意每个lcore都会reserve这么多指针,其它buffer就没法访问这些block了。
-
priv_size是预留的内部空间,比如可以作为一些custom header的存储位置。
-
data_room_size是实际payload size + RTE_PKTMBUF_HEADROOM
-
socket_id可以是0,如果只有1个socket。也可以是任意的,比如SOCKET_ID_ANY,也就是-1。主要涉及到不同的NUMA,会用到不同的DMA。
-
有些驱动比如ice的一些驱动,不支持iova=va的模式,因为PMD需要大块的连续物理内存。
-
收包DMA会搬运到事先分配好的收包内存池。注意发包内存池不需要给驱动额外创建,rte_eth_tx_burst前的buffer已经分配好了。
设备端口初始化
-
rte_eth_dev_count_avail()
看有几个eth port,就是几个eth设备,包括所有PF和VF的数量。 -
rte_eth_dev_info_get(port, &dev_info);
看当前port支持哪些功能,比如最大的rx queue和tx queue数量。 -
rte_eth_dev_configure(port, n_rx_q, n_tx_q + n_free_tx_q, &port_conf)
配置port有几个tx和rx的队列。 -
rte_eth_dev_set_vlan_offload(port_id, vlan_offload)
先获得当前的VLAN offload配置。rte_eth_dev_get_vlan_offload(port_id),再配置vlan offload配置。rte_eth_dev_get_vlan_offload -
rte_eth_dev_vlan_filter(port_id, vlan_id, 1)
给port_id和vlan_id配置offload,1表示使能,也就是允许让这些packet通过,而且收到之后,就把vlan自动remove掉。
发包的时候,VLAN填到mbuf->vlan_tci即可。 -
rte_eth_dev_callback_register(portid, RTE_ETH_EVENT_QUEUE_STATE, eth_queue_state_change_callback, NULL);
可以注册回调函数,比如当RTE_ETH_EVENT_QUEUE_STATE,queue状态发生变化时,调用eth_queue_state_change_callback()处理。
因为某些设备,当用于rx的mbuf被耗尽后,就不会再接收数据了,queue会被停止,这个时候,rx queue需要被重启。
rte_eth_dev_rx_queue_stop(port, queue),rte_eth_dev_rx_queue_start(port, queue)即可。
当链路状态RTE_ETH_EVENT_INTR_LSC发生变化时,也可以注册相应的回调函数去处理。有些网卡支持,有些不支持,可以查看这个网页https://doc.dpdk.org/guides/nics/overview.html -
rte_eth_dev_adjust_nb_rx_tx_desc(portid, &nb_rxd, &nb_txd);
检查rx和tx的descriptor数量,如果超过了limit,就把它们配成limit的值。 -
rte_eth_dev_get_port_by_name(port_name, &port_id)
可以通过设备名字获得port id -
rte_tm_node_add(port, node, parent_node, prio, weight, level, param, error)
支持TM的device,可以配置traffic manager。prio可以配成5,weight可以是5,level可以用宏RTE_TM_NODE_LEVEL_ID_ANY(也就是配置成UINT32_MAX)
初始化队列
-
rte_eth_rx_queue_setup(port, rx_q_id, nb_rx_desc, rte_eth_dev_socket_id(port), NULL, mp)
需要指定mempool,这样接收的驱动就会从这个mempool里去拿buffer。 -
rte_eth_tx_queue_setup(port, tx_q_id, nb_tx_desc, rte_eth_dev_socket_id(port), &txconf);
tx不需要指定mempool,但需要配置tx_free_thresh和tx_rs_thresh。
struct rte_eth_txconf txconf = dev_info.default_txconf;
txconf.tx_free_thresh = nb_tx_desc - txconf.tx_rs_thresh;
如果运行过程中需要增加txq,可以用这个API继续增加。
rte_eth_tx_queue_setup(port, txq, nb_txd, socket_id, &txconf); -
rte_eth_cp_queue_setup(portid, cp_q_id, NUM_PKTS_COMPL_Q, socket_id, &cp_conf);
配置completion queue,会得到通知,表示传输完成。
启动设备
rte_eth_dev_start(port)
rte_eth_link_get_nowait(port, &link);
用link.link_status可以判断link是否起来了,如果是1,表示link已经up了,后面可以用于通信。
停止队列
-
tx queue stop
rte_eth_dev_tx_queue_stop(port, tx_queue);
rte_tm_node_delete(port, tm_node_id, &error); -
rx queue stop
rte_eth_dev_rx_queue_stop(port, tx_queue);
停止设备
rte_eth_dev_stop(port);
rte_eth_dev_close(port);
停止数据流之后再释放这个eth设备。
接收数据
rte_eth_rx_burst(port, 0, mbufs, BURST_SIZE)
rte_pktmbuf_mtod(mbufs[i], struct rte_ether_hdr*)
偏移到eth hdr后,就可以开始处理数据。
rte_pktmbuf_mtod_offset(mbufs[i], struct rte_ipv4_hdr*, sizeof(struct rte_ether_hdr))
也可以偏移到指定的offset。
发送数据
填好eth hdr, ip hdr, udp hdr后,就可以发数据了。
rte_eth_tx_burst(port, 0, &txbuf, 1)
调试
-
看interface和队列配置
interface配置
rte_eth_link_get()
rte_eth_promiscuous_get(i)
rte_eth_dev_get_mtu(i, &mtu)
队列配置
Rx
rte_eth_rx_queue_info_get(port, queue, &queue_info) // 可以知道配置了对少个队列,分别有几个descriptor,是否满了。
rte_eth_rx_descriptor_status(port, queue, offset);
Tx
rte_eth_tx_queue_info_get(port, queue, &queue_info)
rte_eth_tx_descriptor_status(port, queue, offset);
rte_eth_dev_rss_hash_conf_get(port, rss_conf)
TM
rte_tm_capabilities_get()
rte_tm_node_capabilities_get()
rte_tm_node_type_get()
rte_tm_level_capabilities_get()
rte_tm_get_number_of_leaf_nodes()
rte_tm_node_stats_read() -
看数据包情况
rte_eth_stats_get(port, $stats)
stats.ipackets是rx packets,ierrors是rx errors,ibytes是rx bytes,rx_nombuf是没有mbuf,表示rx pool的mbuf用完了,如果有traffic burst,需要增加这个mempool的block数量。
stats.opackets是tx packets,oerrors是tx errors,obytes是tx bytes。
q_ipackets是队列rx packets,q_errors是队列 rx errors,q_ibytes是队列rx bytes。
q_opackets是队列tx packets,q_obytes是队列tx bytes。
rte_eth_xstats_get_names_by_id(port, name, len, ids)
rte_eth_xstats_get_by_id(port, ids, values, len)
还可以使用dpdk_pdump,生成wireshark可用的数据包。或者自己在rte_eth_add_rx_callback(), rte_eth_add_tx_callback()里加上生成pcap函数。
-
把数据从VF port mirror到PF
比如X550的网卡,可以用以下命令mirror到PF,然后在PF上做tcpdump。
echo "write 0x0000F600 0x702" > /sys/kernel/debug/ixgbe/PF_PCI_BDF}/reg_ops echo "write 0x0000F604 0x704" > /sys/kernel/debug/ixgbe/{PF_PCI_BDF}/reg_ops
其中PF_PCI_BDF可以用具体的PCI号,比如0000:00:06.0等表示。
tcpdump -i eth0 -s 1000 -B 2000 -W 5 -C 1000 -w traffic.pcap
其中-s表示每个包的大小,-B表示操作系统抓包的buffer大小,单位1KB,2000表示2MB,-W表示存5个包,-C表示每个包的大小是1000*1000M.
总体流程
rx_ring和sw_ring的index是一一对应的,数据通过DMA来搬移到rx pool的mbuf里。tx ring的代码类似。
其中的DD位(Descriptor Done Status)用于标识一个描述符是否可用。
网卡是否支持硬件卸载可以看这个网页:https://ark.intel.com/content/www/cn/zh/ark/compare.html?productIds=39774,88209,189534,192558
DDIO(Direct Data I/O,也称为Direct Cache Access--DCA):如果网卡支持DDIO,CPU和网卡间直接通过LLC cache交互。
收包时,会把内存的缓冲区和控制结构体预取到Cache,后面直接访问cache,流程是外部设备/控制器->Last Level Cache -> CPU。
发包时,流程是把报文发到网络,会发起一个I/O读请求,从而把数据块通过总线送到网卡上
不支持DDIO时,通过PCI总线发送到内存。 数据流是CPU向内存申请访问->内存往外部设备取数据->Last Level Cache从内存取数据。
RDMA(Remote Direct Memory Access远程直接内存访问):只有X810才支持。用于避免TCP/IP stack process,内存拷贝和进程上下文切换。
原文链接:https://mp.weixin.qq.com/s/-opp8LbqUcVoEGTG7Fmdvg
学习更多dpdk资料
DPDK 学习资料、视频和学习路线图 :https://space.bilibili.com/1600631218
Dpdk/网络协议栈/ vpp /OvS/DDos/NFV/虚拟化/高性能专家 学习地址: https://ke.qq.com/course/5066203?flowToken=1043799
DPDK开发学习资料、教学视频和学习路线图分享有需要的可以自行添加学习交流q 君羊909332607 获取