个人理解,欢迎指正
*参考linux内核源码4.1
基本原理
接收缓存大小的动态调整
接收端要想不成为瓶颈,需要提供的窗口大小win_size>=RTT*speed。linux内核通过上一个RTT的接收情况来估算当前RTT需要的窗口大小(window_clamp)。再通过窗口大小计算出所需的接收缓存大小。在连接将数据拷贝给用户时触发接收存储动态调整。具体详见tcp_rcv_space_adjust。
接收窗口大小的动态调整
一般初始化一个较小的窗口,然后逐渐增加,避免多连接的情况下大家一上来都通告大窗口导致链路拥塞。接收窗口的调整主要受限rcv_ssthresh。在连接启动阶段,rcv_ssthresh会随着连接收包不断的扩大,当rcv_ssthresh增大到一定程度后受限window_clamp,不再扩大。另外当系统内存吃紧的情况下连接会主动缩小rcv_ssthresh,避免连接占用过多内存。
相关变量解释
因为系统的内存有限,为了限制单连接占用过多的内存,连接的发送和接收buf都会有上限。接收方向由sk->sk_rcvbuf决定。连接发包时都会重新计算通告窗口,窗口的选择主要与如下几个变量相关:
sk->sk_rcvbuf:分配给连接的接收使用的buf大小(size of receive buffer in bytes)。通常将数据拷贝给用户后会根据历史接收情况重新计算sk_rcvbuf(具体参见tcp_rcv_space_adjust)。
sk->sk_rmem_alloc:接收已经使用的buf大小。(接收到报文会相应增加,将数据交给用户后会相应减少)
tp->window_clamp:连接窗口的上限(Maximal window to advertise)。通常是通过历史接收情况估算出当前需要通告的最优窗口(具体参见tcp_rcv_space_adjust)。rcv_ssthresh动态调整最终会趋向于这个值。
tp->rcv_ssthresh:当前允许通告的最大窗口(Current window clamp)。窗口的选择大多时候都由rcv_ssthresh决定,而rcv_ssthresh是会动态调整的。而rcv_ssthresh调整的上限就是tp->window_clamp(具体参见tcp_grow_window)。
源码分析
接收缓存大小的动态调整
接收缓存大小的动态调整主要涉及的函数有
1)tcp_rcv_space_adjust():根据上一个RTT的接收情况来估算当前RTT需要的窗口和相应的接收缓存大小。连接将数据拷贝给用户后调用
2)tcp_rcv_rtt_measure():连接不开启时间戳时的RTT估算
3)tcp_rcv_rtt_measure_ts():连接开启时间戳时的RTT估算
具体函数实现:
/*
* This function should be called every time data is copied to user space.
* It calculates the appropriate TCP receive buffer space.
*/
void tcp_rcv_space_adjust(struct sock *sk)
{
struct tcp_sock *tp = tcp_sk(sk);
int time;
int copied;
time = tcp_time_stamp - tp->rcvq_space.time;
if (time < (tp->rcv_rtt_est.rtt >> 3) || tp->rcv_rtt_est.rtt == 0)
return;
/* Number of bytes copied to user in last RTT */
copied = tp->copied_seq - tp->rcvq_space.seq;
if (copied <= tp->rcvq_space.space)
goto new_measure;
/* A bit of theory :
* copied = bytes received in previous RTT, our base window
* To cope with packet losses, we need a 2x factor
* To cope with slow start, and sender growing its cwin by 100 %
* every RTT, we need a 4x factor, because the ACK we are sending
* now is for the next RTT, not the current one :
* <prev RTT . ><current RTT .. ><next RTT .... >
*/
if (sysctl_tcp_moderate_rcvbuf &&
!(sk->sk_userlocks & SOCK_RCVBUF_LOCK)) {
int rcvwin, rcvmem, rcvbuf;
/* minimal window to cope with packet losses, assuming
* steady state. Add some cushion because of small variations.
*/
rcvwin = (copied << 1) + 16 * tp->advmss;
/* If rate increased by 25%,
* assume slow start, rcvwin = 3 * copied
* If rate increased by 50%,
* assume sender can use 2x growth, rcvwin = 4 * copied
*/
if (copied >=
tp->