TCP的发送系列 — 发送缓存的管理(一)(1)(3)
(2) 建立连接以后
当接收到ACK后,会检查是否需要调整发送缓存的上限sk->sk_sndbuf。
tcp_rcv_established / tcp_rcv_state_process
tcp_data_snd_check
tcp_check_space
tcp_new_space
[java]
static inline void tcp_data_snd_check(struct sock *sk)
{
tcp_push_pending_frames(sk); /* 发送数据段 */
tcp_check_space(sk); /* 更新发送缓存 */
}
如果发送队列中有skb被释放了,且设置了同步发送时发送缓存不足的标志,
就检查是否要更新发送缓存的上限、是否要触发有发送缓存可写的事件。
[java] static void tcp_check_space(struct sock *sk) { /* 如果发送队列中有skb被释放了 */ if (sock_flag(sk, SOCK_QUEUE_SHRUNK)) { sock_reset_flag(sk, SOCK_QUEUE_SHRUNK); /* 如果设置了同步发送时,发送缓存不足的标志 */ if (sk->sk_socket && test_bit(SOCK_NOSPACE, &sk->sk_socket->flags)) tcp_new_space(sk); /* 更新发送缓存 */ } }
[java] /* When incoming ACK allowed to free some skb from write_queue, * we remember this event in flag SOCK_QUEUE_SHRUNK and wake up socket * on the exit from tcp input handler. */ static void tcp_new_space(struct sock *sk) { struct tcp_sock *tp = tcp_sk(sk); /* 检查能否扩大发送缓存的上限 */ if (tcp_should_expand_sndbuf(sk)) { tcp_sndbuf_expand(sk); /* 扩大发送缓存的上限 */ tp->snd_cwnd_stamp = tcp_time_stamp; } /* 检查是否需要触发有缓存可写事件 */ sk->sk_write_space(sk); }
在什么情况下允许扩大发送缓存的上限呢?
必须同时满足以下条件:
1. sock有发送缓存不足的标志(上层函数作判断)。
2. 用户没有使用SO_SNDBUF选项。
3. TCP层没有设置内存压力标志。
4. TCP层使用的内存小于tcp_mem[0]。
5. 目前的拥塞控制窗口没有被完全使用掉。
[java] static bool tcp_should_expand_sndbuf(const struct sock *sk) { const struct tcp_sock *tp = tcp_sk(sk); /* If the user specified a specific send buffer setting, do not modify it. * 如果用户使用了SO_SNDBUF选项,就不自动调整了。 */ if (sk->sk_userlocks & SOCK_SNDBUF_LOCK) return false; /* If we are under global TCP memory pressure, do not expand. * 如果TCP设置了内存压力标志,就不扩大发送缓存的上限了。 */ if (sk_under_memory_pressure(sk)) return false; /* If we are under soft global TCP memory pressure, do not expand. */ /* 如果目前TCP层使用的内存超过tcp_mem[0],就不扩大发送缓存的上限了 */ if (sk_memory_allocated(sk) >= sk_prot_mem_limits(sk, 0)) return false; /* If we filled the congestion window, do not expand. * 如果把拥塞控制窗口给用满了,说明拥塞窗口才是限制因素,就不扩大发送缓存的上限了。 */ if (tp->packets_out >= tp->snd_cwnd) return false; return true; }
发送缓存的申请
在tcp_sendmsg()中,如果发送队列的最后一个skb不能追加数据了,就要申请一个新的skb来装载数据。
[java] int tcp_sendmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg, size_t size) { ... if (copy <= 0) { /* 需要使用新的skb来装数据 */ new_segment: /* Allocate new segment. If the interface is SG, * allocate skb fitting to single page. */ /* 如果发送队列的总大小sk_wmem_queued大于等于发送缓存的上限sk_sndbuf, * 或者发送缓存中尚未发送的数据量超过了用户的设置值,就进入等待。 */ if (! sk_stream_memory_free(sk)) goto wait_for_sndbuf; /* 申请一个skb,其线性数据区的大小为: * 通过select_size()得到的线性数据区中TCP负荷的大小 + 最大的协议头长度。 * 如果申请skb失败了,或者虽然申请skb成功,但是从系统层面判断此次申请不合法, * 那么就进入睡眠,等待内存。 */ skb = sk_stream_alloc_skb(sk, select_size(sk, sg), sk->sk_allocation); if (! skb) goto wait_for_memory; ... }
评论暂时关闭