网卡发送数据流程

邻居子系统是位于网络层和数据链路层之间的一个子系统,其作用就是对网络层提供一个封装,让网络层不必关心下层的地址信息,让下层来决定发送到哪个 MAC 地址上。

在邻居子系统中进行arp请求获取mac地址,对 skb 拼装上MAC地址,然后调用 dev_queue_xmit 进入网络子系统中。

int dev_queue_xmit(struct sk_buff *skb)
{
....

//从netdev_queue结构上取下设备的qdisc,获取与此队列关联的排队规则
q = rcu_dereference(dev->qdisc);
//若有队列,先把skb入队,然后在发送
if (q->enqueue) {
//入队,把skb添加到q->q队列中
rc = q->enqueue(skb, q); // pfifo_enqueue
qdisc_run(dev); //开始发送

}

//没有队列的loopback设备,则调用loopback_xmit
dev_hard_start_xmit(..);
...

}

内核协议栈中有进行流量控制的处理,因此SKB数据包首先根据 qdisc 排队规则进入排队队列,然后内核会尽可能多地从 qdisc 里面的队列中取出数据包,把它们交给网络适配器驱动模块进行发送处理。

QDisc (排队规则)是 queueing discipline 的简写,它是理解流量控制( traffic contro l)的基础。无论何时,内核如果需要通过某个网络接口发送数据包,它都需要按照为这个接口配置的 qdisc (排队规则)把数据包加入队列。然后,内核会尽可能多地从 qdisc 里面取出数据包,把它们交给网络适配器驱动模块。最简单的 QDisc 是 pfifo 它不对进入的数据包做任何的处理,数据包采用先入先出的方式通过队列。不过,它会保存网络接口一时无法处理的数据包。

对于环回设备时没有排队规则的,因此直接调用驱动程序 dev_hard_start_xmit 进行发送。对于其他设备,存在排队规则,则 skb 先入队,然后从队列中再取出 skb,最后再调用 dev_hard_start_xmit 进行发送。

qdisc_run 发送数据包调用流程如下:

qdisc_run
|-> __qdisc_run
|-> qdisc_restart
|-> dev_hard_start_xmit
//环回设备为 loopback_xmit, e1000 为 e1000_xmit_frame
|-> dev->hard_start_xmit
|-> netif_schedule(dev)//未成功发送,则放到软中断中发送

在 qdisc_restart 中调用 dev_hard_start_xmit 发送 skb 时,若驱动在忙导致 skb 未发送成功,则把skb重新加入排队队列,然后调用 netif_schedule,在 netif_schedule 中把 dev 加入到 CPU 的 softnet_data结构的 output_queue 队列中,这样把 skb 的发送处理交给 CPU 的内核线程进行重新发送。

static inline int qdisc_restart(struct net_device *dev)
{
// 调用驱动程序来发送数据,若发送成功,则返回
ret = dev_hard_start_xmit(skb, dev);
if (ret == NETDEV_TX_OK) 
return -1;
....

/* 若上面的skb没有成功发送,则把skb入队,把dev加入到CPU的 softnet_data结构的output_queue队列中,触发 NET_TX_SOFTIRQ 软中断,在软中断处理函数net_tx_action中处理output_queue队列,然后重新调用qdisc_run进行重新发送skb。
*/
netif_schedule(dev);
return 1;

}


dev_hard_start_xmit 将数据包传递给驱动进行发送。

对于 e1000 设备驱动程序,hard_start_xmit 回调函数为 e1000_xmit_frame,该字段是在在 e1000_probe 中进行的初始化。

int dev_hard_start_xmit(struct sk_buff *skb, struct net_device *dev)
{
...
//对于环回设备为 loopback_xmit,对于e1000为 e1000_xmit_frame(在 e1000_probe 中设置)
return dev->hard_start_xmit(skb, dev); 
}

在 e1000_xmit_frame 中,先检查 TX desc 是否有足够的描述符,若不够,则将 skb 的排队列加入到 cpu 的 softnet_data 的 output_queue 队列中,通过后续软中断继续发送。

描述符足够的情况下,完成 skb 数据的 DMA 映射并加入到发送环形缓冲区中,同时更新网卡的 TDT 寄存器。此时网卡会感知立即感知到寄存器发生变化,从而进行发送数据。

