linux内核tcp的定时器管理(二)

本文详细探讨Linux内核TCP协议中keepalive、synack和time_wait定时器的工作原理、触发条件及实现细节,包括keepalive定时器用于维持连接活跃、synack定时器用于管理连接队列及重传SYN-ACK,time_wait定时器用于连接关闭后的等待机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 

linux内核tcp的定时器管理(二)

这次我们来看后面的3个定时器;

首先是keep alive定时器。

这个定时器主要是由于一条连接可能长时间idle,此时就需要发送探测包,来探测对端是否存活,如果对端收到这个探测包,它会立即相应一个ack,而当接受到ack之后,我们就能确认对端是存活的。否则我们会认为这个连接除了问题。

这个定时器是当一个新的连接变为establish状态的时候被激活,如果在当定时器超时还没有数据到来,定时器回调函数就会被激活,从而发送探测包。

在linux中,keep alive定时器不仅实现了keep alive而且还实现了syn ack定时器。并且这个定时器必须主动被打开,我们在用户空间通过设置SO_KEEPALIVE 套接子选项来打开这个定时器。而在内核中是通过设置sock的sk_flag域来实现的。


上一篇的blog我们知道keepalive定时器的回掉函数是tcp_keepalive_timer,因此我们就来看这个函数的实现:

下面的keepalive_time_when 是用来计算我们keep alive定时器的时间间隔用的。

tp->keepalive_probes表示探测包的发送次数。

TCP_KEEPIDLE这个选项对应着tp->keepalive_time,表示idle状态的间隔时间,也就是说idle时间超过tp->keepalive_time才会发送探测包
TCP_KEEPINTVL这个选项对应着tp->keepalive_intvl ,而这个间隔表示定时器的间隔,也就是多长时间发送一次探测包。

