linux下ip協議(V4)的實現(三)

這次我們來看數據包如何從4層傳遞到3層。 

先看下面的圖,這張圖表示了4層和3層之間(也就是4層傳輸給3層)的傳輸所需要調用的主要的函數: 


 

我們注意到3層最終會把幀用dst_output函數進行輸出,而這個函數,我們上一次已經講過了,他會調用skb->dst->output這個虛函數(他會對包進行3層的處理),而最終會調用一個XX_finish_output的函數,從而將數據傳遞到neighboring子系統。 

這張我們主要聚焦於ip_push_pending_frames,ip_append_data,ip_append_page,ip_queue_xmit這幾個函數。 

ip_queue_xmit: 

4層協議(主要指tcp 和 sctp)將數據包按照pmtu切片(如果需要),然後3層的工作只需要給傳遞下來的切片加上ip頭就可以了(也就是說調用這個函數的時候,其實4層已經切好片了)。因此這個函數的處理邏輯比較簡單。 

ip_push_pending_frames和後面的2個函數: 

4層調用這幾個函數不會考慮切片,4層調用ip_append_data時會存儲請求,也就是會將數據包排隊(其中每個都不大於pmtu)到一個輸出隊列.這樣的話使3層的處理更加方便和高效。 

當4層需要flush輸出隊列到3層時,他需要顯式的調用ip_push_pending_frames.其實也就是發送包到dst_output. 
ip_append_page只是ip_append_data的一個變體。 

我們還看到rawip和igmp都是直接調用dst_output,也就是直接和3層交互。 

在linux中,每一個bsd socket都被表示爲一個socket的數據結構,而每一個protocol family都被表示爲一個包含着sock的數據結構,這裏我們來看PF_INET的結構: 
Java代碼  收藏代碼
  1. struct inet_sock {  
  2.     /* sk and pinet6 has to be the first two members of inet_sock */  
  3.     struct sock     sk;  
  4. #if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)  
  5.     struct ipv6_pinfo   *pinet6;  
  6. #endif  
  7.     /* Socket demultiplex comparisons on incoming packets. */  
  8. ...................................................  
  9.     struct {  
  10. ..........................................  
  11.     } cork;  
  12. };  


可以看到每個inet_sock都包含一個sock也就是socket,它存儲了每個協議簇的私有部分的數據。這樣只要給定我們一個sock,我們都能通過inet_sk來得到inet_sock的指針。其實按照他們的內存分佈,他們的地址是一樣的。 

而cork域則在ip_append_data和ip_append_page中扮演的重要的角色,它存儲被這兩個函數所需要的正確切片的一些上下文信息。 

接下來來看ip_queue_xmit的實現,這個函數主要是被tcp和sctp所使用,第一個參數表示被傳遞的buffer的指針,第二個參數主要是被sctp來使用,就是是否切片被允許的標誌: 

