Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1242560
  • 博文数量: 122
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 4004
  • 用 户 组: 普通用户
  • 注册时间: 2014-02-20 08:27
文章分类
文章存档

2016年(1)

2015年(21)

2014年(100)

分类: LINUX

2014-09-09 19:53:39

1、基本原理
    UDP发包流程中,当没有cork的情况下,会走过udp_sendmsg到达ip_append_data,该接口是IP层提供的UDP和RAW Socket的发包接口,同时,TCP中用于发送ACK和RST报文的接口ip_send_reply最终也会调用此接口
    该接口的主要作用是:将数据拷贝到适合的skb(利用发送队列中现有的或新创建)中,可能有两种情况:
    1)放入skb的线性区(skb->data)中;
    2)或者放入skb_shared_info的分片(frag)中(当开启SG特性或使用UFO时,都会用到)
    另外,还需要考虑MTU对skb数据进行分割,为IP层的分片做准备。

2、基本流程
    1)如果需要发送的数据长度>MTU,且启用GSO,而且网卡启用UFO,则调用ip_ufo_append_data接口进行处理,默认处理是创建新的page,拷贝数据,并将其链入到skb中的分片(skb_shinfo(skb)->frags)中。
    2)如果sock发送队列为空,或者现有的skb中的空余空间(相对于MTU来说)不足以存放本次需要发送的数据,则新创建skb,并将这次需要发送的新数据拷贝拷新创建的skb中。同时,还需要判断原有的skb中的数据是否超过了MTU,如果超过,那么在拷贝新数据之前,还需要将原有skb进行分段,即将超出MTU的部分数据拷贝到新skb中。
    3)否则,直接利用现有发送队列中的skb,然后判断网卡硬件是否支持SG特性,如果不支持,则将需要发送的新数据拷贝到现有skb的线性数据区(skb->data)中;如果网卡支持SG特性,则将需要发送的新数据拷贝到SG相关的分片(分散聚集IO页面数组)中。
    代码调用流程大致如下:
udp_sendmsg-->
    ip_append_data-->
        __ip_append_data-->
            ip_ufo_append_data //UFO相关处理
            sock_alloc_send_skb/sock_wmalloc //分配新skb
            getfrag //拷贝发送数据到skb中

3、代码分析
ip_append_data():

点击(此处)折叠或打开

  1. /*
  2.  *    ip_append_data() and ip_append_page() can make one large IP datagram
  3.  *    from many pieces of data. Each pieces will be holded on the socket
  4.  *    until ip_push_pending_frames() is called. Each piece can be a page
  5.  *    or non-page data.
  6.  *
  7.  *    Not only UDP, other transport protocols - e.g. raw sockets - can use
  8.  *    this interface potentially.
  9.  *
  10.  *    LATER: length must be adjusted by pad at tail, when it is required.
  11.  */
  12. /*
  13.   * 主要用作UDP和Raw socket的输出接口,但TCP中用于发送ACK和RST的函数ip_send_reply()最终也调用了此接口。
  14.   * 主要作用是将数据拷贝到适合的skb(利用发送队列中现有的或新创建)中,可能有两种情况: 放入skb的线性
  15.   *(skb->data)中,或者放入skb_shared_info的分片(frag)中,同时还需要考虑MTU对skb数据进行分割,为IP层的分片做准备。
  16.   */
  17. int ip_append_data(struct sock *sk, struct flowi4 *fl4,
  18.          int getfrag(void *from, char *to, int offset, int len,
  19.              int odd, struct sk_buff *skb),
  20.          void *from, int length, int transhdrlen,
  21.          struct ipcm_cookie *ipc, struct rtable **rtp,
  22.          unsigned int flags)
  23. {
  24.     struct inet_sock *inet = inet_sk(sk);
  25.     int err;
  26.     /*MSG_PROBE标记表示探测,并非要发送实际数据*/
  27.     if (flags&MSG_PROBE)
  28.         return 0;
  29.     /*如果传输控制块(sock)的的输出队列为空,则需要设置一些临时信息,如果不为空,那就可以使用上次发送时的相关信息*/
  30.     if (skb_queue_empty(&sk->sk_write_queue)) {
  31.         /*填充cork相关数据结构*/
  32.         err = ip_setup_cork(sk, &inet->cork.base, ipc, rtp);
  33.         if (err)
  34.             return err;
  35.     } else {
  36.         transhdrlen = 0;
  37.     }
  38.     /*实际append操作*/
  39.     return __ip_append_data(sk, fl4, &sk->sk_write_queue, &inet->cork.base,
  40.                 sk_page_frag(sk), getfrag,
  41.                 from, length, transhdrlen, flags);
  42. }