Java代码 复制代码  收藏代码
  1. static void tcp_keepalive_timer (unsigned long data)   
  2. {   
  3.     struct sock *sk = (struct sock *) data;   
  4.     struct inet_connection_sock *icsk = inet_csk(sk);   
  5.     struct tcp_sock *tp = tcp_sk(sk);   
  6.     __u32 elapsed;   
  7.   
  8.     /* Only process if socket is not in use. */  
  9.     bh_lock_sock(sk);   
  10. ///如果有用户进程在使用这个sock,则重启定时器。   
  11.     if (sock_owned_by_user(sk)) {   
  12.         /* Try again later. */  
  13.         inet_csk_reset_keepalive_timer (sk, HZ/20);   
  14.         goto out;   
  15.     }   
  16. ///我们上面说过了,keep alive定时器还可以处理ayn ack定时器。紧接着下面我们会分析这个定时器。   
  17.     if (sk->sk_state == TCP_LISTEN) {   
  18.         tcp_synack_timer(sk);   
  19.         goto out;   
  20.     }   
  21.   
  22. ///如果状态为tcp_fin_wait,并且sock已经被关闭。则进入time_wait处理。   
  23.     if (sk->sk_state == TCP_FIN_WAIT2 && sock_flag(sk, SOCK_DEAD)) {   
  24.         if (tp->linger2 >= 0) {   
  25.             const int tmo = tcp_fin_time(sk) - TCP_TIMEWAIT_LEN;   
  26.   
  27.             if (tmo > 0) {   
  28. ///time wait定时器还没有超时,则进入time wait定时器的处理.   
  29.                 tcp_time_wait(sk, TCP_FIN_WAIT2, tmo);   
  30.                 goto out;   
  31.             }   
  32.         }   
  33. ///如果已经超时,则发送一个reset到对端。   
  34.         tcp_send_active_reset(sk, GFP_ATOMIC);   
  35.         goto death;   
  36.     }   
  37.   
  38. ///检测keep alive有没有被打开。如果没有打开,则直接退出。   
  39.     if (!sock_flag(sk, SOCK_KEEPOPEN) || sk->sk_state == TCP_CLOSE)   
  40.         goto out;   
  41.   
  42. ///取出那个空闲间隔时间。   
  43.     elapsed = keepalive_time_when(tp);   
  44.   
  45. /* 1 tp->packets_out判断是否有任何已经传输可是还没有确认的数据包。  
  46. *  2 tcp_send_head用来判断是否有将要发送的包  
  47. * 如果上面有任何一个条件为真,就说明这个连接并不是处于idle状态,此时我们就重启定 *时器。  
  48. */  
  49.     if (tp->packets_out || tcp_send_head(sk))   
  50.         goto resched;   
  51. ///接下来计算最后一次接收包到现在过去的时间。   
  52.     elapsed = tcp_time_stamp - tp->rcv_tstamp;   
  53.   
  54. /*接下来比较idle时间有没有超过keep alive的设置的间隔时间,如果超过了,则说明我 *们需要 发送探测包了。如果没有,则我们需要重新调整keep alive的超时时间。  
  55. */  
  56.     if (elapsed >= keepalive_time_when(tp)) {   
  57. ///这个检测用来检测是否探测包已经超过了重发个数限制。   
  58.         if ((!tp->keepalive_probes && icsk->icsk_probes_out >= sysctl_tcp_keepalive_probes) ||   
  59.              (tp->keepalive_probes && icsk->icsk_probes_out >= tp->keepalive_probes)) {   
  60. ///如果超过了限制,则我们认为连接已经断掉,此时我们发送一个reset给对端。   
  61.             tcp_send_active_reset(sk, GFP_ATOMIC);   
  62.             tcp_write_err(sk);   
  63.             goto out;   
  64.         }   
  65. /*调用tcp_write_wakeup发送一个探测包。如果哦发送成功我们增加icsk-           * >icsk_probes_out,也就是发送次数。并且重新计算发送间隔。  
  66. */  
  67.         if (tcp_write_wakeup(sk) <= 0) {   
  68.             icsk->icsk_probes_out++;   
  69. ///这里计算探测时间间隔,并赋值给elapsed。   
  70.             elapsed = keepalive_intvl_when(tp);   
  71.         } else {   
  72. ///如果探测包丢失了,这表示有可能本地网络有问题,于是我们增加探测时间间隔,   
  73.             elapsed = TCP_RESOURCE_PROBE_INTERVAL;   
  74.         }   
  75.     } else {   
  76. ///减小定时器的时间间隔。   
  77.         elapsed = keepalive_time_when(tp) - elapsed;   
  78.     }   
  79.   
  80.     TCP_CHECK_TIMER(sk);   
  81.     sk_mem_reclaim(sk);   
  82.   
  83. resched:   
  84. ///重启定时器。超时时间为elapsed。   
  85.     inet_csk_reset_keepalive_timer (sk, elapsed);   
  86.     goto out;   
  87.   
  88. death:   
  89.     tcp_done(sk);   
  90.   
  91. out:   
  92.     bh_unlock_sock(sk);   
  93.     sock_put(sk);   
  94. }  