Java代碼  收藏代碼
  1. int ip_queue_xmit(struct sk_buff *skb, int ipfragok)  
  2. {  
  3. ///取出sock,inet_sock以及option  
  4.     struct sock *sk = skb->sk;  
  5.     struct inet_sock *inet = inet_sk(sk);  
  6.     struct ip_options *opt = inet->opt;  
  7.     struct rtable *rt;  
  8.     struct iphdr *iph;  
  9.   
  10.     /* Skip all of this if the packet is already routed, 
  11.      * f.e. by something like SCTP. 
  12.      */  
  13. ///得到相關路由信息,如果buffer已經標記了相應的路由信息,則跳過下面的構造路由表。  
  14.     rt = skb->rtable;  
  15.     if (rt != NULL)  
  16.         goto packet_routed;  
  17.   
  18. ///下面檢測在這個sock中,路由是否已經cache,如果有,則檢測這個路由是否還可以使用。  
  19.     rt = (struct rtable *)__sk_dst_check(sk, 0);  
  20. ///cache不存在,查找新路由。  
  21.     if (rt == NULL) {  
  22.         __be32 daddr;  
  23.   
  24.         /* Use correct destination address if we have options. */  
  25.         daddr = inet->daddr;  
  26. ///檢測source route option  
  27.         if(opt && opt->srr)  
  28.             daddr = opt->faddr;  
  29.   
  30.         {  
  31.             struct flowi fl = { .oif = sk->sk_bound_dev_if,  
  32.                         .nl_u = { .ip4_u =  
  33.                               { .daddr = daddr,  
  34.                             .saddr = inet->saddr,  
  35.                             .tos = RT_CONN_FLAGS(sk) } },  
  36.                         .proto = sk->sk_protocol,  
  37.                         .uli_u = { .ports =  
  38.                                { .sport = inet->sport,  
  39.                              .dport = inet->dport } } };  
  40.   
  41.             /* If this fails, retransmit mechanism of transport layer will 
  42.              * keep trying until route appears or the connection times 
  43.              * itself out. 
  44.              */  
  45.             security_sk_classify_flow(sk, &fl);  
  46. ///如果是 strict source route option,則會在這個函數中進行下一跳的精確匹配。  
  47.             if (ip_route_output_flow(sock_net(sk), &rt, &fl, sk, 0))  
  48.                 goto no_route;  
  49.         }  
  50. ///主要是保存一些設備的features。  
  51.         sk_setup_caps(sk, &rt->u.dst);  
  52.     }  
  53.   
  54. ///clone一個skb->dst,也就是引用計數+1了。  
  55.     skb->dst = dst_clone(&rt->u.dst);  
  56.   
  57. packet_routed:  
  58. ///當有strictroute option的時候,檢測下一跳,如果不等,則丟掉這個包。這裏丟掉包不需要發送icmp,因爲我們本身就是源,因此只需要返回錯誤代碼給高層就行了。  
  59.     if (opt && opt->is_strictroute && rt->rt_dst != rt->rt_gateway)  
  60.         goto no_route;  
  61. ///開始build ip頭。  
  62.   
  63.   
  64. ///移動指針指向ip頭。  
  65.     skb_push(skb, sizeof(struct iphdr) + (opt ? opt->optlen : 0));  
  66. ///保存這個指針到network_head  
  67.     skb_reset_network_header(skb);  
  68. ///取出ip頭  
  69.     iph = ip_hdr(skb);  
  70. ///實例化ip頭。  
  71.     *((__be16 *)iph) = htons((4 << 12) | (5 << 8) | (inet->tos & 0xff));  
  72.     if (ip_dont_fragment(sk, &rt->u.dst) && !ipfragok)  
  73.         iph->frag_off = htons(IP_DF);  
  74.     else  
  75.         iph->frag_off = 0;  
  76.     iph->ttl      = ip_select_ttl(inet, &rt->u.dst);  
  77.     iph->protocol = sk->sk_protocol;  
  78.     iph->saddr    = rt->rt_src;  
  79.     iph->daddr    = rt->rt_dst;  
  80.     /* Transport layer set skb->h.foo itself. */  
  81.   
  82.     if (opt && opt->optlen) {  
  83.         iph->ihl += opt->optlen >> 2;  
  84. ///設定ip頭不進行切片。  
  85.         ip_options_build(skb, opt, inet->daddr, rt, 0);  
  86.     }  
  87. ///設置ip包的id。  
  88.     ip_select_ident_more(iph, &rt->u.dst, sk,  
  89.                  (skb_shinfo(skb)->gso_segs ?: 1) - 1);  
  90.   
  91. ///用來流量控制。  
  92.     skb->priority = sk->sk_priority;  
  93.     skb->mark = sk->sk_mark;  
  94.   
  95. ///這個函數首先進行ip checksum,最終會通過netfilter的hook,從而由netfilter來決定包丟棄還是傳遞給dst_output.  
  96.     return ip_local_out(skb);  
  97.   
  98. no_route:  
  99.     IP_INC_STATS(sock_net(sk), IPSTATS_MIB_OUTNOROUTES);  
  100.     kfree_skb(skb);  
  101.     return -EHOSTUNREACH;  
  102. }  



接下來來看ip_append_data函數,先來看它的參數的含義: 

sk: 這個傳輸包的socket 
getfrag: 這個函數用來複制從4層接收到的負荷到數據幀(3層)。 
from: 4層的data起始指針。 
length: 將要傳輸的數據的大小,包括4層的頭和4層的負荷。 
transhdrlen: 四層頭的大小 
ipc: 需要正確forward數據報的一些信息。 
rt: 路由信息 
flags:這個變量樣子是MSG_XXX,他們包括下面幾個定義: 

  MSG_MORE: 這個是應用程序用來告訴4層這兒將會有更多的小數據包的傳輸,然後將這個標記再傳遞給3層,3層就會提前劃分一個mtu大小的數據包,來組合這些數據幀。 
  MSG_DONTWAIT: 當這個flag被設置,調用ip_append_data將不會阻塞。 
  MSG_PROBE :當這個標記被設置,說明用戶不想要真正的傳輸什麼東西,而是知識探測路徑。例如測試一個pmtu。 

