微信公众号:LinuxerPub 作者:gfree.wind@gmail.com
TCP的定时器(1)
TCP协议是一个相当复杂的协议,其实现依赖于多个定时器的实现。在TCP套接字的初始化函数tcp_v4_init_sock中,会调用tcp_init_xmit_timers初始化TCP的各个定时器。
代码语言:javascript复制1void tcp_init_xmit_timers(struct sock *sk)
2{
3 /* 注册TCP各个定时器的执行函数。 */
4 inet_csk_init_xmit_timers(sk, &tcp_write_timer, &tcp_delack_timer,
5 &tcp_keepalive_timer);
6}
接下来,进入inet_csk_init_xmit_timers。
代码语言:javascript复制 1void inet_csk_init_xmit_timers(struct sock *sk,
2 void (*retransmit_handler)(unsigned long),
3 void (*delack_handler)(unsigned long),
4 void (*keepalive_handler)(unsigned long))
5{
6 struct inet_connection_sock *icsk = inet_csk(sk);
7 setup_timer(&icsk->icsk_retransmit_timer, retransmit_handler,
8 (unsigned long)sk);
9 setup_timer(&icsk->icsk_delack_timer, delack_handler,
10 (unsigned long)sk);
11 setup_timer(&sk->sk_timer, keepalive_handler, (unsigned long)sk);
12 icsk->icsk_pending = icsk->icsk_ack.pending = 0;
13}
inet_csk_init_xmit_timers代码很简单,就是初始化各个timer,分别是“重传和零窗口探测定时器”、“延迟确认定时器”和“Keep-Alive定时器”。 下面我们将针对每个定时器进行分析。
重传定时器
TCP协议是通过“确认 重传”来保证数据的可靠传输。当对端确认超时后,本端则要进行重传,下面我们来分析重传定时器的执行函数。
代码语言:javascript复制 1static void tcp_write_timer(unsigned long data)
2{
3 struct sock *sk = (struct sock *)data;
4 struct inet_connection_sock *icsk = inet_csk(sk);
5 int event;
6 bh_lock_sock(sk);
7 if (sock_owned_by_user(sk)) {
8 /* 用户进程正在使用这个套接字,那就稍后再试 */
9 sk_reset_timer(sk, &icsk->icsk_retransmit_timer, jiffies (HZ / 20));
10 goto out_unlock;
11 }
12 /*
13 套接字状态已经是关闭,或者当前无需执行操作时,直接退出。
14 icsk_pending表示重传定时器要做的事情:目前是重传和0窗口探测。如果为0,则表示没有要做的事情。
15 */
16 if (sk->sk_state == TCP_CLOSE || !icsk->icsk_pending)
17 goto out;
18 /* 未到超时时间,则重新设置重传定时器 */
19 if (time_after(icsk->icsk_timeout, jiffies)) {
20 sk_reset_timer(sk, &icsk->icsk_retransmit_timer, icsk->icsk_timeout);
21 goto out;
22 }
23 /* 获得当前要执行的事件并清零 */
24 event = icsk->icsk_pending;
25 icsk->icsk_pending = 0;
26 switch (event) {
27 case ICSK_TIME_RETRANS:
28 tcp_retransmit_timer(sk);
29 break;
30 case ICSK_TIME_PROBE0:
31 tcp_probe_timer(sk);
32 break;
33 }
34out:
35 sk_mem_reclaim(sk);
36out_unlock:
37 bh_unlock_sock(sk);
38 sock_put(sk);
39}
对于重传来说,进入tcp_retransmit_timer。
代码语言:javascript复制 1void tcp_retransmit_timer(struct sock *sk)
2{
3 struct tcp_sock *tp = tcp_sk(sk);
4 struct inet_connection_sock *icsk = inet_csk(sk);
5 if (!tp->packets_out)
6 goto out;
7 WARN_ON(tcp_write_queue_empty(sk));
8 if (!tp->snd_wnd && !sock_flag(sk, SOCK_DEAD) &&
9 !((1 << sk->sk_state) & (TCPF_SYN_SENT | TCPF_SYN_RECV))) {
10 /* 这是一种意外情况,重传时发现发送窗口为0。*/
11 struct inet_sock *inet = inet_sk(sk);
12 if (sk->sk_family == AF_INET) {
13 LIMIT_NETDEBUG(KERN_DEBUG "TCP: Peer %pI4:%u/%u unexpectedly shrunk window %u:%u (repaired)n",
14 &inet->inet_daddr, ntohs(inet->inet_dport),
15 inet->inet_num, tp->snd_una, tp->snd_nxt);
16 }
17#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
18 else if (sk->sk_family == AF_INET6) {
19 struct ipv6_pinfo *np = inet6_sk(sk);
20 LIMIT_NETDEBUG(KERN_DEBUG "TCP: Peer %pI6:%u/%u unexpectedly shrunk window %u:%u (repaired)n",
21 &np->daddr, ntohs(inet->inet_dport),
22 inet->inet_num, tp->snd_una, tp->snd_nxt);
23 }
24#endif
25 /* 如果已经有TCP_RTO_MAX时间,没有收到对端消息,则设置TCP套接字为错误状态。 */
26 if (tcp_time_stamp - tp->rcv_tstamp > TCP_RTO_MAX) {
27 tcp_write_err(sk);
28 goto out;
29 }
30 /* TCP进入loss状态 */
31 tcp_enter_loss(sk, 0);
32 /* 重传发送队列中的第一个报文,即未确认收到的第一个报文 */
33 tcp_retransmit_skb(sk, tcp_write_queue_head(sk));
34 __sk_dst_reset(sk);
35 goto out_reset_timer;
36 }
37 /* 发送超时的处理 */
38 if (tcp_write_timeout(sk))
39 goto out;
40 if (icsk->icsk_retransmits == 0) {
41 int mib_idx;
42 /* 流控状态的SNMP信息更新 */
43 if (icsk->icsk_ca_state == TCP_CA_Recovery) {
44 if (tcp_is_sack(tp))
45 mib_idx = LINUX_MIB_TCPSACKRECOVERYFAIL;
46 else
47 mib_idx = LINUX_MIB_TCPRENORECOVERYFAIL;
48 } else if (icsk->icsk_ca_state == TCP_CA_Loss) {
49 mib_idx = LINUX_MIB_TCPLOSSFAILURES;
50 } else if ((icsk->icsk_ca_state == TCP_CA_Disorder) ||
51 tp->sacked_out) {
52 if (tcp_is_sack(tp))
53 mib_idx = LINUX_MIB_TCPSACKFAILURES;
54 else
55 mib_idx = LINUX_MIB_TCPRENOFAILURES;
56 } else {
57 mib_idx = LINUX_MIB_TCPTIMEOUTS;
58 }
59 NET_INC_STATS_BH(sock_net(sk), mib_idx);
60 }
61 /* 根据是否使用frto,即虚假超时判断算法,进入不同的状态 */
62 if (tcp_use_frto(sk)) {
63 tcp_enter_frto(sk);
64 } else {
65 tcp_enter_loss(sk, 0);
66 }
67 /* 重传发送队列的第一个报文 */
68 if (tcp_retransmit_skb(sk, tcp_write_queue_head(sk)) > 0) {
69 /* 因为本地拥塞而重传失败,则无需backoff定时器 */
70 if (!icsk->icsk_retransmits)
71 icsk->icsk_retransmits = 1;
72 inet_csk_reset_xmit_timer(sk, ICSK_TIME_RETRANS,
73 min(icsk->icsk_rto, TCP_RESOURCE_PROBE_INTERVAL),
74 TCP_RTO_MAX);
75 goto out;
76 }
77 icsk->icsk_backoff ;
78 icsk->icsk_retransmits ;
79 out_reset_timer:
80 /* 满足下面的所有条件时,则使用线性超时时间,即每次超时的时间是一样的。
81 1. 连接是已连接状态
82 2. 套接字配置了线性超时选项或者系统配置了线性超时
83 3. 当前tcp是一个“瘦的”,即低速的TCP连接
84 4. 超时个数小于指定值
85 */
86 if (sk->sk_state == TCP_ESTABLISHED &&
87 (tp->thin_lto || sysctl_tcp_thin_linear_timeouts) &&
88 tcp_stream_is_thin(tp) &&
89 icsk->icsk_retransmits <= TCP_THIN_LINEAR_RETRIES) {
90 icsk->icsk_backoff = 0;
91 icsk->icsk_rto = min(__tcp_set_rto(tp), TCP_RTO_MAX);
92 } else {
93 /* 正常指数后退超时 */
94 icsk->icsk_rto = min(icsk->icsk_rto << 1, TCP_RTO_MAX);
95 }
96 /* 重设超时定时器 */
97 inet_csk_reset_xmit_timer(sk, ICSK_TIME_RETRANS, icsk->icsk_rto, TCP_RTO_MAX);
98 /* 连重传都超时了,则重置套接字的下一跳。这样后面可以重新选择路由,避免因为路由问题导致报文无法到达对端 */
99 if (retransmits_timed_out(sk, sysctl_tcp_retries1 1, 0, 0))
100 __sk_dst_reset(sk);
101out:;
102}