本篇主要讲网卡的工作原理
最近在做一个网卡仿真程序。主要目的是用程序代替网卡去向内存中填充报文。
网卡与内存的交互方式
1. rx阶段
网卡通过DMA向内存中发送数据包。
在内存中主要有三个数据结构
① DMA环(rx_ring), 其中存储了DMA描述符, DMA描述符指向了实际物理地址(todo), NIC将根据DMA描述符内的address把收到的报文放到相应的地址中。并更改DMA的RX_USED
位, 让驱动程序知道NIC已经向这个槽里放置了报文。
② 软件环(sw_ring), 其中储存了报文池中的可用mbuf地址。
内存分布图:
pmd收包函数源代码分析, 以下代码只保留了重要部分
uint16_t recv_pkts(void *rx_queue, struct rte_mbuf **rx_pkts,
uint16_t nb_pkts)
{
struct macb_rx_queue *rxq;
unsigned int len;
unsigned int entry, next_entry;
struct macb_dma_desc *desc, *ndesc;
uint16_t nb_rx;
struct macb *bp;
struct rte_mbuf *rxm;
struct rte_mbuf *nmb;
struct macb_rx_entry *rxe, *rxn;
uint64_t dma_addr;
nb_rx = 0;
rxq = rx_queue;
bp = rxq->bp;
/* 一次轮询从tail开始检查nb_pkts个数据包 可以的话重置rxe和dma描述符 */
while (nb_rx < nb_pkts) {
u32 ctrl;
bool rxused;
struct rte_ether_hdr *eth_hdr;
uint16_t ether_type;
entry = macb_rx_ring_wrap(bp, rxq->rx_tail);
rxe = macb_rx_entry(rxq, entry); // 获取当前待处理在pool中的mbuf的指针
desc = macb_rx_desc(rxq, entry); // 获取当前待处理的DMA descriptor
/*
* 如果该描述符的RX_USED位是0 证明NIC没有将数据传进rxe中,
* 所以可以这个槽位以及后面的槽位可以直接不用管了
*/
rxused = (desc->addr & MACB_BIT(RX_USED)) ? true : false;
if (!rxused)
break;
/* Ensure ctrl is at least as up-to-date as rxused */
rte_smp_rmb();
ctrl = desc->ctrl;
rxq->rx_tail++;
/* 重新在报文池中分配了一块新的m_buff空间*/
nmb = rte_mbuf_raw_alloc(rxq->mb_pool);
rxm = rxe->mbuf; /* rx_mbuf, 这个是要取得报文 */
rxe->mbuf = nmb; /* 将rxe->mbuf赋值为新的m_buf 下次NIC可以再次使用 */
dma_addr = rte_cpu_to_le_64(rte_mbuf_data_iova_default(nmb));
eth_hdr = rte_pktmbuf_mtod(rxm, struct rte_ether_hdr *);
ether_type = eth_hdr->ether_type;
/* 将原本rxe的m_buf指针给rx_pkts, 相当于返回给调用函数 */
rx_pkts[nb_rx++] = rxm;
/* 给DMA描述符赋值 相当于重置了dma描述符 */
macb_set_addr(bp, desc, dma_addr);
}
return nb_rx;
}
因此, 假设我们需要模拟NIC, 需要做的事情就是主动将USED位赋为1, 然后网sw_ring里对应槽位的mbuf里插入随机报文即可
2.tx阶段
tx阶段是rx阶段的逆过程, 当NIC将待发送报文从tx_queue
里拿走时, TX_USED
位会赋值为1, macb_reclaim_txd
函数会从tx_queue
队列的head遍历, 除掉所有TX_USED=1
的槽位, 这样程序就可以继续往tx_queue
里放置需要放置的报文, NIC就可以从tx_queue
中复制报文并发送。
假设我们需要模拟NIC, 需要做的事情就是主动将USED位赋为1即可, 以及模拟数据。
pmd 发包源代码分析 略