ip_append_data()-->__ip_append_data():

点击(此处)折叠或打开

  1. static int __ip_append_data(struct sock *sk,
  2.              struct flowi4 *fl4,
  3.              struct sk_buff_head *queue,
  4.              struct inet_cork *cork,
  5.              struct page_frag *pfrag,
  6.              int getfrag(void *from, char *to, int offset,
  7.                     int len, int odd, struct sk_buff *skb),
  8.              void *from, int length, int transhdrlen,
  9.              unsigned int flags)
  10. {
  11.     struct inet_sock *inet = inet_sk(sk);
  12.     struct sk_buff *skb;
  13.     /*从cork结构中读取ip_option信息*/
  14.     struct ip_options *opt = cork->opt;
  15.     int hh_len;
  16.     /*用于记录IPsec中扩展首部的长度,未启用IPsec是为0*/
  17.     int exthdrlen;
  18.     int mtu;    
  19.     int copy; /*当前需要拷贝的数据大小。取决于skb中线性区剩余的空间大小和非线性区的大小*/
  20.     int err;
  21.     int offset = 0;
  22.     unsigned int maxfraglen, fragheaderlen;
  23.     int csummode = CHECKSUM_NONE;
  24.     struct rtable *rt = (struct rtable *)cork->dst;
  25.     /*从sock发送队列末尾取skb*/
  26.     skb = skb_peek_tail(queue);

  27.     exthdrlen = !skb ? rt->dst.header_len : 0;
  28.     mtu = cork->fragsize;
  29.     /*获取链路层首部的长度*/
  30.     hh_len = LL_RESERVED_SPACE(rt->dst.dev);
  31.     /*获取IP首部(包括IP选项)的长度*/
  32.     fragheaderlen = sizeof(struct iphdr) + (opt ? opt->optlen : 0);
  33.     /*IP数据包中数据的最大长度,通过mtu计算,并进行8字节对齐,目的是提升计算效率*/
  34.     maxfraglen = ((mtu - fragheaderlen) & ~7) + fragheaderlen;
  35.     /*输出的报文长度不能超过IP数据报能容纳的最大长度(64K)*/
  36.     if (cork->length + length > 0xFFFF - fragheaderlen) {
  37.         ip_local_error(sk, EMSGSIZE, fl4->daddr, inet->inet_dport,
  38.              mtu-exthdrlen);
  39.         return -EMSGSIZE;
  40.     }

  41.     /*
  42.      * transhdrlen > 0 means that this is the first fragment and we wish
  43.      * it won't be fragmented in the future.
  44.      */
  45.     /*如果没有分片,且硬件支持计算校验和,则设置CHECKSUM_PARTIAL标记,由硬件来计算校验和*/
  46.     if (transhdrlen &&
  47.      length + fragheaderlen <= mtu &&
  48.      rt->dst.dev->features & NETIF_F_V4_CSUM &&
  49.      !exthdrlen)
  50.         csummode = CHECKSUM_PARTIAL;
  51.     /*增加cork阻塞的长度*/
  52.     cork->length += length;
  53.     if (((length > mtu) || (skb && skb_is_gso(skb))) &&
  54.      (sk->sk_protocol == IPPROTO_UDP) &&
  55.      (rt->dst.dev->features & NETIF_F_UFO) && !rt->dst.header_len) {
  56.      /*
  57.      * UFO处理,需要满足上述几个条件,主要为:数据长度>mtu + 启用GSO + 网卡启用UFO.
  58.          * 默认处理是创建新的page,拷贝数据,并将其链入到skb中的分片中(skb_shared_info,SG相关)
  59.      */
  60.         err = ip_ufo_append_data(sk, queue, getfrag, from, length,
  61.                      hh_len, fragheaderlen, transhdrlen,
  62.                      maxfraglen, flags);
  63.         if (err)
  64.             goto error;
  65.         return 0;
  66.     }

  67.     /* So, what's going on in the loop below?
  68.      *
  69.      * We use calculated fragment length to generate chained skb,
  70.      * each of segments is IP fragment ready for sending to network after
  71.      * adding appropriate IP header.
  72.      */
  73.     /*如果输出队列中没有skb,那说明队列为空,需要新创建skb来发送数据*/
  74.     if (!skb)
  75.         goto alloc_new_skb;
  76.     /*
  77.      * 存在skb,且没有进入UFO流程,那么这里就要开始进行分段了,length为需要发送的数据长度
  78.      * UDP层的分段实际是在这里完成的。当length 大于该skb中剩余的空间大小(copy)时,则需要分配新
  79.      * 的skb中来存放length中剩余的数据,新分配的skb会也被链入sock的发送队列链表中,最后会通过
  80.      * ip_make_skb,将sock发送链表中的skb中都链入到skb->frag_list中
  81.      */
  82.     while (length > 0) {
  83.         /* Check if the remaining data fits into current packet. */
  84.         /*计算当前skb中还能放多少数据,通过mtu-skb的数据长度计算,因为没有开启UFO,每个数据包长度不能大于mtu*/
  85.         copy = mtu - skb->len;
  86.         /*
  87.          * 如果skb的剩余空间不足以存放完这次需要放入的数据长度length,则将当前skb填满即可,剩余数据留下一个skb发送
  88.          * 其中maxfraglen是8字节对齐后的mtu。
  89.          */
  90.         if (copy < length)
  91.             copy = maxfraglen - skb->len;
  92.         /*
  93.          * 这里分两种情况:1.copy < 0,这表示:当前skb中的数据本身就已经大于MTU了(可能由于老版本中ip_ufo_append_data流程中UFO标记没有设置导致)
  94.          * 那就需要对原来的skb重新进行分段了,需要新分配skb来容纳原来skb中的数据
  95.          * 2.copy==0,这表示当前skb不足以容纳length长度的数据,并且原来的skb中的数据已经填满了(此时数据包刚好为mtu,所以copy刚好为0),此时
  96.          * 需要分配新的skb来装剩下的数据,直至copy>0(表示当前skb中在装完length长度的数据后,还剩余空间,表明这已经是最后一个分段了)循环
  97.          * 结束,以这种方式达到分段的目的。
  98.          */
  99.         if (copy <= 0) {
  100.             char *data;
  101.             unsigned int datalen;
  102.             unsigned int fraglen;
  103.             unsigned int fraggap;
  104.             unsigned int alloclen;
  105.             struct sk_buff *skb_prev;
  106. alloc_new_skb:/*3种原因需要新分配skb: 1.原有的skb数据区空间不足2.sock的输出队列为空3.原来的skb的数据已经大于mtu了,需要重新分段*/
  107.             skb_prev = skb;
  108.             /*由于原有的skb数据区空间不足,而需要分配新skb,计算不足的大小*/
  109.             if (skb_prev)
  110.                 fraggap = skb_prev->len - maxfraglen;
  111.             /*由于sock的输出队列为空*/
  112.             else
  113.                 fraggap = 0;

  114.             /*
  115.              * If remaining data exceeds the mtu,
  116.              * we know we need more fragment(s).
  117.              */
  118.             /*这次需要新分配的数据区大小length加上原来skb中不足的大小,为新skb需要分配的数据区大小*/
  119.             datalen = length + fraggap;
  120.             /*如果新skb需要分配的数据区大小超过了mtu,那这次还是只能分配mtu的大小,剩余数据需要通过循环分配新skb来容纳*/
  121.             if (datalen > mtu - fragheaderlen)
  122.                 datalen = maxfraglen - fragheaderlen;
  123.             /*数据报分片大小需要加上IP首部的长度*/
  124.             fraglen = datalen + fragheaderlen;
  125.             /*如果设置了MSG_MORE标记,表明需要等待新数据,一直到超过mtu为止,则设置"分配空间大小"为mtu*/
  126.             if ((flags & MSG_MORE) &&
  127.              !(rt->dst.dev->features&NETIF_F_SG))
  128.                 alloclen = mtu;
  129.             /*否则需要分配的空间大小为数据报分片大小*/
  130.             else
  131.                 alloclen = fraglen;
  132.             /*再加上IPsec相关的头部长度*/
  133.             alloclen += exthdrlen;

  134.             /* The last fragment gets additional space at tail.
  135.              * Note, with MSG_MORE we overallocate on fragments,
  136.              * because we have no idea what fragment will be
  137.              * the last.
  138.              */
  139.             if (datalen == length + fraggap)
  140.                 alloclen += rt->dst.trailer_len;
  141.             /*
  142.              * 根据是否存在传输层首部,确定分配skb的方法:
  143.              * 如果存在,则说明该分片为分片组中的第一个分片,那就需要考虑更多的情况,比如:发送是否超时、是否发生未处理的致命错误、
  144.              * 发送通道是否已经关闭等;当不存在传输层首部时,说明不是第一个分片,则不需考虑这些情况。
  145.              */
  146.             if (transhdrlen) {
  147.                 /*分配skb,并进行相关处理*/
  148.                 skb = sock_alloc_send_skb(sk,
  149.                         alloclen + hh_len + 15,
  150.                         (flags & MSG_DONTWAIT), &err);
  151.             } else {/*不是第一个分片(分段),直接分片新的skb*/
  152.                 skb = NULL;
  153.                 if (atomic_read(&sk->sk_wmem_alloc) <=
  154.                  2 * sk->sk_sndbuf)
  155.                  /*分配skb*/
  156.                     skb = sock_wmalloc(sk,
  157.                              alloclen + hh_len + 15, 1,
  158.                              sk->sk_allocation);
  159.                 if (unlikely(skb == NULL))
  160.                     err = -ENOBUFS;
  161.                 else
  162.                     /* only the initial fragment is
  163.                      time stamped */
  164.                     cork->tx_flags = 0;
  165.             }
  166.             if (skb == NULL)
  167.                 goto error;

  168.             /*
  169.              *    Fill in the control structures
  170.              */
  171.             /*初始化skb中的相关成员*/
  172.             skb->ip_summed = csummode;
  173.             skb->csum = 0;
  174.             /*留出链路层头部大小*/
  175.             skb_reserve(skb, hh_len);
  176.             /*设置tx_flags*/
  177.             skb_shinfo(skb)->tx_flags = cork->tx_flags;

  178.             /*
  179.              *    Find where to start putting bytes.
  180.              */
  181.             /*在skb中预留存放三层首部和数据的空间*/
  182.             data = skb_put(skb, fraglen + exthdrlen);
  183.             /*设置IP头指针位置*/
  184.             skb_set_network_header(skb, exthdrlen);
  185.             /*计算传输层头部长度*/
  186.             skb->transport_header = (skb->network_header +
  187.                          fragheaderlen);
  188.             /*计算数据存入的位置*/
  189.             data += fragheaderlen + exthdrlen;
  190.             /*
  191.              * 如果上一个skb的数据大于mtu(8字节对齐),那么需要对其进行分段处理,即将一个skb拆开,
  192.              * 将上一个skb中超出的数据和传输层首部并 复制到当前的skb中,并重新计算校验和。
  193.              */
  194.             if (fraggap) {
  195.                 skb->csum = skb_copy_and_csum_bits(
  196.                     skb_prev, maxfraglen,
  197.                     data + transhdrlen, fraggap, 0);
  198.                 /*上一个skb的校验和也需要重新计算*/
  199.                 skb_prev->csum = csum_sub(skb_prev->csum,
  200.                              skb->csum);
  201.                 /*拷贝新数据后,再次移动data指针,更新数据写入的位置*/
  202.                 data += fraggap;
  203.                 /*已8字节对齐的MTU大小截取上一个skb,多余的数据已经拷贝到新的skb中了,需要截掉*/
  204.                 pskb_trim_unique(skb_prev, maxfraglen);
  205.             }
  206.             /*拷贝新数据到数据区*/
  207.             copy = datalen - transhdrlen - fraggap;
  208.             /*getfrag为传入的函数指针,udp默认为ip_generic_getfrag,用于从用户态拷贝数据到内核中(skb的数据区)*/
  209.             if (copy > 0 && getfrag(from, data + transhdrlen, offset, copy, fraggap, skb) < 0) {
  210.                 err = -EFAULT;
  211.                 kfree_skb(skb);
  212.                 goto error;
  213.             }
  214.             /*更新下次拷贝相关的数据*/
  215.             offset += copy;
  216.             length -= datalen - fraggap;
  217.             /*由于传输层首部已经拷贝过了,所以相关变量置0*/
  218.             transhdrlen = 0;
  219.             exthdrlen = 0;
  220.             csummode = CHECKSUM_NONE;

  221.             /*
  222.              * Put the packet on the pending queue.
  223.              */
  224.             /*复制完数据的skb添加到sock输出队列的末尾*/
  225.             __skb_queue_tail(queue, skb);
  226.             continue;
  227.         }
  228.         /*如果当前skb中的剩余数据区大于要拷贝的数据长度,那说明这次要发送的数据可以直接放入当前的skb中,直接拷贝即可。*/
  229.         if (copy > length)
  230.             copy = length;
  231.         /*如果硬件不支持SG(分散聚集特性,使用skb中的非线性区(shared_info))*/
  232.         if (!(rt->dst.dev->features&NETIF_F_SG)) {
  233.             unsigned int off;

  234.             off = skb->len;
  235.             /*将数据拷贝到skb中的线性区*/        
  236.             if (getfrag(from, skb_put(skb, copy),
  237.                     offset, copy, off, skb) < 0) {
  238.                 __skb_trim(skb, off);
  239.                 err = -EFAULT;
  240.                 goto error;
  241.             }
  242.         } else {/*如果硬件支持SG,则将数据拷贝到skb的非线性区(shared_info)的frags[]数组指向的page中,注意:UFO时也会使用这个*/
  243.             int i = skb_shinfo(skb)->nr_frags;

  244.             err = -ENOMEM;
  245.             /*在分片列表(frags)中使用原有分片(返回相应分片的指针)或分配新页来存放数据*/
  246.             if (!sk_page_frag_refill(sk, pfrag))
  247.                 goto error;
  248.             /*
  249.              * 如果传输控制块(sock)中的缓存页pfrag,不是当前skb->shared_info中的最后一个分片(分散聚集IO页面)所在的页面,则直接使用该页面,
  250.              * 将其添加 到分片列表(分散聚集IO页面数组)中,否则说明传输控制块(sock)中的缓存页pfrag就是分散聚集IO页面的最后一个页面,
  251.              * 则直接向其中拷贝数据即可。
  252.              */
  253.             if (!skb_can_coalesce(skb, i, pfrag->page,
  254.                      pfrag->offset)) {
  255.                 err = -EMSGSIZE;
  256.                 if (i == MAX_SKB_FRAGS)
  257.                     goto error;

  258.                 __skb_fill_page_desc(skb, i, pfrag->page,
  259.                          pfrag->offset, 0);
  260.                 skb_shinfo(skb)->nr_frags = ++i;
  261.                 get_page(pfrag->page);
  262.             }
  263.             copy = min_t(int, copy, pfrag->size - pfrag->offset);
  264.             /*拷贝数据至skb中非线性区分片(分散聚集IO页面)*/
  265.             if (getfrag(from,
  266.                  page_address(pfrag->page) + pfrag->offset,
  267.                  offset, copy, skb->len, skb) < 0)
  268.                 goto error_efault;
  269.             /*移动相应数据指针*/
  270.             pfrag->offset += copy;
  271.             /*增加分片大小*/
  272.             skb_frag_size_add(&skb_shinfo(skb)->frags[i - 1], copy);
  273.             /*增加skb数据相关大小*/
  274.             skb->len += copy;
  275.             skb->data_len += copy;
  276.             skb->truesize += copy;
  277.             /*增加sock发送缓存区已分配数据大小*/
  278.             atomic_add(copy, &sk->sk_wmem_alloc);
  279.         }
  280.         offset += copy;
  281.         /*length减去已经拷贝的大小,如果拷完了,则结束循环,否则继续拷贝*/
  282.         length -= copy;
  283.     }

  284.     return 0;

  285. error_efault:
  286.     err = -EFAULT;
  287. error:
  288.     cork->length -= length;
  289.     IP_INC_STATS(sock_net(sk), IPSTATS_MIB_OUTDISCARDS);
  290.     return err;
  291. }
阅读(4939) | 评论(0) | 转发(1) |
给主人留下些什么吧!~~