网卡发送数据流程
邻居子系统是位于网络层和数据链路层之间的一个子系统,其作用就是对网络层提供一个封装,让网络层不必关心下层的地址信息,让下层来决定发送到哪个 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
提醒丨事关考研!12月5日前填报2023年全国硕士研究生招生考试将于12月24日25日进行,湖南省教育考试院提醒广大考生及时了解报考点辖区疫情防控要求,在规定时间内到达考点所在地待考。为进一步做好组考安排和疫情防
三无电子烟诱导青少年群体应予严查近日,科普评测自媒体老爸评测发布了一则名为伪装成零食卖给小孩?这种黑产我见一个曝一个!的视频作品,引发网友广泛关注。这则视频曝光了一款外壳包装类似口香糖的产品,该气体口香糖吸一口就
玩到停不下来的儿童专注力训练打卡(可打印)每天20分钟,30天提高注意力第26天听觉干扰复述句子划消训练1划消训练2通过训练,增强儿童听觉注意力水平,筛选有效信息能力,听动水平,听觉注意力水平,提高语言复述能力,提升听说水
生育那些事(一)我的生育经历儿子已经工作了,可20多年前孕期那些事还记忆犹新。刚怀孕时是很激动,不等我平静下来孕吐如期而至。一闻到油烟味儿就吐,爱人就是那时练就了好厨艺,我也很少做饭了。上班中午带
十四年全职炒股心得收获8。8倍收益,持有长安汽车10年赚了300万本文是以转述的方式,讲诉一位雪球大V十四年来全职炒股的心得体会,从200万到1760万的故事。股市投资经历1996年,我被公派出国常驻,共十四年,2006年底回国。1996年下半年
评论之星漫评勿让糖衣炮弹暗渡陈仓刘腾高艺鸣(湖南师范大学)奶茶杯可乐罐星球杯,3个包邮,厂家直送这些并非真的玩具,而是一种一次性电子烟。标价59159元不等,且有绿豆冰棍生椰拿铁草莓冰激凌等多种口味,其中的利润率
数智化赋能园区转型升级,浪潮数字粮储亮相2022世界物联网博览会文图浪潮智慧粮食珍惜每一粒粮食近日,以格物致智,数实共生为主题的2022世界物联网博览会在无锡举办。浪潮数字粮储受邀参会,全面展示了浪潮在粮食行业及智慧产业园区领域的数字化解决方案
这只卡地亚,为什么这么贵?腕表之家钟表技术今天,我想和各位说一只卡地亚。这只卡地亚,虽然离绝大部分玩家,非常非常遥远,但我依然希望各位玩家能够认识这只手表。因为这不单是最著名最贵的卡地亚,也是名表世界中,最
QuEra将研发可重构中性原子量子计算机(图片来源网络)上个月,借助AmazonBraket,QuEraComputing开始提供对其中性原子量子系统Aquila的访问,Aquila具有256个量子比特。如今,量子公司的
人生如茶(378)作者刘峙锋白落梅说品茶,就是为了品一盏纯粹,一盏美好,一盏慈悲。我们就在茶的安静,茶的温的湿润里,从容不惊的老去。月光这么好,世界很烦使我永远溺于温柔和让步自己何必自寻烦恼我不快乐
灵魂摆渡夹耳田萧瑟,寒风透骨彻响琴峡穿越,死神擦肩怯河西堡街道,灵魂悄出窍霍去病剑上的滴血,祁连山下旌旗猎杀气腾腾,匈奴悲泣失我胭脂山,令我妇女无颜色失我祁连山,使我六畜不蕃息迪拜多哈,波