linux内核-网络报文发送流程

报文的发送是由网络协议栈的上层发起的。网络协议栈上层构造一个需要发送的skb结构后(该skb已经包含了数据链路层的报头),调用dev_queue_xmit函数进行发送;
dev_queue_xmit(skb);
该函数先会处理一些缓冲区重组、计算校验和之类的杂事,然后开始处理报文的发送。
发送报文有两种策略,有队列或无队列。这是由网络设备驱动程序在定义其对应的dev结构时指定的,一般的设备都会使用队列。
dev->qdisc指向一个队列的实例,里面包含了队列本身以及操作队列的方法(enqueue、dequeue、requeue)。这些方法的集合组成了一种队列规则(skb将以某种规则入队、以某种规则出队,并不一定是简单的先进先出),这样的规则可用于流量控制。
网络设备驱动程序可以选择自己的设备使用什么样的队列,或是不使用队列。
对于有队列的设备,dev_queue_xmit调用dev->qdisc->enqueue方法将skb加入队列,然后调用qdisc_run函数。而qdisc_run会调用qdisc_restart来对队列进行处理。
qdisc_restart(dev);
该函数主要的工作就是不断调用dev->qdisc->dequeue方法从队列中取出待发送的报文,然后调用dev->hard_start_xmit方法进行发送。该方法是由设备驱动程序实现的,会直接和网络设备去打交道,将报文发送出去。如果qdisc_restart发送了很长时间(超过1ms)或者cpu需要处理其他进程,将调用netif_schedule函数将dev加入softdate_net的output_queue队列中(其中的设备都是有报文等待发送的,将在稍后被处理)。然后触发一次NET_TX_SOFTIRQ软中断。
qdisc_restart调用dev->qdisc->dequeue方法从队列中取出待发送的报文,如果报文发送失败,qdisc_restart会调用dev->qdisc->requeue方法将skb重新放回队列。同时,还将调用netif_schedule函数将dev加入softdate_net的output_queue队列中(其中的设备都是有报文等待发送的,将在稍后被处理)。然后触发一次NET_TX_SOFTIRQ软中断(见下文中软中断介绍)。于是在下一个中断到来时,对应的软中断处理函数net_tx_action将被调用。
qdisc_restart调用dev->qdisc->dequeue方法从队列中取出待发送的报文,如果dev->hard_start_xmit方法发送报文成功,则表示报文已经送到了网络设备的发送缓冲区,设备会自动将报文发送出去。当该设备在报文发送完成时,会通过中断通知驱动程序。对应的中断处理函数也会触发NET_TX_SOFTIRQ软中断。此外,已发送完成的skb将被加入softdate_net的completion_queue队列中,等待被释放。
软中断NET_TX_SOFTIRQ被触发,将使得net_tx_action函数被调用。该函数主要做了两件事:
1、从softdate_net的completion_queue队列中取出每一个skb,将其释放;
2、对于softdate_net的output_queue队列中的dev,调用qdisc_run继续尝试发送其qdisc队列中的报文;
对于有队列的设备,其队列主要用于流量控制以及发送失败时的缓冲;对于没有队列的设备(比如lo,环回设备),dev_queue_xmit函数则会直接调用dev->hard_start_xmit进行发送,如果失败报文就会被丢弃。

PS:软中断
softirq_vec数据有32个元素,对应的是可以有32个软件中断,但实际上linux只是使用了其中的6个软中断,相应的每一个CPU都会有一个对应的32位的掩码__softirq_pending描述挂起的软中断,每一位对应一个软件中断。
local_softirq_pending()宏用于选择当前CPU所对应的__softirq_penging掩码
raise_softirq_irqoff()函数必须是在禁止中断的情况下执行的,它首先调用__raise_softirq_irqoff()宏激活软件中断,其实也就是设置当前CPU所对应的__softirq_pending所对应的软中断的位,以表示该软中断已激活。如果当前正处于中断或者软中断当中,那么raise_softirq_irqoff执行结束,否则的话就调用wakeup_softirqd()函数激活ksoftirqd/n内核线程来处理软中断。
wakeup_softirqd()函数会唤醒内核线程ksoftirqd。

static int ksoftirqd(void * __bind_cpu)
{
。。。。。。。。。。。。。。。。。。。。。。
while (local_softirq_pending()) {
/* Preempt disable stops cpu going offline.
If already offline, we‘ll be on wrong CPU:
don‘t process */
if (cpu_is_offline((long)__bind_cpu))
goto wait_to_die;
do_softirq();
preempt_enable_no_resched();
cond_resched();
preempt_disable();
}
preempt_enable();
set_current_state(TASK_INTERRUPTIBLE);
}
。。。。。。。。。。。。。。。。。。。。。。。。
return 0;
}

参考:

http://blog.csdn.net/shaohaigod1981/article/details/4776314
http://basiccoder.com/kernel-softirq.html

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。