kk Blog —— 通用基础


date [-d @int|str] [+%s|"+%F %T"]
netstat -ltunp
sar -n DEV 1

tcp重传数据包 tcp_xmit_retransmit_skb

http://blog.csdn.net/shanshanpt/article/details/22202743

当知道需要重传数据结的时候执行这个函数:
对于函数tcp_xmit_retransmit_queue:需要重传哪些包呢到底?
首先是lost、标记的包;
然后还需要处理:之前发送过的但是尚未收到确认的包(向前重传),或者新数据,在这两者之间有一个选择

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
/* This gets called after a retransmit timeout, and the initially
 * retransmitted data is acknowledged.  It tries to continue
 * resending the rest of the retransmit queue, until either
 * we've sent it all or the congestion window limit is reached.
 * If doing SACK, the first ACK which comes back for a timeout
 * based retransmit packet might feed us FACK information again.
 * If so, we use it to avoid unnecessarily retransmissions.
 */
void tcp_xmit_retransmit_queue(struct sock *sk)
{
	const struct inet_connection_sock *icsk = inet_csk(sk);
	struct tcp_sock *tp = tcp_sk(sk);
	struct sk_buff *skb;
	int packet_cnt;

	if (tp->retransmit_skb_hint) {                      // 如果有重传信息
		skb = tp->retransmit_skb_hint;
		packet_cnt = tp->retransmit_cnt_hint;           // 保存cnt值
	} else {
		skb = tcp_write_queue_head(sk);                 // 发送队列
		packet_cnt = 0;
	}
	// 第一步,如果有丢失的包,那么需要重传
	/* First pass: retransmit lost packets. */
	if (tp->lost_out) {  // lost_out > 0
		tcp_for_write_queue_from(skb, sk) {             // 遍历
			__u8 sacked = TCP_SKB_CB(skb)->sacked;      // 获得sacked标识

			if (skb == tcp_send_head(sk))
				   break;
			/* we could do better than to assign each time */
			tp->retransmit_skb_hint = skb;              // 更新两个值
			tp->retransmit_cnt_hint = packet_cnt;

			/* Assume this retransmit will generate
			 * only one packet for congestion window
			 * calculation purposes.  This works because
			 * tcp_retransmit_skb() will chop up the
			 * packet to be MSS sized and all the
			 * packet counting works out.
			 */
			if (tcp_packets_in_flight(tp) >= tp->snd_cwnd)  // 如果传输中的报文数量 > 窗口数量,那么没有必要再发送数据
				return;

			if (sacked & TCPCB_LOST) {                      // 如果是LOST标识
				if (!(sacked & (TCPCB_SACKED_ACKED|TCPCB_SACKED_RETRANS))) {  // 如果丢失了 && 没有被选择确认或者重传
					if (tcp_retransmit_skb(sk, skb)) {      // 重传该数据函数!!!最后再看(1)
						tp->retransmit_skb_hint = NULL;     // 重传之后重置这个值
						return;                             // 返回
					}
					if (icsk->icsk_ca_state != TCP_CA_Loss)
						NET_INC_STATS_BH(LINUX_MIB_TCPFASTRETRANS);
					else
						NET_INC_STATS_BH(LINUX_MIB_TCPSLOWSTARTRETRANS);

					if (skb == tcp_write_queue_head(sk))    // 如果是第一个重传数据,那么重置重传计数器!!!
						inet_csk_reset_xmit_timer(sk, ICSK_TIME_RETRANS,
									  inet_csk(sk)->icsk_rto,
									  TCP_RTO_MAX);
				}

				packet_cnt += tcp_skb_pcount(skb);          // 重传数量
				if (packet_cnt >= tp->lost_out)             // 大于lost的数量,那么break;下面就不是lost数据问题了
					break;
			}
		}
	}

	/* OK, demanded retransmission is finished. */
	// 上面的是必须要重传的,下面的在前向重传和发送新数据之间进行选择
	/* Forward retransmissions are possible only during Recovery. */
	if (icsk->icsk_ca_state != TCP_CA_Recovery)  // 只有在恢复状态才可以这样做,在丢失状态不可以;
		return;                                  // 原因:在丢失状态希望通过可控制的方式进行重传?这一块不是很懂

	/* No forward retransmissions in Reno are possible. */
	if (tcp_is_reno(tp))                         // 前向选择重传只能是SACK下,reno下是不可能的~
		return;

	/* Yeah, we have to make difficult choice between forward transmission
	 * and retransmission... Both ways have their merits...
	 *
	 * For now we do not retransmit anything, while we have some new
	 * segments to send. In the other cases, follow rule 3 for
	 * NextSeg() specified in RFC3517.
	 */ // 下面还是需要选择考虑传输新数据还是前向重传,优先考虑新数据

	if (tcp_may_send_now(sk))                    // 检查是否有新的数据在等待传输(1)
		return;                                  // 以及这些新数据是否可以发送,可以的话返回,不需要做下面事

	/* If nothing is SACKed, highest_sack in the loop won't be valid */
	if (!tp->sacked_out)
		return;
	// 下面开始就是“前向重传”处理
	if (tp->forward_skb_hint)                    // 是否已经缓存这个队列
		skb = tp->forward_skb_hint;
	else
		skb = tcp_write_queue_head(sk);          // 没有

	tcp_for_write_queue_from(skb, sk) {          // 需要遍历
		if (skb == tcp_send_head(sk))            // 到头了
			break;
		tp->forward_skb_hint = skb;

		if (!before(TCP_SKB_CB(skb)->seq, tcp_highest_sack_seq(tp)))   // 不可以超过最大的即highest_sack_seq
			break;

		if (tcp_packets_in_flight(tp) >= tp->snd_cwnd)   // 如果传输中的包数量 > 窗口大小
			break;                                       // 不能再发了

		if (sacked & (TCPCB_SACKED_ACKED|TCPCB_SACKED_RETRANS))     // 已经被sack了或者在sack时已经被重传了
			continue;

		/* Ok, retransmit it. */
		if (tcp_retransmit_skb(sk, skb)) {               // 下面就是传输这个包
			tp->forward_skb_hint = NULL;
			break;
		}

		if (skb == tcp_write_queue_head(sk))             // 如果是第一个重传的包,那么启动设置定时器
			inet_csk_reset_xmit_timer(sk, ICSK_TIME_RETRANS,
						  inet_csk(sk)->icsk_rto,
						  TCP_RTO_MAX);

		NET_INC_STATS_BH(LINUX_MIB_TCPFORWARDRETRANS);
	}
}

