日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > linux >内容正文

linux

linux 网络协议栈变化,ZZ Linux网络协议栈学习

發布時間:2025/3/15 linux 23 豆豆
生活随笔 收集整理的這篇文章主要介紹了 linux 网络协议栈变化,ZZ Linux网络协议栈学习 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

最近學習linux內核網絡協議棧,把數據包接收流程大致理了一下,

前面也看了瀚海書香兄的總結,感覺總結的比我精煉,抓住了主干,是一目了然的那種

我的這篇本來是自己看得,因此把我自己學習中一些遇到的問題寫了出來,可能其他人會覺得廢話比較多,呵呵

另外,因為我看的書Understanding Linux Network Internal只講了ip層及以下,

因此L4層的流程是我自己在代碼中找的,不保證100%正確,

如果有錯誤,還希望大蝦及時指出,防止誤人子弟

NAPI驅動流程:

中斷發生

-->確定中斷原因是數據接收完畢(中斷原因也可能是發送完畢,DMA完畢,甚至是中斷通道上的其他設備中斷)

-->通過netif_rx_schedule將驅動自己的napi結構加入softnet_data的poll_list鏈表,禁用網卡中斷,并發出軟中斷

-->中斷返回時觸發軟中斷net_rx_action,從softnet_data的poll_list上取下剛掛入的napi結構,并且調用其

poll函數,這個poll函數也是驅動自己提供的,比如rtl8139網卡驅動中的rtl8139_poll等。

-->在poll函數中進行輪詢,直到接受完所有的數據或者預算(budget)耗盡。每接收一個報文要分配skb,用eth_type_trans處理并交給netif_receive_skb。

-->如果數據全部接收完(預算沒有用完),則重新使能中斷并將napi從鏈表中取下。如果數據沒接收完,則什么也不作,等待下一次poll函數被調度。

非NAPI流程:

中斷發生

-->確定中斷發生的原因是接收完畢。分配skb,讀入數據,用eth_type_trans處理并且將skb交給netif_rx

-->在netif_rx中,將packet加入到softnet_data的input_pkt_queue末尾(NAPI驅動不使用這個

input_pkt_queue),再通過napi_schedule將softnet_data中的backlog(這也是個napi結構)加入

softnet_data的poll_list,最后發出軟中斷

-->軟中斷net_rx_action從poll_list上取下softnet_data的backlog,調用其poll函數,這個poll函數是內核提供的process_backlog

-->函數process_backlog從softnet_data的input_pkt_queue末尾取下skb,并且直接交給netif_receive_skb處理。

-->如果input_pkt_queue中所有skb都處理完則將backlog從隊列中除去(注意input_pkt_queue中可能有多個網卡加入的報文,因為它是每cpu公用的)并退出循環;如果預算用完后也跳出循環。最后返回接受到的包數

總結:

NAPI和非NAPI的區別

1.NAPI使用中斷+輪詢的方式,中斷產生之后暫時關閉中斷然后輪詢接收完所有的數據包,接著再開中斷。而非NAPI采用純粹中斷的方式,一個中斷接收一個數據包

2.NAPI都有自己的struct napi結構,非NAPI沒有

3.NAPI有自己的poll函數,而且接收數據都是在軟中斷調用poll函數時做的,而非NAPI使用公共的process_backlog函數作為其poll函數,接收數據是在硬件中斷中做的

4.NAPI在poll函數中接收完數據之后直接把skb發給netif_receive_skb,而非NAPI在硬件中斷中接收了數據通過

netif_rx把skb掛到公共的input_pkt_queue上,最后由軟中斷調用的process_backlog函數來將其發送給

netif_receive_skb

驅動以及軟中斷這塊對skb僅僅做了以下簡單處理:

1.調用skb_reserve預留出2個字節的空間,這是為了讓ip首部對齊,因為以太網首部是14字節

2.調用skb_put將tail指向數據末尾

3.調用eth_type_trans進行如下處理:

(1)將skb->dev指向接收設備

(2)將skb->mac_header指向data(此時data就是指向mac起始地址)

(3)調用skb_pull(skb, ETH_HLEN)將skb->data后移14字節指向ip首部

(4)通過比較目的mac地址判斷包的類型,并將skb->pkt_type賦值PACKET_BROADCAST或PACKET_MULTICAST或者PACKET_OTHERHOST,因為PACKET_HOST為0,所以是默認值

(5)最后判斷協議類型,并返回(大部分情況下直接返回eth首部的protocol字段的值),這個返回值被存在skb->protocol字段中

總結,結束后,skb->data指向ip首部,skb->mac_header指向

mac首部,skb->protocol儲存L3的協議代碼,skb->pkt_type已被設置,skb->len等于接收到的報文

長度減去eth首部長度,也就是整個ip報文的總長。其余字段基本上還是默認值。

