TCP重傳定時器

本文主要介紹linux內核中TCP的重傳定時器機制:用到的源代碼是2.6.38
在一個tcp連接中,如果一方過了rto時間內都沒收到對方的ACK,會觸發重傳並調用tcp_write_timer定時器處理函數
其中RTO表示重傳時間,RTO是動態計算的,需要考慮到當前的重傳次數。
tcp_write_timer調用tcp_retransmit_timer重傳處理函數,後者會調用tcp_write_timeout判斷重傳次數是否超過最大次數(默認12次,約9分鐘)

是的話關閉當前連接,否則重傳一個skb,代碼如下

static void tcp_write_timer(unsigned long data)
{
	struct sock *sk = (struct sock *)data;
	struct inet_connection_sock *icsk = inet_csk(sk);
	int event;

	bh_lock_sock(sk);				//加鎖,防止其他下部分任務同時執行
	if (sock_owned_by_user(sk)) {	//sock結構被用戶進程鎖住,暫時放棄並重設定時器(HZ / 20)秒後再執行:HZ是時鐘中斷每秒發生的次數
		/* Try again later */
		sk_reset_timer(sk, &icsk->icsk_retransmit_timer, jiffies + (HZ / 20));
		goto out_unlock;
	}

	if (sk->sk_state == TCP_CLOSE || !icsk->icsk_pending)	//關閉了或者定時器處理函數還沒初始化
		goto out;

	if (time_after(icsk->icsk_timeout, jiffies)) {			//還沒到時間呢
		sk_reset_timer(sk, &icsk->icsk_retransmit_timer, icsk->icsk_timeout);
		goto out;
	}

	event = icsk->icsk_pending;							//TCP有七個定時器,但是隻用了四個來實現,複用了一些不可能同時使用的定時器
	icsk->icsk_pending = 0;								//通過icsk_pending判斷類別

	switch (event) {
	case ICSK_TIME_RETRANS:								//重傳定時器
		tcp_retransmit_timer(sk);						//主要在該函數處理重傳
		break;
	case ICSK_TIME_PROBE0:								//持續定時器
		tcp_probe_timer(sk);
		break;
	}
	TCP_CHECK_TIMER(sk);

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


void tcp_retransmit_timer(struct sock *sk)
{
	struct tcp_sock *tp = tcp_sk(sk);
	struct inet_connection_sock *icsk = inet_csk(sk);

	if (!tp->packets_out)		//沒有要發送的數據
		goto out;

	WARN_ON(tcp_write_queue_empty(sk));

	if (!tp->snd_wnd && !sock_flag(sk, SOCK_DEAD) &&
	    !((1 << sk->sk_state) & (TCPF_SYN_SENT | TCPF_SYN_RECV))) {	//發送窗口已關閉&&sock不處於DEAD狀態&&不在三次握手狀態

		if (tcp_time_stamp - tp->rcv_tstamp > TCP_RTO_MAX) {	//當前時間減去最近收到的ACK,即超過TCP_RTO_MAX時間內沒有收到對方的ACK
			tcp_write_err(sk);						//需要關閉該接口
			goto out;
		}
		tcp_enter_loss(sk, 0);
		tcp_retransmit_skb(sk, tcp_write_queue_head(sk));		<span style="white-space:pre">	</span>//重傳隊列頭部的skb並重設定時器
		__sk_dst_reset(sk);
		goto out_reset_timer;
	}

	if (tcp_write_timeout(sk))							//判斷是否已超過最大重傳數
		goto out;

	if (icsk->icsk_retransmits == 0) {						//第一次重傳
		int mib_idx;
									
		if (icsk->icsk_ca_state == TCP_CA_Recovery) {
			if (tcp_is_sack(tp))						//啓用了SACK
				mib_idx = LINUX_MIB_TCPSACKRECOVERYFAIL;
			else
				mib_idx = LINUX_MIB_TCPRENORECOVERYFAIL;
		} else if (icsk->icsk_ca_state == TCP_CA_Loss) {
			mib_idx = LINUX_MIB_TCPLOSSFAILURES;
		} else if ((icsk->icsk_ca_state == TCP_CA_Disorder) ||
			   tp->sacked_out) {
			if (tcp_is_sack(tp))
				mib_idx = LINUX_MIB_TCPSACKFAILURES;
			else
				mib_idx = LINUX_MIB_TCPRENOFAILURES;
		} else {
			mib_idx = LINUX_MIB_TCPTIMEOUTS;
		}
		NET_INC_STATS_BH(sock_net(sk), mib_idx);				//統計信息
	}

	if (tcp_use_frto(sk)) {								//快速重傳?
		tcp_enter_frto(sk);
	} else {
		tcp_enter_loss(sk, 0);
	}

	if (tcp_retransmit_skb(sk, tcp_write_queue_head(sk)) > 0) {	<span style="white-space:pre">		</span>//重傳隊列頭部skb
		/* Retransmission failed because of local congestion,
		 * do not backoff.
		 */									//這裏重傳失敗是發生在本地,所以是擁塞造成的
		if (!icsk->icsk_retransmits)
			icsk->icsk_retransmits = 1;
		inet_csk_reset_xmit_timer(sk, ICSK_TIME_RETRANS,
					  min(icsk->icsk_rto, TCP_RESOURCE_PROBE_INTERVAL),
					  TCP_RTO_MAX);					//根據RTO設置重傳定時器
		goto out;
	}

	icsk->icsk_backoff++;								//backoff加1,每次重傳會根據這個值當做數組下標得到相應的RTO(指數退避算法的RTO)
	icsk->icsk_retransmits++;						<span style="white-space:pre">	</span>//重傳次數加1

out_reset_timer:									//重設重傳定時器等信息

	if (sk->sk_state == TCP_ESTABLISHED &&
	    (tp->thin_lto || sysctl_tcp_thin_linear_timeouts) &&
	    tcp_stream_is_thin(tp) &&
	    icsk->icsk_retransmits <= TCP_THIN_LINEAR_RETRIES) {
		icsk->icsk_backoff = 0;
		icsk->icsk_rto = min(__tcp_set_rto(tp), TCP_RTO_MAX);
	} else {
		/* Use normal (exponential) backoff */
		icsk->icsk_rto = min(icsk->icsk_rto << 1, TCP_RTO_MAX);
	}
	inet_csk_reset_xmit_timer(sk, ICSK_TIME_RETRANS, icsk->icsk_rto, TCP_RTO_MAX);
	if (retransmits_timed_out(sk, sysctl_tcp_retries1 + 1, 0, 0))
		__sk_dst_reset(sk);

out:;
}

/* A write timeout has occurred. Process the after effects. */
static int tcp_write_timeout(struct sock *sk)
{
	struct inet_connection_sock *icsk = inet_csk(sk);
	int retry_until;
	bool do_reset, syn_set = 0;

	if ((1 << sk->sk_state) & (TCPF_SYN_SENT | TCPF_SYN_RECV)) {	<span style="white-space:pre">		</span>//處理三次握手期間的情況
		if (icsk->icsk_retransmits)						//SYN發生過重傳,需要檢測sock中的路由緩存
			dst_negative_advice(sk);
		retry_until = icsk->icsk_syn_retries ? : sysctl_tcp_syn_retries;	//獲取SYN重傳次數最大值
		syn_set = 1;
	} else {
		if (retransmits_timed_out(sk, sysctl_tcp_retries1, 0, 0)) {	//sysctl_tcp_retries1是一個閾值(默認3),當重傳次數超過該值時需要
			/* Black hole detection */
			tcp_mtu_probing(icsk, sk);					//檢測MTP是否有效

			dst_negative_advice(sk);					//檢測路由緩存
		}

		retry_until = sysctl_tcp_retries2;					//sysctl_tcp_retries2是更大的閾值(15?),重傳超過該值之後必須斷開連接了
		if (sock_flag(sk, SOCK_DEAD)) {						//sock處於DEAD,必要時需要釋放資源
			const int alive = (icsk->icsk_rto < TCP_RTO_MAX);

			retry_until = tcp_orphan_retries(sk, alive);
			do_reset = alive ||
				!retransmits_timed_out(sk, retry_until, 0, 0);

			if (tcp_out_of_resources(sk, do_reset))
				return 1;
		}
	}
	//重傳次數達到對應的上限時應該關閉連接
	if (retransmits_timed_out(sk, retry_until,
				  syn_set ? 0 : icsk->icsk_user_timeout, syn_set)) {
		/* Has it gone just too far? */
		tcp_write_err(sk);
		return 1;
	}
	return 0;
}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章