static void tcp_keepalive_timer (unsigned long data)
{
	struct sock *sk = (struct sock *) data;
	struct inet_connection_sock *icsk = inet_csk(sk);
	struct tcp_sock *tp = tcp_sk(sk);
	__u32 elapsed;

	/* Only process if socket is not in use. */
	bh_lock_sock(sk);
///如果有用户进程在使用这个sock,则重启定时器。
	if (sock_owned_by_user(sk)) {
		/* Try again later. */
		inet_csk_reset_keepalive_timer (sk, HZ/20);
		goto out;
	}
///我们上面说过了,keep alive定时器还可以处理ayn ack定时器。紧接着下面我们会分析这个定时器。
	if (sk->sk_state == TCP_LISTEN) {
		tcp_synack_timer(sk);
		goto out;
	}

///如果状态为tcp_fin_wait,并且sock已经被关闭。则进入time_wait处理。
	if (sk->sk_state == TCP_FIN_WAIT2 && sock_flag(sk, SOCK_DEAD)) {
		if (tp->linger2 >= 0) {
			const int tmo = tcp_fin_time(sk) - TCP_TIMEWAIT_LEN;

			if (tmo > 0) {
///time wait定时器还没有超时,则进入time wait定时器的处理.
				tcp_time_wait(sk, TCP_FIN_WAIT2, tmo);
				goto out;
			}
		}
///如果已经超时,则发送一个reset到对端。
		tcp_send_active_reset(sk, GFP_ATOMIC);
		goto death;
	}

///检测keep alive有没有被打开。如果没有打开,则直接退出。
	if (!sock_flag(sk, SOCK_KEEPOPEN) || sk->sk_state == TCP_CLOSE)
		goto out;

///取出那个空闲间隔时间。
	elapsed = keepalive_time_when(tp);

/* 1 tp->packets_out判断是否有任何已经传输可是还没有确认的数据包。
*  2 tcp_send_head用来判断是否有将要发送的包
* 如果上面有任何一个条件为真,就说明这个连接并不是处于idle状态,此时我们就重启定 *时器。
*/
	if (tp->packets_out || tcp_send_head(sk))
		goto resched;
///接下来计算最后一次接收包到现在过去的时间。
	elapsed = tcp_time_stamp - tp->rcv_tstamp;

/*接下来比较idle时间有没有超过keep alive的设置的间隔时间,如果超过了,则说明我 *们需要 发送探测包了。如果没有,则我们需要重新调整keep alive的超时时间。
*/
	if (elapsed >= keepalive_time_when(tp)) {
///这个检测用来检测是否探测包已经超过了重发个数限制。
		if ((!tp->keepalive_probes && icsk->icsk_probes_out >= sysctl_tcp_keepalive_probes) ||
		     (tp->keepalive_probes && icsk->icsk_probes_out >= tp->keepalive_probes)) {
///如果超过了限制,则我们认为连接已经断掉,此时我们发送一个reset给对端。
			tcp_send_active_reset(sk, GFP_ATOMIC);
			tcp_write_err(sk);
			goto out;
		}
/*调用tcp_write_wakeup发送一个探测包。如果哦发送成功我们增加icsk-           * >icsk_probes_out,也就是发送次数。并且重新计算发送间隔。
*/
		if (tcp_write_wakeup(sk) <= 0) {
			icsk->icsk_probes_out++;
///这里计算探测时间间隔,并赋值给elapsed。
			elapsed = keepalive_intvl_when(tp);
		} else {
///如果探测包丢失了,这表示有可能本地网络有问题,于是我们增加探测时间间隔,
			elapsed = TCP_RESOURCE_PROBE_INTERVAL;
		}
	} else {
///减小定时器的时间间隔。
		elapsed = keepalive_time_when(tp) - elapsed;
	}

	TCP_CHECK_TIMER(sk);
	sk_mem_reclaim(sk);

resched:
///重启定时器。超时时间为elapsed。
	inet_csk_reset_keepalive_timer (sk, elapsed);
	goto out;

death:
	tcp_done(sk);

out:
	bh_unlock_sock(sk);
	sock_put(sk);
}


接下来来看syn ack定时器。

这个定时器主要是用来管理连接队列。而且是用在服务端的。因为当三次握手最后一次,我们发送了syn-ack后,有可能需要重传syn-ack.而重传syn-ack这里一般有两个原因:

1 处于eastablished状态的连接,accept队列已满,使得应用程序不能接受新的连接。

2 当我们发送了syn-ack后,最后一个ack没有接受到,也就是3此握手没有完成。