解釋下ip+append_data的大體架構,在ip_queue_xmit中,也就是tcp協議使用的傳輸中,每次傳遞下來的數據包都要扔給dst_output來處理,而在ip_append_data中,它可以通過MSG_MORE來創建一個最接近mtu大小的數據塊,然後將傳遞下來數據包(小於mtu)的,多個組成一個最接近mtu大小的數據包,然後傳遞給dst_output.而且他還有一個sk_write_queue隊列,這個隊列保存了數據傳輸的請求,也就是將要傳遞給dst_output的數據包(上面所說的最接近mtu大小的數據包)組成一個隊列,從而當ip_push_pending_frams調用時,傳遞給dst_output. 


下面這張圖解釋了,一個不需要切片,並且包含一個ipsec頭的ip包通過ip_append_data後的結果: 


 

這裏要注意3層頭的填充是通過ip_push_pending來進行填充的。而且一般的4層協議不會直接調用ip_push_pending_frams,而是調用它的包裝函數,比如udp就會調用udp_push_pending_frames。 

還有一個要注意的是,當沒有msg_more時,如果有一個大於pmtu的包傳遞下來時,他會切包,其中第一個包爲pmtu大小,第二個包是剩下的大小,然後把這兩個包加入到sk_write_queue隊列。而設置了msg_more,此時第二個包的大小就是pmtu,也就是說當再有小的數據包下來,就不需要再次分配空間,而可以直接加入到剩餘的數據空間中。 

有些硬件設備提供Scatter/Gather I/o這也就意味着能夠交由硬件來組合這些小的數據包(3層可以什麼都不用做,當數據包離開host的時候,硬件會將它組合好),這樣就降低了分配內存和複製數據的開銷。 

由於一個sk buff只會有一個ip頭,因此放到page buff的只會是L4 payload,而不包括頭。這裏就不需要複製,而是直接將數據放到page buff,接下來的圖表示了有Scatter/Gather I/O的情況時,調用ip_append_data之前和之後的區別: 

 
Java代碼  收藏代碼
  1. struct skb_frag_struct {  
  2.     struct page *page;  
  3.     __u32 page_offset;  
  4.     __u32 size;  
  5. };  


這裏可以看到nr_frags域來表示有多少個S/G I/O buffer在這個包中被使用。其實整個S/G I/O buffer相當於一個數組,每個元素都是一個skb_frag_t結構,而這個數組的大小就是nr_frags,最大的size是MAX_SKB_FRAGS. 

這裏要注意,當一個新的幀的大小,大於當前頁的剩餘大小是,他會被分爲兩部分,一部分在當前頁,一部分在新的頁。 

沒有 s/g I/O: 
它會複製數據到當前的data。 


4層可以調用ip_append_data多次,在flush這個buff之前。 

還有一個getfrag,我再說明下,ip_append_data的任務之一就是複製輸入數據到它創建的幀,而不同的協議需要不同的複製操作。比如4層的check sum。有些4層協議就是不需要的。 
因此就有了這樣一個虛函數,不同的協議實現自己的複製函數,然後傳入到ip_append_data. 
這個函數其實也就是將用戶空間的數據複製到內核空間。 
下面這個圖就是一些協議實現的複製函數: 





接下來的這個圖表示了ip_append_data的流程圖: 


 