netif_receive_skb

1.將skb->iif賦值為skb->dev->ifindex,將skb->network_header和

skb->transport_header都指向skb->data,也就是ip首部,然后skb->mac_len=skb-&

gt;network_header-skb->mac_header,正常情況下應該等于ETH_HLEN吧

2.向ptype_all中注冊(通過dev_add_pack)的每一個packet_type調用一次deliver_skb,這里沒有拷貝skb,只是先增加了一下skb->users

3.調用handle_bridge處理橋報文,如果該dev不是一個橋端口則直接返回

4.調用handle_macvlan處理vlan

5.對于每一個在ptype_base中注冊的packet_type(也是用dev_add_pack),調用deliver_skb

6.如果沒有任何一個注冊的packet_type接受skb則直接kfree_skb并且返回NET_RX_DROP。否則返回最后一個pkt_type->func返回的值

總結,需要說一下dev_add_pack,這個函數根據傳入的packet_type的type字

段決定加入哪個隊列,如果是ETH_P_ALL就加入ptype_all,否則計算哈希值并加入ptype_base,通過這個函數注冊的都是L3層的協

議,比如ip,arp,rarp,bootp等,其實還有packet協議族套接字的監聽函數(除了ETH_P_ALL之外都加入ptype_base,

它們對應的接收函數是packet_rcv),這里對于ip來說,接受函數就是ip_rcv。

經過這個函數,又有幾個字段發生變化:

network_header和transport_header都指向ip首部,mac_len為mac首部長度

ip_rcv:

1.丟棄所有pkt_type為OTHER_HOST的包,注意對于將網卡設為混雜模式的監聽進程來說,這個包已經在netif_receive_skb中給它們發送了一份拷貝

2.檢查skb是否被共享,如果被共享需要用skb_clone拷貝一份,因為后面要對skb的內容進行變更

3.常規檢測:如果報文的長度小于ip首部最小長度,丟棄;如果ip協議字段不等于4丟棄;若ip首部長度字段小于5,丟棄;若ip首部長度小于ip首部

長度字段*4,丟棄;如果ip首部校驗和出錯,丟棄;如果skb->len(此時len為整個ip報文長度)小于ip首部總長字段,丟棄;如果ip

首部總長字段小于ip首部長度字段,丟棄;

4.注意第三步中skb->len是可以小于ip首部的總長字段的,因為根據代碼注釋,傳輸介質有可能在末尾添加了padding,在這種情況下,

會調用pskb_trim_rcsum將多余的結尾部分砍掉(通過把skb->tail往前移),并且還要將檢查和無效化

5.此處調用NF_INET_PRE_ROUTING鉤子函數

總結,ip_rcv主要進行的常規檢查,唯一對skb進行操作的就是將結尾的填充字段砍掉。

ip_rcv_finish:

1.首先,如果skb->dst為空,說明還不確定這個ip報文的目的地是本機還是別的機器,這時通過ip_route_input來找到rtable并且賦給skb->rtable

2.如果ip首部長度字段大于5則調用ip_rcv_options處理ip選項。該函數調用ip_options_compile將選項全部處理放在

skb的cb字段中,作為一個struct

ip_options(還要詳細看ip_options_compile)。如果有源站路由選項則檢查設備是否支持源站路由(軟件支持,可配置),則調用

ip_options_rcv_srr(此函數也還需認真看)填寫源站路由。

3.添加統計信息并調用dst_input,dst_input只是調用skb->dst->input函數,這個skb->dst就

是前面用ip_route_input確定的,而根據dst類型的不同,這個input函數可能是ip_local_deliver或者

ip_forward,這里我們看ip_local_deliver。

總結,ip_rcv_finish改變了skb->dst字段(如果本來

skb->dst字段已經有值則不改變)和skb->cb字段(在ip_rcv_options中將ip首部選項編譯之后放入cb)。

ip_options_compile可以改變報文內容,比如填寫路由記錄選項,填寫時間戳選項等

ip_local_deliver

1.如果ip首部offset或者MF不為0,則調用ip_defrag進行ip分片的重組,ip_defrag只在成功完全重組了一個報文之后才會返回

0,其他情況都是返回非0,如果返回非0就會從ip_local_deliver返回。ip_defrag也比較復雜,需要細看,總體來說就是將分片放在

一個哈希表中,開啟定時器,來一個分片就與前面屬于同一ip報文的分片合并(兩個分片是否屬于同一個ip報文是通過ip的id字段,源目的地址,L4協議

等多個參數確定的,可參考ip4_frag_match)

2.鉤子NF_INET_LOCAL_IN,并調用ip_local_deliver_finish

總結,從上兩個函數可以看出NF_INET_PRE_ROUTING和

