DPDK以太网部分代码整理

总体流程如图所示,初始化时配置设备和队列,运行时通过rte_eth_tx_burst和rte_eth_rx_burst来收发包。

收包内存池创建

rte_pktmbuf_pool_create(pool_name, num, cache_size, priv_size, data_room_size, socket_id);

  1. num是pool里block个数

  2. cache_size是一些lcore的指针,指向本mempool里的buffer,注意每个lcore都会reserve这么多指针,其它buffer就没法访问这些block了。

  3. priv_size是预留的内部空间,比如可以作为一些custom header的存储位置。

  4. data_room_size是实际payload size + RTE_PKTMBUF_HEADROOM

  5. socket_id可以是0,如果只有1个socket。也可以是任意的,比如SOCKET_ID_ANY,也就是-1。主要涉及到不同的NUMA,会用到不同的DMA。

  6. 有些驱动比如ice的一些驱动,不支持iova=va的模式,因为PMD需要大块的连续物理内存。

  7. 收包DMA会搬运到事先分配好的收包内存池。注意发包内存池不需要给驱动额外创建,rte_eth_tx_burst前的buffer已经分配好了。

设备端口初始化

  1. rte_eth_dev_count_avail()
    看有几个eth port,就是几个eth设备,包括所有PF和VF的数量。

  2. rte_eth_dev_info_get(port, &dev_info);
    看当前port支持哪些功能,比如最大的rx queue和tx queue数量。

  3. rte_eth_dev_configure(port, n_rx_q, n_tx_q + n_free_tx_q, &port_conf)
    配置port有几个tx和rx的队列。

  4. 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

  5. rte_eth_dev_vlan_filter(port_id, vlan_id, 1)
    给port_id和vlan_id配置offload,1表示使能,也就是允许让这些packet通过,而且收到之后,就把vlan自动remove掉。
    发包的时候,VLAN填到mbuf->vlan_tci即可。

  6. 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

  7. rte_eth_dev_adjust_nb_rx_tx_desc(portid, &nb_rxd, &nb_txd);
    检查rx和tx的descriptor数量,如果超过了limit,就把它们配成limit的值。

  8. rte_eth_dev_get_port_by_name(port_name, &port_id)
    可以通过设备名字获得port id

  9. 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)

初始化队列

  1. rte_eth_rx_queue_setup(port, rx_q_id, nb_rx_desc, rte_eth_dev_socket_id(port), NULL, mp)
    需要指定mempool,这样接收的驱动就会从这个mempool里去拿buffer。

  2. 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);

  3. 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了,后面可以用于通信。

停止队列

  1. tx queue stop
    rte_eth_dev_tx_queue_stop(port, tx_queue);
    rte_tm_node_delete(port, tm_node_id, &error);

  2. 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)

调试

  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()

  2. 看数据包情况
    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函数。

  1. 把数据从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 获取

猜你喜欢

转载自blog.csdn.net/weixin_60043341/article/details/126623758