本文主要涉及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():
点击(此处)折叠或打开
- /*
- * ip_append_data中,如果满足ufo的条件,则进入这里处理。
- * 主要是利用了skb中的frags[]区域存放数据,该区域原本是用于SG场景的,但UFO此时也复用了这段区域。
- */
- static inline int ip_ufo_append_data(struct sock *sk,
- struct sk_buff_head *queue,
- int getfrag(void *from, char *to, int offset, int len,
- int odd, struct sk_buff *skb),
- void *from, int length, int hh_len, int fragheaderlen,
- int transhdrlen, int maxfraglen, unsigned int flags)
- {
- struct sk_buff *skb;
- int err;
- /* There is support for UDP fragmentation offload by network
- * device, so create one single skb packet containing complete
- * udp datagram
- */
- /*从sock请求队列队尾取skb,如果为空,需要重新分配skb*/
- if ((skb = skb_peek_tail(queue)) == NULL) {
- /*重新分配一个skb*/
- skb = sock_alloc_send_skb(sk,
- hh_len + fragheaderlen + transhdrlen + 20,
- (flags & MSG_DONTWAIT), &err);
- if (skb == NULL)
- return err;
- /* reserve space for Hardware header */
- /*留出链路层头的空间*/
- skb_reserve(skb, hh_len);
- /* create space for UDP/IP header */
- /*留出传输层和IP层头部大小的空间*/
- skb_put(skb, fragheaderlen + transhdrlen);
- /* initialize network header pointer */
- /*初始化IP头*/
- skb_reset_network_header(skb);
- /* initialize protocol header pointer */
- /*初始化传输层头指针*/
- skb->transport_header = skb->network_header + fragheaderlen;
- skb->csum = 0;
- /*将新分配的skb放入sock的发送队列中*/
- __skb_queue_tail(queue, skb);
- } else if (skb_is_gso(skb)) {
- /*当前skb中已设置GSO,直接append*/
- goto append;
- }
- /*
- * 如果当前skb中未设置GSO标记,说明是因为length > mtu进入到这里的,需要设置GSO标记
- * 老版本中将这里的几行代码放到了前面的if中(也就是说只设置发送队列中第一个skb的GSO标记),会导致小包+大包场景下出现分段错误,导致内存混乱
- * 原因是当sock发送队列不为空时,没有设置SKB_GSO_UDP标记,导致当小包+大包组合时,本来应该走UFO流程的,在这种情况下没有走UFO
- * 流程,而进入了UDP的分段流程,而在分段时出现了计算错误,导致skb的数据区混乱被覆盖。
- */
- skb->ip_summed = CHECKSUM_PARTIAL;
- /* specify the length of each IP datagram fragment */
- skb_shinfo(skb)->gso_size = maxfraglen - fragheaderlen;
- skb_shinfo(skb)->gso_type = SKB_GSO_UDP;
- append:/*向skb中添加数据*/
- return skb_append_datato_frags(sk, skb, getfrag, from,
- (length - transhdrlen));
- }
__ip_append_data->ip_ufo_append_data->skb_append_datato_frags():
点击(此处)折叠或打开
- /**
- * skb_append_datato_frags - append the user data to a skb
- * @sk: sock structure
- * @skb: skb structure to be appened with user data.
- * @getfrag: call back function to be used for getting the user data
- * @from: pointer to user message iov
- * @length: length of the iov message
- *
- * Description: This procedure append the user data in the fragment part
- * of the skb if any page alloc fails user this procedure returns -ENOMEM
- */
- /*将用户数据拷贝到skb数据区中,UFO流程调用,本质是将数据拷贝到skb_shared_info->frag[]对应的非线性区中*/
- int skb_append_datato_frags(struct sock *sk, struct sk_buff *skb,
- int (*getfrag)(void *from, char *to, int offset,
- int len, int odd, struct sk_buff *skb),
- void *from, int length)
- {
- /*获取非线性区skb_shared_info->frag[]中frags的数量*/
- int frg_cnt = skb_shinfo(skb)->nr_frags;
- int copy;
- int offset = 0;
- int ret;
- /*使用per task的pfrag,用于提升性能*/
- struct page_frag *pfrag = ¤t->task_frag;
- do {
- /* Return error if we don't have space for new frag */
- /*skb_shared_info->frag[]的大小是固定的,静态的,17,不能超过这个大小*/
- if (frg_cnt >= MAX_SKB_FRAGS)
- return -EMSGSIZE;
- /*判断是否能在现有的pfrag中append数据,不过不能的话(空间不够),则新分配page链入pfrag中,用于后面向其中拷贝数据,如果失败,则表示内存不足了。*/
- if (!sk_page_frag_refill(sk, pfrag))
- return -ENOMEM;
- /* copy the user data to page */
- /*需要拷贝的数据大小,如果pfrag中空间够,就拷贝全部数据;如果不够,则只能先将其填满,下一次循环时重新分配新的page,再往里拷贝*/
- copy = min_t(int, length, pfrag->size - pfrag->offset);
- /*从用户态拷贝数据到pfrag的page中,实际调用ip_generic_getfrags*/
- ret = getfrag(from, page_address(pfrag->page) + pfrag->offset,
- offset, copy, 0, skb);
- /*拷贝失败,返回错误*/
- if (ret < 0)
- return -EFAULT;
- /* copy was successful so update the size parameters */
- /*拷贝成功,将pfrag中的page作为非线性区中的frag链入skb_shared_info->frag[]中,并更新相关计数和大小*/
- skb_fill_page_desc(skb, frg_cnt, pfrag->page, pfrag->offset,
- copy);
- /*非线性区中的frag数量增加*/
- frg_cnt++;
- /*offset增加*/
- pfrag->offset += copy;
- /*
- * 增加page引用计数,因为又有新的地方(skb非线性区)引用了它
- * Fixme:这里不需要锁保护下?其它地方在put话,是否可能导致page提前释放?
- */
- get_page(pfrag->page);
- /*skb truesize(总的大小,包括线性区和非线性区和skb结构自身的大小)增加*/
- skb->truesize += copy;
- /*增加sock发送缓冲区的大小,数据发送出去后会减去相应的大小*/
- atomic_add(copy, &sk->sk_wmem_alloc);
- /*skb中的数据长度增加,包括线性区和非线性区*/
- skb->len += copy;
- /*skb非线性区数据长度*/
- skb->data_len += copy;
- offset += copy;
- /*length减去已经拷贝的部分,如果小于0,则结束循环,否则继续分配新的frag拷贝*/
- length -= copy;
- } while (length > 0);
- return 0;
- }
__ip_append_data->ip_ufo_append_data->skb_append_datato_frags->sk_page_frag_refill():
点击(此处)折叠或打开
- /*使用原有frag或分配新页来存放数据*/
- bool sk_page_frag_refill(struct sock *sk, struct page_frag *pfrag)
- {
- int order;
- if (pfrag->page) {
- /*如果现有分片(pfrag->page)还没有使用,则使用现有分片*/
- if (atomic_read(&pfrag->page->_count) == 1) {
- pfrag->offset = 0;
- return true;
- }
- /*如果现有分片中还有剩余空间,则也使用现有分片*/
- if (pfrag->offset < pfrag->size)
- return true;
- put_page(pfrag->page);
- }
- /* We restrict high order allocations to users that can afford to wait */
- /*
- * 否则,就需要分配新页作为新分配存放数据了,如果设置__GFP_WAIT,则表示在内存不足时可以等待回收,
- * 此时可以分配更大的内存8个page(order=3),否则只分配1个page
- */
- order = (sk->sk_allocation & __GFP_WAIT) ? SKB_FRAG_PAGE_ORDER : 0;
- do {
- gfp_t gfp = sk->sk_allocation;
- if (order)
- gfp |= __GFP_COMP | __GFP_NOWARN;
- /*分配新页,放入pfrag中,用于后面向其中拷贝数据*/
- pfrag->page = alloc_pages(gfp, order);
- if (likely(pfrag->page)) {
- pfrag->offset = 0;
- pfrag->size = PAGE_SIZE << order;
- return true;
- }
- } while (--order >= 0);
- sk_enter_memory_pressure(sk);
- sk_stream_moderate_sndbuf(sk);
- return false;
- }