第二个比较好理解,那么第一个呢。第一个之所以会这样,当accept队列满了之后,我们无法从半连接队列拷贝连接到accept队列,而我们拷贝半连接队列中的连接这个动作,是当ack到来时,我们才会激发这个动作,因此当accept队列满了,如果ack到来,我们会标记这个sock的acked域为1,此时由于不能拷贝连接,因此我们会等段时间重传这个syn-ack,从而等待ack的到来后拷贝这个半连接队列。

这个定时器什么时候会被启动呢,第一次一个连接加入到半连接队列的时候激活这个定时器。


Java代码 复制代码  收藏代码
  1. static inline void inet_csk_reqsk_queue_added(struct sock *sk,   
  2.                           const unsigned long timeout)   
  3. {   
  4.     if (reqsk_queue_added(&inet_csk(sk)->icsk_accept_queue) == 0)   
  5.         inet_csk_reset_keepalive_timer(sk, timeout);   
  6. }  
static inline void inet_csk_reqsk_queue_added(struct sock *sk,
					      const unsigned long timeout)
{
	if (reqsk_queue_added(&inet_csk(sk)->icsk_accept_queue) == 0)
		inet_csk_reset_keepalive_timer(sk, timeout);
}


而reqsk_queue_added的返回值为qlen,这个我们前面的blog已经介绍过了,这个值表示的是当前在半连接队列并且没有move到accept队列的连接个数。

而这个定时器什么时候被取消呢,和上面类似,也就是当最后一个在半连接队列中的连接被move到accept队列的时候。

Java代码 复制代码  收藏代码
  1. static inline void inet_csk_reqsk_queue_removed(struct sock *sk,   
  2.                         struct request_sock *req)   
  3. {   
  4.     if (reqsk_queue_removed(&inet_csk(sk)->icsk_accept_queue, req) == 0)   
  5.         inet_csk_delete_keepalive_timer(sk);   
  6. }  
static inline void inet_csk_reqsk_queue_removed(struct sock *sk,
						struct request_sock *req)
{
	if (reqsk_queue_removed(&inet_csk(sk)->icsk_accept_queue, req) == 0)
		inet_csk_delete_keepalive_timer(sk);
}


接下来我们来看定时器的实现,它的实现函数就是tcp_synack_timer。这个函数直接会调用inet_csk_reqsk_queue_prune.

先来大概的介绍下这个函数的功能。

当半连接队列的连接个数超过最大个数的一半时,我们需要为接受年轻的连接(也就是没有重传过的连接)保留一半的空间。半连接队列里面要尽量保持young的连接,并remove掉一些长时间空闲或者没有accept的连接。这个清理工作的定时器就是syn ack定时器,而他的间隔是TCP_SYNQ_INTERVAL:

Java代码 复制代码  收藏代码
  1. #define TCP_SYNQ_INTERVAL   (HZ/5)  
#define TCP_SYNQ_INTERVAL	(HZ/5)


可以看到它的间隔是每秒5次。

最后我们要知道导致syn ack段的重传一般有下面两种情况:

1 3此握手完毕,可是accept 队列已满,此时inet_request_sock的acked域为1.

2 三次握手并没有完成,也就是最后的ack没有收到。此时inet_request_sock的acked域为0.

这个函数主要就是用来清理半连接队列中的一些老的连接,并重新发送syn-ack.

现在来看inet_csk_reqsk_queue_prune的实现:

