网卡发送数据流程
邻居子系统是位于网络层和数据链路层之间的一个子系统,其作用就是对网络层提供一个封装,让网络层不必关心下层的地址信息,让下层来决定发送到哪个 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
直升机悬空在地面上方100米,12小时后会不会到地球另一边?答案想必大家都知道,那就是不会到地球另一边,而仍旧会停留在悬空的地方。为什么不会到地球的另一边?简单讲就是惯性作用,直升机在地面上空会随着地球一直旋转,这就是惯性作用,我们上初中物
太突然!联合国紧急呼吁,欧美股市集体暴拉,美联储要转向?华尔街最大空头发声,反弹能持续多久?幸福来得太突然!在经历了黑色9月之后,周二,全球股市再度上演狂欢。欧洲股市大幅反弹超3,美股盘前期指亦是全线大涨,美股开盘后三大指数均涨超2,大宗商品非美货币国债等集体大反攻。那么
全球汽车零部件哪家强?宁德时代未进前20强,米其林只排在第9名2022全球汽车供应链核心企业竞争力白皮书在前不久发布,机构对全球汽车零部件企业相关数据进行收集整理,形成了以企业营收为依据的数据名单。今年零部件收入的入围门槛为197。91亿元,
全球股市熊来了非美货币贬到央妈干预,资产何处藏身?上周大亏损,加密货币组合大跌75,最多亏到150万,暂时清仓了。资深矿工加密货币炒家小古(化名)近期对第一财经记者感叹。最近,这种情景在股市非美货币的汇市也屡见不鲜,只不过加密货币
全球半导体进入下行周期,半导体这个细分领域将穿越周期全球半导体进入下行周期,芯荒变芯慌,但走势各有不同。随着光刻机龙头阿斯麦和台积电股价分别在去年11月和今年1月触顶进入下行趋势,或许暗示着全球半导体的高光时刻正在过去。叠加受全球高
我的日记23共业国庆节日的第3天,也是独自呆在宿舍的第3天。人在孤独的时候,才会思索。今天脑袋里忽然想起一个问题,那就是业报。业报有两种,一是自业,二是共业。自业,是自己拥有的,包括身体田园宅舍和
性格古板和思想灵活文袁运录都说江山易改,本性难移。人的性格,就跟人的身高长相一样,一辈子都很难改变,但是人的思想说改就能改。古人有句话叫朝闻道夕可死。早上明白了道理,晚上就可以改变偏执的思想。事实上
祸从口出指的是闲聊人是一种社会性动物,社会交流是一种重要的心理需求,但是也不可否认,很多的麻烦事就是因为说了一些不该说的话。人在情绪激动的时候容易口无遮拦,人在骄傲自满的时候容易一吐为快,人在毫无戒
重阳节秋愁清风弹奏着优美的旋律谱成一首爱的乐章思念化成一条无形的红丝带缠绕在枝头在落叶的叹息里在伊人的梦里向着家乡眺望夕阳在南归雁的哀鸣中一脸忧伤遥远的家乡爸爸妈妈的双鬓被岁月漂白在妖娆的菊
人生无常,一口气缓不过来,也就没了随心文案有些路,不走不甘心,走了满身伤痕。人生就是这样,无论怎样选择都有遗憾,都抵不过命运的安排。下午惊闻一好友突发心肌梗塞,急诊没抢救过来,前后也就几个小时,没啦,彻底的没了!听
经典好句十条(百年孤独)1他大权独揽却在孤独中陷入迷途,开始失去方向。马尔克斯百年孤独2不仅孩子长的更快,连人的感情也变了样马尔克斯百年孤独3他像只小鸡一样把头缩在双肩里,额头抵上树干便一动不动了。家里人