文本做为一个 TCP 发送缓冲区问题的解析的姊妹篇存在。此次说的是接收缓冲区的问题。html
Clinet 与 Server 之间创建一条 TCP 链接,Server 经过 SO_RCVBUF 选项设置链接的接收缓冲区为 2048 字节。Clinet 每隔 100 ms 经过 send() 一个载荷长度很小(2 字节)的 TCP 报文,但 Server 端不调用 recv(),这意味着 Server 收到的 TCP 报文都会存放在接收缓冲区,而当接收缓冲区满时,便应该向 Client 通告零窗口(Zero-Window)git
但实际状况是:github
下面是抓包的结果算法
因此,为何会这样?segmentfault
与发送缓冲区的实现一致,当用户经过 SO_RCVBUF 选项设置缓冲区大小时,内核会将这个设置值加倍,好比咱们在这里设置 2048 字节,内核实际的缓冲区大小时 4096 字节缓存
case SO_RCVBUF: ...... /* * We double it on the way in to account for * "struct sk_buff" etc. overhead. Applications * assume that the SO_RCVBUF setting they make will * allow that much actual data to be received on that * socket. * * Applications are unaware that "struct sk_buff" and * other overheads allocate from the receive buffer * during socket buffer allocation. * * And after considering the possible alternatives, * returning the value we actually used in getsockopt * is the most desirable behavior. */ sk->sk_rcvbuf = max_t(u32, val * 2, SOCK_MIN_RCVBUF); break;
这样作的缘由注释中有写,是由于sk_buff
有额外开销(下面会细说),而接收缓冲区的大小是指用于缓存全部收到的sk_buff
的内存,它会包含额外开销,因此这里内核将这个值扩大一倍。app
sk
上还有两个字段十分重要,sk->sk_rmem_alloc
表示当前已经使用的接收缓冲区内存,sk->forward_alloc
表示预先向内核分配到的内存。这两个字段有什么关系呢?socket
打个比方,sk->forward_alloc
就比如充值的点卡,sk->sk_rmem_alloc
则用来记录实际花销。当须要花费内存时,内核老是先看sk->forward_alloc
有没有,若是没有,则向系统申请内存,放到sk->forward_alloc
里,以后再花费时,发现sk->forward_alloc
还有,就直接扣这里面的就好了。而当sk->forward_alloc
花光时,则又会从新充值.tcp
内核一次充值sk->forward_alloc
的大小为 1 个 PAGESIZE,通常为 4K。ide
sk_buff
的结构在 skbuff详解或者skb_data中已经有详细描述。我画了一张图
sk_buff
分为sk_buff
结构自己、线性区域(linear area)和非线性区域(nonlinear area)。sk_buff
结构自己的大小在不一样版本内核略有不一样,但通过 2 的整次冥向上取整以后都为 256 Bytes,线性区域又分为数据存储区(skb->head
到skb->end
之间的部分)和skb_shared_info
区域, 整个区域大小根据建立sk_buff
时请求的大小肯定,好比咱们的例子中的包含 2B 用户数据的 TCP 报文,其线性区域大小通过向上取整后为 512B (预留头 64 Bytes + MAC头14 Bytes + IP头20 Bytes + TCP头 32 Bytes + 用户数据 2 Bytes + skb_shared_info结构 320 Bytes = 452 Bytes)。非线性区域不必定存在,若是存在则是挂在skb_shared_info
的frag
槽中,skb_shared_info
一共有MAX_SKB_FRAGS+1
个槽,在PAGESIZE=4K的状况下,这个值为 18。
三部分占用的内存总和才是一个sk_buff
真正占用的内存(也就是应该受到sk->rcvbuf
限制的),它的值记录在skb->truesize
,而skb->datalen
表示非线性区域用户数据的长度,skb->len
表示线性+非线性区域用户数据的长度。
因此在咱们例子中,Server端收到的sk_buff
的长度信息应该是
skb->truesize = 768 skb->datalen = 0 skb->len = 2
上面的例子中,一个sk_buff
存储 2 字节用户数据,却要占用 768 字节的内存。若是收到的每一个报文都要这么保存,那么接收缓冲区会被当即耗尽。所以内核在接收队列有sk_buff
尚未被用户读取时,若是再收到sk_buff
,会先尝试将后一个sk_buff
的数据放到接收队列的最后一个sk_buff
中。
static int __must_check tcp_queue_rcv(struct sock *sk, struct sk_buff *skb, int hdrlen, bool *fragstolen) { int eaten; struct sk_buff *tail = skb_peek_tail(&sk->sk_receive_queue); __skb_pull(skb, hdrlen); eaten = (tail && tcp_try_coalesce(sk, tail, skb, fragstolen)) ? 1 : 0; tcp_rcv_nxt_update(tcp_sk(sk), TCP_SKB_CB(skb)->end_seq); if (!eaten) { __skb_queue_tail(&sk->sk_receive_queue, skb); skb_set_owner_r(skb, sk); } return eaten; }
sk_buff
也不是随意就能够 Merge 的,它须要目标sk_buff
有足够的空间容纳它的数据。
咱们知道sk_buff
分为线性区域和非线性区域,内核在合并sk_buff
时,若是此时尚未使用非线性区域(skb_shared_info->nr_frag = 0),而且线性区域的剩余空间能装下新到的sk_buff
,那么就将用户数据拷贝到目标sk_buff
,不然再去尝试占用一个非线性区域中空闲的槽
那么,在咱们的环境中,队列尾的sk_buff
线性区域还能添加多少用户数据呢?
答案是 512 - 452 = 60 字节。也就是说,第 2 到第 31 个sk_buff
能够直接合并到第 1 个sk_buff
的线性区域,这些报文并不会增长缓冲区的内存占用sk->rmem_alloc
,由于它们已经计算在sk->truesize
了.
此时
tail_skb->truesize = 768 tail_skb->datalen = 0 tail_skb->len = 62 sk->sk_rmem_alloc = 768 sk->sk_forward_alloc = 3328
第 32 个报文时到达时,因为线性区域已经满了,它只能去占据非线性区域的槽
那么,它将占用多少接收缓冲区的内存呢?
第 32 个报文的信息和此时 sk 的状态为
skb->truesize = 768 skb->datalen = 0 skb->len = 34 // 这里还有 32 字节的 TCP 首部
它将占用除了skb->truesize - sizeof(struct sk_buff) = 512
的接收缓冲区空间,换句话说,存入非线性区域能节省一个sk_buff
结构的空间。
因此,第 32 个报文到达后,
tail_skb->truesize = 1280 tail_skb->datalen = 2 tail_skb->len = 64 sk->sk_rmem_alloc = 1280 sk->sk_forward_alloc = 2816
以此类推,第 33 个报文到达时,会变成
tail_skb->truesize = 1792 tail_skb->datalen = 4 tail_skb->len = 66 sk->sk_rmem_alloc = 1792 sk->sk_forward_alloc = 2304
到第 37 个报文,
tail_skb->truesize = 3840 tail_skb->datalen = 12 tail_skb->len = 74 sk->sk_rmem_alloc = 3840 sk->sk_forward_alloc = 256
也就是说,sk->sk_rmem_alloc
增长多少,sk->sk_forward_alloc
就相应减小多少。
当sk->sk_forward_alloc = 256
已经不够一个skb->truesize = 768
时,内核会调用下面的 tcp_try_rmem_schedule()
此时,sk->sk_rmem_alloc
并无超过sk->sk_rcvbuf
,因此下面的第一个条件并不知足,此时便会调用sk_rmem_schedule()
对sk->sk_forward_alloc
进行充值
static int tcp_try_rmem_schedule(struct sock *sk, struct sk_buff *skb, unsigned int size) { if (atomic_read(&sk->sk_rmem_alloc) > sk->sk_rcvbuf || !sk_rmem_schedule(sk, skb, size)) { if (tcp_prune_queue(sk) < 0) return -1; // code omitted } }
以后的报文依然会会消耗sk->sk_forward_alloc
,同时增长sk->sk_rmem_alloc
相同的数值,甚至超过sk->sk_rcvbuf
而当sk->sk_forward_alloc
再次耗尽,内核再次调用tcp_try_rmem_schedule()
时,此时sk->sk_rmem_alloc
已经超过了sk->sk_rcvbuf
,所以内核会调用tcp_prune_queue
对接收队列上的报文进行修剪。
此次的手段是将非线性区域的用户数据移动到线性区域。
但是线性区域不是满了吗?
内核采用的作法是将sk_buff
的 headroom
腾出来(不要预留区域、MAC头、IP头、TCP头了),而后进行非线性区域到线性区域的移动。
好比在咱们的例子中,接收队列的tail_skb
的基本信息为
prune 以前,
tail_skb->truesize = 7936 tail_skb->datalen = 28 tail_skb->len = 90 tail_skb->data - tail_skb->head = 130 tail_skb->end - tail_skb->tail = 0 sk->sk_rmem_alloc = 7936 sk->sk_forward_alloc = 256
而在 prune 以后,
tail_skb->truesize = 768 tail_skb->datalen = 0 tail_skb->len = 90 tail_skb->data - tail_skb->head = 0 tail_skb->end - tail_skb->tail = 130 sk->sk_rmem_alloc = 768 sk->sk_forward_alloc = 3328
通过这一番腾挪,tail_skb
没有了非线性区域,而且线性区域的尾部就又能够装 130 字节(65 个报文)。
而当又收到 65 个报文以后,tail_skb
的线性区域又耗尽了,此后的报文又会开始往非线性区域累加。
直到sk->sk_rmem_alloc
又接近sk->sk_rcvbuf
tail_skb->truesize = 7936 tail_skb->datalen = 28 tail_skb->len = 220 tail_skb->data - tail_skb->head = 0 tail_skb->end - tail_skb->tail = 0 sk->sk_rmem_alloc = 7936 sk->sk_forward_alloc = 256
这个时候内核会对tail_skb
的线性空间进行扩容,扩容以后线性空间能容纳 684 字节的用户数据了(其中 220 字节已经被使用)
tail_skb->truesize = 1280 tail_skb->datalen = 0 tail_skb->len = 220 tail_skb->data - tail_skb->head = 0 tail_skb->end - tail_skb->tail = 484 sk->sk_rmem_alloc = 1280 sk->sk_forward_alloc = 2816
再这以后,又是先填充线性区域,再填充非线性区域,再填充和扩容.....
RFC 中规定 TCP 使用滑动窗口来接收报文,而且在首部中向对端通告可用的窗口大小
那么应该通告多大的窗口呢? 不一样内核有不一样的实现
就初始通告窗口来讲。Linux 的初始通告窗口设置为接收接收缓冲区的一半,且向下调整为 MSS 的整数倍。在咱们的实验环境中,MSS 为 1448 Bytes,而接收缓冲区一半大小为 4096/2 = 2048 Bytes,所以,Server 的初始通告窗口也就是 1448 Bytes。
<p align="center"><img src="/assets/img/sk_rcvbuf/window-init.PNG"></p>
一样,后续的 TCP 报文也须要向对方通告接收窗口大小,但 RFC 1122 要求 TCP 还须要采起必定措施避免出现糊涂窗口综合征(SWS)
A TCP MUST include a SWS avoidance algorithm in the receiver.
RFC 1122 将接收缓冲区分为了如下三部分:其中,RCV.USER
为收到而且已经ACK,但尚未被用户读取走的部分。RCV.WND
为通告的窗口大小,静默状态下(没有报文传输时),RCV.USER=0,RCV.WND=RCV.BUFF.
|<---------- RCV.BUFF ---------------->| 1 2 3 |<-RCV.USER->|<--- RCV.WND ---->| ----|------------|------------------|------|---- RCV.NXT 1 - RCV.USER = data received but not yet consumed; 2 - RCV.WND = space advertised to sender; 3 - Reduction = space available but not yet advertised.
The solution to receiver SWS is to avoid advancing the right window edge RCV.NXT+RCV.WND in small increments, even if data is received from the network in small segments.
对于接收端来讲,它应该避免小步幅(small increments)地推动窗口的右边沿(RCV.NXT+RCV.WND),怎么作呢? RFC 的推荐算法是
The suggested SWS avoidance algorithm for the receiver is to keep RCV.NXT+RCV.WND fixed until the reduction satisfies:
RCV.BUFF - RCV.USER - RCV.WND >= min( Fr * RCV.BUFF, Eff.snd.MSS )
Fr
的推荐值为 1/2。翻译过来就是,当知足可用的缓冲区超过了 min(缓冲区的一半,有效MSS) 时,将RCV.WND
设置为RCV.BUFF-RCV.USER
,不然接收端就不推动右边沿。
回到咱们环境中, 咱们能够发现 Linux 并无采用 RFC 的建议,除了初始接收窗口的选择以外,因为咱们 Server 没有调用recv()
,按照 RFC 的算法,当收到报文时,RCV.NXT 推动,窗口又边沿不变,必然使得 RCV.WND 变小。
但从抓包结果来看,Server 通告的窗口大小变化趋势是先在必定时间内保持不变,后来才逐渐减少,再不变一段时间,再减少....。
为何会这样呢?
在内核__tcp_select_window()
的注释中是这么写的
/* * Unfortunately, the recommended algorithm breaks header prediction, * since header prediction assumes th->window stays fixed. * * Strictly speaking, keeping th->window fixed violates the receiver * side SWS prevention criteria. The problem is that under this rule * a stream of single byte packets will cause the right side of the * window to always advance by a single byte. */
意思是 Linux 内核本身的首部预测算法与 RFC 中建议的接收端糊涂窗口综合征避免算法自己就是有冲突的。而 Linux 选择了使用首部预测算法。
什么是首部预测?
在 Linux 内核中,有两条路径处理收到的报文:快速路径(fast path)和慢速路径(slow path)。正如其名,快速路径处理预期中的报文,慢速路径则能够处理预期外的报文,好比乱序报文、紧急数据等。
Linux 老是倾向于走快速路径,这须要报文知足预测条件,内核将预测报文存放在 tp->pred_flags, 该标志预测收到报文的 TCP 首部中的 [11:14] 偏移的内容。
0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Source Port | Destination Port | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Sequence Number | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Acknowledgment Number | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Data | |U|A|P|R|S|F| | | Offset| Reserved |R|C|S|S|Y|I| Window | <---- tp->pred_flags | | |G|K|H|T|N|N| | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Checksum | Urgent Pointer | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Options | Padding | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | data | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
能够看到,通告窗口就在[11:14]范围内,因此走快速的条件之一是收到报文的通告窗口不能变化。
还有一个走快速路径的条件是,收到报文的大小不能超过sk->sk_forward_alloc
的剩余值,若果超过,则说明sk
上预先分配的内存空间不够了,须要走慢速路径从新分配内存.
void tcp_rcv_established(struct sock *sk, struct sk_buff *skb, const struct tcphdr *th, unsigned int len) { // code omitted... if ((int)skb->truesize > sk->sk_forward_alloc) goto step5; }
TCP 报文中通告窗口的值由 tcp_select_window()
决定
static u16 tcp_select_window(struct sock *sk) { struct tcp_sock *tp = tcp_sk(sk); u32 old_win = tp->rcv_wnd; u32 cur_win = tcp_receive_window(tp); u32 new_win = __tcp_select_window(sk); // 计算新的窗口 /* Never shrink the offered window */ if (new_win < cur_win) { if (new_win == 0) NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPWANTZEROWINDOWADV); new_win = ALIGN(cur_win, 1 << tp->rx_opt.rcv_wscale); } // code omitted }
上面的逻辑关键在于使用 __tcp_select_window(sk)
计算新的窗口大小。注意那个条件判断,它的依据是
RFC 规定了 offered 过窗口不能 shrink,这并非说通告窗口的值只能变大不能变小,而是说滑动窗口右边沿不能左移。比方说在某个时刻 Server 的通告窗口为 1448,当它收到 X 字节用户数据后,不容许将窗口减少为比 1448 - X 还小的值。
来看看新的窗口是怎么作的
u32 __tcp_select_window(struct sock *sk) { struct inet_connection_sock *icsk = inet_csk(sk); struct tcp_sock *tp = tcp_sk(sk); /* MSS for the peer's data. Previous versions used mss_clamp * here. I don't know if the value based on our guesses * of peer's MSS is better for the performance. It's more correct * but may be worse for the performance because of rcv_mss * fluctuations. --SAW 1998/11/1 */ int mss = icsk->icsk_ack.rcv_mss; int free_space = tcp_space(sk); int allowed_space = tcp_full_space(sk); int full_space = min_t(int, tp->window_clamp, allowed_space);
这里的 mss 取自icsk->icsk_ack.rcv_mss
,这个值其实是估计值,用来估算对方的MSS
,依据就是若是对方发送的最大的 TCP 报文中的载荷长度(在tcp_measure_rcv_mss()
中更新),在咱们的环境中,
因为 Clinet 一直都是发送的 2 字节的小包,因此这个值在咱们的环境中一直都是初始值 TCP_MSS_DEFAULT(536)
当sysctl_tcp_adv_win_scale = 1
时(默认值),allowed_space
表示整个接收缓冲区的空间的一半,free_space
则表示接收缓冲区中空闲空间的一半,
if (free_space < (allowed_space >> 4) || free_space < mss) return 0;
当空闲空间不到整个空间的 1/16 或者小于估算MSS
时,本端计算出来的新窗口为 0,但这只是计算出的值,TCP头中通告窗口字段因为 RFC 规定窗口不能收缩,所以并不必定真正会通告零窗口(Zero-Window)
接下来计算新窗口为估算MSS
的整数倍或者保持不变
/* Get the largest window that is a nice multiple of mss. * Window clamp already applied above. * If our current window offering is within 1 mss of the * free space we just keep it. This prevents the divide * and multiply from happening most of the time. * We also don't do any window rounding when the free space * is too small. */ if (window <= free_space - mss || window > free_space) window = (free_space / mss) * mss; else if (mss == full_space && free_space > window + (full_space >> 1)) window = free_space;
前面铺垫了这么多,如今咱们来将它们串起来,看看本文最开始的问题是如何产生的。
经过 SO_RCVBUF 选项,设置接收缓冲区大小为 2048 字节。内核将这个值加倍,因而sk->sk_rcvbuf = 4096
,初始通告窗口为 1448 字节。同时接收缓冲区内存消耗为sk->sk_rmem_alloc = 0
第一个 skb (len = 2, truesieze = 768)到达时,此时接收队列上没有其余 skb,因而直接挂在了队列上,整个 skb 的报文内存占用计入sk->rmem_alloc = 768
.
后续若干个 skb 到达时,填入第一个 skb 的线性区域,sk->sk_rmem_alloc
始终保持为 768 字节,sk->sk_forward_alloc
始终保持为 3328 字节 。
对窗口来讲,sk->sk_rmem_alloc
没有变化,每次收到 2 字节数据,会让计算出的cur_win = old_win - 2
old_win = 1448 cur_win = 1446 new_win = 1448
这个阶段,为了让收包一直走快速路径,Server 一直都向对端通告 1448 字节的窗口。
在开始往非线性区域填充以后,每一个 skb 都将让sk->sk_rmem_alloc
增长 512 字节,sk->sk_forward_alloc
减少 512 字节。这样 6 个报文以后, sk->sk_forward_alloc
就消耗地差很少了,容不下下一个报文了。怎么办?再预分配啊,因而sk->sk_forward_alloc
变成了 4096 字节。以后又能够愉快地再增长sk->sk_rmem_alloc
了。
也许你会疑问,这个过程当中不会 check sk->sk_rcvbuf = 4096
限制吗?实际上,内核只会在慢速路径为sk->sk_forward_alloc
分配内存时才会去 check 是否超过了缓冲区大小,走快速路径时压根无论这个限制。
在咱们的例子中,内核第一次走慢速路径为sk->sk_forward_alloc
预分配内存时,sk->sk_rmem_alloc
只是接近但并无到 4096 字节,因此sk->sk_forward_alloc
能顺利地加上 4K 字节。这让sk->sk_rmem_alloc
以后能够超过sk->sk_rcvbuf
,内核TCP的缓冲区限制是一条软限制,它容许超过,但超事后就不能再为sk->sk_forward_alloc
预分配内存了。
再看窗口,第 1 个填入非线性区域的报文到达后,free_space
减少了,致使new_win
的值也减少
old_win = 1448 cur_win = 1446 new_win = 1072 // 536 的 两倍
虽然new_win
只有 1072 字节,但因为窗口不能收缩的原则,Server 端也只能老老实实地通告 cur_win = 1446
,比以前的少了两个字节。
当第二个报文到达以后,一样的道理,通告窗口又减少 2 个字节,变成了 1444 字节。
接下来 144二、1440、143八、1436 .....
在 prune 以前,Server 通告窗口大小为 1420 字节 (并非按照内存占用算出这么多,而是 TCP 只能减小到这么多)
如前面所说,第一次 prune 以后,tail_skb
会腾出线性空间,接下来的报文又能够往线性空间里塞了,这段时间内,通告窗口将保持不变,依旧为 1420 字节。
同 Step 2 相似,为了让收包一直走快速路径,这个阶段 Server 一直都向对端通告 1420 字节的窗口。
同 Step 3 相似,这个阶段受制于窗口不能收缩的原则,Server 通告的窗口缓慢地从 1420 字节开始减少。141八、141六、1414......直到下一次 prune 发生。
接下来,重复 Step 5 至 Step 6。Server 通告的发送窗口也就依次保持不变、减少、不变、减少.....
Step 7 最后阶段的 TCP prune
最后阶段的 TCP prune 前夕,窗口大小为 874 字节。
tail_skb
和 sk
的状态是这样的:
tail_skb->truesize = 7936 tail_skb->datalen = 22 tail_skb->len = 1750 tail_skb->data - tail_skb->head = 0 tail_skb->end - tail_skb->tail = 0 sk->sk_rmem_alloc = 7936 sk->sk_forward_alloc = 256
此时收到一个新的报文,因为 sk->sk_forward_alloc
已经不够了,因此会走慢速路径去 prune 接收队列上的 tail_skb
prune 结果是这样的:
tail_skb->truesize = 4352 tail_skb->datalen = 0 tail_skb->len = 1750 tail_skb->data - tail_skb->head = 0 tail_skb->end - tail_skb->tail = 202 sk->sk_rmem_alloc = 4352 sk->sk_forward_alloc = 3840
tail_skb->truesize
和sk->sk_rmem_alloc
缩小到了 4352 字节,线性区域尾部腾出了 202 字节的空间,非线性区没有了。
可是它占用的内存超过了sk->sk_rcvbuf
, 所以 tcp_prune_queue()
和 tcp_try_rmem_schedule()
相继返回了 -1 !丢弃此报文!
static int tcp_try_rmem_schedule(struct sock *sk, struct sk_buff *skb, unsigned int size) { if (atomic_read(&sk->sk_rmem_alloc) > sk->sk_rcvbuf || !sk_rmem_schedule(sk, skb, size)) { if (tcp_prune_queue(sk) < 0) return -1; if (!sk_rmem_schedule(sk, skb, size)) { if (!tcp_prune_ofo_queue(sk)) return -1; if (!sk_rmem_schedule(sk, skb, size)) return -1; } } return 0; }
也就是说,此刻 Server 端的sk
已经不去接收新的报文了!但它以前通告的窗口明明还有 874 字节, 因此 Clinet 才会不停地重传....
那么这个时候sk
是真的装不下新的报文了吗?显然不是,它本身的线性空间已经腾出 202 字节了,何况还有 3840 字节的非线性空间可使用。
因此咱们能够将tcp_try_rmem_schedule
修改成即便tcp_prune_queue
失败(没有能把sk->sk_rmem_alloc
压缩到sk->sk_rcvbuf
以内),若是此时接收队列尾的sk_buff
能够容纳这个报文的话,仍是让其返回成功。
static int tcp_try_rmem_schedule(struct sock *sk, struct sk_buff *skb, unsigned int size) { if (atomic_read(&sk->sk_rmem_alloc) > sk->sk_rcvbuf || !sk_rmem_schedule(sk, skb, size)) { - if (tcp_prune_queue(sk) < 0) + if (tcp_prune_queue(sk) < 0 && + size > skb_tailroom(skb_peek_tail(sk)) && + size > sk->sk_forward_alloc) + return -1; if (!sk_rmem_schedule(sk, skb, size)) { if (!tcp_prune_ofo_queue(sk)) return -1; if (!sk_rmem_schedule(sk, skb, size)) return -1; } } return 0; }
个人我的主页