static int
e1000_xmit_frame(struct sk_buff *skb, struct net_device *netdev)
{
...
/*计算发送 skb 所需的描述符数量,用 count 变量表示。然后根据分片情况,对 count 进行相应调整。*/

/*检查 TX Queue 以确保有足够可用的描述符。如果没有,则返回 NETDEV_TX_BUSY,这将导致 qdisc 将 skb 重新入队,
然后把netdev加入到cpu的softnet_data的output_queue队列中,后续通过软中断再次发送*/
if (unlikely(e1000_maybe_stop_tx(netdev, tx_ring, count + 2))) {
spin_unlock_irqrestore(&tx_ring->tx_lock, flags);
return NETDEV_TX_BUSY;
}

...

/*将skb数据映射的dma地址存放到发送环形缓冲区,同时更新TDT寄存器*/
e1000_tx_queue(adapter, tx_ring, tx_flags,
//将 skb->data 数据映射到 RAM 的 DMA 区域,以允许设备通过 DMA 从 RAM 中读取数据
e1000_tx_map(adapter, tx_ring, skb, first,
max_per_txd, nr_frags, mss));

...

/* 发送结束之后,驱动要检查确保有足够的描述符用于下一次发送。如果不够,TX Queue 将被 关闭*/
e1000_maybe_stop_tx(netdev, tx_ring, MAX_SKB_FRAGS + 2);

return NETDEV_TX_OK;
}

上面说到,在 e1000_xmit_frame 中若发送描述符不够,会把 dev 加入到 cpu 的 softnet_data 的 output_queue 队列中。

因为 qdisc_restart 调用 e1000_xmit_frame 发送数据后,会接着调用netif_schedule 触发发送软中断,在发送软中断中继续发送未成功发送的 skb。

软中断处理流程:

static void net_tx_action(struct softirq_action *h)
{
struct softnet_data *sd = &__get_cpu_var(softnet_data);
//释放已经传输成功的skb
if (sd->completion_queue) {
...
__kfree_skb(skb);
}

// 若 output_queue 上有待发送skb的网络设备,发送其队列上的skb
if (sd->output_queue) {
...
qdisc_run(dev);
}
}


网卡发送数据

DMA 感知到 TDT 的改变后,找到 tx descriptor ring 中下一个将要使用的descriptor。

DMA 通过 PCI 总线将 descriptor 的数据缓存区复制到 Tx FIFO,复制完后,通过 MAC 芯片将数据包发送出去。

发送完后,网卡更新TDH,启动硬中断通知 CPU 释放数据缓存区中的数据包。

CPU 收到硬中段通知后,调用硬中断流程:

static irqreturn_t
e1000_intr(int irq, void *data)
{
... 

for (i = 0; i < E1000_MAX_INTR; i++)
if (unlikely(!adapter->clean_rx(adapter, adapter->rx_ring) & //e1000_clean_rx_irq (e1000_up中设置的函数)
!e1000_clean_tx_irq(adapter, adapter->tx_ring)))
break;

...

return IRQ_HANDLED;
}

在硬中断中调用 e1000_clean_tx_irq ,解除 skb->data 的DMA映射同时释放已成功发送的 skb。

static boolean_t
e1000_clean_tx_irq(struct e1000_adapter *adapter,
struct e1000_tx_ring *tx_ring)
{
...
while (eop_desc->upper.data & cpu_to_le32(E1000_TXD_STAT_DD)) {
for (cleaned = FALSE; !cleaned; ) {
...
 e1000_unmap_and_free_tx_resource(adapter, buffer_info);
...
}

eop = tx_ring->buffer_info[i].next_to_watch;
eop_desc = E1000_TX_DESC(*tx_ring, eop);

}
...

return cleaned;
}


总结

1、网卡驱动创建 tx descriptor ring(一致性 DMA 内存),将 tx descriptor ring 的总线地址写入网卡寄存器 TDBA

2、协议栈通过 dev_queue_xmit() 将 sk_buff 下送网卡驱动

3、网卡驱动将 sk_buff 放入 tx descriptor ring,更新 TDT

4、DMA 感知到 TDT 的改变后,找到 tx descriptor ring 中下一个将要使用的 descriptor

5、DMA 通过 PCI 总线将 descriptor 的数据缓存区复制到 Tx FIFO

6、复制完后,通过 MAC 芯片将数据包发送出去

7、发送完后,网卡更新 TDH,启动硬中断通知 CPU 解除 skb->data 的DMA 映射同时释放已成功发送的 skb

展开阅读全文

页面更新:2024-04-25

标签:网卡   数据   寄存器   子系统   队列   内核   流程   规则   地址   设备   网络

1 2 3 4 5

上滑加载更多 ↓
推荐阅读:
友情链接:
更多:

本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828  

© CopyRight 2008-2024 All Rights Reserved. Powered By bs178.com 闽ICP备11008920号-3
闽公网安备35020302034844号

Top