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_data的output_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。