网络子系统在链路层的收发过程剖析(四)

 

5),数据包的发送

 

       数据包的发送为接收的反过程,发送过程较之接收过程的复杂性在于它有一个流量控制层(Trafficing Control Layer),用于实现QoS,但不是本文关注的目标。

 

(1)__netif_schedule ()

当内核有数据包等待发送时,它会间接调用__netif_schedule ()去处理这些数据包:

 

void __netif_schedule(struct net_device *dev)

{

       if (!test_and_set_bit(__LINK_STATE_SCHED, &dev->state)) {

              unsigned long flags;

              struct softnet_data *sd;

 

              local_irq_save(flags);

              sd = &__get_cpu_var(softnet_data);

              dev->next_sched = sd->output_queue;

              sd->output_queue = dev;

              raise_softirq_irqoff(NET_TX_SOFTIRQ);

              local_irq_restore(flags);

       }

}

 

这个函数的功能很简单,就是将要有数据要发送的设备加softnet_dataoutput_queue队列的头部,这里要注意,一个设备加入是有条件的,如果一个设备的状态为__LINK_STATE_SCHED时,表示这个设备已经被scheduled,就不必要再一次执行这个函数了。然后这个函数触发软中断,由软中断去执行net_tx_action()

 

(2)net_tx_action()

这个函数的功能有两个,其一是释放softirq_action中完成队列completion_queue中的skb

 

我们知道,当系统运行在中断上下文中,它应该执行的时间应该越短越好,但如果我们需要在中断上下文中释放SKB,这就需要比较长的时间了,所以在个时间段里处理内核的释放并不是一个好的选择。所以,网络子系统在softirq_action结构中设置了一个完成队列completion_queue,当内核要在中断上下文中释放skb时,它将调dev_kfree_skb_irq(skb)

 

static inline void dev_kfree_skb_irq(struct sk_buff *skb)

{

       if (atomic_dec_and_test(&skb->users)) {

              struct softnet_data *sd;

              unsigned long flags;

 

              local_irq_save(flags);

              sd = &__get_cpu_var(softnet_data);

              skb->next = sd->completion_queue;

              sd->completion_queue = skb;

              raise_softirq_irqoff(NET_TX_SOFTIRQ);

              local_irq_restore(flags);

       }

}

 

可以看到,它并没有真正的释放skb空间,而只是将它链入完成队列completion_queue中,并触发软中断,由软中断来执行真正的释放操作,这就是上面提到的net_tx_action()来完成的,这是它的任务之一:

 

clist = sd->completion_queue;

              sd->completion_queue = NULL;

              local_irq_enable();

 

              while (clist) {

                     struct sk_buff *skb = clist;

                     clist = clist->next;

 

                     BUG_TRAP(!atomic_read(&skb->users));

                     __kfree_skb(skb);

              }

 

net_tx_action()的另一个任务,也是根本的任务,当然是发送数据包了:

       if (sd->output_queue) {

              struct net_device *head;

 

              local_irq_disable();

              head = sd->output_queue;

              sd->output_queue = NULL;

              local_irq_enable();

 

              while (head) {

                     struct net_device *dev = head;

                     head = head->next_sched;

 

                     smp_mb__before_clear_bit();

                     clear_bit(__LINK_STATE_SCHED, &dev->state);

 

                     if (spin_trylock(&dev->queue_lock)) {

                            qdisc_run(dev);

                            spin_unlock(&dev->queue_lock);

                     } else {

                            netif_schedule(dev);

                     }

              }

       }

 

正常情况下,它会将output_queue队列中的有待发送的队列中的设备遍历一次,并对各个设备调用qdisc_run(dev)发送数据包。在这里,qdisc_run(dev)是属于QoS的内容了。这里我们只需要知道,qdisc_run(dev)会选择“合适”的skb然后传递给dev_hard_start_xmit(skb, dev)

 

 

(3)dev_hard_start_xmit(skb, dev)

这也只是一个包装函数,它首先看有没有注册的sniffer,要是存在的话(netdev_nit不等于0),便将一个副本通过dev_queue_xmit_nit(skb, dev)发送给它:

       if (likely(!skb->next)) {

              if (netdev_nit)

                     dev_queue_xmit_nit(skb, dev);

 

再之后,就是调用驱动程序的hard_start_xmit完成最后的发送工作了:

 

       return dev->hard_start_xmit(skb, dev);

 

hard_start_xmit()只要是跟硬件打交道,一般是通知DMA完成数据的发送工作。这里还有一个问题是,如果驱动或是硬件本身不支持scatter/gather IO,在上面传送过来的数据又是存在分片的(fragments,即skb_shinfo(skb)->nr_frags不等于0),它只能通过skb_linearize(skb)将原来的skb重新组装成一个没有分片的skb再进行DMA。

你可能感兴趣的:(工作,struct,网络,action,任务,output)