Java代码 复制代码  收藏代码
  1. void inet_csk_reqsk_queue_prune(struct sock *parent,   
  2.                 const unsigned long interval,   
  3.                 const unsigned long timeout,   
  4.                 const unsigned long max_rto)   
  5. {   
  6. ///这里省略了一些变量。   
  7. .............................................   
  8.   
  9.     if (lopt == NULL || lopt->qlen == 0)   
  10.         return;   
  11. ///首先判断qlen是否已经超过了最大半连接数的一半,这里max_qlen_log是一个最大值的指数。如果超过了则开始计算半连接队列中的老的连接的最大重传次数(syn-ack重传).后面我们就会通过这个值来判断这个连接是否应该丢弃掉。   
  12.     if (lopt->qlen>>(lopt->max_qlen_log-1)) {   
  13. ///得到young连接的个数   
  14.         int young = (lopt->qlen_young<<1);   
  15. ///开始计算重试次数,初始值是最大的重传次数。   
  16.         while (thresh > 2) {   
  17. ///这里表示young连接现在在半连接队列中占据更多得个数。   
  18.             if (lopt->qlen < young)   
  19.                 break;   
  20. ///这里可以看到当young连接的个数越多,thresh会越大,也就是重试次数会越大,而当young连接的个数越小,重试的次数就越小。这里很好得体现了我们需要保持young连接在半连接队列中的个数的领先。   
  21.             thresh--;   
  22.             young <<= 1;   
  23.         }   
  24.     }   
  25.   
  26. ///如果用户设置了tcp_defer_accept选项,则设置最大重试次数为rskq_defer_accept   
  27.     if (queue->rskq_defer_accept)   
  28.         max_retries = queue->rskq_defer_accept;   
  29.   
  30. ///计算我们需要检测的半连接队列的个数(由于半连接队列是一个hash链表,并且它的数量可能非常巨大,我们如果每次都遍历全部,那样对协议栈的效率影响太大了,因此我们这里会计算一个预计值(这个值也就是表示遍历几个链表).   
  31.     budget = 2 * (lopt->nr_table_entries / (timeout / interval));   
  32. ///第一次clock_hand的值会为0,可是每次遍历完半连接队列,我们就会把最后的i,也就是最后一个hash链表的位置付给clock_hand,从而下一次遍历会从这里开始。   
  33.     i = lopt->clock_hand;   
  34.   
  35. ///开始遍历半连接队列。   
  36.     do {   
  37. ///取出链表。   
  38.         reqp=&lopt->syn_table[i];   
  39. ///遍历链表   
  40.         while ((req = *reqp) != NULL) {   
  41. ///首先判断这个request是否已经超时。这里要注意req->expires和syn-ack定时器的超时时间是不同的,它表示这个半连接的超时时间。每个半连接都有可能不同。   
  42.             if (time_after_eq(now, req->expires)) {   
  43. /*1 接下来判断重试次数是否小于thresh.  
  44. * 2 acked域是否被设置(也就是表示accept队列已满),并且重试次数小于最大重试次数。  
  45. * 3 重发syn ack是否成功。  
  46. * 第1个和第二个判断是或的关系,而与第三个判断是与的关系。  
  47. */  
  48.                 if ((req->retrans < thresh ||   
  49.                      (inet_rsk(req)->acked && req->retrans < max_retries))   
  50.                     && !req->rsk_ops->rtx_syn_ack(parent, req)) {   
  51. ///到达这里重新发送syn-ack成功。此时需要更新一些域。   
  52.                     unsigned long timeo;   
  53. ///重试次数加1,如果第一次重传则将qlen_young见一。也就是说qlen_young保存的就是young连接的个数。   
  54.                     if (req->retrans++ == 0)   
  55.                         lopt->qlen_young--;   
  56. ///根据重传次数更新超时值(这里就是增大超时时间)。   
  57.                     timeo = min((timeout << req->retrans), max_rto);   
  58.                     req->expires = now + timeo;   
  59.                     reqp = &req->dl_next;   
  60.                     continue;   
  61.                 }   
  62.   
  63. ///否则从半连接队列中,丢掉这个连接。   
  64.                 inet_csk_reqsk_queue_unlink(parent, req, reqp);   
  65.                 reqsk_queue_removed(queue, req);   
  66.                 reqsk_free(req);   
  67.                 continue;   
  68.             }   
  69.             reqp = &req->dl_next;   
  70.         }   
  71. ///更新i,这里是为了循环处理,下面的图会很直观说明这个。   
  72.         i = (i + 1) & (lopt->nr_table_entries - 1);   
  73.   
  74.     } while (--budget > 0);   
  75. ///更新clock_hand.   
  76.     lopt->clock_hand = i;   
  77. ///重启定时器   
  78.     if (lopt->qlen)   
  79.         inet_csk_reset_keepalive_timer(parent, interval);   
  80. }  