NF_INET_LOCAL_IN之間的區別,前者還沒有經過路由處理,即skb->dst一般還沒有確定,而后者是已經確定了

skb->dst且dst為本地地址,假如skb->dst不是本地地址則會調用ip_forward,這就不會觸發

NF_INET_LOCAL_IN了。另外NF_INET_PRE_ROUTING尚未對ip分片進行合并處理,而NF_INET_LOCAL_IN抓到

的數據包是已經合并成的ip報文了

ip_local_deliver_finish

1.將skb->data繼續移動指向傳輸層首部,并且將skb->transport_header也指向傳輸層首部,接下來開始處理

2.首先從ip首部取得傳輸層協議號,然后用這個協議號調用raw_local_deliver將skb傳給raw_v4_hashinfo哈希表中的原始套接字協議

3.再利用protocol值作為下標取得inet_protos全局數組中的注冊協議(對于tcp,udp,icmp分別是

tcp_protocol,udp_protocol,icmp_protocol)。如果找到了對應的協議處理結構,就把skb交給該結構的

handler函數處理(對于tcp,udp,icmp分別是tcp_v4_rcv,udp_rcv,icmp_rcv)。如果沒找到對應的處理結構,則

回發一個icmp協議不可達的目的不可達報文,并釋放skb。

總結:這里又一次移動了skb->data指針,將其指向傳輸層首部,同時設置了

transport_header也指向傳輸層首部。raw_v4_hashinfo和inet_protos都是一個256項的全局數組,以協議號為下

標保存了各個協議的處理結構。這兩個數組就像是L4層的ptype_base,根據本層的協議號來決定處理函數

注意區別raw_v4_hashinfo和上面的ptype_all,前者是AF_INET的SOCK_RAW套接字注冊的接收結構,而后者是

AF_PACKET套接字注冊的接收結構,可見raw套接字是經過了ip層處理的而packet是在netif_receive_skb中接收的,尚未經

過任何處理,其中一個顯著區別就是raw經過了ip_defrag而packet沒有

對于udp來說,inet_protos中的結構是全局變量udp_protocol,它的handler函數是udp_rcv

udp_rcv所做的就是直接調用__udp4_lib_rcv(skb, udp_hash, IPPROTO_UDP);

__udp4_lib_rcv

此函數中會調用__udp4_lib_lookup_skb-->()__udp4_lib_lookup()來查找此udp包對應的socket,主要是查找源目的地址和端口號都符合的socket。

如果查找到了對應的socket,則調用udp_queue_rcv_skb將skb放入udp的接收隊列,然后返回0

如果沒有查找到對應的socket,要向源地址發送一個ICMP端口不可達消息

udp_queue_rcv_skb

它經過__udp_queue_rcv_skb(sk,

skb)-->__udp_queue_rcv_skb-->skb_queue_tail一系列調用過程將skb加入socket的接收隊

列sk->sk_receive_queuek末尾。其中還要檢測接收緩沖區是否已經滿。

接著調用sk->sk_data_ready(sk, skb_len)通知socket有數據就緒,可以讀了。一般情況下這個函數對應sock_def_readable,這個函數的功能就是喚醒在sk->sk_sleep上睡眠的進程

那么是誰在這里睡眠呢?在調用recvfrom系統調用接收報文的時候,會經過這樣一個流程

sys_socketcall

-->sys_recvfrom

-->sock_recvmsg

-->__sock_recvmsg

-->sock->ops->recvmsg,這個sock->ops對應全局變量inet_dgram_ops,里面的recvmsg對應sock_common_recvmsg

-->sock_common_recvmsg

-->sk->sk_prot->recvmsg,這個sk->sk_prot對應全局變量udp_prot,里面的recvmsg對應udp_recvmsg

-->udp_recvmsg

-->__skb_recv_datagram

在__skb_recv_datagram中,會首先嘗試從sk->sk_receive_queue上取下數據包,如果發現隊列中沒有數據包,則

開始在sk->sk_skeep上睡眠。而上面sock_def_readable喚醒的就是這里睡眠的進程。

可以看到,在__skb_recv_datagram中被喚醒后,函數又嘗試從sk->sk_receive_queue上取下數據包,這時當然會

成功,成功之后返回到udp_recvmsg。udp_recvmsg再進行一些簡單的檢測之后就調用copy_to_user將數據拷貝到用戶空間了

(其實這里并不是簡單調用copy_to_user,還要處理很多情況,比如用戶使用的msghdr可能包含多個iovec,skb可能有多個frags

等等)

這樣,一個udp數據包就從網卡到達了用戶的緩沖區

閱讀(713) | 評論(0) | 轉發(0) |

創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎

總結

以上是生活随笔為你收集整理的linux 网络协议栈变化,ZZ Linux网络协议栈学习的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。