看一下检查是否有新的数据需要传输的函数:tcp_may_send_now

因为此处涉及到Nagle算法,所以先简介一下:

Nagle算法:如果发送端欲多次发送包含少量字符的数据包(一般情况下,后面统一称长度小于MSS的数据包为小包,称长度等于MSS的数据包为大包),则发送端会先将第一个小包发送出去,而将后面到达的少量字符数据都缓存起来而不立即发送,直到收到接收端对前一个数据包报文段的ACK确认、或当前字符属于紧急数据,或者积攒到了一定数量的数据(比如缓存的字符数据已经达到数据包报文段的最大长度)等多种情况才将其组成一个较大的数据包发送出去。

1
2
3
4
5
6
7
8
9
10
int tcp_may_send_now(struct sock *sk)
{
	struct tcp_sock *tp = tcp_sk(sk);
	struct sk_buff *skb = tcp_send_head(sk);                 // 获得需要发送的数据头部

	return (skb &&                                           // 尚有新数据需要传输
		tcp_snd_test(sk, skb, tcp_current_mss(sk, 1),        // 看下面这个函数:检查是否这些新的数据需要尽快发送出去
				 (tcp_skb_is_last(sk, skb) ?     // 是否是最后一个包
				  tp->nonagle : TCP_NAGLE_PUSH)));
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/* This checks if the data bearing packet SKB (usually tcp_send_head(sk))
 * should be put on the wire right now.  If so, it returns the number of
 * packets allowed by the congestion window.
 */
static unsigned int tcp_snd_test(struct sock *sk, struct sk_buff *skb,
				 unsigned int cur_mss, int nonagle)
{
	struct tcp_sock *tp = tcp_sk(sk);
	unsigned int cwnd_quota;

	tcp_init_tso_segs(sk, skb, cur_mss);                     // 看看这个包的tso信息,便于后期和其他包一起处理

	if (!tcp_nagle_test(tp, skb, cur_mss, nonagle))          // 使用Nagle测试是不是数据现在就允许被发送,看下面函数(1)
		return 0;                                            // 如果不可以就返回了

	cwnd_quota = tcp_cwnd_test(tp, skb);                     // 返回还可以发送几个窗口的数据
	if (cwnd_quota && !tcp_snd_wnd_test(tp, skb, cur_mss))   // 如果有窗口数据可以发送 &&
		cwnd_quota = 0;                                      // 不可发送,设置=0

	return cwnd_quota;
}

看Nagle测试函数tcp_nagle_test:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/* Return non-zero if the Nagle test allows this packet to be
 * sent now.
 */
static inline int tcp_nagle_test(struct tcp_sock *tp, struct sk_buff *skb,
				 unsigned int cur_mss, int nonagle)           // 注意:测试返回1就是说明那个数据包现在允许直接发送出去
{                                     // 而Nagle对于小包是缓存一起发送的,除了第一个包、最后一个包
	/* Nagle rule does not apply to frames, which sit in the middle of the
	 * write_queue (they have no chances to get new data).
	 *
	 * This is implemented in the callers, where they modify the 'nonagle'
	 * argument based upon the location of SKB in the send queue.
	 */
	if (nonagle & TCP_NAGLE_PUSH)                // 设置了这个标识是因为说明可能是第一个包或者第二个包,或者其他一些允许的原因呢
		return 1;                                // Nagle允许直接发送包出去

	/* Don't use the nagle rule for urgent data (or for the final FIN).
	 * Nagle can be ignored during F-RTO too (see RFC4138).
	 */
	if (tp->urg_mode || (tp->frto_counter == 2) ||          // 注意对于紧急数据来说不可以使用Nagle规则!上面说过Nagle是缓存处理数据,紧急数据不可以!
		(TCP_SKB_CB(skb)->flags & TCPCB_FLAG_FIN))          // 注意结束包(FIN)和F-RTO标识包都需要立马发送出去
		return 1;

	if (!tcp_nagle_check(tp, skb, cur_mss, nonagle))        // 在Nagle算法下,是否允许发送这个包?返回0则允许立刻发送
		return 1;

	return 0;
}

tcp_nagle_check函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/* Return 0, if packet can be sent now without violation Nagle's rules:   Nagle算法允许下面条件的包可以正常发送
 * 1. It is full sized.                                          // 大小等于MSS,即缓存满,或者是大包
 * 2. Or it contains FIN. (already checked by caller)            // 是结束包FIN
 * 3. Or TCP_NODELAY was set.                                    // 不允许延迟的包
 * 4. Or TCP_CORK is not set, and all sent packets are ACKed.    // TCP_CORK没有设置
 *    With Minshall's modification: all sent small packets are ACKed.
 */
static inline int tcp_nagle_check(const struct tcp_sock *tp,
				 const struct sk_buff *skb,
				  unsigned mss_now, int nonagle)
{
	return (skb->len < mss_now &&                           // 检查在Nagle算法情况下,是不是可以发送这个包
		((nonagle & TCP_NAGLE_CORK) ||                      // 满足上面四个条件就OK
		 (!nonagle && tp->packets_out && tcp_minshall_check(tp))));
}

tcp_cwnd_test函数用于测试在当前的拥塞窗口情况下,最多还可以发送几个新数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/* Can at least one segment of SKB be sent right now, according to the
 * congestion window rules?  If so, return how many segments are allowed.
 */
static inline unsigned int tcp_cwnd_test(struct tcp_sock *tp,   // 根据当前的拥塞窗口,返回当前还可以发送几个segs
					 struct sk_buff *skb)
{
	u32 in_flight, cwnd;

	/* Don't be strict about the congestion window for the final FIN.  */
	if ((TCP_SKB_CB(skb)->flags & TCPCB_FLAG_FIN) &&       // 如果是最后的FIN包
		tcp_skb_pcount(skb) == 1)
		return 1;                                       // 返回一个OK

	in_flight = tcp_packets_in_flight(tp);              // 获得还在传输中的包
	cwnd = tp->snd_cwnd;                                // 获得当前窗口大小
	if (in_flight < cwnd)
		return (cwnd - in_flight);                      // 剩下的部分都是可以发送的

	return 0;
}

主要是用于测试最后一个数据是不是在窗口内,在则可以发送,不在则不可以发送

1
2
3
4
5
6
7
8
9
10
11
/* Does at least the first segment of SKB fit into the send window? */
static inline int tcp_snd_wnd_test(struct tcp_sock *tp, struct sk_buff *skb,
				   unsigned int cur_mss)
{
	u32 end_seq = TCP_SKB_CB(skb)->end_seq;

	if (skb->len > cur_mss)   // skb数据长度比MSS长
		end_seq = TCP_SKB_CB(skb)->seq + cur_mss;       // 最后一个seq

	return !after(end_seq, tcp_wnd_end(tp));            // 最后一个seq是不是在窗口内,不在则不可以发送
}

tcp重传数据包 tcp_retransmit_skb 函数

http://blog.csdn.net/shanshanpt/article/details/22202999

基于CentOS6.5 2.6.32-504.16.2.el6.x86_64

tcp_retransmit_skb 重传数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
/* This retransmits one SKB.  Policy decisions and retransmit queue
 * state updates are done by the caller.  Returns non-zero if an
 * error occurred which prevented the send.
 */ // 如果消耗很多的内存做其他事,那么就没有多余的来做队列的处理了
int tcp_retransmit_skb(struct sock *sk, struct sk_buff *skb)
{
	struct tcp_sock *tp = tcp_sk(sk);
	struct inet_connection_sock *icsk = inet_csk(sk);
	unsigned int cur_mss;
	int err;

	/* Inconslusive MTU probe */
	if (icsk->icsk_mtup.probe_size) {
		icsk->icsk_mtup.probe_size = 0;
	}

	/* Do not sent more than we queued. 1/4 is reserved for possible
	 * copying overhead: fragmentation, tunneling, mangling etc.
	 */
	if (atomic_read(&sk->sk_wmem_alloc) >                                    // sk_wmem_alloc:传输队列大小
		min(sk->sk_wmem_queued + (sk->sk_wmem_queued >> 2), sk->sk_sndbuf))  // sk_wmem_queud:固定的队列大小
		return -EAGAIN;

	if (before(TCP_SKB_CB(skb)->seq, tp->snd_una)) {         // 若这样,说明是有一部分数据才需要重传,形如:seq---snd_una---end_seq,前面一半已收到ACK
		if (before(TCP_SKB_CB(skb)->end_seq, tp->snd_una))   // 若这样,说明全部ACK,无需重传,BUG
			BUG();
		if (tcp_trim_head(sk, skb, tp->snd_una - TCP_SKB_CB(skb)->seq))      // 将无须重传的部分去掉
			return -ENOMEM;
	}

	if (inet_csk(sk)->icsk_af_ops->rebuild_header(sk))
		return -EHOSTUNREACH; /* Routing failure or similar. */

	cur_mss = tcp_current_mss(sk);

	/* If receiver has shrunk his window, and skb is out of
	 * new window, do not retransmit it. The exception is the
	 * case, when window is shrunk to zero. In this case
	 * our retransmit serves as a zero window probe.
	 */
	if (!before(TCP_SKB_CB(skb)->seq, tcp_wnd_end(tp))       // 如果数据在窗口后面,不会发送
		&& TCP_SKB_CB(skb)->seq != tp->snd_una)
		return -EAGAIN;
	if (skb->len > cur_mss) {                                // 如果skb长度 > MSS
		if (tcp_fragment(sk, skb, cur_mss, cur_mss))         // 先分片,并调整packet_out等统计值。再传送
			return -ENOMEM; /* We'll try again later. */
	} else {
		int oldpcount = tcp_skb_pcount(skb);

		if (unlikely(oldpcount > 1)) {
			tcp_init_tso_segs(sk, skb, cur_mss);             // 按当前mss重置skb->gso_XXX
			tcp_adjust_pcount(sk, skb, oldpcount - tcp_skb_pcount(skb)); // 调整packet_out等统计值
		}
	}

	tcp_retrans_try_collapse(sk, skb, cur_mss);              // 尝试和后几个包合并后一起重传出去,加快速度

	/* Some Solaris stacks overoptimize and ignore the FIN on a
	 * retransmit when old data is attached.  So strip it off
	 * since it is cheap to do so and saves bytes on the network.
	 */ //Solaris系统的协议栈有时候会忽略重传SKB上带有的FIN标志的payload,将payload全部剥离掉,节省网络流量
	if (skb->len > 0 &&
		(TCP_SKB_CB(skb)->tcp_flags & TCPHDR_FIN) &&
		tp->snd_una == (TCP_SKB_CB(skb)->end_seq - 1)) {
		if (!pskb_trim(skb, 0)) {
			/* Reuse, even though it does some unnecessary work */
			tcp_init_nondata_skb(skb, TCP_SKB_CB(skb)->end_seq - 1,
						 TCP_SKB_CB(skb)->tcp_flags);
			skb->ip_summed = CHECKSUM_NONE;
		}
	}

	/* Make a copy, if the first transmission SKB clone we made
	 * is still in somebody's hands, else make a clone.
	 */
	TCP_SKB_CB(skb)->when = tcp_time_stamp;

	/* make sure skb->data is aligned on arches that require it
	 * and check if ack-trimming & collapsing extended the headroom
	 * beyond what csum_start can cover.
	 */
	if (unlikely((NET_IP_ALIGN && ((unsigned long)skb->data & 3)) ||
			 skb_headroom(skb) >= 0xFFFF)) {
		struct sk_buff *nskb = __pskb_copy(skb, MAX_TCP_HEADER,
						   GFP_ATOMIC);
		err = nskb ? tcp_transmit_skb(sk, nskb, 0, GFP_ATOMIC) :
				 -ENOBUFS;
	} else {
		err = tcp_transmit_skb(sk, skb, 1, GFP_ATOMIC);     // 这个才是正在的传输函数
	}

	if (err == 0) {                                         // 发送成功,那么就需要更新TCP统计信息
		/* Update global TCP statistics. */
		TCP_INC_STATS(sock_net(sk), TCP_MIB_RETRANSSEGS);

		tp->total_retrans++;                                // 整体重传数量++

#if FASTRETRANS_DEBUG > 0
		if (TCP_SKB_CB(skb)->sacked & TCPCB_SACKED_RETRANS) {
			if (net_ratelimit())
				printk(KERN_DEBUG "retrans_out leaked.\n");
		}
#endif
		if (!tp->retrans_out)
			tp->lost_retrans_low = tp->snd_nxt;
		TCP_SKB_CB(skb)->sacked |= TCPCB_RETRANS;
		tp->retrans_out += tcp_skb_pcount(skb);             // 重传出去的数量+=。。。

		/* Save stamp of the first retransmit. */
		if (!tp->retrans_stamp)
			tp->retrans_stamp = TCP_SKB_CB(skb)->when;      // 第一次重传时间戳

		tp->undo_retrans += tcp_skb_pcount(skb);

		/* snd_nxt is stored to detect loss of retransmitted segment,
		 * see tcp_input.c tcp_sacktag_write_queue().
		 */
		TCP_SKB_CB(skb)->ack_seq = tp->snd_nxt;
	}
	return err;
}

tcp_retrans_try_collapse 重传时尝试和后几个包合并后传出去

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
// 只做简单合并,所以条件设置严格
/* Check if coalescing SKBs is legal. */
static int tcp_can_collapse(struct sock *sk, struct sk_buff *skb)
{
	if (tcp_skb_pcount(skb) > 1)         // skb只包含一个数据包,没有TSO分包
		return 0;
	/* TODO: SACK collapsing could be used to remove this condition */
	if (skb_shinfo(skb)->nr_frags != 0)  // 数据都在线性空间,非线性空间中没有数据
		return 0;
	if (skb_cloned(skb))                 // 不是clone
		return 0;
	if (skb == tcp_send_head(sk))
		return 0;
	/* Some heurestics for collapsing over SACK'd could be invented */
	if (TCP_SKB_CB(skb)->sacked & TCPCB_SACKED_ACKED)  // 已经被sack的当然不用重传
		return 0;

	return 1;
}

/* Collapse packets in the retransmit queue to make to create
 * less packets on the wire. This is only done on retransmission.
 */
static void tcp_retrans_try_collapse(struct sock *sk, struct sk_buff *to,
					 int space)
{
	struct tcp_sock *tp = tcp_sk(sk);
	struct sk_buff *skb = to, *tmp;
	int first = 1;

	if (!sysctl_tcp_retrans_collapse)
		return;
	if (TCP_SKB_CB(skb)->tcp_flags & TCPHDR_SYN)  // SYN包不合并
		return;

	tcp_for_write_queue_from_safe(skb, tmp, sk) {
		if (!tcp_can_collapse(sk, skb))           // 要和并的包判断是否符合条件
			break;

		space -= skb->len;

		if (first) {
			first = 0;
			continue;
		}

		if (space < 0)
			break;
		/* Punt if not enough space exists in the first SKB for
		 * the data in the second
		 */
		if (skb->len > skb_tailroom(to))          // 第一个包的tailroom空间足够容下该包
			break;

		if (after(TCP_SKB_CB(skb)->end_seq, tcp_wnd_end(tp))) // 大于窗口不合并
			break;

		tcp_collapse_retrans(sk, to);             // 进行两个包的合并
	}
}

tcp_collapse_retrans 重传合并

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
/* Collapses two adjacent SKB's during retransmission. */
static void tcp_collapse_retrans(struct sock *sk, struct sk_buff *skb)
{
	struct tcp_sock *tp = tcp_sk(sk);
	struct sk_buff *next_skb = tcp_write_queue_next(sk, skb);
	int skb_size, next_skb_size;

	skb_size = skb->len;
	next_skb_size = next_skb->len;

	BUG_ON(tcp_skb_pcount(skb) != 1 || tcp_skb_pcount(next_skb) != 1);

	tcp_highest_sack_combine(sk, next_skb, skb);

	tcp_unlink_write_queue(next_skb, sk);    // 将要合并的包从队列中删掉

	skb_copy_from_linear_data(next_skb, skb_put(skb, next_skb_size),
				  next_skb_size);            // 将数据copy到前一个包上,调整前一个的len,tail

	if (next_skb->ip_summed == CHECKSUM_PARTIAL)
		skb->ip_summed = CHECKSUM_PARTIAL;

	if (skb->ip_summed != CHECKSUM_PARTIAL)
		skb->csum = csum_block_add(skb->csum, next_skb->csum, skb_size);

	/* Update sequence range on original skb. */
	TCP_SKB_CB(skb)->end_seq = TCP_SKB_CB(next_skb)->end_seq;  // end_seq 等于后一个包的end_seq,所以如果skb->end_seq > next_skb->seq,就会合并出一个len>end_seq-seq的异常数据(内核保证了sk_write_queue不会出现这情况)

	/* Merge over control information. This moves PSH/FIN etc. over */
	TCP_SKB_CB(skb)->tcp_flags |= TCP_SKB_CB(next_skb)->tcp_flags;

	/* All done, get rid of second SKB and account for it so
	 * packet counting does not break.
	 */
	TCP_SKB_CB(skb)->sacked |= TCP_SKB_CB(next_skb)->sacked & TCPCB_EVER_RETRANS;

	/* changed transmit queue under us so clear hints */
	tcp_clear_retrans_hints_partial(tp);
	if (next_skb == tp->retransmit_skb_hint)
		tp->retransmit_skb_hint = skb;

	tcp_adjust_pcount(sk, next_skb, tcp_skb_pcount(next_skb)); // 调整pcount

	sk_wmem_free_skb(sk, next_skb);        // 合并到了前一个包上,所以释放这个包
}

拥塞避免处理函数 tcp_reno_cong_avoid

http://blog.csdn.net/shanshanpt/article/details/22201847

慢启动和快速重传拥塞避免算法,函数tcp_reno_cong_avoid
在“慢开始”阶段,每收到一个ACK,cwnd++一次,那么一个RTT之后,cwnd就会加倍
拥塞避免阶段,其实就是在一个RTT时间内将cwnd++一次( 注意在不丢包的情况下 )

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/*
 * TCP Reno congestion control
 * This is special case used for fallback as well.
 */
/* This is Jacobson's slow start and congestion avoidance.
 * SIGCOMM '88, p. 328.
 */
void tcp_reno_cong_avoid(struct sock *sk, u32 ack, u32 in_flight)
{
	struct tcp_sock *tp = tcp_sk(sk);         // 获取tcp_sock
	// 函数返回1说明拥塞窗口被限制,我们需要增加拥塞窗口,否则的话,就不需要增加拥塞窗口。
	if (!tcp_is_cwnd_limited(sk, in_flight))  // 是否已经达到拥塞窗口的限制值(1)
		return;

	/* In "safe" area, increase. */
	if (tp->snd_cwnd <= tp->snd_ssthresh)     // 如果发送窗口大小还 比 慢开始门限小,那么还是慢开始处理
		tcp_slow_start(tp);                   // 下面进入慢开始处理 (2)
	/* In dangerous area, increase slowly. */
	else if (sysctl_tcp_abc) {                // 否则进入拥塞避免阶段!!每个RTT时间就加1
		/* RFC3465: Appropriate Byte Count
		 * increase once for each full cwnd acked              // 基本思想就是:经过一个RTT时间就将snd_cwnd增加一个单位!
		 */                                                    // 一个RTT时间可以认为是当前拥塞窗口发送出去的数据的所有ACK都被接收到
		if (tp->bytes_acked >= tp->snd_cwnd*tp->mss_cache) {   // 当前的拥塞窗口的所有段都被ack了,窗口才被允许增加。
			tp->bytes_acked -= tp->snd_cwnd*tp->mss_cache;     // ACK处理过的及删除去了
			if (tp->snd_cwnd < tp->snd_cwnd_clamp)             // 不允许发送窗口大小超过snd_cwnd_clamp值
				tp->snd_cwnd++;
		}
	} else {                                       // 每接收到一个ACK,窗口增大(1/snd_cwnd),使用cnt计数
		/* In theory this is tp->snd_cwnd += 1 / tp->snd_cwnd */
		if (tp->snd_cwnd_cnt >= tp->snd_cwnd) {    // 线性增长计数器 >= 阈值
			if (tp->snd_cwnd < tp->snd_cwnd_clamp) // 如果窗口还没有达到阈值
				tp->snd_cwnd++;                    // 那么++增大窗口
			tp->snd_cwnd_cnt = 0;
		} else
			tp->snd_cwnd_cnt++;                    // 否则仅仅是增大线性递增计数器
	}
}

下面看一下“慢开始”算法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
void tcp_slow_start(struct tcp_sock *tp)           // 每到达一个ACK,snd_cwnd就加1。这意味着每个RTT,拥塞窗口就会翻倍。
{
	int cnt; /* increase in packets */

	/* RFC3465: ABC Slow start
	 * Increase only after a full MSS of bytes is acked
	 *
	 * TCP sender SHOULD increase cwnd by the number of
	 * previously unacknowledged bytes ACKed by each incoming
	 * acknowledgment, provided the increase is not more than L
	 */
	if (sysctl_tcp_abc && tp->bytes_acked < tp->mss_cache)                     // 如果ack确认的数据少于一个MSS大小,不需要增大窗口
		return;
	// 限制cnt的值
	if (sysctl_tcp_max_ssthresh > 0 && tp->snd_cwnd > sysctl_tcp_max_ssthresh) // 发送窗口超过最大门限值
		cnt = sysctl_tcp_max_ssthresh >> 1;     /* limited slow start */       // 窗口减半~~~~~
	else
		cnt = tp->snd_cwnd;          /* exponential increase */                // 否则还是原来的窗口

	/* RFC3465: ABC
	 * We MAY increase by 2 if discovered delayed ack
	 */
	if (sysctl_tcp_abc > 1 && tp->bytes_acked >= 2*tp->mss_cache) // 如果启动了延迟确认,那么当接收到的ACK大于等于两个MSS的时候才加倍窗口大小
		cnt <<= 1;
	tp->bytes_acked = 0;  // 清空

	tp->snd_cwnd_cnt += cnt;
	while (tp->snd_cwnd_cnt >= tp->snd_cwnd) {  // 这里snd_cwnd_cnt是snd_cwnd的几倍,拥塞窗口就增加几。
		tp->snd_cwnd_cnt -= tp->snd_cwnd;       // ok
		if (tp->snd_cwnd < tp->snd_cwnd_clamp)  // 判断窗口大小
			tp->snd_cwnd++;  // + +
	}
}

最后看一下这个函数:tcp_is_cwnd_limited,基本的意思就是判断需不需要增大拥塞窗口。

关于gso:主要功能就是尽量的延迟数据包的传输,以便与在最恰当的时机传输数据包。如果支持gso,就有可能是tso 延迟了数据包,因此这里会进行几个相关的判断,来看需不需要增加拥塞窗口。

关于burst:主要用来控制网络流量的突发性增大,也就是说当left数据(还能发送的数据段数)大于burst值的时候,我们需要暂时停止增加窗口,因为此时有可能我们这边数据发送过快。其实就是一个平衡权值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int tcp_is_cwnd_limited(const struct sock *sk, u32 in_flight)  // 第二个参数是正在网络中传输,还没有收到确认的报数量
{
	const struct tcp_sock *tp = tcp_sk(sk);
	u32 left;

	if (in_flight >= tp->snd_cwnd)    // 比较发送未确认和发送拥塞窗口的大小
		return 1;                     // 如果未确认的大,那么需要增大拥塞窗口

	if (!sk_can_gso(sk))              // 如果没有gso延时处理所有包,不需要增大窗口
		return 0;

	left = tp->snd_cwnd - in_flight;  // 得到还能发送的数据包的数量
	if (sysctl_tcp_tso_win_divisor)
		return left * sysctl_tcp_tso_win_divisor < tp->snd_cwnd;
	else
		return left <= tcp_max_burst(tp); // 如果还可以发送的数量>burst,说明发送太快,不需要增大窗口。
}