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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM