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

2016年(1)

2015年(21)

2014年(100)

分类: LINUX

2015-06-04 17:16:24

1、基本原理
本文主要涉及ip_append_data中的UFO(UDP Fragment Offload)相关流程,主要由ip_ufo_append_data函数完成相关功能。比较简单。
UFO(UDP Fragment Offload)是硬件网卡提供的一种特性,由内核和驱动配合完成相关功能。其目的是由网卡硬件来完成本来需要软件进行的分段(分片)操作用于提升效率和性能。如大家所知,在网络上传输的数据包不能大于mtu,当用户发送大于mtu的数据报文时,通常会在传输层(或者在特殊情况下在IP层分片,比如ip转发或ipsec时)就会按mtu大小进行分段,防止发送出去的报文大于mtu,为提升该操作的性能,新的网卡硬件基本都实现了UFO功能,可以使分段(或分片)操作在网卡硬件完成,此时用户态就可以发送长度大于mtu的包,而且不必在协议栈中进行分段(或分片)。
ip_ufo_append_data函数大致原理为:当硬件支持且打开了UFO、udp包大小大于mtu会进入此流程,将用户态数据拷贝拷skb中的非线性区中(即skb_shared_info->frags[],原本用于SG)。

2、基本流
主要流程为:从sock发送队列中取skb,如果发送队列为空,则新分配一个skb;如果不为空,则直接使用该skb;然后,判断per task的page_frag中是否有空间可用,有的话,就直接从用户态拷贝数据到该page_frag中,如果没有空间,则分配新的page,放入page_frag中,然后再从用户态拷贝数据到其中,最后将该page_frag中的page链入skb的非线性区中(即skb_shared_info->frags[]).
进入ip_ufo_append_data的调用流程为:
udp_sendmsg-->
    ip_append_data-->
        __ip_append_data-->
            ip_ufo_append_data

3、代码分
__ip_append_data->ip_ufo_append_data():

点击(此处)折叠或打开

  1. /*
  2.   * ip_append_data中,如果满足ufo的条件,则进入这里处理。
  3.   * 主要是利用了skb中的frags[]区域存放数据,该区域原本是用于SG场景的,但UFO此时也复用了这段区域。
  4.   */
  5. static inline int ip_ufo_append_data(struct sock *sk,
  6.             struct sk_buff_head *queue,
  7.             int getfrag(void *from, char *to, int offset, int len,
  8.              int odd, struct sk_buff *skb),
  9.             void *from, int length, int hh_len, int fragheaderlen,
  10.             int transhdrlen, int maxfraglen, unsigned int flags)
  11. {
  12.     struct sk_buff *skb;
  13.     int err;

  14.     /* There is support for UDP fragmentation offload by network
  15.      * device, so create one single skb packet containing complete
  16.      * udp datagram
  17.      */
  18.     /*从sock请求队列队尾取skb,如果为空,需要重新分配skb*/
  19.     if ((skb = skb_peek_tail(queue)) == NULL) {
  20.         /*重新分配一个skb*/
  21.         skb = sock_alloc_send_skb(sk,
  22.             hh_len + fragheaderlen + transhdrlen + 20,
  23.             (flags & MSG_DONTWAIT), &err);

  24.         if (skb == NULL)
  25.             return err;

  26.         /* reserve space for Hardware header */
  27.         /*留出链路层头的空间*/
  28.         skb_reserve(skb, hh_len);

  29.         /* create space for UDP/IP header */
  30.         /*留出传输层和IP层头部大小的空间*/
  31.         skb_put(skb, fragheaderlen + transhdrlen);

  32.         /* initialize network header pointer */
  33.         /*初始化IP头*/
  34.         skb_reset_network_header(skb);

  35.         /* initialize protocol header pointer */
  36.         /*初始化传输层头指针*/
  37.         skb->transport_header = skb->network_header + fragheaderlen;

  38.         skb->csum = 0;

  39.         /*将新分配的skb放入sock的发送队列中*/
  40.         __skb_queue_tail(queue, skb);
  41.     } else if (skb_is_gso(skb)) {
  42.         /*当前skb中已设置GSO,直接append*/
  43.         goto append;
  44.     }
  45.     /*
  46.      * 如果当前skb中未设置GSO标记,说明是因为length > mtu进入到这里的,需要设置GSO标记
  47.      * 老版本中将这里的几行代码放到了前面的if中(也就是说只设置发送队列中第一个skb的GSO标记),会导致小包+大包场景下出现分段错误,导致内存混乱
  48.      * 原因是当sock发送队列不为空时,没有设置SKB_GSO_UDP标记,导致当小包+大包组合时,本来应该走UFO流程的,在这种情况下没有走UFO
  49.      * 流程,而进入了UDP的分段流程,而在分段时出现了计算错误,导致skb的数据区混乱被覆盖。
  50.      */
  51.     skb->ip_summed = CHECKSUM_PARTIAL;
  52.     /* specify the length of each IP datagram fragment */
  53.     skb_shinfo(skb)->gso_size = maxfraglen - fragheaderlen;
  54.     skb_shinfo(skb)->gso_type = SKB_GSO_UDP;

  55. append:/*向skb中添加数据*/
  56.     return skb_append_datato_frags(sk, skb, getfrag, from,
  57.                  (length - transhdrlen));
  58. }

__ip_append_data->ip_ufo_append_data->skb_append_datato_frags():

点击(此处)折叠或打开

  1. /**
  2.  * skb_append_datato_frags - append the user data to a skb
  3.  * @sk: sock structure
  4.  * @skb: skb structure to be appened with user data.
  5.  * @getfrag: call back function to be used for getting the user data
  6.  * @from: pointer to user message iov
  7.  * @length: length of the iov message
  8.  *
  9.  * Description: This procedure append the user data in the fragment part
  10.  * of the skb if any page alloc fails user this procedure returns -ENOMEM
  11.  */
  12. /*将用户数据拷贝到skb数据区中,UFO流程调用,本质是将数据拷贝到skb_shared_info->frag[]对应的非线性区中*/
  13. int skb_append_datato_frags(struct sock *sk, struct sk_buff *skb,
  14.             int (*getfrag)(void *from, char *to, int offset,
  15.                     int len, int odd, struct sk_buff *skb),
  16.             void *from, int length)
  17. {
  18.     /*获取非线性区skb_shared_info->frag[]中frags的数量*/
  19.     int frg_cnt = skb_shinfo(skb)->nr_frags;
  20.     int copy;
  21.     int offset = 0;
  22.     int ret;
  23.     /*使用per task的pfrag,用于提升性能*/
  24.     struct page_frag *pfrag = &current->task_frag;

  25.     do {
  26.         /* Return error if we don't have space for new frag */
  27.         /*skb_shared_info->frag[]的大小是固定的,静态的,17,不能超过这个大小*/
  28.         if (frg_cnt >= MAX_SKB_FRAGS)
  29.             return -EMSGSIZE;
  30.         /*判断是否能在现有的pfrag中append数据,不过不能的话(空间不够),则新分配page链入pfrag中,用于后面向其中拷贝数据,如果失败,则表示内存不足了。*/
  31.         if (!sk_page_frag_refill(sk, pfrag))
  32.             return -ENOMEM;

  33.         /* copy the user data to page */
  34.         /*需要拷贝的数据大小,如果pfrag中空间够,就拷贝全部数据;如果不够,则只能先将其填满,下一次循环时重新分配新的page,再往里拷贝*/
  35.         copy = min_t(int, length, pfrag->size - pfrag->offset);
  36.         /*从用户态拷贝数据到pfrag的page中,实际调用ip_generic_getfrags*/
  37.         ret = getfrag(from, page_address(pfrag->page) + pfrag->offset,
  38.              offset, copy, 0, skb);
  39.         /*拷贝失败,返回错误*/
  40.         if (ret < 0)
  41.             return -EFAULT;

  42.         /* copy was successful so update the size parameters */
  43.         /*拷贝成功,将pfrag中的page作为非线性区中的frag链入skb_shared_info->frag[]中,并更新相关计数和大小*/
  44.         skb_fill_page_desc(skb, frg_cnt, pfrag->page, pfrag->offset,
  45.                  copy);
  46.         /*非线性区中的frag数量增加*/
  47.         frg_cnt++;
  48.         /*offset增加*/
  49.         pfrag->offset += copy;
  50.         /*
  51.          * 增加page引用计数,因为又有新的地方(skb非线性区)引用了它
  52.          * Fixme:这里不需要锁保护下?其它地方在put话,是否可能导致page提前释放?
  53.          */
  54.         get_page(pfrag->page);
  55.         /*skb truesize(总的大小,包括线性区和非线性区和skb结构自身的大小)增加*/
  56.         skb->truesize += copy;
  57.         /*增加sock发送缓冲区的大小,数据发送出去后会减去相应的大小*/
  58.         atomic_add(copy, &sk->sk_wmem_alloc);
  59.         /*skb中的数据长度增加,包括线性区和非线性区*/
  60.         skb->len += copy;
  61.         /*skb非线性区数据长度*/
  62.         skb->data_len += copy;
  63.         offset += copy;
  64.         /*length减去已经拷贝的部分,如果小于0,则结束循环,否则继续分配新的frag拷贝*/
  65.         length -= copy;

  66.     } while (length > 0);

  67.     return 0;
  68. }

__ip_append_data->ip_ufo_append_data->skb_append_datato_frags->sk_page_frag_refill():

点击(此处)折叠或打开

  1. /*使用原有frag或分配新页来存放数据*/
  2. bool sk_page_frag_refill(struct sock *sk, struct page_frag *pfrag)
  3. {
  4.     int order;

  5.     if (pfrag->page) {
  6.         /*如果现有分片(pfrag->page)还没有使用,则使用现有分片*/
  7.         if (atomic_read(&pfrag->page->_count) == 1) {
  8.             pfrag->offset = 0;
  9.             return true;
  10.         }
  11.         /*如果现有分片中还有剩余空间,则也使用现有分片*/
  12.         if (pfrag->offset < pfrag->size)
  13.             return true;
  14.         put_page(pfrag->page);
  15.     }

  16.     /* We restrict high order allocations to users that can afford to wait */
  17.     /*
  18.      * 否则,就需要分配新页作为新分配存放数据了,如果设置__GFP_WAIT,则表示在内存不足时可以等待回收,
  19.      * 此时可以分配更大的内存8个page(order=3),否则只分配1个page
  20.         */
  21.     order = (sk->sk_allocation & __GFP_WAIT) ? SKB_FRAG_PAGE_ORDER : 0;

  22.     do {
  23.         gfp_t gfp = sk->sk_allocation;

  24.         if (order)
  25.             gfp |= __GFP_COMP | __GFP_NOWARN;
  26.         /*分配新页,放入pfrag中,用于后面向其中拷贝数据*/        
  27.         pfrag->page = alloc_pages(gfp, order);
  28.         if (likely(pfrag->page)) {
  29.             pfrag->offset = 0;
  30.             pfrag->size = PAGE_SIZE << order;
  31.             return true;
  32.         }
  33.     } while (--order >= 0);

  34.     sk_enter_memory_pressure(sk);
  35.     sk_stream_moderate_sndbuf(sk);
  36.     return false;
  37. }


阅读(3746) | 评论(2) | 转发(1) |
给主人留下些什么吧!~~

humjb_19832015-07-14 10:02:18

littlenewer:我也正好在看这一块,遇到两个问题想请教:
1. UFO本身是软件能力,为什么要求硬件支持且打开UFO才能执行UFO
2. ip_ufo_append_data最后调用return skb_append_datato_frags(sk, skb, getfrag, from,(length - transhdrlen))。从udp_sendmsg可知length为原始数据长度加上UDP头的长度。对于queue中的第一个报文来说,transhdrlen是UDP头的长度,所以相减后就是from中源数据的长度。这点没问题。但是对于非第一个报文来说,transhdrlen长度为0(见ip_append_data),这样想减后的值要比from中的数据多出一个UDP头,那后面拷贝数据时岂不是要出问题

1.UFO不是软件能力吧,应该是硬件能力,需要软件配合实现。这个您再确认下~
2.这里是UFO的流程,UDP分段的流程在UFO流程之后,也就是说进入UFO流程时,该UDP报文肯定是没有分段的,即这里没有非第一个报文的说法,应该都是有UDP头的。

回复 | 举报

littlenewer2015-07-09 20:36:57

我也正好在看这一块,遇到两个问题想请教:
1. UFO本身是软件能力,为什么要求硬件支持且打开UFO才能执行UFO
2. ip_ufo_append_data最后调用return skb_append_datato_frags(sk, skb, getfrag, from,(length - transhdrlen))。从udp_sendmsg可知length为原始数据长度加上UDP头的长度。对于queue中的第一个报文来说,transhdrlen是UDP头的长度,所以相减后就是from中源数据的长度。这点没问题。但是对于非第一个报文来说,transhdrlen长度为0(见ip_append_data),这样想减后的值要比from中的数据多出一个UDP头,那后面拷贝数据时岂不是要出问题