本文主要涉及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;
- }