下來我們來看它的具體實現: 
Java代碼  收藏代碼
  1. int ip_append_data(struct sock *sk,  
  2.            int getfrag(void *from, char *to, int offset, int len,  
  3.                    int odd, struct sk_buff *skb),  
  4.            void *from, int length, int transhdrlen,  
  5.            struct ipcm_cookie *ipc, struct rtable *rt,  
  6.            unsigned int flags)  
  7. {  
  8. ///取出取出相關的變量。  
  9.     struct inet_sock *inet = inet_sk(sk);  
  10.     struct sk_buff *skb;  
  11.   
  12.     struct ip_options *opt = NULL;  
  13.     int hh_len;  
  14.     int exthdrlen;  
  15.     int mtu;  
  16.     int copy;  
  17.     int err;  
  18.     int offset = 0;  
  19.     unsigned int maxfraglen, fragheaderlen;  
  20.     int csummode = CHECKSUM_NONE;  
  21.   
  22. ///如果只是探測路徑則直接返回。  
  23.     if (flags&MSG_PROBE)  
  24.         return 0;  
  25. ///當sk_write_queue 爲空,意味着創建的是第一個ip幀。因此需要初始化一些相關域。  
  26.     if (skb_queue_empty(&sk->sk_write_queue)) {  
  27.         /* 
  28.          * setup for corking. 
  29.          */  
  30. ///初始化cork的一些相關域。  
  31.         opt = ipc->opt;  
  32.         if (opt) {  
  33.             if (inet->cork.opt == NULL) {  
  34.                 inet->cork.opt = kmalloc(sizeof(struct ip_options) + 40, sk->sk_allocation);  
  35.                 if (unlikely(inet->cork.opt == NULL))  
  36.                     return -ENOBUFS;  
  37.             }  
  38.             memcpy(inet->cork.opt, opt, sizeof(struct ip_options)+opt->optlen);  
  39.             inet->cork.flags |= IPCORK_OPT;  
  40.             inet->cork.addr = ipc->addr;  
  41.         }  
  42.         dst_hold(&rt->u.dst);  
  43.         inet->cork.fragsize = mtu = inet->pmtudisc == IP_PMTUDISC_PROBE ?  
  44.                         rt->u.dst.dev->mtu :  
  45.                         dst_mtu(rt->u.dst.path);  
  46.         inet->cork.dst = &rt->u.dst;  
  47.         inet->cork.length = 0;  
  48.   
  49.         sk->sk_sndmsg_page = NULL;  
  50.         sk->sk_sndmsg_off = 0;  
  51.         if ((exthdrlen = rt->u.dst.header_len) != 0) {  
  52. ///加上擴展頭和傳輸層的頭的大小。  
  53.             length += exthdrlen;  
  54.             transhdrlen += exthdrlen;  
  55.         }  
  56.     } else {  
  57.         rt = (struct rtable *)inet->cork.dst;  
  58.         if (inet->cork.flags & IPCORK_OPT)  
  59.             opt = inet->cork.opt;  
  60. ///不是第一個幀,則需要把ipsec頭和4層的僞頭的大小賦值爲0.(因爲同一個sk,共享相同的頭。  
  61.         transhdrlen = 0;  
  62.         exthdrlen = 0;  
  63.         mtu = inet->cork.fragsize;  
  64.     }  
  65. ///得到2層頭的大小(也就是預留2層頭的大小).  
  66.     hh_len = LL_RESERVED_SPACE(rt->u.dst.dev);  
  67. ///得到3層頭的大小。  
  68.     fragheaderlen = sizeof(struct iphdr) + (opt ? opt->optlen : 0);  
  69. ///ip包的大小。基於路由pmtu。  
  70.     maxfraglen = ((mtu - fragheaderlen) & ~7) + fragheaderlen;  
  71.   
  72. ///由於ip包的最大大小爲64kb(oxFFFF),因此拒絕大於這個數據包。  
  73.     if (inet->cork.length + length > 0xFFFF - fragheaderlen) {  
  74.         ip_local_error(sk, EMSGSIZE, rt->rt_dst, inet->dport, mtu-exthdrlen);  
  75.         return -EMSGSIZE;  
  76.     }  
  77.   
  78.     /* 
  79.      * transhdrlen > 0 means that this is the first fragment and we wish 
  80.      * it won't be fragmented in the future. 
  81.      */  
  82. ///檢測checksum是否需要硬件來做。  
  83.     if (transhdrlen &&  
  84.         length + fragheaderlen <= mtu &&  
  85.         rt->u.dst.dev->features & NETIF_F_V4_CSUM &&  
  86.         !exthdrlen)  
  87.         csummode = CHECKSUM_PARTIAL;  
  88.   
  89.     inet->cork.length += length;  
  90. ///檢測長度是否大於mtu,以及是否是udp協議。然後進行udp分片。  
  91.     if (((length> mtu) || !skb_queue_empty(&sk->sk_write_queue)) &&  
  92.         (sk->sk_protocol == IPPROTO_UDP) &&  
  93.         (rt->u.dst.dev->features & NETIF_F_UFO)) {  
  94. ///進行udp分片。  
  95.         err = ip_ufo_append_data(sk, getfrag, from, length, hh_len,  
  96.                      fragheaderlen, transhdrlen, mtu,  
  97.                      flags);  
  98.         if (err)  
  99.             goto error;  
  100.         return 0;  
  101.     }  
  102.   
  103.     if ((skb = skb_peek_tail(&sk->sk_write_queue)) == NULL)  
  104.         goto alloc_new_skb;  
  105.   
  106. ///開始將數據複製到創建的幀。  
  107.     while (length > 0) {  
  108.         /* Check if the remaining data fits into current packet. */  
  109.         copy = mtu - skb->len;  
  110.   
  111. ///空間不足時(也就是當前幀剩餘的大小不夠放入將要複製的數據).  
  112.         if (copy < length)  
  113.             copy = maxfraglen - skb->len;  
  114. ///幀太大,需要切片。  
  115.         if (copy <= 0) {  
  116.   
  117.             char *data;  
  118.             unsigned int datalen;  
  119.             unsigned int fraglen;  
  120.             unsigned int fraggap;  
  121.             unsigned int alloclen;  
  122.             struct sk_buff *skb_prev;  
  123. alloc_new_skb:  
  124.             skb_prev = skb;  
  125. ///檢測上一個skb是否存在  
  126.             if (skb_prev)  
  127. ///存在取得他的fraggap(小於8字節的).這裏要解釋下fraggap.除了最後一個ip幀,所有的ip幀都必須使他的ip幀的負荷的大小爲8字節的倍數。因此當kernel分配一個新的buffer時,他可能需要移動一些數據從前一個buffer的尾部到新的buffer的頭部。  
  128.                 fraggap = skb_prev->len - maxfraglen;  
  129.             else  
  130.                 fraggap = 0;  
  131.   
  132.             /* 
  133.              * If remaining data exceeds the mtu, 
  134.              * we know we need more fragment(s). 
  135.              */  
  136. ///得到數據長度  
  137.             datalen = length + fraggap;  
  138.             if (datalen > mtu - fragheaderlen)  
  139.                 datalen = maxfraglen - fragheaderlen;  
  140.             fraglen = datalen + fragheaderlen;  
  141. ///如果flag爲MSG_MORE並且設備設備不支持Scatter/Gather I/O.則需要分配一塊等於mtu的內存。  
  142.             if ((flags & MSG_MORE) &&  
  143.                 !(rt->u.dst.dev->features&NETIF_F_SG))  
  144.                 alloclen = mtu;  
  145.             else  
  146.                 alloclen = datalen + fragheaderlen;  
  147.   
  148.             /* The last fragment gets additional space at tail. 
  149.              * Note, with MSG_MORE we overallocate on fragments, 
  150.              * because we have no idea what fragment will be 
  151.              * the last. 
  152.              */  
  153.             if (datalen == length + fraggap)  
  154.                 alloclen += rt->u.dst.trailer_len;  
  155.   
  156. ///alloc相應的skb。  
  157.             if (transhdrlen) {  
  158.                 skb = sock_alloc_send_skb(sk,  
  159.                         alloclen + hh_len + 15,  
  160.                         (flags & MSG_DONTWAIT), &err);  
  161.             } else {  
  162.                 skb = NULL;  
  163.                 if (atomic_read(&sk->sk_wmem_alloc) <=  
  164.                     2 * sk->sk_sndbuf)  
  165.                     skb = sock_wmalloc(sk,  
  166.                                alloclen + hh_len + 151,  
  167.                                sk->sk_allocation);  
  168.                 if (unlikely(skb == NULL))  
  169.                     err = -ENOBUFS;  
  170.             }  
  171. ///檢測是否成功  
  172.             if (skb == NULL)  
  173.                 goto error;  
  174. ///設置校驗位  
  175.             skb->ip_summed = csummode;  
  176.             skb->csum = 0;  
  177.             skb_reserve(skb, hh_len);  
  178.   
  179. ///得到數據位置。  
  180.             data = skb_put(skb, fraglen);  
  181.             skb_set_network_header(skb, exthdrlen);  
  182. ///得到傳輸層的頭部。  
  183.             skb->transport_header = (skb->network_header +  
  184.                          fragheaderlen);  
  185.             data += fragheaderlen;  
  186. ///檢測是否有fraggap.  
  187.             if (fraggap) {  
  188.                 skb->csum = skb_copy_and_csum_bits(  
  189.                     skb_prev, maxfraglen,  
  190.                     data + transhdrlen, fraggap, 0);  
  191.                 skb_prev->csum = csum_sub(skb_prev->csum,  
  192.                               skb->csum);  
  193.                 data += fraggap;  
  194.                 pskb_trim_unique(skb_prev, maxfraglen);  
  195.             }  
  196.   
  197. ///得到所需要拷貝的數據的大小  
  198.             copy = datalen - transhdrlen - fraggap;  
  199. ///開始拷貝數據。  
  200.             if (copy > 0 && getfrag(from, data + transhdrlen, offset, copy, fraggap, skb) < 0) {  
  201.                 err = -EFAULT;  
  202.                 kfree_skb(skb);  
  203.                 goto error;  
  204.             }  
  205.   
  206.             offset += copy;  
  207.             length -= datalen - fraggap;  
  208.             transhdrlen = 0;  
  209.             exthdrlen = 0;  
  210.             csummode = CHECKSUM_NONE;  
  211.   
  212.             /* 
  213.              * Put the packet on the pending queue. 
  214.              */  
  215. ///加這個包到write_queue隊列。  
  216.             __skb_queue_tail(&sk->sk_write_queue, skb);  
  217.             continue;  
  218.         }  
  219.   
  220.         if (copy > length)  
  221.             copy = length;  
  222.   
  223. ///如果不支持Scatter/Gather I/O.則直接拷貝數據  
  224.         if (!(rt->u.dst.dev->features&NETIF_F_SG)) {  
  225.             unsigned int off;  
  226.   
  227.             off = skb->len;  
  228.             if (getfrag(from, skb_put(skb, copy),  
  229.                     offset, copy, off, skb) < 0) {  
  230.                 __skb_trim(skb, off);  
  231.                 err = -EFAULT;  
  232.                 goto error;  
  233.             }  
  234.         } else {  
  235. ///如果支持S/G I/O則開始進行相應操作  
  236. ///i爲當前已存儲的個數。  
  237.             int i = skb_shinfo(skb)->nr_frags;  
  238. //取出skb_frag_t指針。  
  239.             skb_frag_t *frag = &skb_shinfo(skb)->frags[i-1];  
  240. ///得到當前的物理頁。  
  241.             struct page *page = sk->sk_sndmsg_page;  
  242. ///得到當前的物理頁的位移(也就是我們接下來要存儲的位置的位移)  
  243.             int off = sk->sk_sndmsg_off;  
  244.             unsigned int left;  
  245.   
  246. ///如果有足夠的空間則將數據放進相應的物理頁的位置。  
  247.             if (page && (left = PAGE_SIZE - off) > 0) {  
  248. ///當剩餘的空間不夠放將要拷貝的數據時,則先將剩餘的空間拷貝完畢。然後下次循環再進行拷貝剩下的。  
  249.                 if (copy >= left)  
  250.                     copy = left;  
  251.                 if (page != frag->page) {  
  252.                     if (i == MAX_SKB_FRAGS) {  
  253.                         err = -EMSGSIZE;  
  254.                         goto error;  
  255.                     }  
  256.                     get_page(page);  
  257. ///填充頁  
  258.                     skb_fill_page_desc(skb, i, page, sk->sk_sndmsg_off, 0);  
  259.                     frag = &skb_shinfo(skb)->frags[i];  
  260.                 }  
  261.             }   
  262. ///檢測是否存儲空間已滿。(此時說明page不存在或者,剩餘大小威0,此時需要重新alloc一個物理頁。  
  263. else if (i < MAX_SKB_FRAGS) {  
  264. ///檢測所需拷貝的數據的大小是否大於頁的大小。  
  265.                 if (copy > PAGE_SIZE)  
  266.                     copy = PAGE_SIZE;  
  267. ///則新分配一個頁。  
  268.                 page = alloc_pages(sk->sk_allocation, 0);  
  269.                 if (page == NULL)  {  
  270.                     err = -ENOMEM;  
  271.                     goto error;  
  272.                 }  
  273.                 sk->sk_sndmsg_page = page;  
  274.                 sk->sk_sndmsg_off = 0;  
  275.   
  276.                 skb_fill_page_desc(skb, i, page, 00);  
  277.                 frag = &skb_shinfo(skb)->frags[i];  
  278.             } else {  
  279.                 err = -EMSGSIZE;  
  280.                 goto error;  
  281.             }  
  282. ///調用getfrag,填充相應的數據包(4層傳遞下來的數據)  
  283.             if (getfrag(from, page_address(frag->page)+frag->page_offset+frag->size, offset, copy, skb->len, skb) < 0) {  
  284.                 err = -EFAULT;  
  285.                 goto error;  
  286.             }  
  287.             sk->sk_sndmsg_off += copy;  
  288.             frag->size += copy;  
  289.             skb->len += copy;  
  290.             skb->data_len += copy;  
  291.             skb->truesize += copy;  
  292.             atomic_add(copy, &sk->sk_wmem_alloc);  
  293.         }  
  294. ///計算下次需要再拷貝的。。  
  295.         offset += copy;  
  296.         length -= copy;  
  297.     }  
  298.   
  299.     return 0;  
  300.   
  301. error:  
  302.     inet->cork.length -= length;  
  303.     IP_INC_STATS(sock_net(sk), IPSTATS_MIB_OUTDISCARDS);  
  304.     return err;  
  305. }  


