TCP的定时器实现(1)——重传定时器

2019-04-10 14:58:47 浏览数 (1)

微信公众号: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}

0 人点赞