Linux數(shù)據(jù)報文處理

一、網(wǎng)卡把報文傳到內(nèi)核的流程圖



? 圖1. 網(wǎng)卡傳遞數(shù)據(jù)包到內(nèi)核的流程圖

1. 網(wǎng)卡在啟動時會申請一個接收ring buffer,其條目都會指向一個skb的內(nèi)存。

2. DMA完成數(shù)據(jù)報文從網(wǎng)卡硬件到內(nèi)存到拷貝后,網(wǎng)卡發(fā)送一個中斷通知CPU。

3. CPU執(zhí)行網(wǎng)卡驅(qū)動注冊的中斷處理函數(shù),中斷處理函數(shù)只做一些必要的工作,如讀取硬件狀態(tài)等,并把當(dāng)前該網(wǎng)卡掛在NAPI的鏈表中,同時會“觸發(fā)”NET_RX_SOFTIRQ(其實就是設(shè)置對應(yīng)軟中斷的標(biāo)志位)。

4. CPU中斷處理函數(shù)返回后,會檢查是否有軟中斷需要執(zhí)行。因第三步設(shè)置了NET_RX_SOFTIRQ,則執(zhí)行報文接收軟中斷。

5. 在NET_RX_SOFTIRQ軟中斷中,執(zhí)行NAPI操作,回調(diào)第三步掛載的驅(qū)動poll函數(shù)。

6. 驅(qū)動會對interface進(jìn)行poll操作,檢查網(wǎng)卡是否有接收完畢的數(shù)據(jù)報文。

7. 將網(wǎng)卡中已經(jīng)接收完畢的數(shù)據(jù)報文取出,繼續(xù)在軟中斷進(jìn)行后續(xù)處理。

注:驅(qū)動對interface執(zhí)行poll操作時,會嘗試循環(huán)檢查網(wǎng)卡是否有接收完畢的報文,直到設(shè)置的budget上限,或者已經(jīng)就緒報文。

二、接收軟中斷將報文分發(fā)給協(xié)議棧的示意圖


圖2. 接收軟中斷將報文分發(fā)給對應(yīng)協(xié)議棧

1. 假設(shè)是CPU0在執(zhí)行接收軟中斷net_rx_action

2. 接收報文的網(wǎng)卡是否支持XDP,若支持且啟用,則進(jìn)行xdp的檢查。

3. 查看是否啟用了RPS & RFS。若啟用且目的CPU非當(dāng)前CPU,則將數(shù)據(jù)報文enque到目的CPU的backlog隊列中,并在軟中斷中發(fā)送IPI中斷給目的CPU。目的CPU的IPI處理函數(shù)會將目的CPU的backlog掛到其NAPI列表中,這樣由其他CPU發(fā)送過來的報文,就會在其后面NAPI中被處理。

4. CPU0負(fù)責(zé)處理當(dāng)前報文。如果網(wǎng)卡沒有vlan offload,則需要軟件剝掉vlan頭,以便后面的報文處理。

5. 在分發(fā)報文時,可能會有多個handler關(guān)心此報文。所以在分發(fā)時,需要先增加skb的引用計數(shù),然后傳遞給該handler。

(注:感謝 @alawnfeng同學(xué)指正,我之前記錯了,寫成clone了)

6. 將報文分發(fā)給“ETH_P_ALL”的handler,即關(guān)心所有以太網(wǎng)報文的handler(不限于任何協(xié)議)

7. 通過以太網(wǎng)報文的協(xié)議,將數(shù)據(jù)報文分發(fā)給該協(xié)議的handler,如IPv4,IPv6,PPPoE等。

三、協(xié)議棧將數(shù)據(jù)報文發(fā)給套接字(以IPv4為例)的流程圖


圖3. 協(xié)議棧將報文發(fā)給套接字的流程圖

報文從上到下的分發(fā)過程很相似。每層協(xié)議都會包含上層協(xié)議類型,然后根據(jù)類型進(jìn)行分發(fā)。以IPv4協(xié)議棧(inet協(xié)議族)來說,初始化階段,調(diào)用dev_add_pack注冊了二層以太網(wǎng)的IP協(xié)議類型。而四層協(xié)議icmp、udp、tcp也是在inet初始化階段,調(diào)用inet_add_protocol注冊。

那么,報文接收的流程如下:

1. __netif_receive_skb_core處于二層協(xié)議處理階段,其根據(jù)以太網(wǎng)的報文類型,從packet_type中找到匹配的三層協(xié)議。

2. 進(jìn)入ip報文的處理函數(shù)ip_rcv,進(jìn)行netfiler的prerouting階段的檢查。

3. 獲得四層協(xié)議類型,調(diào)用其early_demux。這是一個優(yōu)化,對于符合條件的報文,可以盡早處理。

4. 查找路由。對于發(fā)給本機的IP報文,其路由的input處理函數(shù),即ip_local_deliver。

5. 繼續(xù)netfilter的localin階段的檢查。

6. 通過檢查后,將報文發(fā)給本機的raw socket。

7. 根據(jù)四層協(xié)議類型,調(diào)用匹配的四層協(xié)議處理函數(shù)。對于UDP報文來說,就是udp_rcv。

8. 根據(jù)源端口和目的端口,確定socket套接字。

9. 將skb報文加入套接字的接收隊列。

四、報文從應(yīng)用層到網(wǎng)卡的流程圖


圖4. 應(yīng)用層發(fā)包到網(wǎng)卡的流程

1. 應(yīng)用層調(diào)用socket系統(tǒng)調(diào)用,內(nèi)核會在內(nèi)部根據(jù)協(xié)議類型,創(chuàng)建一個socket對象,且其成員變量ops指向udp_proto(以UDP為例)。

2. 應(yīng)用層調(diào)用send,write等發(fā)送函數(shù),將socket fd和數(shù)據(jù)data傳給內(nèi)核。

3. 內(nèi)核通過fd獲得socket對象,并將應(yīng)用層的數(shù)據(jù)復(fù)制到內(nèi)核,調(diào)用socket成員變量對應(yīng)的sendmsg。

4. 內(nèi)核調(diào)用ip_route_output_flow查詢路由。

5. 調(diào)用ip_make_skb,申請一個skb用于發(fā)送報文,并填充了IP頭。

6. 調(diào)用udp_send、ip_send_skb,填充UDP報文頭,計算IP頭的checksum等。

7. 調(diào)用ip_localout,到達(dá)本機IP層發(fā)送報文的最后階段,進(jìn)行netfilter localout階段的檢查。

8. 調(diào)用neigh_output,即鄰居層填充二層目的MAC。如果沒有ARP信息,則緩存報文,發(fā)送ARP報文,進(jìn)行二層地址解析。

9. 調(diào)用dev_queue_xmit,進(jìn)入qdisc schedule即流量控制,也就是TC的實現(xiàn)。

10. 默認(rèn)情況,網(wǎng)卡的qdisc策略是FIFO。被schedule的數(shù)據(jù)報文,通過dev_hard_start_xmit調(diào)用網(wǎng)卡驅(qū)動的ndo_start_xmit,將報文交給網(wǎng)卡進(jìn)行發(fā)送。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容