void inet_csk_reqsk_queue_prune(struct sock *parent,
				const unsigned long interval,
				const unsigned long timeout,
				const unsigned long max_rto)
{
///这里省略了一些变量。
.............................................

	if (lopt == NULL || lopt->qlen == 0)
		return;
///首先判断qlen是否已经超过了最大半连接数的一半,这里max_qlen_log是一个最大值的指数。如果超过了则开始计算半连接队列中的老的连接的最大重传次数(syn-ack重传).后面我们就会通过这个值来判断这个连接是否应该丢弃掉。
	if (lopt->qlen>>(lopt->max_qlen_log-1)) {
///得到young连接的个数
		int young = (lopt->qlen_young<<1);
///开始计算重试次数,初始值是最大的重传次数。
		while (thresh > 2) {
///这里表示young连接现在在半连接队列中占据更多得个数。
			if (lopt->qlen < young)
				break;
///这里可以看到当young连接的个数越多,thresh会越大,也就是重试次数会越大,而当young连接的个数越小,重试的次数就越小。这里很好得体现了我们需要保持young连接在半连接队列中的个数的领先。
			thresh--;
			young <<= 1;
		}
	}

///如果用户设置了tcp_defer_accept选项,则设置最大重试次数为rskq_defer_accept
	if (queue->rskq_defer_accept)
		max_retries = queue->rskq_defer_accept;

///计算我们需要检测的半连接队列的个数(由于半连接队列是一个hash链表,并且它的数量可能非常巨大,我们如果每次都遍历全部,那样对协议栈的效率影响太大了,因此我们这里会计算一个预计值(这个值也就是表示遍历几个链表).
	budget = 2 * (lopt->nr_table_entries / (timeout / interval));
///第一次clock_hand的值会为0,可是每次遍历完半连接队列,我们就会把最后的i,也就是最后一个hash链表的位置付给clock_hand,从而下一次遍历会从这里开始。
	i = lopt->clock_hand;

///开始遍历半连接队列。
	do {
///取出链表。
		reqp=&lopt->syn_table[i];
///遍历链表
		while ((req = *reqp) != NULL) {
///首先判断这个request是否已经超时。这里要注意req->expires和syn-ack定时器的超时时间是不同的,它表示这个半连接的超时时间。每个半连接都有可能不同。
			if (time_after_eq(now, req->expires)) {
/*1 接下来判断重试次数是否小于thresh.
* 2 acked域是否被设置(也就是表示accept队列已满),并且重试次数小于最大重试次数。
* 3 重发syn ack是否成功。
* 第1个和第二个判断是或的关系,而与第三个判断是与的关系。
*/
				if ((req->retrans < thresh ||
				     (inet_rsk(req)->acked && req->retrans < max_retries))
				    && !req->rsk_ops->rtx_syn_ack(parent, req)) {
///到达这里重新发送syn-ack成功。此时需要更新一些域。
					unsigned long timeo;
///重试次数加1,如果第一次重传则将qlen_young见一。也就是说qlen_young保存的就是young连接的个数。
					if (req->retrans++ == 0)
						lopt->qlen_young--;
///根据重传次数更新超时值(这里就是增大超时时间)。
					timeo = min((timeout << req->retrans), max_rto);
					req->expires = now + timeo;
					reqp = &req->dl_next;
					continue;
				}

///否则从半连接队列中,丢掉这个连接。
				inet_csk_reqsk_queue_unlink(parent, req, reqp);
				reqsk_queue_removed(queue, req);
				reqsk_free(req);
				continue;
			}
			reqp = &req->dl_next;
		}
///更新i,这里是为了循环处理,下面的图会很直观说明这个。
		i = (i + 1) & (lopt->nr_table_entries - 1);

	} while (--budget > 0);
///更新clock_hand.
	lopt->clock_hand = i;
///重启定时器
	if (lopt->qlen)
		inet_csk_reset_keepalive_timer(parent, interval);
}


