TCP在收到数据段但是无需马上确认时设定,如果在超时时间之内有数据要发送到对端,则确认会随着数据一起发送,即捎带ACK,如果达到超时时间则执行定时器回调立即发送ack;
启动定时器:
延迟确认定时器调用inet_csk_reset_xmit_timer(sk, ICSK_TIME_DACK, xx, xxx)函数进行启动,启动时间点包含以下三个;
1. 建立连接时,客户端的第三次握手可能会被延迟确认,如果有数据要输出、设置了TCP_DEFER_ACCEPT选项或者不在快速确认模式下,则启动延迟确认定时器;
static int tcp_rcv_synsent_state_process(struct sock *sk, struct sk_buff *skb,
const struct tcphdr *th)
{
if (sk->sk_write_pending ||
icsk->icsk_accept_queue.rskq_defer_accept ||
icsk->icsk_ack.pingpong) {
/* Save one ACK. Data will be ready after
* several ticks, if write_pending is set.
*
* It may be deleted, but with this feature tcpdumps
* look so _wonderfully_ clever, that I was not able
* to stand against the temptation 8) --ANK
*/
inet_csk_schedule_ack(sk);
tcp_enter_quickack_mode(sk);
inet_csk_reset_xmit_timer(sk, ICSK_TIME_DACK,
TCP_DELACK_MAX, TCP_RTO_MAX); discard:
tcp_drop(sk, skb);
return ;
} else {
tcp_send_ack(sk);
}
}
2. 在立即发送确认时,如果分配内存失败,则启动延迟确认定时器;
/* This routine sends an ack and also updates the window. */
/* TCPTODO */
void tcp_send_ack(struct sock *sk)
{
struct sk_buff *buff; /* If we have been reset, we may not send again. */
if (sk->sk_state == TCP_CLOSE)
return; tcp_ca_event(sk, CA_EVENT_NON_DELAYED_ACK); /* We are not putting this on the write queue, so
* tcp_transmit_skb() will set the ownership to this
* sock.
*/
buff = alloc_skb(MAX_TCP_HEADER,
sk_gfp_mask(sk, GFP_ATOMIC | __GFP_NOWARN));
if (unlikely(!buff)) {
inet_csk_schedule_ack(sk);
inet_csk(sk)->icsk_ack.ato = TCP_ATO_MIN;
inet_csk_reset_xmit_timer(sk, ICSK_TIME_DACK,
TCP_DELACK_MAX, TCP_RTO_MAX);
return;
} /* Reserve space for headers and prepare control bits. */
skb_reserve(buff, MAX_TCP_HEADER);
tcp_init_nondata_skb(buff, tcp_acceptable_seq(sk), TCPHDR_ACK); /* We do not want pure acks influencing TCP Small Queues or fq/pacing
* too much.
* SKB_TRUESIZE(max(1 .. 66, MAX_TCP_HEADER)) is unfortunately ~784
*/
skb_set_tcp_pure_ack(buff); /* Send it off, this clears delayed acks for us. */
skb_mstamp_get(&buff->skb_mstamp);
tcp_transmit_skb(sk, buff, , (__force gfp_t));
}
3. 未启用tcp_low_latency情况下,当有进程正在读取TCP流时,此时prequeue队列存在TCP段且消耗的内存未达到上限,且没有ACK需要发送时,启动延迟确认定时器;
bool tcp_prequeue(struct sock *sk, struct sk_buff *skb)
{
if (skb_queue_len(&tp->ucopy.prequeue) == ) {
wake_up_interruptible_sync_poll(sk_sleep(sk),
POLLIN | POLLRDNORM | POLLRDBAND);
if (!inet_csk_ack_scheduled(sk))
inet_csk_reset_xmit_timer(sk, ICSK_TIME_DACK,
( * tcp_rto_min(sk)) / ,
TCP_RTO_MAX);
}
return true;
}
定时器回调函数:
tcp_delack_timer函数进行必要的状态检查,处理队列中的数据包,如果有ack要发送,则使用即使ack模式立即发送ack;
/**
* tcp_delack_timer() - The TCP delayed ACK timeout handler
* @data: Pointer to the current socket. (gets casted to struct sock *)
*
* This function gets (indirectly) called when the kernel timer for a TCP packet
* of this socket expires. Calls tcp_delack_timer_handler() to do the actual work.
*
* Returns: Nothing (void)
*/
static void tcp_delack_timer(unsigned long data)
{
struct sock *sk = (struct sock *)data; bh_lock_sock(sk);
/* 传输控制块未被锁定 */
if (!sock_owned_by_user(sk)) {
/* 调用超时处理函数 */
tcp_delack_timer_handler(sk);
} else {
/* 打阻塞标记告知ack要立即发送*/
inet_csk(sk)->icsk_ack.blocked = ;
__NET_INC_STATS(sock_net(sk), LINUX_MIB_DELAYEDACKLOCKED);
/* deleguate our work to tcp_release_cb() */
/* 交给tcp_release_cb处理超时回调 */
if (!test_and_set_bit(TCP_DELACK_TIMER_DEFERRED, &sk->sk_tsq_flags))
sock_hold(sk);
}
bh_unlock_sock(sk);
sock_put(sk);
}
/* Called with BH disabled */
void tcp_delack_timer_handler(struct sock *sk)
{
struct tcp_sock *tp = tcp_sk(sk);
struct inet_connection_sock *icsk = inet_csk(sk); sk_mem_reclaim_partial(sk); /* 关闭或者监听状态 || 未启动延迟ack定时器*/
if ((( << sk->sk_state) & (TCPF_CLOSE | TCPF_LISTEN)) ||
!(icsk->icsk_ack.pending & ICSK_ACK_TIMER))
goto out; /* 尚未达到超时时间,重新设定定时器 */
if (time_after(icsk->icsk_ack.timeout, jiffies)) {
sk_reset_timer(sk, &icsk->icsk_delack_timer, icsk->icsk_ack.timeout);
goto out;
} /* 清除延迟ack标记 */
icsk->icsk_ack.pending &= ~ICSK_ACK_TIMER; /* prequeue队列不为空 */
if (!skb_queue_empty(&tp->ucopy.prequeue)) {
struct sk_buff *skb; __NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPSCHEDULERFAILED); /* 数据包出队,调用接收函数tcp_v4_do_rcv处理包 */
while ((skb = __skb_dequeue(&tp->ucopy.prequeue)) != NULL)
sk_backlog_rcv(sk, skb); /* 消耗的内存清0 */
tp->ucopy.memory = ;
} /* 有ack需要发送 */
if (inet_csk_ack_scheduled(sk)) { /* 即时ack模式 */
if (!icsk->icsk_ack.pingpong) {
/* Delayed ACK missed: inflate ATO. */
/* 计算超时时间 */
icsk->icsk_ack.ato = min(icsk->icsk_ack.ato << , icsk->icsk_rto);
}
/* 延迟ack模式 */
else {
/* Delayed ACK missed: leave pingpong mode and
* deflate ATO.
*/
/* 设置为即时ack模式 */
icsk->icsk_ack.pingpong = ; /* 重置超时时间 */
icsk->icsk_ack.ato = TCP_ATO_MIN;
} /* 发送ack */
tcp_send_ack(sk);
__NET_INC_STATS(sock_net(sk), LINUX_MIB_DELAYEDACKS);
} out:
if (tcp_under_memory_pressure(sk))
sk_mem_reclaim(sk);
}