之前一篇寫的不完整,重新寫一篇
openwrt資料發送過程 這裡使用的是ath9k網卡驅動,硬體平台是tp-link tl-wr841n v7.1 路由器
1. packet_sendmsg()
linux kernel發送資料的接口函數是packet_sendmsg,本質上對應了user space的sendmsg實作。上層通過調用sendmsg實作資料的發送。将待發送的資料放入kernel space中。
在核心檔案夾linux-3.3.8的子目錄:/net/packet中,找到檔案af_packet.c,這個檔案裡定義了如下一個結構:
這個結構采用了c99标準的初始化方式,基本用法是“.成員=變量值”(已經預先定義了一個proto_ops結構,裡面包含sendmsg函數指針)。這個結構中把上層的sendmsg函數和下層的packet_sendmsg函數對應了起來。上層通過調用sendmsg就将資料傳遞給了packet_sendmsg函數。下面我們就從核心态的packet_sendmsg出發來研究一下資料的發送過程。
下面來看看packet_sendmsg()的實作(位于linux-3.3.8/net/packet檔案夾下的af_packet.c檔案中)
2. packet_snd()
調用packet_snd()(位于linux-3.3.8/net/packet檔案夾下的af_packet.c檔案中)。
3. dev_queue_xmit()
調用dev_queue_xmit()(位于linux-3.3.8/net/core檔案夾下的dev.c檔案中)。
4. __dev_xmit_skb()
調用__dev_xmit_skb()(位于linux-3.3.8/net/core檔案夾下的dev.c檔案中)。
5. sch_direct_xmit()
調用sch_direct_xmit()(位于linux-3.3.8/net/sched檔案夾下的sch_generic.c檔案中)。
6. dev_hard_start_xmit()
調用dev_hard_start_xmit()(位于linux-3.3.8/net/core檔案夾下的dev.c檔案中)。
7. ops->ndo_start_xmit
這裡調用了ops->ndo_start_xmit,那麼ndo_start_xmit對應哪個函數?
為了探究ndo_start_xmit對應的函數,我們需要回頭看看在驅動啟動過程章節末尾提到的函數ieee80211_register_hw()。(位于openwrt核心檔案夾子目錄/net/mac80211,檔案main.c中)。
7.1 ieee80211_register_hw()
7.2 ieee80211_if_add()
調用函數ieee80211_if_add()(位于openwrt核心檔案夾子目錄/net/mac80211,檔案iface.c中)。
7.3 ieee80211_setup_sdata()
繼續跟進ieee80211_setup_sdata()(位于openwrt核心檔案夾子目錄/net/mac80211,檔案iface.c中)。
從上述代碼可以看出,ieee80211_dataif_ops這一組函數指針被注冊到裝置中。而ieee80211_dataif_ops的定義如下(位于openwrt核心檔案夾子目錄/net/mac80211,檔案iface.c中):
顯然,ndo_start_xmit對應的函數是ieee80211_subif_start_xmit()
8. ieee80211_subif_start_xmit()
調用ieee80211_subif_start_xmit()(位于openwrt核心檔案夾子目錄/net/mac80211,檔案tx.c中)。
9. ieee80211_xmit()
調用ieee80211_xmit()(位于openwrt核心檔案夾子目錄/net/mac80211,檔案tx.c中)。
10. ieee80211_tx()
調用ieee80211_tx(),若資料成功被發送則傳回true,沒有被發送出去則傳回false(位于openwrt核心檔案夾子目錄/net/mac80211,檔案tx.c中)。
11. __ieee80211_tx()
調用__ieee80211_tx()(位于openwrt核心檔案夾子目錄/net/mac80211,檔案tx.c中)。
12. ieee80211_tx_frags()
調用ieee80211_tx_frags()(位于openwrt核心檔案夾子目錄/net/mac80211,檔案tx.c中)。
為了清晰的了解整個發送過程,現在對函數做一個詳細的分析:skb_queue_walk_safe不是函數,而是核心中定義的一個宏,将其展開來是如下這種形式:
用在這裡的含義就是當隊列不為空時,對隊列中的元素進行操作。
接着使用spin_lock_irqsave将自旋鎖上鎖,也就是将中斷關閉,其中spin_lock指的是自旋鎖。将中斷關閉以後,系統無法采用中斷的方式發送資訊,這有助于對目前的發送狀态進行判決,然後再解鎖進行資訊的發送。
接下來就是對發送狀态的判斷的部分了,首先判斷隊列是否因為某個因素停止了或延遲隊列中有待發送的資料,如果是,則判斷對方站點是否已經脫離了信道,如果已經脫離了信道并且還有可發送的資訊則将資訊推送出去(這一特性由ieee80211_tx_intfl_offchan_tx_ok标記,該标記表示在發送隊列停止、且系統進行脫離信道的操作時,目前資料是可以被發送的(說起來有點拗口,程式注釋中的原句是used to indicate that a frame can betransmitted while the queues
are stopped for off-channel operation))。推送資料時需要先用spin_unlock_irqrestore對自旋鎖進行解鎖(置為1),将中斷開啟,以便讓系統利用中斷向目标站點發送資料。發送完成後,通過函數ieee80211_purge_tx_queue将隊列中的已發送資料清除掉,後面的資料頂到前面來。
如果隊列停止了并且隊列中的元素不能被發送,那麼就将待發送的元素存入延遲發送隊列,然後将隊首元素丢棄(根據程式來看,其實這個資料還是被發送出去了,隻是發送出去以後沒有站點可以對其進行接收罷了)。
如果隊列沒有停止并且延遲隊列中沒有待發送的資料,則直接将自旋鎖解鎖,将資料發送出去,發送成功後調用_skb_unlink(skb,skbs)将skb從待發送的隊列中移除。然後進入下一輪循環處理下一個待發送的資料。
最後将資料發送出去的函數是drv_tx()。下面對其進行分析。
13. drv_tx()
調用drv_tx()(位于openwrt核心檔案夾子目錄/net/mac80211,檔案driver-ops.h中)
在ieee80211_alloc_hw()函數體中有這樣的代碼(位于檔案openwrt核心檔案夾子目錄/net/mac80211,檔案main.c中):
而這個ops又是從ieee80211_alloc_hw()的參數傳進來的(系統啟動進行初始化時執行ath_pci_probe函數調用ieee80211_alloc_hw()進行傳遞,ath_pci_probe函數定義于openwrt核心檔案夾子目錄/drivers/net/wireless/ath/ath9k/,檔案pci.c中),也就是ath9k_ops(定義于openwrt核心檔案夾子目錄/drivers/net/wireless/ath/ath9k/,檔案main.c中)。我們在驅動啟動過程中提到過。
結構ath9k_ops如下所示:
是以local->ops-tx()實際上就觸發了ath9k_tx()
14. ath9k_tx()
調用ath9k_tx()(定義于openwrt核心檔案夾子目錄/drivers/net/wireless/ath/ath9k/,檔案main.c中)
15. ath_tx_start()
調用ath_tx_start()(定義于openwrt核心檔案夾子目錄/drivers/net/wireless/ath/ath9k/,檔案xmit.c中)
經過這些函數調用步驟之後,資訊被傳送出去。