下面这个图就是syn ack定时器的schedule。




最后我们来看 time_wait定时器。

我们知道当主动发起close的那端进入time_wait状态后,需要等待2个MSL那么长的时间,才能完全关闭这个连接。

而这个定时器什么时候被激活呢,当我们调用tcp_fin(以后我们会详细分析tcp连接的断开过程)或者tcp_close时,如果我们处于TCP_FIN_WAIT2状态,这时就会调用tcp_time_wait来启动这个定时器。

当我们进入TIME_WAIT状态,我们需要等待2个msl,才能完全关闭这个连接。linux实现这个是将所有的处于TW状态的sock放在了inet_ehash_bucket hash表的twchain中(详见前面的blog).

在linux中有两种途径执行tw定时器,其实也就是定时器的等待时间,一种就是2个msl,一种是基于RTO,计算出来的超时时间。选取哪一种依赖于下面的两个条件:

1 是否TW状态的sock recyle被允许.(也就是sysctl_tw_recycle被打开)
2 我么能够记住最近的段的时间戳。

如果上面两个环境都能满足,我们则调用tcp_v4_remember_stamp来检测是否对端的信息我们有保存。如果有保存,我们就进入recycle模式。

下面就来看tcp_time_wait的实现。

Java代码 复制代码  收藏代码
  1. void tcp_time_wait(struct sock *sk, int state, int timeo)   
  2. {   
  3.     struct inet_timewait_sock *tw = NULL;   
  4.     const struct inet_connection_sock *icsk = inet_csk(sk);   
  5.     const struct tcp_sock *tp = tcp_sk(sk);   
  6.     int recycle_ok = 0;   
  7. ///判断是否要进入recycle模式。   
  8.     if (tcp_death_row.sysctl_tw_recycle && tp->rx_opt.ts_recent_stamp)   
  9.         recycle_ok = icsk->icsk_af_ops->remember_stamp(sk);   
  10. ///检测tw状态的sock的桶的数目是否已经到达限制。如果没有则alloc一个tw。   
  11.     if (tcp_death_row.tw_count < tcp_death_row.sysctl_max_tw_buckets)   
  12.         tw = inet_twsk_alloc(sk, state);   
  13.   
  14.     if (tw != NULL) {   
  15. ///复制相关的属性到tcptw和tw的域。   
  16.         struct tcp_timewait_sock *tcptw = tcp_twsk((struct sock *)tw);   
  17. ///这里重新计算RTO.   
  18.         const int rto = (icsk->icsk_rto << 2) - (icsk->icsk_rto >> 1);   
  19.   
  20.         tw->tw_rcv_wscale    = tp->rx_opt.rcv_wscale;   
  21.         tcptw->tw_rcv_nxt    = tp->rcv_nxt;   
  22.         tcptw->tw_snd_nxt    = tp->snd_nxt;   
  23.         tcptw->tw_rcv_wnd    = tcp_receive_window(tp);   
  24.         tcptw->tw_ts_recent  = tp->rx_opt.ts_recent;   
  25.         tcptw->tw_ts_recent_stamp = tp->rx_opt.ts_recent_stamp;   
  26.   
  27.         /* Linkage updates. */  
  28. ///这里将tw链接到tcp_hashinfo这个hash链表的tw chain中,而这个链表就是我们前面blog讲过的全局的包含所有sock的hash表struct inet_hashinfo;   
  29.         __inet_twsk_hashdance(tw, sk, &tcp_hashinfo);   
  30.   
  31. ///超时时间不能小于rto。   
  32.         if (timeo < rto)   
  33.             timeo = rto;   
  34. ///是否是recycle模式   
  35.         if (recycle_ok) {   
  36. ///如果是则tw等待时间为RTO   
  37.             tw->tw_timeout = rto;   
  38.         } else {   
  39. ///否则就设置超时时间为TCP_TIMEWAIT_LEN   
  40.             tw->tw_timeout = TCP_TIMEWAIT_LEN;   
  41.             if (state == TCP_TIME_WAIT)   
  42.                 timeo = TCP_TIMEWAIT_LEN;   
  43.         }   
  44. ///开始schedule TW状态的sock。   
  45.         inet_twsk_schedule(tw, &tcp_death_row, timeo,   
  46.                    TCP_TIMEWAIT_LEN);   
  47.         inet_twsk_put(tw);   
  48.     } else {   
  49.   
  50.         LIMIT_NETDEBUG(KERN_INFO "TCP: time wait bucket table overflow\n");   
  51.     }   
  52.   
  53.     tcp_update_metrics(sk);   
  54.     tcp_done(sk);   
  55. }  