在上面的代碼中,我們可以看到同一個物理頁,有可能被sk_sndmsg_page和skb_frag_t 所共享,可以看下下面的圖: 


 


接下來來看ip_append_page,這個函數比較簡單,我們大概分析下就可以了。 

我們知道內核提供給用戶空間的一個零拷貝的接口sendfile.這個接口只能當設備提供Scatter/Gather I/O的時候,才能使用。而它的實現就是基於ip_append_page這個函數來實現的。如果設備不支持S/G I/O,ip_append_page會直接返回錯誤。 

它的邏輯實現和ip_append_page最後面那段實現很相似,不過有些不同,當加一個新的幀到page時,ip_append_page它會merge新的和也在當前頁的前一個幀。它會通過調用skb_can_coalesce來進行檢測這個。然後當merge是可能的,它就會update前一個幀的長度。 
當merge是不可能的時候,處理和ip_append_data相似。 
下面就是ip_append_page的一些代碼片段: 

Java代碼  收藏代碼
  1. if (skb_can_coalesce(skb, i, page, offset)) {  
  2.             skb_shinfo(skb)->frags[i-1].size += len;  
  3.         } else if (i < MAX_SKB_FRAGS) {  
  4.             get_page(page);  
  5.             skb_fill_page_desc(skb, i, page, offset, len);  
  6.         } else {  
  7.             err = -EMSGSIZE;  
  8.             goto error;  
  9.         }  


