Delayed Acknowledgment Timer

The purpose of delayed acknowledgment is to minimize the number of separate ACKs that are sent. The receiver does not send an ACK as soon as it can. Instead, it holds on to the ACK in the hopes of piggybacking the ACK on an outgoing data packet. The delayed acknowledgment timer is set to the amount of time to hold the ACK waiting for outgoing data to be ready. The function, tcp_delack_timer, defined in file, linux/net/ipv4/tcp_timer. c, is called when the delayed acknowledgment timer expires, indicating that we have now given up finding an outgoing packet to carry our ACK. The timer value is maintained between a minimum value, TCP_DELACK_MIN, defined as 1/25 second, and a maximum value, TCP_DELACK_MAX, defined as 1/5 second.

As in the other TCP timer functions, tcp_delack_timer is called with a pointer to the sock structure for the current open socket.

static void tcp delack timer(unsigned long data) {

struct sock *sk = (struct sock*)data; struct tcp opt *tp = tcp sk(sk);

bh lock sock(sk);

If the socket is locked, the timer is set ahead and an attempt is made later.

if (sock owned by user(sk)) { tp->ack.blocked = 1;

NET_INC_STATS_BH(DelayedACKLocked);

if (!mod_timer(&tp->delack_timer, jiffies + TCP_DELACK_MIN)) sock hold(sk);

goto out unlock;

Tcp_mem_reclaim accounts for reclaiming memory from any TCP pages allocated in queues. If the socket is in the TCP_CLOSE state and there is no pending acknowledge event, we exit the timer without sending an ACK. Next, we check to see if we got here somehow even though the timer has not yet expired, in which case we exit.

tcp mem reclaim(sk);

"if (sk->state == TCP_CLOSE || !(tp->ack.pending&TCP_ACK_TIMER)) goto out;

if (!mod timer(&tp->delack timer, tp->ack.timeout)) sock hold(sk);

goto out;

if (!mod timer(&tp->delack timer, tp->ack.timeout))

sock hold(sk); goto out;

Since the delayed ACK timer has fired, the pending timer event can be removed from the acknowledgment structure to indicate we are done processing the event.

The field prequeue points to incoming packets that have not been processed yet. Since this timer went off before we could acknowledge these packets, they are put back on the backlog queue for later processing, and failure statistics are incremented for each of these packets.

if (skb queue len(&tp->ucopy.prequeue)) { struct sk buff *skb;

NET_ADD_STATS_BH(TCPSchedulerFailed, skb queue len(&tp->ucopy.prequeue)); while ((skb = skb dequeue(&tp->ucopy.prequeue)) != NULL)

sk->backlog rcv(sk, skb); tp->ucopy.memory = 0;

Here, we check to see if there is a scheduled ACK.

If we have a scheduled ACK, it means that the timer expired before the delayed ACK could be sent out. We check the current acknowledgment mode in the pingpong field of the ack structure. If it is zero, we must be in quick acknowledgment mode, so we inflate the value of the acknowledgment timeout (ATO) in the ato field. This increases the amount of time until the next ACK timeout expires. However, if we are in delayed acknowledgment mode, we decrease the ATO to the minimum amount, TCP_ATO_MIN. In addition, we switch to fast acknowledgment mode by turning off delayed acknowledgment in the pingpong field. This will force the next

ACK to go out as soon as TCP_ATO_MIN time elapses without waiting for an outgoing data segment to carry the ACK.

tp->ack.ato = min(tp->ack.ato << 1, tp->rto); } else {

tp->ack.pingpong = 0; tp->ack.ato = TCP_ATO_MIN;

Finally, we send the ACK and increase the delayed ACK counter. Next, we clean up and exit the timer.

tcp send ack(sk); NET_INC_STATS_BH(DelayedACKs);

TCP_CHECK_TIMER(sk);

out:

if (tcp memory pressure) tcp mem reclaim(sk); out unlock:

Continue reading here: Route Cache Garbage Collection

Was this article helpful?

0 0