在tcp_transmit_skb 中如果skb->len != tcp_header_size 也就是有载荷时,就会调用下述函数处理延迟确认事宜
/* Congestion state accounting after a packet has been sent.
如果此时距离最近接收到数据包的时间间隔足够短,
说明双方处于你来我往的双向数据传输中,就进入延迟确认模式
icsk->icsk_ack.ato在ACK的发送过程中扮演了重要角色,作用是??
A:ato为ACK Timeout,指ACK的超时时间。但延迟确认定时器的超时时间为icsk->icsk_ack.timeout,
ato只是计算timeout的一个中间变量,会根接收到的数据包的时间间隔来做动态调整。
一般如果接收到的数据包的时间间隔变小,ato也会相应的变小。
如果接收到的数据包的时间间隔变大,ato也会相应的变大。
ato的最小值为40ms,ato的最大值一般为200ms或一个RTT。
所以在实际传输过程中,我们看到的ACK的超时时间,是处于40ms ~ min(200ms, RTT)之间的
在tcp_send_delayed_ack()中会把ato赋值给icsk->icsk_ack.timeout,用作延迟确认定时器的超时时间。
*/
static void tcp_event_data_sent(struct tcp_sock *tp,
struct sock *sk)
{
struct inet_connection_sock *icsk = inet_csk(sk);
const u32 now = tcp_time_stamp;
if (tcp\_packets\_in\_flight(tp) == 0)
tcp\_ca\_event(sk, CA\_EVENT\_TX\_START);/\* first transmit when no packets in flight \*/
tp->lsndtime = now;/\* 更新最近发送数据包的时间\*/
/\* If it is a reply for ato after last received
\* packet, enter pingpong mode.
\*///如果距离上次接收到数据包的时间在ato内,则进入延迟确认模式
if ((u32)(now - icsk->icsk\_ack.lrcvtime) < icsk->icsk\_ack.ato)
icsk->icsk\_ack.pingpong = 1;
}
icsk->icsk_delack_timer的激活函数为inet_csk_reset_xmit_timer(),此函数共负责了5个定时器的激活工作。延迟确认定时器的另一个激活函数为tcp_send_delayed_ack(),用于判断发送快速确认还是延迟确认。
else if (what == ICSK_TIME_DACK) {
icsk->icsk_ack.pending |= ICSK_ACK_TIMER;/* 延迟确认定时器启动标志 */
icsk->icsk_ack.timeout = jiffies + when;//Delay ACK定时器超时时刻
sk_reset_timer(sk, &icsk->icsk_delack_timer, icsk->icsk_ack.timeout);
}
/**
* 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 {
/*
如果延迟确认定时器触发时,发现用户进程正在使用此socket,就把blocked置为1。
* 之后在接收到新数据、或者将数据复制到用户空间之后,会马上发送ACK。
*/
inet_csk(sk)->icsk_ack.blocked = 1;
__NET_INC_STATS(sock_net(sk), LINUX_MIB_DELAYEDACKLOCKED);
/* deleguate our work to tcp_release_cb()
if (flags & TCPF_DELACK_TIMER_DEFERRED) {
tcp_delack_timer_handler(sk);
__sock_put(sk);
}
*///设置标记,等应用程序release_sock的时候调用tcp_delack_timer_handler
if (!test_and_set_bit(TCP_DELACK_TIMER_DEFERRED, &sk->sk_tsq_flags))//如果delack推迟执行,也会处理prequeue队列
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 (((1 << 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;
}
icsk->icsk\_ack.pending &= ~ICSK\_ACK\_TIMER; /\* 清除延迟ack标记 \*/
if (!skb\_queue\_empty(&tp->ucopy.prequeue)) { /\* 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 = 0;
}
/\* 有ack需要发送 \*/
if (inet\_csk\_ack\_scheduled(sk)) {
if (!icsk->icsk\_ack.pingpong) {//不能延时,但却有ack被推迟了
/\* Delayed ACK missed: inflate ATO. 即时ack模式 计算超时时间\*/
icsk->icsk\_ack.ato = min(icsk->icsk\_ack.ato << 1, icsk->icsk\_rto);
} else { /\* 延迟ack模式 \*/
/\* Delayed ACK missed: leave pingpong mode and
\* deflate ATO.
\*/ /\* 重置超时时间以及设置为快速ack模式 \*/
icsk->icsk\_ack.pingpong = 0;
icsk->icsk\_ack.ato = TCP\_ATO\_MIN;
}
tcp\_send\_ack(sk); /\* 发送ack \*/
\_\_NET\_INC\_STATS(sock\_net(sk), LINUX\_MIB\_DELAYEDACKS);
}
out:
if (tcp_under_memory_pressure(sk))
sk_mem_reclaim(sk);
}
注意 tcp_recvmsg读完sk_receive_queue之后,如果没有读满应用程序缓存,则会处理prequeue和backlog队列,并从中读取。
另外,及时读满了缓存,也会在函数退出前,处理prequeue和backlog队列。
http://www.cnhalo.net/2016/07/13/linux-tcp-prequeue-backlog/
综上所述:应用层也会支持处理数据包,处理数据包后reales_sock时 也会调用 延时确认定时器
if (flags & TCPF_DELACK_TIMER_DEFERRED) {
tcp_delack_timer_handler(sk);
__sock_put(sk);
}
(1)发送SYN后收到SYN|ACK时:tcp_rcv_synsent_state_process
==》inet_csk_reset_xmit_timer
设置延迟ACK定时器,超时时间200ms
(2)发送ACK时无法申请skb:tcp_send_ack
---》
(3)有数据放入prequeue队列中时:
(4)调用__tcp_ack_snd_check函数发送ACK时满足一下条件
(1)收到少于一个MSS的数据或通告窗口缩小
(2)没有处于快速ACK模式
(3)无乱序数据
手机扫一扫
移动阅读更方便
你可能感兴趣的文章