void tcp_time_wait(struct sock *sk, int state, int timeo)
{
	struct inet_timewait_sock *tw = NULL;
	const struct inet_connection_sock *icsk = inet_csk(sk);
	const struct tcp_sock *tp = tcp_sk(sk);
	int recycle_ok = 0;
///判断是否要进入recycle模式。
	if (tcp_death_row.sysctl_tw_recycle && tp->rx_opt.ts_recent_stamp)
		recycle_ok = icsk->icsk_af_ops->remember_stamp(sk);
///检测tw状态的sock的桶的数目是否已经到达限制。如果没有则alloc一个tw。
	if (tcp_death_row.tw_count < tcp_death_row.sysctl_max_tw_buckets)
		tw = inet_twsk_alloc(sk, state);

	if (tw != NULL) {
///复制相关的属性到tcptw和tw的域。
		struct tcp_timewait_sock *tcptw = tcp_twsk((struct sock *)tw);
///这里重新计算RTO.
		const int rto = (icsk->icsk_rto << 2) - (icsk->icsk_rto >> 1);

		tw->tw_rcv_wscale	= tp->rx_opt.rcv_wscale;
		tcptw->tw_rcv_nxt	= tp->rcv_nxt;
		tcptw->tw_snd_nxt	= tp->snd_nxt;
		tcptw->tw_rcv_wnd	= tcp_receive_window(tp);
		tcptw->tw_ts_recent	= tp->rx_opt.ts_recent;
		tcptw->tw_ts_recent_stamp = tp->rx_opt.ts_recent_stamp;

		/* Linkage updates. */
///这里将tw链接到tcp_hashinfo这个hash链表的tw chain中,而这个链表就是我们前面blog讲过的全局的包含所有sock的hash表struct inet_hashinfo;
		__inet_twsk_hashdance(tw, sk, &tcp_hashinfo);

///超时时间不能小于rto。
		if (timeo < rto)
			timeo = rto;
///是否是recycle模式
		if (recycle_ok) {
///如果是则tw等待时间为RTO
			tw->tw_timeout = rto;
		} else {
///否则就设置超时时间为TCP_TIMEWAIT_LEN
			tw->tw_timeout = TCP_TIMEWAIT_LEN;
			if (state == TCP_TIME_WAIT)
				timeo = TCP_TIMEWAIT_LEN;
		}
///开始schedule TW状态的sock。
		inet_twsk_schedule(tw, &tcp_death_row, timeo,
				   TCP_TIMEWAIT_LEN);
		inet_twsk_put(tw);
	} else {

		LIMIT_NETDEBUG(KERN_INFO "TCP: time wait bucket table overflow\n");
	}

	tcp_update_metrics(sk);
	tcp_done(sk);
}


我们这里对TW定时器介绍的比较简略,后面当我们详细介绍tcp的关闭的4次握手时,会详细分析tw状态,倒是还会再联系这边进行分析。
 
 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值