以高通emac驱动为示例(kernel 3.18),简要分析网络设备子系统数据发送过程;先逆向查找调用栈,在总结整个过程(主要关注帧的传输,流动的过程)。
由3532行可知,mac驱动中的ndo_start_xmit是在__netdev_start_xmit函数中被调用的,而该函数又被netdev_start_xmit封装了一层;接着查找该函数被谁调用;
从第3行和第4行可知,主要是在net/core/dev.c中的xmit_one函数和xmit_list函数调用netdev_start_xmit函数;接着查看xmit_one和xmit_list的调用者;
由此可见,也在net/core/dev.c中,分别是dev_hard_start_xmit和dev_hard_start_xmit_list函数;在往上查找调用者;
可见这两个函数主要都是被__dev_queue_xmit函数和sch_direct_xmit函数调用;接着再分别看看_这两个函数的调用者;
由此可知__dev_queue_xmit函数最终被dev_queue_xmit,dev_queue_xmit_accel,dev_queue_xmit_list三个函数封装,到此既是网络设备子系统的最边界了;而sch_direct_xmit函数则是被__dev_xmit_skb和qdisc_restart两个函数调用;接下来分别看下__dev_queue_xmit函数和__dev_xmit_skb函数以及qdisc_restart函数的实现;
从注释可知,该函数是将缓冲区(skb)排队,然后传输到网络设备;
由3023行可知,__dev_xmit_skb函数被此函数给调用了,条件是该网络设备有队列;
在3056行和3059行能分别看到dev_hard_start_xmit和dev_hard_start_xmit_list函数被调用;由3027行的注释可知,调用这两个中之一的函数的最早的前提条件是,这个是没有队列的网络设备(虚拟网络设备,如loopback,隧道之类的虚拟网络设备),其次是这个设备已经up;而以太网是有队列的网络设备,所以主要调用的是__dev_xmit_skb函数,接着看看__dev_xmit_skb函数的实现;
__dev_xmit_skb函数的实现,主要关注2886行和2906行 的条件分支;其中2886行的条件是:
- 该队列设置了旁通标志;(TCQ_F_CAN_BYPASS)
- 该队列中没有待发送的数据;(! qdisc_qlen())
- 该队列没有再运行;(disc_run_begin())
2906行的条件则是:
- 该队列没有再运行;(disc_run_begin())
在这个2886行的分支里,能看到2896行调用了sch_direct_xmit函数,且当该函数返回值大于0时,会调用__qdisc_run函数;__qdisc_run函数在2906行的分支里也被调用了,所以现在需要对sch_direct_xmit函数和__qdisc_run函数的实现进行简要分析;
从该函数的注释可知,主要作用是尽可能传输多个skb,当队列非空是返回值大于0,当队列为空或者处于throttle状态时返回值为0;从162行的分支可知,当skb有效,且发送队列不是处于停止状态时,才会调用dev_hard_start_xmit函数或者dev_hard_start_xmit_list函数来发送数据;
接着看下__qdisc_run函数的实现:
由244行可知__qdisc_run在循环调用qdisc_restart函数,而qdisc_restart函数则是将skb从队列取出,在调用sch_direct_xmit函数来传输skb;从246行到248行的注释可知,当超过配额或者需要切换到另一个cpu时,会执行__net_schedule函数触发软中断;由此因此需要看看__net_schedule函数的NET_TX_SOFTIRQ软中断处理函数net_tx_action;
从3543行可知,这便是主要传输数据的分支,从3552行可知其会循环取出存在的队列,然后获取队列的锁成功时,便会调用qdisc_run函数,而当获取队列的锁失败时,则会重新调度;
由109行可知qdisc_run函数是__qdisc_run函数封装了一层的函数;接着到了看dev_hard_start_xmit和dev_hard_start_xmit_list函数的实现了;
从2687行和2691行可知,dev_hard_start_xmit函数是循环调用xmit_one来传输skb;从2717行可知dev_hard_start_xmit_list函数则是直接封装了xmit_list函数;接下来看看xmit_one和xmit_list函数的实现;
从2644行可知,xmit_one函数直接调用了netdev_start_xmit函数;xmit_list函数也是一样,在2675行直接调用netdev_start_xmit函数;不过它们在调用该函数前都有通过dev_queue_xmit_nit函数将skb复制到其他网络节流器(tap)中;到此帧的整个传输过程已简析完,现在总结下正向的过程:
- dev_queue_xmit
- __dev_queue_xmit
- __dev_xmit_skb (dev_hard_start_xmit (无队列规则的虚拟设备))
- __qdisc_run
- qdisc_restart (__netif_schedule -> net_tx_action -> qdisc_run -> __qdisc_run)
- sch_direct_xmit
- dev_hard_start_xmit
- xmit_one
- netdev_start_xmit
- __netdev_start_xmit
- ndo_start_xmit (emac_start_xmit 高通emac)