ip_append_page只被udp使用。tcp不使用ip_append_data和ip_push_pending_frams是因爲它把一些邏輯放到tcp_sendmsg來實現了。因此相似的,0拷貝接口,tcp不使用ip_append_page是因爲他在do_tcp_sendpage中實現了相同的邏輯。 

最後我們來看ip_push_pending_frams函數。 

這個函數相當於一個notify函數,當4層決定傳輸幀到ip層的時候,他就需要調用這個函數.通過前面我們知道此時所有的數據(如果不支持Scatter/Gather I/O),都在sk_write_queue中。 

這個函數要做的其實很簡單,就是從sk_write_queue中取出數據,加上ip頭,然後通過dst_output發送給3層。 

當數據從sk_write_queue從移除後,加入到frag_list鏈表中。 
下面這張圖表示了從sk_write_queue中移除buffer之前和之後的區別(沒有考慮Scatter/Gather I/O). 


 


接下來來看它的實現: 

Java代碼  收藏代碼
  1. int ip_push_pending_frames(struct sock *sk)  
  2. {  
  3. ///初始化一些數據  
  4.     struct sk_buff *skb, *tmp_skb;  
  5.     struct sk_buff **tail_skb;  
  6.     struct inet_sock *inet = inet_sk(sk);  
  7.     struct net *net = sock_net(sk);  
  8.     struct ip_options *opt = NULL;  
  9.     struct rtable *rt = (struct rtable *)inet->cork.dst;  
  10.     struct iphdr *iph;  
  11.     __be16 df = 0;  
  12.     __u8 ttl;  
  13.     int err = 0;  
  14. ///取得第一個buffer  
  15.     if ((skb = __skb_dequeue(&sk->sk_write_queue)) == NULL)  
  16.         goto out;  
  17. ///得到他的frag_list.  
  18.     tail_skb = &(skb_shinfo(skb)->frag_list);  
  19.   
  20.     /* move skb->data to ip header from ext header */  
  21.     if (skb->data < skb_network_header(skb))  
  22.         __skb_pull(skb, skb_network_offset(skb));  
  23. ///開始遍歷並取出所有的buffer到frag_list.  
  24.     while ((tmp_skb = __skb_dequeue(&sk->sk_write_queue)) != NULL) {  
  25.         __skb_pull(tmp_skb, skb_network_header_len(skb));  
  26.         *tail_skb = tmp_skb;  
  27.         tail_skb = &(tmp_skb->next);  
  28.         skb->len += tmp_skb->len;  
  29.         skb->data_len += tmp_skb->len;  
  30.         skb->truesize += tmp_skb->truesize;  
  31.         __sock_put(tmp_skb->sk);  
  32.         tmp_skb->destructor = NULL;  
  33.         tmp_skb->sk = NULL;  
  34.     }  
  35.   
  36.     /* Unless user demanded real pmtu discovery (IP_PMTUDISC_DO), we allow 
  37.      * to fragment the frame generated here. No matter, what transforms 
  38.      * how transforms change size of the packet, it will come out. 
  39.      */  
  40.     if (inet->pmtudisc < IP_PMTUDISC_DO)  
  41.         skb->local_df = 1;  
  42.   
  43.     /* DF bit is set when we want to see DF on outgoing frames. 
  44.      * If local_df is set too, we still allow to fragment this frame 
  45.      * locally. */  
  46.     if (inet->pmtudisc >= IP_PMTUDISC_DO ||  
  47.         (skb->len <= dst_mtu(&rt->u.dst) &&  
  48.          ip_dont_fragment(sk, &rt->u.dst)))  
  49. ///標記ip頭不要被切片。  
  50.     df = htons(IP_DF);  
  51. ///如果在頭中包含ip option,則給option賦值,然後下面會處理這個option。  
  52.     if (inet->cork.flags & IPCORK_OPT)  
  53.         opt = inet->cork.opt;  
  54. ///如果是多播,則賦值多播的ttl  
  55.     if (rt->rt_type == RTN_MULTICAST)  
  56.         ttl = inet->mc_ttl;  
  57.     else  
  58.         ttl = ip_select_ttl(inet, &rt->u.dst);  
  59.   
  60. ///得到ip頭的指針。  
  61.     iph = (struct iphdr *)skb->data;  
  62. ///開始初始化ip頭。  
  63.     iph->version = 4;  
  64.     iph->ihl = 5;  
  65.     if (opt) {  
  66.         iph->ihl += opt->optlen>>2;  
  67.         ip_options_build(skb, opt, inet->cork.addr, rt, 0);  
  68.     }  
  69.     iph->tos = inet->tos;  
  70.     iph->frag_off = df;  
  71. ///得到ip包的id。  
  72.     ip_select_ident(iph, &rt->u.dst, sk);  
  73.     iph->ttl = ttl;  
  74.     iph->protocol = sk->sk_protocol;  
  75.     iph->saddr = rt->rt_src;  
  76.     iph->daddr = rt->rt_dst;  
  77.   
  78.     skb->priority = sk->sk_priority;  
  79.     skb->mark = sk->sk_mark;  
  80.     skb->dst = dst_clone(&rt->u.dst);  
  81.   
  82. ///如果協議是ICMP則進行相關處理。  
  83.     if (iph->protocol == IPPROTO_ICMP)  
  84.         icmp_out_count(net, ((struct icmphdr *)  
  85.             skb_transport_header(skb))->type);  
  86.   
  87.     /* Netfilter gets whole the not fragmented skb. */  
  88. ///輸出到4層,這個函數上面有介紹過,會通過一個netfilter的hook.  
  89.     err = ip_local_out(skb);  
  90.     if (err) {  
  91.         if (err > 0)  
  92.             err = inet->recverr ? net_xmit_errno(err) : 0;  
  93.         if (err)  
  94.             goto error;  
  95.     }  
  96.   
  97. out:  
  98.     ip_cork_release(inet);  
  99.     return err;  
  100.   
  101. error:  
  102.     IP_INC_STATS(net, IPSTATS_MIB_OUTDISCARDS);  
  103.     goto out;  
  104. }  


