TCP定时器 之 坚持定时器
坚持定时器在接收方通告接收窗口为0,阻止发送端继续发送数据时设定。
由于连接接收端的发送窗口通告不可靠(只有数据才会确认,ACK不会确认),如果一个确认丢失了,双方就有可能因为等待对方而使连接终止:接收放等待接收数据(因为它已经向发送方通过了一个非0窗口),而发送方在等待允许它继续发送数据的窗口更新。
为了防止上面的情况,发送方在接收到0窗口通告后,启动一个坚持定时器来周期的发送1字节的数据,以便发现接收方窗口是否已经增大。这些从发送方发出的报文段称为窗口探测;
下面来分析坚持定时器的实现代码:
启动定时器:
通过inet_csk_reset_xmit_timer来启动坚持定时器,其类型设置为ICSK_TIME_PROBE0,其最终通过icsk_retransmit_timer重传定时器来实现,只是通过类型来区分当前使用的是哪种定时器,以及超时时需要分给哪个对应类型的回调;
1 inet_csk_reset_xmit_timer(sk, ICSK_TIME_PROBE0, when, TCP_RTO_MAX);
1 /* 2 * Reset the retransmission timer 3 */ 4 static inline void inet_csk_reset_xmit_timer(struct sock *sk, const int what, 5 unsigned long when, 6 const unsigned long max_when) 7 { 8 struct inet_connection_sock *icsk = inet_csk(sk); 9 10 if (when > max_when) { 11 when = max_when; 12 } 13 14 if (what == ICSK_TIME_RETRANS || what == ICSK_TIME_PROBE0 || 15 what == ICSK_TIME_EARLY_RETRANS || what == ICSK_TIME_LOSS_PROBE || 16 what == ICSK_TIME_REO_TIMEOUT) { 17 icsk->icsk_pending = what; 18 icsk->icsk_timeout = jiffies + when; 19 sk_reset_timer(sk, &icsk->icsk_retransmit_timer, icsk->icsk_timeout); 20 } else if (what == ICSK_TIME_DACK) { 21 icsk->icsk_ack.pending |= ICSK_ACK_TIMER; 22 icsk->icsk_ack.timeout = jiffies + when; 23 sk_reset_timer(sk, &icsk->icsk_delack_timer, icsk->icsk_ack.timeout); 24 } 25 26 }
定时器回调函数:
因为是共用重传定时器,所以超时会进入到超时定时器的处理流程,在进入tcp_write_timer_handler后才会根据定时器的类型来区分出该定时器是坚持定时器,进而调用tcp_probe_timer来进行对应的处理;
1 static void tcp_write_timer(unsigned long data) 2 { 3 struct sock *sk = (struct sock *)data; 4 5 bh_lock_sock(sk); 6 /* 没被用户系统调用锁定 */ 7 if (!sock_owned_by_user(sk)) { 8 /* 调用超时处理函数 */ 9 tcp_write_timer_handler(sk); 10 } else { 11 /* delegate our work to tcp_release_cb() */ 12 /* 交给tcp_release_cb处理 */ 13 if (!test_and_set_bit(TCP_WRITE_TIMER_DEFERRED, &sk->sk_tsq_flags)) 14 sock_hold(sk); 15 } 16 bh_unlock_sock(sk); 17 sock_put(sk); 18 }
1 /* Called with bottom-half processing disabled. 2 Called by tcp_write_timer() */ 3 void tcp_write_timer_handler(struct sock *sk) 4 { 5 struct inet_connection_sock *icsk = inet_csk(sk); 6 int event; 7 8 /* 连接处于CLOSE或者LISTEN状态或者 没有指定待处理事件类型 */ 9 if (((1 << sk->sk_state) & (TCPF_CLOSE | TCPF_LISTEN)) || 10 !icsk->icsk_pending) 11 goto out; 12 13 /* 超时时间未到,则重新设置定时器超时时间 */ 14 if (time_after(icsk->icsk_timeout, jiffies)) { 15 sk_reset_timer(sk, &icsk->icsk_retransmit_timer, icsk->icsk_timeout); 16 goto out; 17 } 18 19 /* 获取事件类型 */ 20 event = icsk->icsk_pending; 21 22 switch (event) { 23 case ICSK_TIME_REO_TIMEOUT: 24 tcp_rack_reo_timeout(sk); 25 break; 26 case ICSK_TIME_LOSS_PROBE: 27 tcp_send_loss_probe(sk); 28 break; 29 /* 重传定时器重传 */ 30 case ICSK_TIME_RETRANS: 31 icsk->icsk_pending = 0; 32 tcp_retransmit_timer(sk); 33 break; 34 /* 坚持定时器探测0窗口 */ 35 case ICSK_TIME_PROBE0: 36 icsk->icsk_pending = 0; 37 tcp_probe_timer(sk); 38 break; 39 } 40 41 out: 42 sk_mem_reclaim(sk); 43 }
tcp_probe_timer函数主要对是否发送探测进行各种情况的检查,包括无需额外探测,时间戳检查,重试次数检查,孤儿套接字资源检查等,检查失败则关闭连接,成功则发送探测数据段;
1 static void tcp_probe_timer(struct sock *sk) 2 { 3 struct inet_connection_sock *icsk = inet_csk(sk); 4 struct tcp_sock *tp = tcp_sk(sk); 5 int max_probes; 6 u32 start_ts; 7 8 /* 9 (1)如果存在发送出去未被确认的段, 10 要么被确认返回窗口,要么重传,无需额外构造探测包 11 (2)或者发送队列没有待发送的段,无数据需要发, 12 不关心窗口情况 13 则无需另外组织探测数据 14 */ 15 if (tp->packets_out || !tcp_send_head(sk)) { 16 /* 探测次数清零 */ 17 icsk->icsk_probes_out = 0; 18 return; 19 } 20 21 /* RFC 1122 4.2.2.17 requires the sender to stay open indefinitely as 22 * long as the receiver continues to respond probes. We support this by 23 * default and reset icsk_probes_out with incoming ACKs. But if the 24 * socket is orphaned or the user specifies TCP_USER_TIMEOUT, we 25 * kill the socket when the retry count and the time exceeds the 26 * corresponding system limit. We also implement similar policy when 27 * we use RTO to probe window in tcp_retransmit_timer(). 28 */ 29 /* 待发送数据的时间戳 */ 30 start_ts = tcp_skb_timestamp(tcp_send_head(sk)); 31 /* 时间戳为0,设置一下 */ 32 if (!start_ts) 33 skb_mstamp_get(&tcp_send_head(sk)->skb_mstamp); 34 /* 有时间戳则判断是否超过了用户设置时间 */ 35 else if (icsk->icsk_user_timeout && 36 (s32)(tcp_time_stamp - start_ts) > icsk->icsk_user_timeout) 37 goto abort; 38 39 /* 最大探测次数设置为连接状态的重试次数 */ 40 max_probes = sock_net(sk)->ipv4.sysctl_tcp_retries2; 41 /* 套接口即将关闭 */ 42 if (sock_flag(sk, SOCK_DEAD)) { 43 /* 退避指数计算的超时时间< 最大时间(RTT),大于数据无法返回了 */ 44 const bool alive = inet_csk_rto_backoff(icsk, TCP_RTO_MAX) < TCP_RTO_MAX; 45 46 /* 获取在本端关闭tcp前重试次数上限 */ 47 max_probes = tcp_orphan_retries(sk, alive); 48 49 /* 超过了最大RTO时间或者退避指数达到了探测最大次数 */ 50 if (!alive && icsk->icsk_backoff >= max_probes) 51 goto abort; 52 53 /* 超过资源限制,关闭了连接,无需发送 */ 54 if (tcp_out_of_resources(sk, true)) 55 return; 56 } 57 58 /* 探测次数超过了最大探测次数,错误处理,关闭连接 */ 59 if (icsk->icsk_probes_out > max_probes) { 60 abort: tcp_write_err(sk); 61 } else { 62 /* Only send another probe if we didn't close things up. */ 63 /* 发送探测 */ 64 tcp_send_probe0(sk); 65 } 66 }
tcp_send_probe0调用tcp_write_wakeup发送探测段,并且根据发送结果来设定不同的退避指数,探测次数,下一次探测时间等,并重置定时器;
1 /* A window probe timeout has occurred. If window is not closed send 2 * a partial packet else a zero probe. 3 */ 4 void tcp_send_probe0(struct sock *sk) 5 { 6 struct inet_connection_sock *icsk = inet_csk(sk); 7 struct tcp_sock *tp = tcp_sk(sk); 8 struct net *net = sock_net(sk); 9 unsigned long probe_max; 10 int err; 11 12 /* 发送探测报文 */ 13 err = tcp_write_wakeup(sk, LINUX_MIB_TCPWINPROBE); 14 15 /* 16 (1)如果存在发送出去未被确认的段, 17 要么被确认返回窗口,要么重传,无需额外构造探测包 18 (2)或者发送队列没有待发送的段,无数据需要发, 19 不关心窗口情况 20 则无需另外组织探测数据 21 */ 22 if (tp->packets_out || !tcp_send_head(sk)) { 23 /* Cancel probe timer, if it is not required. */ 24 icsk->icsk_probes_out = 0; 25 icsk->icsk_backoff = 0; 26 return; 27 } 28 29 /* 发送成功或者非本地拥塞导致的失败 */ 30 if (err <= 0) { 31 /* 退避指数未达到重试上限,递增 */ 32 if (icsk->icsk_backoff < net->ipv4.sysctl_tcp_retries2) 33 icsk->icsk_backoff++; 34 /* 探测次数递增 */ 35 icsk->icsk_probes_out++; 36 /* 探测时间设置为rto_max */ 37 probe_max = TCP_RTO_MAX; 38 } else { 39 /* If packet was not sent due to local congestion, 40 * do not backoff and do not remember icsk_probes_out. 41 * Let local senders to fight for local resources. 42 * 43 * Use accumulated backoff yet. 44 */ 45 /* 本地拥塞发送失败 */ 46 47 /* 设置探测次数 */ 48 if (!icsk->icsk_probes_out) 49 icsk->icsk_probes_out = 1; 50 51 /* 设置探测时间为probe_interval */ 52 probe_max = TCP_RESOURCE_PROBE_INTERVAL; 53 } 54 55 /* 重置探测定时器 */ 56 inet_csk_reset_xmit_timer(sk, ICSK_TIME_PROBE0, 57 tcp_probe0_when(sk, probe_max), 58 TCP_RTO_MAX); 59 }