(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;
...
}




相关内容