接下來我們會來簡要的介紹4層使用上面的函數接口和3層如何把幀傳遞給2層的接口: 
先來看udp_sendmsg的代碼片段: 

Java代碼  收藏代碼
  1. up->len += ulen;  
  2.     getfrag  =  is_udplite ?  udplite_getfrag : ip_generic_getfrag;  
  3. ///將要傳輸的包交給ip_append_data來處理  
  4.     err = ip_append_data(sk, getfrag, msg->msg_iov, ulen,  
  5.             sizeof(struct udphdr), &ipc, rt,  
  6.             corkreq ? msg->msg_flags|MSG_MORE : msg->msg_flags);  
  7.     if (err)  
  8.         udp_flush_pending_frames(sk);  
  9.     else if (!corkreq)  
  10. ///如果需要傳遞給3層,則調用udp_push_pending_frames,這個函數是對ip_push_pending_frames的簡單封裝。  
  11.         err = udp_push_pending_frames(sk);  
  12.     else if (unlikely(skb_queue_empty(&sk->sk_write_queue)))  
  13.         up->pending = 0;  
  14.     release_sock(sk);  


我們現在知道4層到3層之後,最終通過dst_output來把幀進行輸出,這個函數在單播的情況下,是被實例化爲ip_output.這裏和前面的netfilter一樣,還存在一個ip_output_finish方法,當通過netfilter hook後,如果這個包可以被netfilter放過,那麼幀就會傳遞到ip_output_finish方法,然後再調用ip_output_finish2方法。而最終dev_queue_xmit(前面的blog有介紹,也就是2層的傳輸方法)會被調用(這裏是通過hh->hh_output方法或者fst->neighbour->output 2個虛函數)來傳輸。 


Java代碼  收藏代碼
  1. static inline int ip_finish_output2(struct sk_buff *skb)  
  2. {  
  3. .........................................  
  4.   
  5.     if (dst->hh)  
  6.         return neigh_hh_output(dst->hh, skb);  
  7.     else if (dst->neighbour)  
  8.         return dst->neighbour->output(skb);  
  9. ...................................  
  10. }  
發佈了7 篇原創文章 · 獲贊 4 · 訪問量 8萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章