轉自http://abcdxyzk.github.io/blog/2015/04/18/kernel-net-gro/git
http://www.pagefault.info/?p=159github
GRO(Generic receive offload)在內核2.6.29以後合併進去的,做者是一個華裔Herbert Xu ,GRO的簡介能夠看這裏:算法
http://lwn.net/Articles/358910/api
先來描述一下GRO的做用,GRO是針對網絡接受包的處理的,而且只是針對NAPI類型的驅動,所以若是要支持GRO,不只要內核支持,並且驅動也必須調用相應的藉口,用ethtool -K gro on來設置,若是報錯就說明網卡驅動自己就不支持GRO。緩存
GRO相似tso,但是tso只支持發送數據包,這樣你tcp層大的段會在網卡被切包,而後再傳遞給對端,而若是沒有gro,則小的段會被一個個送到協議棧,有了gro以後,就會在接收端作一個反向的操做(想對於tso).也就是將tso切好的數據包組合成大包再傳遞給協議棧。網絡
若是實現了GRO支持的驅動是這樣子處理數據的,在NAPI的回調poll方法中讀取數據包,而後調用GRO的接口napi_gro_receive或者napi_gro_frags來將數據包feed進協議棧。而具體GRO的工做就是在這兩個函數中進行的,他們最終都會調用__napi_gro_receive
。下面就是napi_gro_receive,它最終會調用napi_skb_finish以及__napi_gro_receive
。數據結構
1
2 3 4 5 6 |
gro_result_t napi_gro_receive(struct napi_struct *napi, struct sk_buff *skb) { skb_gro_reset_offset(skb); return napi_skb_finish(__napi_gro_receive(napi, skb), skb); } |
而後GRO何時會將數據feed進協議棧呢,這裏會有兩個退出點,一個是在napi_skb_finish裏,他會經過判斷__napi_gro_receive
的返回值,來決定是須要將數據包當即feed進協議棧仍是保存起來,還有一個點是當napi的循環執行完畢時,也就是執行napi_complete的時候,先來看napi_skb_finish,napi_complete咱們後面會詳細介紹。tcp
在NAPI驅動中,直接調用netif_receive_skb會將數據feed 進協議棧,所以這裏若是返回值是NORMAL,則直接調用netif_receive_skb來將數據送進協議棧。函數
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
gro_result_t napi_skb_finish(gro_result_t ret, struct sk_buff *skb) { switch (ret) { case GRO_NORMAL: //將數據包送進協議棧 if (netif_receive_skb(skb)) ret = GRO_DROP; break; //表示skb能夠被free,由於gro已經將skb合併並保存起來。 case GRO_DROP: case GRO_MERGED_FREE: //free skb kfree_skb(skb); break; //這個表示當前數據已經被gro保存起來,可是並無進行合併,所以skb還須要保存。 case GRO_HELD: case GRO_MERGED: break; } return ret; } |
GRO的主要思想就是,組合一些相似的數據包(基於一些數據域,後面會介紹到)爲一個大的數據包(一個skb),而後feed給協議棧,這裏主要是利用Scatter-gather IO,也就是skb的struct skb_shared_info域(我前面的blog講述ip分片的時候有詳細介紹這個域)來合併數據包。spa
在每一個NAPI的實例都會包括一個域叫gro_list,保存了咱們積攢的數據包(將要被merge的).而後每次進來的skb都會在這個鏈表裏面進行查找,看是否須要merge。而gro_count表示當前的gro_list中的skb的個數。
1
2 3 4 5 6 7 8 9 |
struct napi_struct { ................................................ //個數 unsigned int gro_count; ...................................... //積攢的數據包 struct sk_buff *gro_list; struct sk_buff *skb; }; |
緊接着是gro最核心的一個數據結構napi_gro_cb,它是保存在skb的cb域中,它保存了gro要使用到的一些上下文,這裏每一個域kernel的註釋都比較清楚。到後面咱們會看到這些域的具體用途。
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
struct napi_gro_cb { /* Virtual address of skb_shinfo(skb)->frags[0].page + offset. */ void *frag0; /* Length of frag0. */ unsigned int frag0_len; /* This indicates where we are processing relative to skb->data. */ int data_offset; /* This is non-zero if the packet may be of the same flow. */ int same_flow; /* This is non-zero if the packet cannot be merged with the new skb. */ int flush; /* Number of segments aggregated. */ int count; /* Free the skb? */ int free; }; |
每一層協議都實現了本身的gro回調函數,gro_receive和gro_complete,gro系統會根據協議來調用對應回調函數,其中gro_receive是將輸入skb儘可能合併到咱們gro_list中。而gro_complete則是當咱們須要提交gro合併的數據包到協議棧時被調用的。
下面就是ip層和tcp層對應的回調方法:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
static const struct net_protocol tcp_protocol = { .handler = tcp_v4_rcv, .err_handler = tcp_v4_err, .gso_send_check = tcp_v4_gso_send_check, .gso_segment = tcp_tso_segment, //gso回調 .gro_receive = tcp4_gro_receive, .gro_complete = tcp4_gro_complete, .no_policy = 1, .netns_ok = 1, }; static struct packet_type ip_packet_type __read_mostly = { .type = cpu_to_be16(ETH_P_IP), .func = ip_rcv, .gso_send_check = inet_gso_send_check, .gso_segment = inet_gso_segment, //gso回調 .gro_receive = inet_gro_receive, .gro_complete = inet_gro_complete, }; |
gro的入口函數是napi_gro_receive,它的實現很簡單,就是將skb包含的gro上下文reset,而後調用__napi_gro_receive
,最終經過napi_skb_finis來判斷是否須要講數據包feed進協議棧。
1
2 3 4 5 6 7 |
gro_result_t napi_gro_receive(struct napi_struct *napi, struct sk_buff *skb) { //reset gro對應的域 skb_gro_reset_offset(skb); return napi_skb_finish(__napi_gro_receive(napi, skb), skb); } |
napi_skb_finish一開始已經介紹過了,這個函數主要是經過判斷傳遞進來的ret(__napi_gro_receive
的返回值),來決定是否須要feed數據進協議棧。它的第二個參數是前面處理過的skb。
這裏再來看下skb_gro_reset_offset,首先要知道一種狀況,那就是skb自己不包含數據(包括頭也沒有),而全部的數據都保存在skb_shared_info中(支持S/G的網卡有可能會這麼作).此時咱們若是想要合併的話,就須要將包頭這些信息取出來,也就是從skb_shared_info的frags[0]中去的,在 skb_gro_reset_offset中就有作這個事情,而這裏就會把頭的信息保存到napi_gro_cb 的frags0中。而且此時frags必然不會在high mem,要麼是線性區,要麼是dma(S/G io)。 來看skb_gro_reset_offset。
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
void skb_gro_reset_offset(struct sk_buff *skb) { NAPI_GRO_CB(skb)->data_offset = 0; NAPI_GRO_CB(skb)->frag0 = NULL; NAPI_GRO_CB(skb)->frag0_len = 0; //若是mac_header和skb->tail相等而且地址不在高端內存,則說明包頭保存在skb_shinfo中,因此咱們須要從frags中取得對應的數據包 if (skb->mac_header == skb->tail && !PageHighMem(skb_shinfo(skb)->frags[0].page)) { // 能夠看到frag0保存的就是對應的skb的frags的第一個元素的地址 // frag0的做用是: 有些包的包頭會存在skb->frag[0]裏面,gro合併時會調用skb_gro_header_slow將包頭拉到線性空間中,那麼在非線性skb->frag[0]中的包頭部分就應該刪掉。 NAPI_GRO_CB(skb)->frag0 = page_address(skb_shinfo(skb)->frags[0].page) + skb_shinfo(skb)->frags[0].page_offset; //而後保存對應的大小。 NAPI_GRO_CB(skb)->frag0_len = skb_shinfo(skb)->frags[0].size; } } |
接下來就是__napi_gro_receive
,它主要是遍歷gro_list,而後給same_flow賦值,這裏要注意,same_flow是一個標記,表示某個skb是否有可能會和當前要處理的skb是相同的流,而這裏的相同會在每層都進行判斷,也就是在設備層,ip層,tcp層都會判斷,這裏就是設備層的判斷了。這裏的判斷很簡單,有2個條件:
1 設備是否相同
2 mac的頭必須相等
若是上面兩個條件都知足,則說明兩個skb有多是相同的flow,因此設置same_flow,以便與咱們後面合併。
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
static gro_result_t __napi_gro_receive(struct napi_struct *napi, struct sk_buff *skb) { struct sk_buff *p; if (netpoll_rx_on(skb)) return GRO_NORMAL; //遍歷gro_list,而後判斷是否有可能兩個skb 類似。 for (p = napi->gro_list; p; p = p->next) { //給same_flow賦值 NAPI_GRO_CB(p)->same_flow = (p->dev == skb->dev) && !compare_ether_header(skb_mac_header(p), skb_gro_mac_header(skb)); NAPI_GRO_CB(p)->flush = 0; } //調用dev_gro_receiv return dev_gro_receive(napi, skb); } |
接下來來看dev_gro_receive,這個函數咱們分作兩部分來看,第一部分是正常處理部分,第二部份是處理frag0的部分。
來看如何判斷是否支持GRO,這裏每一個設備的features會在驅動初始化的時候被初始化,而後若是支持GRO,則會包括NETIF_F_GRO。 還有要注意的就是,gro不支持切片的ip包,由於ip切片的組包在內核的ip會作一遍,所以這裏gro若是合併的話,沒有多大意義,並且還增長複雜度。
在dev_gro_receive中會遍歷對應的ptype(也就是協議的類鏈表,之前的blog有詳細介紹),而後調用對應的回調函數,通常來講這裏會調用文章開始說的ip_packet_type,也就是 inet_gro_receive。
而 inet_gro_receive的返回值表示咱們須要馬上feed 進協議棧的數據包,若是爲空,則說明不須要feed數據包進協議棧。後面會分析到這裏他的詳細算法。
而若是當inet_gro_receive正確返回後,若是same_flow沒有被設置,則說明gro list中不存在能和當前的skb合併的項,所以此時須要將skb插入到gro list中。這個時候的返回值就是HELD。
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
enum gro_result dev_gro_receive(struct napi_struct *napi, struct sk_buff *skb) { struct sk_buff **pp = NULL; struct packet_type *ptype; __be16 type = skb->protocol; struct list_head *head = &ptype_base[ntohs(type) & PTYPE_HASH_MASK]; int same_flow; int mac_len; enum gro_result ret; //判斷是否支持gro if (!(skb->dev->features & NETIF_F_GRO)) goto normal; //判斷是否爲切片的ip包 if (skb_is_gso(skb) || skb_has_frags(skb)) goto normal; rcu_read_lock(); //開始遍歷對應的協議表 list_for_each_entry_rcu(ptype, head, list) { if (ptype->type != type || ptype->dev || !ptype->gro_receive) continue; skb_set_network_header(skb, skb_gro_offset(skb)); mac_len = skb->network_header - skb->mac_header; skb->mac_len = mac_len; NAPI_GRO_CB(skb)->same_flow = 0; NAPI_GRO_CB(skb)->flush = 0; NAPI_GRO_CB(skb)->free = 0; //調用對應的gro接收函數 pp = ptype->gro_receive(&napi->gro_list, skb); break; } rcu_read_unlock(); //若是是沒有實現gro的協議則也直接調到normal處理 if (&ptype->list == head) goto normal; //到達這裏,則說明gro_receive已經調用過了,所以進行後續的處理 //獲得same_flow same_flow = NAPI_GRO_CB(skb)->same_flow; //看是否有須要free對應的skb ret = NAPI_GRO_CB(skb)->free ? GRO_MERGED_FREE : GRO_MERGED; //若是返回值pp部位空,則說明pp須要立刻被feed進協議棧 if (pp) { struct sk_buff *nskb = *pp; *pp = nskb->next; nskb->next = NULL; //調用napi_gro_complete 將pp刷進協議棧 napi_gro_complete(nskb); napi->gro_count--; } //若是same_flow有設置,則說明skb已經被正確的合併,所以直接返回。 if (same_flow) goto ok; //查看是否有設置flush和gro list的個數是否已經超過限制 // BUG: 這裏是有點不對的,由於這時的skb是比gro_list中的skb更晚到的,可是卻被先feed進了協議棧 if (NAPI_GRO_CB(skb)->flush || napi->gro_count >= MAX_GRO_SKBS) goto normal; //到達這裏說明skb對應gro list來講是一個新的skb,也就是說當前的gro list並不存在能夠和skb合併的數據包,所以此時將這個skb插入到gro_list的頭。 napi->gro_count++; NAPI_GRO_CB(skb)->count = 1; skb_shinfo(skb)->gso_size = skb_gro_len(skb); //將skb插入到gro list的頭 skb->next = napi->gro_list; napi->gro_list = skb; //設置返回值 ret = GRO_HELD; |
而後就是處理frag0的部分,以及不支持gro的處理。 frag0的做用是: 有些包的包頭會存在skb->frag[0]裏面,gro合併時會調用skb_gro_header_slow將包頭拉到線性空間中,那麼在非線性skb->frag[0]中的包頭部分就應該刪掉。
這裏要須要對skb_shinfo的結構比較瞭解,我在之前的blog對這個有很詳細的介紹,能夠去查閱。
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
pull: //是否須要拷貝頭 if (skb_headlen(skb) < skb_gro_offset(skb)) { //獲得對應的頭的大小 int grow = skb_gro_offset(skb) - skb_headlen(skb); BUG_ON(skb->end - skb->tail < grow); //開始拷貝 memcpy(skb_tail_pointer(skb), NAPI_GRO_CB(skb)->frag0, grow); skb->tail += grow; skb->data_len -= grow; //更新對應的frags[0] skb_shinfo(skb)->frags[0].page_offset += grow; skb_shinfo(skb)->frags[0].size -= grow; //若是size爲0了,則說明第一個頁所有包含頭,所以須要將後面的頁所有移動到前面。 if (unlikely(!skb_shinfo(skb)->frags[0].size)) { put_page(skb_shinfo(skb)->frags[0].page); //開始移動。 memmove(skb_shinfo(skb)->frags, skb_shinfo(skb)->frags + 1, --skb_shinfo(skb)->nr_frags * sizeof(skb_frag_t)); } } ok: return ret; normal: ret = GRO_NORMAL; goto pull; } |
接下來就是inet_gro_receive,這個函數是ip層的gro receive回調函數,函數很簡單,首先取得ip頭,而後判斷是否須要從frag複製數據,若是須要則複製數據
1
2 3 4 5 6 7 8 9 10 11 12 |
//獲得偏移 off = skb_gro_offset(skb); //獲得頭的整個長度(mac+ip) hlen = off + sizeof(*iph); //獲得ip頭 iph = skb_gro_header_fast(skb, off); //是否須要複製 if (skb_gro_header_hard(skb, hlen)) { iph = skb_gro_header_slow(skb, hlen, off); if (unlikely(!iph)) goto out; } |
而後就是一些校驗工做,好比協議是否支持gro_reveive,ip頭是否合法等等
1
2 3 4 5 6 7 8 9 10 11 12 13 |
proto = iph->protocol & (MAX_INET_PROTOS - 1); rcu_read_lock(); ops = rcu_dereference(inet_protos[proto]); //是否支持gro if (!ops || !ops->gro_receive) goto out_unlock; //ip頭是否合法, iph->version = 4, iph->ipl = 5 if (*(u8 *)iph != 0x45) goto out_unlock; //ip頭教研 if (unlikely(ip_fast_csum((u8 *)iph, iph->ihl))) goto out_unlock; |
而後就是核心的處理部分,它會遍歷整個gro_list,而後進行same_flow和是否須要flush的判斷。
這裏ip層設置same_flow是根據下面的規則的:
1 4層的協議必須相同
2 tos域必須相同
3 源,目的地址必須相同
若是3個條件一個不知足,則會設置same_flow爲0。 這裏還有一個就是判斷是否須要flush 對應的skb到協議棧,這裏的判斷條件是這樣子的。
1 ip包的ttl不同
2 ip包的id順序不對
3 若是是切片包
若是上面兩個條件某一個知足,則說明skb須要被flush出gro。
不過這裏要注意只有兩個數據包是same flow的狀況下,纔會進行flush判斷。緣由很簡單,都不是有可能進行merge的包,天然不必進行flush了。
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
//取出id id = ntohl(*(__be32 *)&iph->id); //判斷是否須要切片 flush = (u16)((ntohl(*(__be32 *)iph) ^ skb_gro_len(skb)) | (id ^ IP_DF)); id >>= 16; //開始遍歷gro list for (p = *head; p; p = p->next) { struct iphdr *iph2; //若是上一層已經不可能same flow則直接繼續下一個 if (!NAPI_GRO_CB(p)->same_flow) continue; //取出ip頭 iph2 = ip_hdr(p); //開始same flow的判斷 if ((iph->protocol ^ iph2->protocol) | (iph->tos ^ iph2->tos) | ((__force u32)iph->saddr ^ (__force u32)iph2->saddr) | ((__force u32)iph->daddr ^ (__force u32)iph2->daddr)) { NAPI_GRO_CB(p)->same_flow = 0; continue; } //開始flush的判斷。這裏注意若是不是same_flow的話,就不必進行flush的判斷。 /* All fields must match except length and checksum. */ NAPI_GRO_CB(p)->flush |= (iph->ttl ^ iph2->ttl) | ((u16)(ntohs(iph2->id) + NAPI_GRO_CB(p)->count) ^ id); NAPI_GRO_CB(p)->flush |= flush; } NAPI_GRO_CB(skb)->flush |= flush; //pull ip頭進gro,這裏更新data_offset skb_gro_pull(skb, sizeof(*iph)); //設置傳輸層的頭的位置 skb_set_transport_header(skb, skb_gro_offset(skb)); //調用傳輸層的reveive方法。 pp = ops->gro_receive(head, skb); out_unlock: rcu_read_unlock(); out: NAPI_GRO_CB(skb)->flush |= flush; } |
而後就是tcp層的gro方法,它的主要實現函數是tcp_gro_receive,他的流程和inet_gro_receiv相似,就是取得tcp的頭,而後對gro list進行遍歷,最終會調用合併方法。
首先來看gro list遍歷的部分,它對same flow的要求就是source必須相同,若是不一樣則設置same flow爲0.若是相同則跳到found部分,進行合併處理。
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
//遍歷gro list for (; (p = *head); head = &p->next) { //若是ip層已經不可能same flow則直接進行下一次匹配 if (!NAPI_GRO_CB(p)->same_flow) continue; th2 = tcp_hdr(p); //判斷源地址 if (*(u32 *)&th->source ^ *(u32 *)&th2->source) { NAPI_GRO_CB(p)->same_flow = 0; continue; } goto found; } |
接下來就是當找到可以合併的skb的時候的處理,這裏首先來看flush的設置,這裏會有4個條件:
1 擁塞狀態被設置(TCP_FLAG_CWR).
2 tcp的ack的序列號不匹配 (這是確定的,由於它只是對tso或者說gso進行反向操做)
3 skb的flag和從gro list中查找到要合併skb的flag 若是他們中的不一樣位 不包括TCP_FLAG_CWR | TCP_FLAG_FIN | TCP_FLAG_PSH,這三個任意一個域。
4 tcp的option域不一樣
若是上面4個條件有一個知足,則會設置flush爲1,也就是找到的這個skb(gro list中)必須被刷出到協議棧。
這裏談一下flags域的設置問題首先若是當前的skb設置了cwr,也就是發生了擁塞,那麼天然前面被緩存的數據包須要立刻被刷到協議棧,以便與tcp的擁塞控制立刻進行。
而FIN和PSH這兩個flag天然不須要一致,由於這兩個和其餘的不是互斥的。
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
found: flush = NAPI_GRO_CB(p)->flush; //若是設置擁塞,則確定須要刷出skb到協議棧 flush |= (__force int)(flags & TCP_FLAG_CWR); //若是相差的域是除了這3箇中的,就須要flush出skb flush |= (__force int)((flags ^ tcp_flag_word(th2)) & ~(TCP_FLAG_CWR | TCP_FLAG_FIN | TCP_FLAG_PSH)); //ack的序列號必須一致 flush |= (__force int)(th->ack_seq ^ th2->ack_seq); //tcp的option頭必須一致 for (i = sizeof(*th); i < thlen; i += 4) flush |= *(u32 *)((u8 *)th + i) ^ *(u32 *)((u8 *)th2 + i); mss = skb_shinfo(p)->gso_size; // 0-1 = 0xFFFFFFFF, 因此skb的數據部分長度爲0的包是不會被合併的 flush |= (len - 1) >= mss; flush |= (ntohl(th2->seq) + skb_gro_len(p)) ^ ntohl(th->seq); //若是flush有設置則不會調用 skb_gro_receive,也就是不須要進行合併,不然調用skb_gro_receive進行數據包合併 if (flush || skb_gro_receive(head, skb)) { mss = 1; goto out_check_final; } p = *head; th2 = tcp_hdr(p); //更新p的頭。到達這裏說明合併完畢,所以須要更新合併完的新包的頭。 tcp_flag_word(th2) |= flags & (TCP_FLAG_FIN | TCP_FLAG_PSH); |
從上面咱們能夠看到若是tcp的包被設置了一些特殊的flag好比PSH,SYN這類的就必須立刻把數據包刷出到協議棧。
下面就是最終的一些flags判斷,好比第一個數據包進來都會到這裏來判斷。
1
2 3 4 5 6 7 8 9 10 11 12 |
out_check_final: flush = len < mss; //根據flag獲得flush flush |= (__force int)(flags & (TCP_FLAG_URG | TCP_FLAG_PSH | TCP_FLAG_RST | TCP_FLAG_SYN | TCP_FLAG_FIN)); if (p && (!NAPI_GRO_CB(skb)->same_flow || flush)) pp = head; out: NAPI_GRO_CB(skb)->flush |= flush; |
這裏要知道每次咱們只會刷出gro list中的一個skb節點,這是由於每次進來的數據包咱們也只會匹配一個。所以若是遇到須要刷出的數據包,會在dev_gro_receive中先刷出gro list中的,而後再將當前的skb feed進協議棧。
最後就是gro最核心的一個函數skb_gro_receive,它的主要工做就是合併,它有2個參數,第一個是gro list中和當前處理的skb是same flow的skb,第二個就是咱們須要合併的skb。
這裏要注意就是farg_list,其實gro對待skb_shared_info和ip層切片,組包很相似,就是frags放Scatter-Gather I/O的數據包,frag_list放線性數據。這裏gro 也是這樣的,若是過來的skb支持Scatter-Gather I/O而且數據是隻放在frags中,則會合並frags,若是過來的skb不支持Scatter-Gather I/O(數據頭仍是保存在skb中),則合併很簡單,就是新建一個skb而後拷貝當前的skb,並將gro list中的skb直接掛載到farg_list。
先來看支持Scatter-Gather I/O的處理部分。
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
//一些須要用到的變量 struct sk_buff *p = *head; struct sk_buff *nskb; //當前的skb的 share_ino struct skb_shared_info *skbinfo = skb_shinfo(skb); //當前的gro list中的要合併的skb的share_info struct skb_shared_info *pinfo = skb_shinfo(p); unsigned int headroom; unsigned int len = skb_gro_len(skb); unsigned int offset = skb_gro_offset(skb); unsigned int headlen = skb_headlen(skb); //若是有frag_list的話,則直接去非Scatter-Gather I/O部分處理,也就是合併到frag_list. if (pinfo->frag_list) goto merge; else if (headlen <= offset) { //支持Scatter-Gather I/O的處理 skb_frag_t *frag; skb_frag_t *frag2; int i = skbinfo->nr_frags; //這裏遍歷是從後向前。 int nr_frags = pinfo->nr_frags + i; offset -= headlen; if (nr_frags > MAX_SKB_FRAGS) return -E2BIG; //設置pinfo的frags的大小,能夠看到就是加上skb的frags的大小 pinfo->nr_frags = nr_frags; skbinfo->nr_frags = 0; frag = pinfo->frags + nr_frags; frag2 = skbinfo->frags + i; //遍歷賦值,其實就是地址賦值,這裏就是將skb的frag加到pinfo的frgas後面。 do { *--frag = *--frag2; } while (--i); //更改page_offet的值 frag->page_offset += offset; //修改size大小 frag->size -= offset; //更新skb的相關值 skb->truesize -= skb->data_len; skb->len -= skb->data_len; skb->data_len = 0; NAPI_GRO_CB(skb)->free = 1; //最終完成 goto done; } else if (skb_gro_len(p) != pinfo->gso_size) return -E2BIG; |
這裏gro list中的要被合併的skb咱們叫作skb_s.
接下來就是不支持支持Scatter-Gather I/O(skb的頭放在skb中)的處理。這裏處理也比較簡單,就是複製一個新的nskb,而後它的頭和skb_s同樣,而後將skb_s掛載到nskb的frag_list上,而且把新建的nskb掛在到gro list中,代替skb_s的位置,而當前的skb
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
headroom = skb_headroom(p); nskb = alloc_skb(headroom + skb_gro_offset(p), GFP_ATOMIC); if (unlikely(!nskb)) return -ENOMEM; //複製頭 __copy_skb_header(nskb, p); nskb->mac_len = p->mac_len; skb_reserve(nskb, headroom); __skb_put(nskb, skb_gro_offset(p)); //設置各層的頭 skb_set_mac_header(nskb, skb_mac_header(p) - p->data); skb_set_network_header(nskb, skb_network_offset(p)); skb_set_transport_header(nskb, skb_transport_offset(p)); __skb_pull(p, skb_gro_offset(p)); //複製數據 memcpy(skb_mac_header(nskb), skb_mac_header(p), p->data - skb_mac_header(p)); //對應的gro 域的賦值 *NAPI_GRO_CB(nskb) = *NAPI_GRO_CB(p); //能夠看到frag_list被賦值 skb_shinfo(nskb)->frag_list = p; skb_shinfo(nskb)->gso_size = pinfo->gso_size; pinfo->gso_size = 0; skb_header_release(p); nskb->prev = p; //更新新的skb的數據段 nskb->data_len += p->len; nskb->truesize += p->len; // 應該改爲 nskb->truesize += p->truesize; 更準確 nskb->len += p->len; //將新的skb插入到gro list中 *head = nskb; nskb->next = p->next; p->next = NULL; p = nskb; merge: if (offset > headlen) { skbinfo->frags[0].page_offset += offset - headlen; skbinfo->frags[0].size -= offset - headlen; offset = headlen; } __skb_pull(skb, offset); //將skb插入新的skb的(或者老的skb,當frag list自己存在)fraglist // 這裏是用p->prev來記錄了p->fraglist的最後一個包,因此在gro向協議棧提交時最好加一句skb->prev = NULL; p->prev->next = skb; p->prev = skb; skb_header_release(skb); |