ip_append_data函数是传输层调用缓冲要发送的数据包,主要调用协议数据包有UDP数据包、TCP管理报文。当数据包缓冲到一定大小(PMTU)就调用ip_push_pending_frames函数将数据包发送给IP层,这样可以提高效率,由ip_push_pending_frames维护IP协议头。如果传输层要提高响应时间,可以在每次调用ip_append_data函数后立即调用ip_push_pending_frames将数据包发送到网络层。
1、ip_append_data主要任务
1.1.缓冲传输层协议发送的数据段
将传输层发送过来的数据放在缓冲区,这些缓冲区的组织方式、分配的容量代销在需要进行IP数据包分割时,使得IP数据包的分割更容易实现,包括将那些数据段一某种方式放入缓冲区,在后来的网络层和数据链路层加入协议头部更方便。
1.2.优化内存分配
ip_append_data分配缓冲区数据包内存时考虑套接字中的数据包控制信息和输出网络设备的能力
a、短时间内分配多个数据段需要缓冲,这时控制信息可以设置MSG_MORE标志,ip_append_data就会分配一个较大的缓冲区,这样上层传过来的数据包就可以直接放入缓冲区,不要多次申请内存,提高效率。
b、硬件是否支持Scatler/Gather I/O功能,分段操作可以安排在内存页面中,
c、处理传输层校验和,传输层校验和是skb->ip_summed数据域。
2、ip_append_data输入参数
int ip_append_data(struct sock *sk,
int getfrag(void *from, char *to, int offset, int len,
int odd, struct sk_buff *skb),
void *from, int length, int transhdrlen,
struct ipcm_cookie *ipc, struct rtable **rtp,
unsigned int flags)
sk:与数据包绑定的套接字,这个数据域包含了一些参数(如IP选项),以后在ip_push_pending_frame函数填入IP协议头。
getfrag:这时一个函数指针,传输层的不同协议都有各自的实现函数,作用是将传输层协议从套接字收到的数据包赋值到缓冲 区, getfrag的输入参数:数据包的来源地址、赋值到缓冲区的目标地址、数据包长度。
from:传输层发送给网络层缓冲区的数据包的起始地址,可以是一个内核地址空间的指针也可以是一个用户地址空间的指针。
length:发送数据包的总长度。
transhdrlen:传输层协议头大小。
ipc:需要发送数据包的相关信息,包括目的地址、输出网络设备、IP选项。
rtp:数据包发送路由在路由表高速缓存中的记录入口
flags:改变了的的形式是MSG_XXX,定义在include/linux/socket.h中,ip_append_data会用到三个标志
MSG_MORE:应用程序使用,告诉传输层协议短时间内有更多的书就数据包要发送,分配内存时就会分配一个打开内 存。
MSG_DONTWAIT:设置了这个标志,ip_append_data调用就不会被阻塞,比如在为套接字数据结构sk分配内存时,会调用
会调用sock_alloc_send_skb,如果资源已经耗尽,ip_append_data的运行可以在堆中按时间阻塞,在 阻塞之前堆中通常有部 分内存会变为有效,套接字数据结构获取需要的内存,ip_append_data函数继续 执行,或者超时后堆中仍无有效的内存管供套接字使用,ip_append_data执行失败,这个标志就是在阻 塞等待性和直接退出做一个选择。
MSG_PROBE:可以探测发送路径的信息,如该标志可以探测给定IP地址路径上的PMTU。
3、ip_append_data函数分析
3.1.判断sk_wirite_queue队列
ip_append_data的输出就是sk_write_queue队列,当ip_append_data函数分配一个新的sk_buff数据结构来处理新的IP数据包是,ip_append_data就会将这个sk_buff放到套接字sk管理IP数据包的输出队列sk_wirte_queue,sk_write_queue就是所有要发送的sk_buff缓冲区链表。所以ip_append_data首先要判断函数是否正在创建sk_wirte_queue队列的第一个IP数据包,还是向sk_write_queue队列中加入数据包的后续缓冲区。当创建sk_write_queue队列中的第一个IP数据段时需要情况sk_write_queue队列。创建sk_write_queue队列的第一个成员,需要初始化inet->cork和inet数据结构的部分数据域。
...
//判断是否需要创建sk_write_queue队列中第一个IP数据段
//sk_write_queue队列第一个ip数据段为空时
if (skb_queue_empty(&sk->sk_write_queue)) {
/*
* setup for corking.
*/
opt = ipc->opt;
//有ip选项
if (opt) {
if (inet->cork.opt == NULL) {
//为ip选项分片内存
inet->cork.opt = kmalloc(sizeof(struct ip_options) + 40, sk->sk_allocation);
if (unlikely(inet->cork.opt == NULL))
return -ENOBUFS;
}
//赋值ip选项到inet结构体中
memcpy(inet->cork.opt, opt, sizeof(struct ip_options)+opt->optlen);
//设置ip选项标志
inet->cork.flags |= IPCORK_OPT;
inet->cork.addr = ipc->addr;
}
rt = *rtp;
if (unlikely(!rt))
return -EFAULT;
/*
* We steal reference to this route, caller should not release it
*/
*rtp = NULL;
inet->cork.fragsize = mtu = inet->pmtudisc == IP_PMTUDISC_PROBE ?
rt->u.dst.dev->mtu :
dst_mtu(rt->u.dst.path); //根据PMTU的值判断是否对数据分片
//缓冲路由信息到struct cork数据结构中
inet->cork.dst = &rt->u.dst;
inet->cork.length = 0;
sk->sk_sndmsg_page = NULL;
sk->sk_sndmsg_off = 0;
if ((exthdrlen = rt->u.dst.header_len) != 0) {
length += exthdrlen;
transhdrlen += exthdrlen;
}
} else {
...
如果sk_write_queue队列中已经有了IP数据段,说明已经初始化了struct cork和struct inet_socks数据结构的相关数据域,就要用这些数据域来初始化一些局部变量:rt路由表高速缓冲区中记录入口,mtu发送路由上的最大发送单元, opt:IP选项,只有第一个IP数据段才需要加入传输层协议头和IPsec协议头,后续缓冲区添加数据不需要加入这些协议头,这时transhdrlen和exthdrle需要清零。
} else {
//已经创建了第一个IP数据段,只是往缓冲区后追加数据
rt = (struct rtable *)inet->cork.dst;
//赋值ip选项
if (inet->cork.flags & IPCORK_OPT)
opt = inet->cork.opt;
//sk_write_queue队列中中已经有了第一个IP数据包,传输层头长度需要清零
transhdrlen = 0;
//额外的协议头长需要清零
exthdrlen = 0;
mtu = inet->cork.fragsize;
}
3.2.创建IP数据段
从传输层传来的每个IP数据段的大小不一样,赋值数据到缓冲区前要初始化以下几个局部变量,链路层协议头长度hh_len,ip协议头长度fragheaderlen,总的数据包长度maxfraglen,还需要验证接受到的IP数据端长度不能超过64K,因为IP数据包最大长度就是64K,最后初始化产生校验和的方式
...
//初始化链路层头部长度
hh_len = LL_RESERVED_SPACE(rt->u.dst.dev);
//初始化ip协议头头长度
fragheaderlen = sizeof(struct iphdr) + (opt ? opt->optlen : 0);
//一个数据包的总长度
maxfraglen = ((mtu - fragheaderlen) & ~7) + fragheaderlen;
//ip数据包的最大长度64k,16位表示
//判断数据包长度是否超过64k
if (inet->cork.length + length > 0xFFFF - fragheaderlen) {
ip_local_error(sk, EMSGSIZE, rt->rt_dst, inet->inet_dport,
mtu-exthdrlen);
return -EMSGSIZE;
}
/*
* transhdrlen > 0 means that this is the first fragment and we wish
* it won't be fragmented in the future.
*/
//初始化产生校验和的方式
if (transhdrlen &&
length + fragheaderlen <= mtu &&
rt->u.dst.dev->features & NETIF_F_V4_CSUM &&
!exthdrlen)
csummode = CHECKSUM_PARTIAL;
...
3.3 IP数据段赋值函数指针getfrag
不同的协议赋值数据包到缓冲区使用不同的函数,主要原因是不同的协议计算传输层的校验和不一样,数据包的来源不一样。getfrag函数的输出参数有:from、to、offset、len,意思是从from指针所指地址处复制len个字节到to+offer地址,以下是不同协议对应不同的getfrag函数。
协议 | 数据复制函数 |
ICMP | icmp_glue_bits |
UDP | ip_generic_getfrag |
裸IP | ip_generic_getfrag |
TCP | ip_reply_glue_bits |
3.4. ip_append_data函数的主循环
ip_append_data主循环流程图
(1)输入参数length的变化
length是要复制数据包的总长度,每次循环要更新length直到length等于0,数据复制完成。
(2)MSG_MORE标志
MSG_MORE标志指明传输层会多次调用ip_append_data函数向缓冲区追加数据。
(3)NETIF_F_SG标志
是否支持Scatter/Gather I/O功能。
(4)分配信的sk_buff数据结构
只有两种情况对会分配sk_buff数据结构,第一种是sk_write_queue队列为空时也就是ip_append_data在处理sk_write_queue队列中的第一个数据段,第二种情况是sk_write_queue的最后一个成员空间已经填满了。主循环前要处理第一个情况sk_write_queue队列为空。
...
//获取sk_write_queue最后一个sk_buff成员
skb = skb_peek_tail(&sk->sk_write_queue);
//加上要复制数据段的长度
inet->cork.length += length;
if (((length > mtu) || (skb && skb_is_gso(skb))) &&
(sk->sk_protocol == IPPROTO_UDP) &&
(rt->u.dst.dev->features & NETIF_F_UFO)) {
//调用getfrag包ip数据段赋值到缓冲区也就是sk_write_queue队列中
err = ip_ufo_append_data(sk, getfrag, from, length, hh_len,
fragheaderlen, transhdrlen, mtu,
flags);
if (err)
goto error;
return 0;
}
//sk_write_queue最后一个成员为NULL就需要分配sk_buff数据结构
if (!skb)
goto alloc_new_skb;
...
(4)while循环体
while主循环体内第一部分处理第二种分配sk_buff的情况,首先初始化缓冲区剩余空间长度mtu - skb->len,如果length大于剩余空间就要分配一个新的IP数据段。copy有三种情况
a、copy > 0 skb的数据缓冲区还有空间可以存放数据,ip_append_data就利用这部分空间存活数据包段。
b、copy = 0 这时sk_write_queue队列中最后一个sk_buff成员的数据缓冲区空间已经填满,需要重新分配sk_buff,将数据包复制到新的sk_buff缓冲区,然后加入到sk_write_queue队列中。
c、coye < 0 拷贝为负值,说明数据包必须从IP数据段中移动到一个新的IP数据段中。
//数据包没有复制完进入主循环
while (length > 0) {
/* Check if the remaining data fits into current packet. */
//剩余缓冲区长度
copy = mtu - skb->len;
if (copy < length)
copy = maxfraglen - skb->len;
//缓冲区空间不足需要重新分配空间
//如果copy < 0要把部分IP数据段移动到一个新的IP数据段中
if (copy <= 0) {
char *data;
unsigned int datalen;
unsigned int fraglen;
unsigned int fraggap;
unsigned int alloclen;
struct sk_buff *skb_prev;
alloc_new_skb:
skb_prev = skb;
if (skb_prev)
fraggap = skb_prev->len - maxfraglen;
else
fraggap = 0;
/*
* If remaining data exceeds the mtu,
* we know we need more fragment(s).
*/
datalen = length + fraggap;
if (datalen > mtu - fragheaderlen)
datalen = maxfraglen - fragheaderlen;
fraglen = datalen + fragheaderlen;
//多次发送标志,一次分配一个大块内存
if ((flags & MSG_MORE) &&
!(rt->u.dst.dev->features&NETIF_F_SG))
alloclen = mtu;
else
//单次调用或者不支持Scatter/Gather I/O功能
alloclen = datalen + fragheaderlen;
/* The last fragment gets additional space at tail.
* Note, with MSG_MORE we overallocate on fragments,
* because we have no idea what fragment will be
* the last.
*/
if (datalen == length + fraggap)
alloclen += rt->u.dst.trailer_len;
if (transhdrlen) {
//为skb分配空间
skb = sock_alloc_send_skb(sk,
alloclen + hh_len + 15,
(flags & MSG_DONTWAIT), &err);
} else {
skb = NULL;
if (atomic_read(&sk->sk_wmem_alloc) <=
2 * sk->sk_sndbuf)
skb = sock_wmalloc(sk,
alloclen + hh_len + 15, 1,
sk->sk_allocation);
if (unlikely(skb == NULL))
err = -ENOBUFS;
else
/* only the initial fragment is
time stamped */
ipc->shtx.flags = 0;
}
if (skb == NULL)
goto error;
/*
* Fill in the control structures
*/
skb->ip_summed = csummode;
skb->csum = 0;
skb_reserve(skb, hh_len);
*skb_tx(skb) = ipc->shtx;
/*
* Find where to start putting bytes.
*/
//偏移ip头部长度
data = skb_put(skb, fraglen);
//设置传输层头部
skb_set_network_header(skb, exthdrlen);
skb->transport_header = (skb->network_header +
fragheaderlen);
data += fragheaderlen;
if (fraggap) {
skb->csum = skb_copy_and_csum_bits(
skb_prev, maxfraglen,
data + transhdrlen, fraggap, 0);
skb_prev->csum = csum_sub(skb_prev->csum,
skb->csum);
data += fraggap;
pskb_trim_unique(skb_prev, maxfraglen);
}
copy = datalen - transhdrlen - fraggap;
//拷贝数据包到缓冲区
if (copy > 0 && getfrag(from, data + transhdrlen, offset, copy, fraggap, skb) < 0) {
err = -EFAULT;
kfree_skb(skb);
goto error;
}
offset += copy;
length -= datalen - fraggap;
transhdrlen = 0;
exthdrlen = 0;
csummode = CHECKSUM_NONE;
/*
* Put the packet on the pending queue.
*/
//将新的skb添加到sk_wirte_queue队列中
__skb_queue_tail(&sk->sk_write_queue, skb);
continue;
}
//拷贝空间大于剩余长度,就拷贝剩余数据包的长度
if (copy > length)
copy = length;
//不支持Scatter/Gather I/O
if (!(rt->u.dst.dev->features&NETIF_F_SG)) {
unsigned int off;
off = skb->len;
//拷贝数据包到缓冲区
if (getfrag(from, skb_put(skb, copy),
offset, copy, off, skb) < 0) {
__skb_trim(skb, off);
err = -EFAULT;
goto error;
}
} else {
//支持Scatter/Gather I/O
int i = skb_shinfo(skb)->nr_frags;
skb_frag_t *frag = &skb_shinfo(skb)->frags[i-1];
struct page *page = sk->sk_sndmsg_page;
int off = sk->sk_sndmsg_off;
unsigned int left;
//是否分配了页面缓冲区
if (page && (left = PAGE_SIZE - off) > 0) {
if (copy >= left)
copy = left;
if (page != frag->page) {
if (i == MAX_SKB_FRAGS) {
err = -EMSGSIZE;
goto error;
}
get_page(page);
skb_fill_page_desc(skb, i, page, sk->sk_sndmsg_off, 0);
frag = &skb_shinfo(skb)->frags[i];
}
} else if (i < MAX_SKB_FRAGS) {
//未分配页面缓冲区
if (copy > PAGE_SIZE)
copy = PAGE_SIZE;
//分配页面缓冲
page = alloc_pages(sk->sk_allocation, 0);
if (page == NULL) {
err = -ENOMEM;
goto error;
}
sk->sk_sndmsg_page = page;
sk->sk_sndmsg_off = 0;
skb_fill_page_desc(skb, i, page, 0, 0);
frag = &skb_shinfo(skb)->frags[i];
} else {
err = -EMSGSIZE;
goto error;
}
//复制数据包到缓冲区
if (getfrag(from, page_address(frag->page)+frag->page_offset+frag->size, offset, copy, skb->len, skb) < 0) {
err = -EFAULT;
goto error;
}
sk->sk_sndmsg_off += copy;
frag->size += copy;
skb->len += copy;
skb->data_len += copy;
skb->truesize += copy;
atomic_add(copy, &sk->sk_wmem_alloc);
}
//更新拷贝数据包的偏移量
offset += copy;
//剩余还未拷贝数据包的长度
length -= copy;
}
ip_append_data函数完整代码:
int ip_append_data(struct sock *sk,
int getfrag(void *from, char *to, int offset, int len,
int odd, struct sk_buff *skb),
void *from, int length, int transhdrlen,
struct ipcm_cookie *ipc, struct rtable **rtp,
unsigned int flags)
{
struct inet_sock *inet = inet_sk(sk);
struct sk_buff *skb;
//ip选项
struct ip_options *opt = NULL;
//数据链路头长度
int hh_len;
//ipsec协议头长度
int exthdrlen;
int mtu;
int copy;
int err;
int offset = 0;
unsigned int maxfraglen, fragheaderlen;
int csummode = CHECKSUM_NONE;
struct rtable *rt;
if (flags&MSG_PROBE)
return 0;
//判断是否需要创建sk_write_queue队列中第一个IP数据段
//sk_write_queue队列第一个ip数据段为空时
if (skb_queue_empty(&sk->sk_write_queue)) {
/*
* setup for corking.
*/
opt = ipc->opt;
//有ip选项
if (opt) {
if (inet->cork.opt == NULL) {
//为ip选项分片内存
inet->cork.opt = kmalloc(sizeof(struct ip_options) + 40, sk->sk_allocation);
if (unlikely(inet->cork.opt == NULL))
return -ENOBUFS;
}
//赋值ip选项到inet结构体中
memcpy(inet->cork.opt, opt, sizeof(struct ip_options)+opt->optlen);
//设置ip选项标志
inet->cork.flags |= IPCORK_OPT;
inet->cork.addr = ipc->addr;
}
rt = *rtp;
if (unlikely(!rt))
return -EFAULT;
/*
* We steal reference to this route, caller should not release it
*/
*rtp = NULL;
inet->cork.fragsize = mtu = inet->pmtudisc == IP_PMTUDISC_PROBE ?
rt->u.dst.dev->mtu :
dst_mtu(rt->u.dst.path); //根据PMTU的值判断是否对数据分片
//缓冲路由信息到struct cork数据结构中
inet->cork.dst = &rt->u.dst;
inet->cork.length = 0;
sk->sk_sndmsg_page = NULL;
sk->sk_sndmsg_off = 0;
if ((exthdrlen = rt->u.dst.header_len) != 0) {
length += exthdrlen;
transhdrlen += exthdrlen;
}
} else {
//已经创建了第一个IP数据段,只是往缓冲区后追加数据
rt = (struct rtable *)inet->cork.dst;
//赋值ip选项
if (inet->cork.flags & IPCORK_OPT)
opt = inet->cork.opt;
//sk_write_queue队列中中已经有了第一个IP数据包,传输层头长度需要清零
transhdrlen = 0;
exthdrlen = 0;
mtu = inet->cork.fragsize;
}
//初始化链路层头部长度
hh_len = LL_RESERVED_SPACE(rt->u.dst.dev);
//初始化ip协议头头长度
fragheaderlen = sizeof(struct iphdr) + (opt ? opt->optlen : 0);
//一个数据包的总长度
maxfraglen = ((mtu - fragheaderlen) & ~7) + fragheaderlen;
//ip数据包的最大长度64k,16位表示
//判断数据包长度是否超过64k
if (inet->cork.length + length > 0xFFFF - fragheaderlen) {
ip_local_error(sk, EMSGSIZE, rt->rt_dst, inet->inet_dport,
mtu-exthdrlen);
return -EMSGSIZE;
}
/*
* transhdrlen > 0 means that this is the first fragment and we wish
* it won't be fragmented in the future.
*/
//初始化产生校验和的方式
if (transhdrlen &&
length + fragheaderlen <= mtu &&
rt->u.dst.dev->features & NETIF_F_V4_CSUM &&
!exthdrlen)
csummode = CHECKSUM_PARTIAL;
//获取sk_write_queue最后一个sk_buff成员
skb = skb_peek_tail(&sk->sk_write_queue);
//加上要复制数据段的长度
inet->cork.length += length;
if (((length > mtu) || (skb && skb_is_gso(skb))) &&
(sk->sk_protocol == IPPROTO_UDP) &&
(rt->u.dst.dev->features & NETIF_F_UFO)) {
//调用getfrag包ip数据段赋值到缓冲区也就是sk_write_queue队列中
err = ip_ufo_append_data(sk, getfrag, from, length, hh_len,
fragheaderlen, transhdrlen, mtu,
flags);
if (err)
goto error;
return 0;
}
/* So, what's going on in the loop below?
*
* We use calculated fragment length to generate chained skb,
* each of segments is IP fragment ready for sending to network after
* adding appropriate IP header.
*/
//sk_write_queue最后一个成员为NULL就需要分配sk_buff数据结构
if (!skb)
goto alloc_new_skb;
//数据包没有复制完进入主循环
while (length > 0) {
/* Check if the remaining data fits into current packet. */
//剩余缓冲区长度
copy = mtu - skb->len;
if (copy < length)
copy = maxfraglen - skb->len;
//缓冲区空间不足需要重新分配空间
//如果copy < 0要把部分IP数据段移动到一个新的IP数据段中
if (copy <= 0) {
char *data;
unsigned int datalen;
unsigned int fraglen;
unsigned int fraggap;
unsigned int alloclen;
struct sk_buff *skb_prev;
alloc_new_skb:
skb_prev = skb;
if (skb_prev)
fraggap = skb_prev->len - maxfraglen;
else
fraggap = 0;
/*
* If remaining data exceeds the mtu,
* we know we need more fragment(s).
*/
datalen = length + fraggap;
if (datalen > mtu - fragheaderlen)
datalen = maxfraglen - fragheaderlen;
fraglen = datalen + fragheaderlen;
//多次发送标志,一次分配一个大块内存
if ((flags & MSG_MORE) &&
!(rt->u.dst.dev->features&NETIF_F_SG))
alloclen = mtu;
else
//单次调用或者不支持Scatter/Gather I/O功能
alloclen = datalen + fragheaderlen;
/* The last fragment gets additional space at tail.
* Note, with MSG_MORE we overallocate on fragments,
* because we have no idea what fragment will be
* the last.
*/
if (datalen == length + fraggap)
alloclen += rt->u.dst.trailer_len;
if (transhdrlen) {
//为skb分配空间
skb = sock_alloc_send_skb(sk,
alloclen + hh_len + 15,
(flags & MSG_DONTWAIT), &err);
} else {
skb = NULL;
if (atomic_read(&sk->sk_wmem_alloc) <=
2 * sk->sk_sndbuf)
skb = sock_wmalloc(sk,
alloclen + hh_len + 15, 1,
sk->sk_allocation);
if (unlikely(skb == NULL))
err = -ENOBUFS;
else
/* only the initial fragment is
time stamped */
ipc->shtx.flags = 0;
}
if (skb == NULL)
goto error;
/*
* Fill in the control structures
*/
skb->ip_summed = csummode;
skb->csum = 0;
skb_reserve(skb, hh_len);
*skb_tx(skb) = ipc->shtx;
/*
* Find where to start putting bytes.
*/
//偏移ip头部长度
data = skb_put(skb, fraglen);
//设置传输层头部
skb_set_network_header(skb, exthdrlen);
skb->transport_header = (skb->network_header +
fragheaderlen);
data += fragheaderlen;
if (fraggap) {
skb->csum = skb_copy_and_csum_bits(
skb_prev, maxfraglen,
data + transhdrlen, fraggap, 0);
skb_prev->csum = csum_sub(skb_prev->csum,
skb->csum);
data += fraggap;
pskb_trim_unique(skb_prev, maxfraglen);
}
copy = datalen - transhdrlen - fraggap;
//拷贝数据包到缓冲区
if (copy > 0 && getfrag(from, data + transhdrlen, offset, copy, fraggap, skb) < 0) {
err = -EFAULT;
kfree_skb(skb);
goto error;
}
offset += copy;
length -= datalen - fraggap;
transhdrlen = 0;
exthdrlen = 0;
csummode = CHECKSUM_NONE;
/*
* Put the packet on the pending queue.
*/
//将新的skb添加到sk_wirte_queue队列中
__skb_queue_tail(&sk->sk_write_queue, skb);
continue;
}
//拷贝空间大于剩余长度,就拷贝剩余数据包的长度
if (copy > length)
copy = length;
//不支持Scatter/Gather I/O
if (!(rt->u.dst.dev->features&NETIF_F_SG)) {
unsigned int off;
off = skb->len;
//拷贝数据包到缓冲区
if (getfrag(from, skb_put(skb, copy),
offset, copy, off, skb) < 0) {
__skb_trim(skb, off);
err = -EFAULT;
goto error;
}
} else {
//支持Scatter/Gather I/O
int i = skb_shinfo(skb)->nr_frags;
skb_frag_t *frag = &skb_shinfo(skb)->frags[i-1];
struct page *page = sk->sk_sndmsg_page;
int off = sk->sk_sndmsg_off;
unsigned int left;
//是否分配了页面缓冲区
if (page && (left = PAGE_SIZE - off) > 0) {
if (copy >= left)
copy = left;
if (page != frag->page) {
if (i == MAX_SKB_FRAGS) {
err = -EMSGSIZE;
goto error;
}
get_page(page);
skb_fill_page_desc(skb, i, page, sk->sk_sndmsg_off, 0);
frag = &skb_shinfo(skb)->frags[i];
}
} else if (i < MAX_SKB_FRAGS) {
//未分配页面缓冲区
if (copy > PAGE_SIZE)
copy = PAGE_SIZE;
//分配页面缓冲
page = alloc_pages(sk->sk_allocation, 0);
if (page == NULL) {
err = -ENOMEM;
goto error;
}
sk->sk_sndmsg_page = page;
sk->sk_sndmsg_off = 0;
skb_fill_page_desc(skb, i, page, 0, 0);
frag = &skb_shinfo(skb)->frags[i];
} else {
err = -EMSGSIZE;
goto error;
}
//复制数据包到缓冲区
if (getfrag(from, page_address(frag->page)+frag->page_offset+frag->size, offset, copy, skb->len, skb) < 0) {
err = -EFAULT;
goto error;
}
sk->sk_sndmsg_off += copy;
frag->size += copy;
skb->len += copy;
skb->data_len += copy;
skb->truesize += copy;
atomic_add(copy, &sk->sk_wmem_alloc);
}
//更新拷贝数据包的偏移量
offset += copy;
//剩余还未拷贝数据包的长度
length -= copy;
}
return 0;
error:
inet->cork.length -= length;
IP_INC_STATS(sock_net(sk), IPSTATS_MIB_OUTDISCARDS);
return err;
}