• TCP定时器 之 延迟确认定时器


    TCP在收到数据段但是无需马上确认时设定,如果在超时时间之内有数据要发送到对端,则确认会随着数据一起发送,即捎带ACK,如果达到超时时间则执行定时器回调立即发送ack;

    启动定时器:

    延迟确认定时器调用inet_csk_reset_xmit_timer(sk, ICSK_TIME_DACK, xx, xxx)函数进行启动,启动时间点包含以下三个;

    1. 建立连接时,客户端的第三次握手可能会被延迟确认,如果有数据要输出、设置了TCP_DEFER_ACCEPT选项或者不在快速确认模式下,则启动延迟确认定时器;

     1 static int tcp_rcv_synsent_state_process(struct sock *sk, struct sk_buff *skb,
     2                      const struct tcphdr *th)
     3 {
     4         if (sk->sk_write_pending ||
     5             icsk->icsk_accept_queue.rskq_defer_accept ||
     6             icsk->icsk_ack.pingpong) {
     7             /* Save one ACK. Data will be ready after
     8              * several ticks, if write_pending is set.
     9              *
    10              * It may be deleted, but with this feature tcpdumps
    11              * look so _wonderfully_ clever, that I was not able
    12              * to stand against the temptation 8)     --ANK
    13              */
    14             inet_csk_schedule_ack(sk);
    15             tcp_enter_quickack_mode(sk);
    16             inet_csk_reset_xmit_timer(sk, ICSK_TIME_DACK,
    17                           TCP_DELACK_MAX, TCP_RTO_MAX);
    18 
    19 discard:
    20             tcp_drop(sk, skb);
    21             return 0;
    22         } else {
    23             tcp_send_ack(sk);
    24         }
    25 }

    2. 在立即发送确认时,如果分配内存失败,则启动延迟确认定时器;

     1 /* This routine sends an ack and also updates the window. */
     2 /* TCPTODO */
     3 void tcp_send_ack(struct sock *sk)
     4 {
     5     struct sk_buff *buff;
     6 
     7     /* If we have been reset, we may not send again. */
     8     if (sk->sk_state == TCP_CLOSE)
     9         return;
    10 
    11     tcp_ca_event(sk, CA_EVENT_NON_DELAYED_ACK);
    12 
    13     /* We are not putting this on the write queue, so
    14      * tcp_transmit_skb() will set the ownership to this
    15      * sock.
    16      */
    17     buff = alloc_skb(MAX_TCP_HEADER,
    18              sk_gfp_mask(sk, GFP_ATOMIC | __GFP_NOWARN));
    19     if (unlikely(!buff)) {
    20         inet_csk_schedule_ack(sk);
    21         inet_csk(sk)->icsk_ack.ato = TCP_ATO_MIN;
    22         inet_csk_reset_xmit_timer(sk, ICSK_TIME_DACK,
    23                       TCP_DELACK_MAX, TCP_RTO_MAX);
    24         return;
    25     }
    26 
    27     /* Reserve space for headers and prepare control bits. */
    28     skb_reserve(buff, MAX_TCP_HEADER);
    29     tcp_init_nondata_skb(buff, tcp_acceptable_seq(sk), TCPHDR_ACK);
    30 
    31     /* We do not want pure acks influencing TCP Small Queues or fq/pacing
    32      * too much.
    33      * SKB_TRUESIZE(max(1 .. 66, MAX_TCP_HEADER)) is unfortunately ~784
    34      */
    35     skb_set_tcp_pure_ack(buff);
    36 
    37     /* Send it off, this clears delayed acks for us. */
    38     skb_mstamp_get(&buff->skb_mstamp);
    39     tcp_transmit_skb(sk, buff, 0, (__force gfp_t)0);
    40 }

    3. 未启用tcp_low_latency情况下,当有进程正在读取TCP流时,此时prequeue队列存在TCP段且消耗的内存未达到上限,且没有ACK需要发送时,启动延迟确认定时器;

     1 bool tcp_prequeue(struct sock *sk, struct sk_buff *skb)
     2 {
     3     if (skb_queue_len(&tp->ucopy.prequeue) == 1) {
     4         wake_up_interruptible_sync_poll(sk_sleep(sk),
     5                        POLLIN | POLLRDNORM | POLLRDBAND);
     6         if (!inet_csk_ack_scheduled(sk))
     7             inet_csk_reset_xmit_timer(sk, ICSK_TIME_DACK,
     8                           (3 * tcp_rto_min(sk)) / 4,
     9                           TCP_RTO_MAX);
    10     }
    11     return true;
    12 }

    定时器回调函数:

    tcp_delack_timer函数进行必要的状态检查,处理队列中的数据包,如果有ack要发送,则使用即使ack模式立即发送ack;

     1 /**
     2  *  tcp_delack_timer() - The TCP delayed ACK timeout handler
     3  *  @data:  Pointer to the current socket. (gets casted to struct sock *)
     4  *
     5  *  This function gets (indirectly) called when the kernel timer for a TCP packet
     6  *  of this socket expires. Calls tcp_delack_timer_handler() to do the actual work.
     7  *
     8  *  Returns: Nothing (void)
     9  */
    10 static void tcp_delack_timer(unsigned long data)
    11 {
    12     struct sock *sk = (struct sock *)data;
    13 
    14     bh_lock_sock(sk);
    15     /* 传输控制块未被锁定 */
    16     if (!sock_owned_by_user(sk)) {
    17         /* 调用超时处理函数 */
    18         tcp_delack_timer_handler(sk);
    19     } else {
    20         /* 打阻塞标记告知ack要立即发送*/
    21         inet_csk(sk)->icsk_ack.blocked = 1;
    22         __NET_INC_STATS(sock_net(sk), LINUX_MIB_DELAYEDACKLOCKED);
    23         /* deleguate our work to tcp_release_cb() */
    24         /* 交给tcp_release_cb处理超时回调 */
    25         if (!test_and_set_bit(TCP_DELACK_TIMER_DEFERRED, &sk->sk_tsq_flags))
    26             sock_hold(sk);
    27     }
    28     bh_unlock_sock(sk);
    29     sock_put(sk);
    30 }
     1 /* Called with BH disabled */
     2 void tcp_delack_timer_handler(struct sock *sk)
     3 {
     4     struct tcp_sock *tp = tcp_sk(sk);
     5     struct inet_connection_sock *icsk = inet_csk(sk);
     6 
     7     sk_mem_reclaim_partial(sk);
     8 
     9     /* 关闭或者监听状态 || 未启动延迟ack定时器*/
    10     if (((1 << sk->sk_state) & (TCPF_CLOSE | TCPF_LISTEN)) ||
    11         !(icsk->icsk_ack.pending & ICSK_ACK_TIMER))
    12         goto out;
    13 
    14     /* 尚未达到超时时间,重新设定定时器 */
    15     if (time_after(icsk->icsk_ack.timeout, jiffies)) {
    16         sk_reset_timer(sk, &icsk->icsk_delack_timer, icsk->icsk_ack.timeout);
    17         goto out;
    18     }
    19 
    20     /* 清除延迟ack标记 */
    21     icsk->icsk_ack.pending &= ~ICSK_ACK_TIMER;
    22 
    23     /* prequeue队列不为空 */
    24     if (!skb_queue_empty(&tp->ucopy.prequeue)) {
    25         struct sk_buff *skb;
    26 
    27         __NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPSCHEDULERFAILED);
    28 
    29         /* 数据包出队,调用接收函数tcp_v4_do_rcv处理包 */
    30         while ((skb = __skb_dequeue(&tp->ucopy.prequeue)) != NULL)
    31             sk_backlog_rcv(sk, skb);
    32 
    33         /* 消耗的内存清0 */
    34         tp->ucopy.memory = 0;
    35     }
    36 
    37     /* 有ack需要发送 */
    38     if (inet_csk_ack_scheduled(sk)) {
    39 
    40         /*  即时ack模式 */
    41         if (!icsk->icsk_ack.pingpong) {
    42             /* Delayed ACK missed: inflate ATO. */
    43             /* 计算超时时间 */
    44             icsk->icsk_ack.ato = min(icsk->icsk_ack.ato << 1, icsk->icsk_rto);
    45         } 
    46         /* 延迟ack模式 */
    47         else {
    48             /* Delayed ACK missed: leave pingpong mode and
    49              * deflate ATO.
    50              */
    51             /* 设置为即时ack模式 */
    52             icsk->icsk_ack.pingpong = 0;
    53 
    54             /* 重置超时时间 */
    55             icsk->icsk_ack.ato      = TCP_ATO_MIN;
    56         }
    57 
    58         /* 发送ack */
    59         tcp_send_ack(sk);
    60         __NET_INC_STATS(sock_net(sk), LINUX_MIB_DELAYEDACKS);
    61     }
    62 
    63 out:
    64     if (tcp_under_memory_pressure(sk))
    65         sk_mem_reclaim(sk);
    66 }
  • 相关阅读:
    k8s应用持久化存储和StorageClass(10)
    K8s控制器Statefulset(11)
    LeetCode面试题 08.11. 硬币(完全背包)
    打怪兽问题
    二叉树两个节点的最近公共祖先问题
    复制带随机指针的链表
    二叉树中最大的二叉搜索子树的大小
    零钱兑换问题
    Netty 学习(九):解码源码说明
    二叉树的直径和最大距离问题
  • 原文地址:https://www.cnblogs.com/wanpengcoder/p/11749449.html
Copyright © 2020-2023  润新知