kk Blog —— 通用基础


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

TCP接收窗口的调整算法

TCP接收窗口的调整算法(上)
TCP接收窗口的调整算法(中)
TCP接收窗口的调整算法(下)


TCP接收窗口的调整算法(上)

我们知道TCP首部中有一个16位的接收窗口字段,它可以告诉对端:我现在能接收多少数据。TCP的流控制主要就是通过调整接收窗口的大小来进行的。

本文内容:分析TCP接收窗口的调整算法,包括一些相关知识和初始接收窗口的取值。

内核版本:3.2.12

数据结构

以下是涉及到的数据结构。

1
2
3
4
5
6
7
8
9
10
struct tcp_sock {  
	...  
	/* 最早接收但未确认的段的序号,即当前接收窗口的左端*/  
	u32 rcv_wup; /* rcv_nxt on last window update sent */  
	u16 advmss; /* Advertised MSS. 本端能接收的MSS上限,建立连接时用来通告对端*/  
	u32 rcv_ssthresh; /* Current window clamp. 当前接收窗口大小的阈值*/  
	u32 rcv_wnd; /* Current receiver window,当前的接收窗口大小*/  
	u32 window_clamp; /* 接收窗口的最大值,这个值也会动态调整*/  
	...  
}
1
2
3
4
5
6
7
struct tcp_options_received {  
	...  
		snd_wscale : 4, /* Window scaling received from sender, 对端接收窗口扩大因子 */  
		rcv_wscale : 4; /* Window scaling to send to receiver, 本端接收窗口扩大因子 */  
	u16 user_mss; /* mss requested by user in ioctl */  
	u16 mss_clamp; /* Maximal mss, negotiated at connection setup,对端的最大mss */  
}
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
/** 
 * struct sock - network layer representation of sockets 
 * @sk_rcvbuf: size of receive buffer in bytes 
 * @sk_receive_queue: incoming packets 
 * @sk_write_queue: packet sending queue 
 * @sk_sndbuf: size of send buffer in bytes 
 */  
struct sock {  
	...  
	struct sk_buff_head sk_receive_queue;  
	/* 表示接收队列sk_receive_queue中所有段的数据总长度*/  
#define sk_rmem_alloc sk_backlog.rmem_alloc  
  
	int sk_rcvbuf; /* 接收缓冲区长度的上限*/  
	int sk_sndbuf; /* 发送缓冲区长度的上限*/  
  
	struct sk_buff_head sk_write_queue;  
	...  
}  
  
struct sk_buff_head {  
	/* These two members must be first. */  
	struct sk_buff *next;  
	struct sk_buff *prev;  
	__u32 qlen;  
	spinlock_t lock;  
};

TCP的核心系列 — SACK和DSACK的实现

TCP的核心系列 — SACK和DSACK的实现(一)
TCP的核心系列 — SACK和DSACK的实现(二)
TCP的核心系列 — SACK和DSACK的实现(三)
TCP的核心系列 — SACK和DSACK的实现(四)
TCP的核心系列 — SACK和DSACK的实现(五)
TCP的核心系列 — SACK和DSACK的实现(六)
TCP的核心系列 — SACK和DSACK的实现(七)


TCP的核心系列 — SACK和DSACK的实现(一)

TCP的实现中,SACK和DSACK是比较重要的一部分。

SACK和DSACK的处理部分由Ilpo Järvinen (ilpo.jarvinen@helsinki.fi) 维护。

tcp_ack()处理接收到的带有ACK标志的数据段时,如果此ACK处于慢速路径,且此ACK的记分牌不为空,则调用
tcp_sacktag_write_queue()来根据SACK选项标记发送队列中skb的记分牌状态。

笔者主要分析18和37这两个版本的实现。
相对而言,18版本的逻辑清晰,但效率较低;37版本的逻辑复杂,但效率较高。

本文主要内容:18版tcp_sacktag_write_queue()的实现,也即18版SACK和DSACK的实现。

18版数据结构

1
2
3
4
5
/* 这就是一个SACK块 */
struct tcp_sack_block {
	u32 start_seq;  /* 起始序号 */
	u32 end_seq;    /* 结束序号 */
};
1
2
3
4
5
6
7
8
9
10
11
12
13
struct tcp_sock {
	...
	/* Options received (usually on last packet, some only on SYN packets). */
	struct tcp_options_received rx_opt;
	...
	struct tcp_sack_block recv_sack_cache[4]; /* 保存收到的SACK块,用于提高效率*/
	...
	/* 快速路径中使用,上次第一个SACK块的结束处,现在直接从这里开始处理 */
	struct sk_buff *fastpath_skb_hint;
	int fastpath_cnt_hint;  /* 快速路径中使用,上次记录的fack_count,现在继续累加 */
	...

};
1
2
3
4
5
6
7
8
9
10
struct tcp_options_received {
	...
	u16 saw_tstamp : 1,    /* Saw TIMESTAMP on last packet */
		tstamp_ok : 1,     /* TIMESTAMP seen on SYN packet */
		dsack : 1,         /* D-SACK is scheduled, 下一个发送段是否存在D-SACK */
		sack_ok : 4,       /* SACK seen on SYN packet, 接收方是否支持SACK */
		...
	u8 num_sacks;          /* Number of SACK blocks, 下一个发送段中SACK块数 */
	...
};

linux进程调度之FIFO和RR调度策略

http://blog.chinaunix.net/uid-24774106-id-3379478.html

严格地说,对于优先级对于实时进程和普通进程的意义是不一样的。

1、在一定程度上,实时进程优先级高,实时进程存在,就没有普通进程占用CPU的机会,(但是前一篇博文也讲过了,实时组调度出现在内核以后,允许普通进程占用少量的CPU时间,取决于配置)。

2、对于实时进程而言,高优先级的进程存在,低优先级的进程是轮不上的,没机会跑在CPU上,所谓实时进程的调度策略,指的是相同优先级之间的调度策略。如果是FIFO实时进程在占用CPU,除非出现以下事情,否则FIFO一条道跑到黑。
a)FIFO进程良心发现,调用了系统调用sched_yield 自愿让出CPU
b) 更高优先级的进程横空出世,抢占FIFO进程的CPU。有些人觉得很奇怪,怎么FIFO占着CPU,为啥还能有更高优先级的进程出现呢。别忘记,我们是多核多CPU ,如果其他CPU上出现了一个比FIFO优先级高的进程,可能会push到FIFO进程所在的CPU上。
c) FIFO进程停止(TASK_STOPPED or TASK_TRACED状态)或者被杀死(EXIT_ZOMBIE or EXIT_DEAD状态)
d) FIFO进程执行了阻塞调用并进入睡眠(TASK_INTERRUPTIBLE OR TASK_UNINTERRUPTIBLE)。

如果是进程的调度策略是时间片轮转RR,那么,除了前面提到的abcd,RR实时进程耗尽自己的时间片后,自动退到对应优先级实时队列的队尾,重新调度。

1
2
3
4
5
6
7
8
struct sched_param {
	/* ... */
	int sched_priority;
	/* ... */
};
int sched_setscheduler (pid_t pid,
				int policy,
				const struct sched_param *sp);

sched_setscheduler函数的第二个参数调度方法 :

1
2
3
4
5
6
#define SCHED_OTHER 0
#define SCHED_FIFO 1
#define SCHED_RR 2
#ifdef __USE_GNU
# define SCHED_BATCH 3
#endif

SCHED_OTHER表示普通进程,对于普通进程,第三个参数sp->sched_priority只能是0
SCHED_FIFO 和SCHED_RR表示实时进程的调度策略,第三个参数的取值范围为[1,99]。
如果sched_setscheduler 优先级设置的值和调度策略不符合的话,会返回失败的。

应用层和内核层的优先级含义是不同的:
首先说实时进程:实时进程的优先级设置可以通过sched_setsheduler设置,也可以通过sched_setparam设置优先级的大小。 int sched_setparam (pid_t pid, const struct sched_param *sp);

在用户层或者应用层,1表示优先级最低,99表示优先级最高。但是在内核中,[0,99]表示的实时进程的优先级,0最高,99最低。[100,139]是普通进程折腾的范围。应用层比较天真率直,就看大小,数字大,则优先级高。ps查看进程的优先级也是如此。有意思的是,应用层实时进程最高优先级的99,在ps看进程优先级的时候,输出的是139.

对于普通进程,是通过nice系统调用来调整优先级的。从内核角度讲[100,139]是普通进程的优先级的范围,100最高,139最低,默认是120。普通进程的优先级的作用和实时进程不同,普通进程优先级表示的是占的CPU时间。深入linux内核架构中提到,普通优先级越高(100最高,139最低),享受的CPU time越多,相邻的两个优先级,高一级的进程比低一级的进程多占用10%的CPU,比如内核优先级数值为120的进程要比数值是121的进程多占用10%的CPU。

内核中有一个数组:prio_to_weight[20]表示的是默认优先级120的权重,数值为1024,prio_to_weight[21]表示nice值为1,优先级为121的进程的权重,数值为820。这就到了CFS的原理了

1
2
3
4
5
6
7
8
9
10
static const int prio_to_weight[40] = {
 /* -20 */ 88761, 71755, 56483, 46273, 36291,
 /* -15 */ 29154, 23254, 18705, 14949, 11916,
 /* -10 */ 9548, 7620, 6100, 4904, 3906,
 /* -5 */ 3121, 2501, 1991, 1586, 1277,
 /* 0 */ 1024, 820, 655, 526, 423,
 /* 5 */ 335, 272, 215, 172, 137,
 /* 10 */ 110, 87, 70, 56, 45,
 /* 15 */ 36, 29, 23, 18, 15,
};

假如有1台电脑,10个人玩,怎么才公平。
1 约定好时间片,每人玩1小时,玩完后记账,张XX 1小时,谁玩的时间短,谁去玩
2 引入优先级的概念,李四有紧急情况,需要提高他玩电脑的时间,怎么办,玩1个小时,记账半小时,那么同等情况下,李四会比其他人被选中玩电脑的频率要高,就体现了这个优先级的概念。
3 王五也有紧急情况,但是以考察,不如李四的紧急,好吧,玩1个小时,记账45分钟。
4 情况有变化,听说这里有电脑,突然又来了10个人,如果按照每人玩1小时的时间片,排在最后的那哥们早就开始骂人了,怎么办?时间片动态变化,根据人数来确定时间片。人越多,每个人玩的时间越少,防止哥们老捞不着玩,耐心耗尽,开始骂人。

这个记账就是我们prio_to_weight的作用。我就不多说了,prio_to_weight[20]就是基准,玩一小时,记账一小时,数组20以前的值是特权一级,玩1小时记账20分钟之类的享有特权的,数组20之后是倒霉蛋,玩1小时,记账1.5小时之类的倒霉蛋。 CFS这种调度好在大家都能捞着玩。

对于FIFO而言,一旦sleep过后,高优先级运行,低优先级是没戏运行的,同等优先级的进程,先运行的不运行完,后运行的也没戏。
对于RR而言,高优先级的先运行,同等优先级的进程过家家,你玩完,我玩,我玩完你再玩,每个进程耗费一个时间片的时间。对于Linux,RR时间片是100ms:

1
#define DEF_TIMESLICE        (100 * HZ / 1000)

TCP拥塞状态机 tcp_fastretrans_alert

这里主要说的是TCP拥塞情况下的状态状态处理

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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
/* Process an event, which can update packets-in-flight not trivially.
 * Main goal of this function is to calculate new estimate for left_out,
 * taking into account both packets sitting in receiver's buffer and
 * packets lost by network.
 *
 * Besides that it does CWND reduction, when packet loss is detected
 * and changes state of machine.
 *
 * It does _not_ decide what to send, it is made in function
 * tcp_xmit_retransmit_queue().
 */
static void tcp_fastretrans_alert(struct sock *sk, int pkts_acked, int flag)
{
	struct inet_connection_sock *icsk = inet_csk(sk);
	struct tcp_sock *tp = tcp_sk(sk);
	int is_dupack = !(flag & (FLAG_SND_UNA_ADVANCED | FLAG_NOT_DUP));         // 判断是不是重复的ACK
	int do_lost = is_dupack || ((flag & FLAG_DATA_SACKED) &&                  // 判断是不是丢包:若是重复ACK 或者 SACK而且提前确认中没有到的包数量>重拍指标
					(tcp_fackets_out(tp) > tp->reordering));  // 后面会单独说说SACK和FACK内容,觉得总是理解不好
	int fast_rexmit = 0;

	if (WARN_ON(!tp->packets_out && tp->sacked_out))                          // 如果packet_out为0,那么不可能有sacked_out
		tp->sacked_out = 0;
	if (WARN_ON(!tp->sacked_out && tp->fackets_out))
		tp->fackets_out = 0;

	/* Now state machine starts.                               // 下面开始状态处理
	 * A. ECE, hence prohibit cwnd undoing, the reduction is required. */
	if (flag & FLAG_ECE)                                     // 如果是ECE
		tp->prior_ssthresh = 0;                                // 禁止拥塞窗口撤销,并开始减小拥塞窗口

	/* B. In all the states check for reneging SACKs. */
	if (tcp_check_sack_reneging(sk, flag))                     // 检查ACK是不是确认了已经被SACK选择确认的包了
		return;

	/* C. Process data loss notification, provided it is valid. */
	if (tcp_is_fack(tp) && (flag & FLAG_DATA_LOST) &&          // 提前确认、数据丢失
		before(tp->snd_una, tp->high_seq) &&               // 我们需要注意high_seq 可以标志为LOST的段序号的最大值
		icsk->icsk_ca_state != TCP_CA_Open &&              // 状态不是OPEN
		tp->fackets_out > tp->reordering) {                // 同上面说的
		tcp_mark_head_lost(sk, tp->fackets_out - tp->reordering);   // 发现丢包,需要标志出丢失的包。 (1) 这个函数后面看
		NET_INC_STATS_BH(LINUX_MIB_TCPLOSS);
	}

	/* D. Check consistency of the current state. */
	tcp_verify_left_out(tp);                                   // #define tcp_verify_left_out(tp) WARN_ON(tcp_left_out(tp) > tp->packets_out)
				                                   // 检查丢失的包应该比发送出去的包小,即确定确定left_out < packets_out
	/* E. Check state exit conditions. State can be terminated
	 *    when high_seq is ACKed. */                           // 下面检测状态退出条件!当high_seq 被确认的时候,这个状态就可以终止了
	if (icsk->icsk_ca_state == TCP_CA_Open) {                  // 如果是open状态
		BUG_TRAP(tp->retrans_out == 0);                        // 重传数量应该=0才是合理的
		tp->retrans_stamp = 0;                                 // 将重传发送时间置0
	} else if (!before(tp->snd_una, tp->high_seq)) {           // 如果high_seq已经被确认
		switch (icsk->icsk_ca_state) {
		case TCP_CA_Loss:
			icsk->icsk_retransmits = 0;                // 超时重传次数归零
			if (tcp_try_undo_recovery(sk))             // 尝试将前面的拥塞窗口的调整撤销,在这种情况下弄不清楚包的情况(2)
				return;                                // 如果使用了SACK,那么不管undo成功与否,都会返回Open态
			break;

		case TCP_CA_CWR:   // 发生某些道路拥塞,需要减慢发送速度
			/* CWR is to be held something *above* high_seq
			 * is ACKed for CWR bit to reach receiver. */
			if (tp->snd_una != tp->high_seq) {
				tcp_complete_cwr(sk);                 // 完成道路拥塞情况处理,就是减小cwnd(3)
				tcp_set_ca_state(sk, TCP_CA_Open);    // 将状态设置成OPEN
			}
			break;

		case TCP_CA_Disorder:
			tcp_try_undo_dsack(sk);                          // 尝试撤销cwnd的减少,因为DSACK确认了所有的重传数据(4)
			if (!tp->undo_marker ||                          // 跟踪了重传数据包?
				/* For SACK case do not Open to allow to undo
				 * catching for all duplicate ACKs. */
				tcp_is_reno(tp) || tp->snd_una != tp->high_seq) {   // 没有SACK || 两者不同步
				tp->undo_marker = 0;
				tcp_set_ca_state(sk, TCP_CA_Open);           // 将状态转换成OPEN
			}
			break;

		case TCP_CA_Recovery:
			if (tcp_is_reno(tp))                             // 没有SACK
				tcp_reset_reno_sack(tp);                     // sacked_out=0
			if (tcp_try_undo_recovery(sk))                   // 尝试撤销
				return;
			tcp_complete_cwr(sk);                            // 完成处理
			break;
		}
	}

	/* F. Process state. */
	switch (icsk->icsk_ca_state) {
	case TCP_CA_Recovery:
		if (!(flag & FLAG_SND_UNA_ADVANCED)) {               // snd_una没有改变
			if (tcp_is_reno(tp) && is_dupack)                // 不是SACK,而且是重复的ACK
				tcp_add_reno_sack(sk);                       // 接收到重复的ACK,tp->sacked_out++; 并且检查新的reorder问题(5)
		} else
			do_lost = tcp_try_undo_partial(sk, pkts_acked);  // 部分ACK接收并撤销窗口操作(6)注意返回的是是否需要重传表示
		break;                                               // 1代表重传,0代表不需要重传
	case TCP_CA_Loss:
		if (flag & FLAG_DATA_ACKED)                          // 如果是数据确认
			icsk->icsk_retransmits = 0;                      // 超时重传置次数0
		if (tcp_is_reno(tp) && flag & FLAG_SND_UNA_ADVANCED) // 没有ACK,&& snd_una改变了
			tcp_reset_reno_sack(tp);                         // 重置sacked=0
		if (!tcp_try_undo_loss(sk)) {                        // 尝试撤销拥塞调整,然后进入OPEN状态(7)
			tcp_moderate_cwnd(tp);                           // 调整窗口(8)
			tcp_xmit_retransmit_queue(sk);                   // 重传丢失的包(9)
			return;
		}
		if (icsk->icsk_ca_state != TCP_CA_Open)
			return;
		/* Loss is undone; fall through to processing in Open state. */
	default:
		if (tcp_is_reno(tp)) {                        // 么有SACK,那么就是RENO算法处理:收到三个dup-ACK(即sacked_out==3),就开始重传
			if (flag & FLAG_SND_UNA_ADVANCED)         // 如果收到少于 3 个 dupack 后又收到累计确认,则会重置之前的 sacked_out 计数
				tcp_reset_reno_sack(tp);              // 重新置0
			if (is_dupack)                            // 如果收到一个dup-ack,将sacked_out++
				tcp_add_reno_sack(sk);
		}

		if (icsk->icsk_ca_state == TCP_CA_Disorder)
			tcp_try_undo_dsack(sk);                   // DSACK确认了所有重传数据

		if (!tcp_time_to_recover(sk)) {               // 判断是否进入恢复状态
			tcp_try_to_open(sk, flag);                // 如果不可以,那么会判断是否进入Open、Disorder、CWR等状态
			return;                                   // 只有收到三个dup-ack时候,才进入快速回复,否则都返回
		}

		/* MTU probe failure: don't reduce cwnd */
		if (icsk->icsk_ca_state < TCP_CA_CWR &&
			icsk->icsk_mtup.probe_size &&
			tp->snd_una == tp->mtu_probe.probe_seq_start) {
			tcp_mtup_probe_failed(sk);            // MTU探测失败
			/* Restores the reduction we did in tcp_mtup_probe() */
			tp->snd_cwnd++;
			tcp_simple_retransmit(sk);            // 做一个简单的转发,而不使用回退机制。用于路径MTU发现。 
			return;
		}
		// 说明已经收到第 3 个连续 dupack,此时 sacked_out = 3,进入恢复态
		/* Otherwise enter Recovery state */
		// 进入恢复状态
		if (tcp_is_reno(tp))
			NET_INC_STATS_BH(LINUX_MIB_TCPRENORECOVERY);
		else
			NET_INC_STATS_BH(LINUX_MIB_TCPSACKRECOVERY);

		tp->high_seq = tp->snd_nxt;
		tp->prior_ssthresh = 0;
		tp->undo_marker = tp->snd_una;
		tp->undo_retrans = tp->retrans_out;

		if (icsk->icsk_ca_state < TCP_CA_CWR) {
			if (!(flag & FLAG_ECE))
				tp->prior_ssthresh = tcp_current_ssthresh(sk);  // 根据状态获取当前门限值
			tp->snd_ssthresh = icsk->icsk_ca_ops->ssthresh(sk); // 更新
			TCP_ECN_queue_cwr(tp);
		}

		tp->bytes_acked = 0;
		tp->snd_cwnd_cnt = 0;
		tcp_set_ca_state(sk, TCP_CA_Recovery);                  // 键入恢复状态
		fast_rexmit = 1;                                        // 快速重传
	}

	if (do_lost || (tcp_is_fack(tp) && tcp_head_timedout(sk)))  // 如果丢失需要重传 || 超时重传
		tcp_update_scoreboard(sk, fast_rexmit);                 // 标志丢失和超时的数据包,增加lost_out(10)
	tcp_cwnd_down(sk, flag);                                    // 减小cwnd窗口(11)
	tcp_xmit_retransmit_queue(sk);                              // 重传丢失包
}

下面看一下里面的函数:

先看:tcp_mark_head_lost:通过给丢失的数据包标志TCPCB_LOST,就可以表明哪些数据包需要重传。

注意参数:packets = fackets_out - reordering,其实就是sacked_out + lost_out。被标志为LOST的段数不能超过packets。

那么packets 就是标记丢失的包们数量

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
/* Mark head of queue up as lost. With RFC3517 SACK, the packets is
 * is against sacked "cnt", otherwise it's against facked "cnt"
 */
static void tcp_mark_head_lost(struct sock *sk, int packets)
{
	struct tcp_sock *tp = tcp_sk(sk);
	struct sk_buff *skb;
	int cnt, oldcnt;
	int err;
	unsigned int mss;

	BUG_TRAP(packets <= tp->packets_out);       // 丢失的包不可能比所有发出去的包的数量
	if (tp->lost_skb_hint) {                    // 如果已经有标识为丢失的段了
		skb = tp->lost_skb_hint;                // 下一个需要标记的数据段
		cnt = tp->lost_cnt_hint;                // 已经标记了多少段
	} else {
		skb = tcp_write_queue_head(sk);         // 获得链表的第一个结构元素
		cnt = 0;                                // 初始化标记了0个数据
	}
	// 下面开始遍历
	tcp_for_write_queue_from(skb, sk) {
		if (skb == tcp_send_head(sk))           // return sk->sk_send_head; 即snd_nxt,那么还没有发送不需要处理,break;
			break;
		/* TODO: do this better */
		/* this is not the most efficient way to do this... */
		tp->lost_skb_hint = skb;                // 更新丢失队列信息
		tp->lost_cnt_hint = cnt;

		if (after(TCP_SKB_CB(skb)->end_seq, tp->high_seq))   // high_seq是最大的标记为LOST的号,不可以超过这个
			break;                              // 若这个skb超过,退出

		oldcnt = cnt;                           // 保存cnt
		if (tcp_is_fack(tp) || tcp_is_reno(tp) ||
			(TCP_SKB_CB(skb)->sacked & TCPCB_SACKED_ACKED))
			cnt += tcp_skb_pcount(skb);         // 表示这个段已经被标记

		if (cnt > packets) {
			if (tcp_is_sack(tp) || (oldcnt >= packets))  // 已经超过了丢失包数量,break
				break;

			mss = skb_shinfo(skb)->gso_size;             // 得到MSS
			err = tcp_fragment(sk, skb, (packets - oldcnt) * mss, mss);   // 下面分配,前面说过了
			if (err < 0)
				break;
			cnt = packets;
		}
		// 下面这一段就是做标记动作
		if (!(TCP_SKB_CB(skb)->sacked & (TCPCB_SACKED_ACKED|TCPCB_LOST))) {
			TCP_SKB_CB(skb)->sacked |= TCPCB_LOST;   // 标识
			tp->lost_out += tcp_skb_pcount(skb);     // 丢失包+=
			tcp_verify_retransmit_hint(tp, skb);     // 其实就是标记这个丢失,加入重传标记队列
		   }
	}
	tcp_verify_left_out(tp);
}

看一下tcp_verify_retransmit_hint函数:

1
2
3
4
5
6
7
8
9
10
11
static void tcp_verify_retransmit_hint(struct tcp_sock *tp, struct sk_buff *skb)
{
	if ((tp->retransmit_skb_hint == NULL) ||
		before(TCP_SKB_CB(skb)->seq,
		   TCP_SKB_CB(tp->retransmit_skb_hint)->seq))
		tp->retransmit_skb_hint = skb;                         // 加入这个队列

	if (!tp->lost_out ||
		after(TCP_SKB_CB(skb)->end_seq, tp->retransmit_high))  // 如果最后一个数据标号比high大,明显更新high
		tp->retransmit_high = TCP_SKB_CB(skb)->end_seq;
}

OK,再看一下这个函数tcp_try_undo_recovery:

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
static int tcp_try_undo_recovery(struct sock *sk)
{
	struct tcp_sock *tp = tcp_sk(sk);

	if (tcp_may_undo(tp)) {                               // 如果可以undo
		/* Happy end! We did not retransmit anything
		 * or our original transmission succeeded.
		 */
		DBGUNDO(sk, inet_csk(sk)->icsk_ca_state == TCP_CA_Loss ? "loss" : "retrans");
		tcp_undo_cwr(sk, 1);                              // 具体处理
		if (inet_csk(sk)->icsk_ca_state == TCP_CA_Loss)
			NET_INC_STATS_BH(LINUX_MIB_TCPLOSSUNDO);
		else
			NET_INC_STATS_BH(LINUX_MIB_TCPFULLUNDO);
		tp->undo_marker = 0;
	}
	if (tp->snd_una == tp->high_seq && tcp_is_reno(tp)) {
		/* Hold old state until something *above* high_seq
		 * is ACKed. For Reno it is MUST to prevent false
		 * fast retransmits (RFC2582). SACK TCP is safe. */
		tcp_moderate_cwnd(tp);                            // 更新窗口大小
		return 1;
	}
	tcp_set_ca_state(sk, TCP_CA_Open);
	return 0;
}

OK看一下tcp_may_undo函数:检测能否撤销

1
2
3
4
static inline bool tcp_may_undo(const struct tcp_sock *tp)
{
	return tp->undo_marker && (!tp->undo_retrans || tcp_packet_delayed(tp));
}

首先得有undo_marker标识才OK!然后undo_retrans的意思是最近的Recovery时间内重传的数据包个数,如果收到一个DSACK那么undo_retrans减一,如果最后等于0,那么说明都被确认了,没有必要重传,所以没有必要调整窗口。或tcp_packet_delayed(tp)条件。如下:

1
2
3
4
5
6
static inline int tcp_packet_delayed(struct tcp_sock *tp)
{
	return !tp->retrans_stamp ||
		(tp->rx_opt.saw_tstamp && tp->rx_opt.rcv_tsecr &&
		 (__s32)(tp->rx_opt.rcv_tsecr - tp->retrans_stamp) < 0);     // 接收ACK时间在重传数据之前
}

下面 看一下这个函数tcp_complete_cwr:

1
2
3
4
5
6
7
static inline void tcp_complete_cwr(struct sock *sk)
{
	struct tcp_sock *tp = tcp_sk(sk);
	tp->snd_cwnd = min(tp->snd_cwnd, tp->snd_ssthresh);              // 调整窗口
	tp->snd_cwnd_stamp = tcp_time_stamp;
	tcp_ca_event(sk, CA_EVENT_COMPLETE_CWR);                         // 出发事件
}
1
2
3
4
5
6
7
8
9
/* CWND moderation, preventing bursts due to too big ACKs
 * in dubious situations.
 */
static inline void tcp_moderate_cwnd(struct tcp_sock *tp)            // 修改窗口值
{
	tp->snd_cwnd = min(tp->snd_cwnd,
			   tcp_packets_in_flight(tp) + tcp_max_burst(tp));       // 防止怀疑的ACK情况,所以取min值
	tp->snd_cwnd_stamp = tcp_time_stamp;
}

再看看这个函数tcp_try_undo_dsack:当DSACK确认所有的重传数据,那么undo_retrans=0,那么需要回复窗口原来的情况

1
2
3
4
5
6
7
8
9
10
11
12
/* Try to undo cwnd reduction, because D-SACKs acked all retransmitted data */
static void tcp_try_undo_dsack(struct sock *sk)
{
	struct tcp_sock *tp = tcp_sk(sk);

	if (tp->undo_marker && !tp->undo_retrans) {          // 所有的段都被确认了
		DBGUNDO(sk, "D-SACK");
		tcp_undo_cwr(sk, 1);                             // 撤销(1)
		tp->undo_marker = 0;
		NET_INC_STATS_BH(LINUX_MIB_TCPDSACKUNDO);
	}
}

撤销函数

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
static void tcp_undo_cwr(struct sock *sk, const int undo)
{
	struct tcp_sock *tp = tcp_sk(sk);

	if (tp->prior_ssthresh) {                            // 如果保存了旧的门限值
		const struct inet_connection_sock *icsk = inet_csk(sk);

		if (icsk->icsk_ca_ops->undo_cwnd)
			tp->snd_cwnd = icsk->icsk_ca_ops->undo_cwnd(sk);           // 这个函数可以自己添加
		else
			tp->snd_cwnd = max(tp->snd_cwnd, tp->snd_ssthresh << 1);   // 如果没有定义那个函数,那么做简单的处理

		if (undo && tp->prior_ssthresh > tp->snd_ssthresh) {
			tp->snd_ssthresh = tp->prior_ssthresh;
			TCP_ECN_withdraw_cwr(tp);
		}
	} else {                                            // 没有保存旧的阈值
		tp->snd_cwnd = max(tp->snd_cwnd, tp->snd_ssthresh);
	}
	tcp_moderate_cwnd(tp);                              // 上面已经说了
	tp->snd_cwnd_stamp = tcp_time_stamp;

	/* There is something screwy going on with the retrans hints after
	   an undo */
	tcp_clear_all_retrans_hints(tp);                    // 清空所有的重传信息
}

接收到重复的ACK,那么需要对sacked_out处理,看函数tcp_add_reno_sack:

1
2
3
4
5
6
7
8
9
/* Emulate SACKs for SACKless connection: account for a new dupack. */

static void tcp_add_reno_sack(struct sock *sk)
{
	struct tcp_sock *tp = tcp_sk(sk);
	tp->sacked_out++;                           // 收到重复的ACK,那么这个值++
	tcp_check_reno_reordering(sk, 0);           // 检查是否有reordering(1)
	tcp_verify_left_out(tp);
}

看看这个检查reordering函数:

1
2
3
4
5
6
7
8
9
10
/* If we receive more dupacks than we expected counting segments
 * in assumption of absent reordering, interpret this as reordering.
 * The only another reason could be bug in receiver TCP.
 */
static void tcp_check_reno_reordering(struct sock *sk, const int addend)
{
	struct tcp_sock *tp = tcp_sk(sk);
	if (tcp_limit_reno_sacked(tp))       // 检查sack的数量是否超过限度
		tcp_update_reordering(sk, tp->packets_out + addend, 0); // 如果是reordering则更新reordering
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/* Limits sacked_out so that sum with lost_out isn't ever larger than
 * packets_out. Returns zero if sacked_out adjustement wasn't necessary.
 */
int tcp_limit_reno_sacked(struct tcp_sock *tp)          // 限制sacked_out目的是使得sacked_out + lost_out <= packeted_out
{
	u32 holes;

	holes = max(tp->lost_out, 1U);                      // 获得hole
	holes = min(holes, tp->packets_out);

	if ((tp->sacked_out + holes) > tp->packets_out) {   // 如果大于发出的包,那么reordering就需要了
		tp->sacked_out = tp->packets_out - holes;       // 因为此处的dup-ack是reorder造成的
		return 1;
	}
	return 0;
}

下面看看更新reordering函数tcp_update_reordering:

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
static void tcp_update_reordering(struct sock *sk, const int metric,
				  const int ts)
{
	struct tcp_sock *tp = tcp_sk(sk);
	if (metric > tp->reordering) {                          // 如果现在的数量 > 之前的reorder
		tp->reordering = min(TCP_MAX_REORDERING, metric);   // 获得ordering值(注意不能超过最大设置值)

		/* This exciting event is worth to be remembered. 8) */
		if (ts)
			NET_INC_STATS_BH(LINUX_MIB_TCPTSREORDER);       // 统计信息
		else if (tcp_is_reno(tp))
			NET_INC_STATS_BH(LINUX_MIB_TCPRENOREORDER);
		else if (tcp_is_fack(tp))
			NET_INC_STATS_BH(LINUX_MIB_TCPFACKREORDER);
		else
			NET_INC_STATS_BH(LINUX_MIB_TCPSACKREORDER);
#if FASTRETRANS_DEBUG > 1
		printk(KERN_DEBUG "Disorder%d %d %u f%u s%u rr%d\n",
			   tp->rx_opt.sack_ok, inet_csk(sk)->icsk_ca_state,
			   tp->reordering,
			   tp->fackets_out,
			   tp->sacked_out,
			   tp->undo_marker ? tp->undo_retrans : 0);
#endif
		tcp_disable_fack(tp);       // 禁用fack(fack是基于有序的,因为已经使用order了,所以禁用fack)
	}
}

下面再看一下这个tcp_try_undo_partial函数:在恢复状态,收到部分ACK确认,使用这个函数撤销拥塞调整。

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
/* Undo during fast recovery after partial ACK. */

static int tcp_try_undo_partial(struct sock *sk, int acked)
{
	struct tcp_sock *tp = tcp_sk(sk);
	/* Partial ACK arrived. Force Hoe's retransmit. */     // 收到部分ACK,对于SACK来说不需要重传,对于RENO需要
	int failed = tcp_is_reno(tp) || (tcp_fackets_out(tp) > tp->reordering);  // 或者facked_out数量比reordering要大

	if (tcp_may_undo(tp)) {                                // 是否可以调整(上面已说)
		/* Plain luck! Hole if filled with delayed
		 * packet, rather than with a retransmit.
		 */
		if (tp->retrans_out == 0)                          // 重传包=0
			tp->retrans_stamp = 0;                         // 重置重传时间

		tcp_update_reordering(sk, tcp_fackets_out(tp) + acked, 1);   // 需要更新reordering( 上面 )

		DBGUNDO(sk, "Hoe");
		tcp_undo_cwr(sk, 0);                               // 撤销操作( 上面 )
		NET_INC_STATS_BH(LINUX_MIB_TCPPARTIALUNDO);

		/* So... Do not make Hoe's retransmit yet.
		 * If the first packet was delayed, the rest
		 * ones are most probably delayed as well.
		 */
		failed = 0;                // 表示不用重传了,可以发送新的数据
	}
	return failed;                 // 返回是否需要重传
}

下面继续看tcp_try_undo_loss函数:收到部分确认之后,从loss状态撤销窗口调整

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
/* Undo during loss recovery after partial ACK. */
static int tcp_try_undo_loss(struct sock *sk)
{
	struct tcp_sock *tp = tcp_sk(sk);

	if (tcp_may_undo(tp)) {                            // 如果可以undo
		struct sk_buff *skb;
		tcp_for_write_queue(skb, sk) {                 // 遍历整个发送queue
			if (skb == tcp_send_head(sk))              // 直到还没有发送的数据头之前(前面的都已经发送)
				break;
			TCP_SKB_CB(skb)->sacked &= ~TCPCB_LOST;    // 清除LOST标记
		}

		tcp_clear_all_retrans_hints(tp);               // 清除所有的重传信息

		DBGUNDO(sk, "partial loss");
		tp->lost_out = 0;                              // 重置
		tcp_undo_cwr(sk, 1);                           // 撤销窗口调整
		NET_INC_STATS_BH(LINUX_MIB_TCPLOSSUNDO);
		inet_csk(sk)->icsk_retransmits = 0;
		tp->undo_marker = 0;
		if (tcp_is_sack(tp))
			tcp_set_ca_state(sk, TCP_CA_Open);         // 设置状态OPEN
		return 1;
	}
	return 0;
}

下面看一下tcp_update_scoreboard函数:其实就是更新lost包数量,这个涉及到不同的算法不一样的结果,没有SACK(reno),有SACK,有FACK情况

1) 没有SACK:每次收到重复的ACK或部分ack时,标志一个包为丢失。

2) 有SACK:sacked_out - reordering > 0 时候,标记为这么多丢失,若小于0,标记为1个丢失(前提是有重传标识)

3) 有FACK:fackets_out - reordering >0 时候,标记为这么多丢失,若小于0,标记为1个丢失

( 注意:小于0的情况是因为考虑到reordering情况 )

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
/* Account newly detected lost packet(s) */

static void tcp_update_scoreboard(struct sock *sk, int fast_rexmit)
{
	struct tcp_sock *tp = tcp_sk(sk);

	if (tcp_is_reno(tp)) {                            // 最普通的,没有SACK情况
		tcp_mark_head_lost(sk, 1);                    // 标记为一个丢失
	} else if (tcp_is_fack(tp)) {                     // 如果是fack
		int lost = tp->fackets_out - tp->reordering;  // 判断这个值大小
		if (lost <= 0)
			lost = 1;                                 // 小于0指标记一个
		tcp_mark_head_lost(sk, lost);                 // 否则标记所有的
	} else {   // 仅仅有SACK情况
		int sacked_upto = tp->sacked_out - tp->reordering;
		if (sacked_upto < fast_rexmit)
			sacked_upto = fast_rexmit;
		tcp_mark_head_lost(sk, sacked_upto);          // 同上
	}

	/* New heuristics: it is possible only after we switched
	 * to restart timer each time when something is ACKed.
	 * Hence, we can detect timed out packets during fast
	 * retransmit without falling to slow start.
	 */
	if (tcp_is_fack(tp) && tcp_head_timedout(sk)) {   // 下面检查超时包( 先检查第一个数据包是否超时 )
		struct sk_buff *skb;

		skb = tp->scoreboard_skb_hint ? tp->scoreboard_skb_hint
			: tcp_write_queue_head(sk);

		tcp_for_write_queue_from(skb, sk) {
			if (skb == tcp_send_head(sk))
				break;
			if (!tcp_skb_timedout(sk, skb))           // 检查所有的超时包,没有超时的就break
				break;

			if (!(TCP_SKB_CB(skb)->sacked & (TCPCB_SACKED_ACKED|TCPCB_LOST))) {
				TCP_SKB_CB(skb)->sacked |= TCPCB_LOST;   // 标记为lost
				tp->lost_out += tcp_skb_pcount(skb);     // 增加lost数量
				tcp_verify_retransmit_hint(tp, skb);
			}
		}

		tp->scoreboard_skb_hint = skb;

		   tcp_verify_left_out(tp);
	}
}

下面继续看这个减小窗口函数:tcp_cwnd_down,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/* Decrease cwnd each second ack. */ // 每收到2个确认将拥塞窗口减1,直到拥塞窗口等于慢启动阈值。
static void tcp_cwnd_down(struct sock *sk, int flag)
{
	struct tcp_sock *tp = tcp_sk(sk);
	int decr = tp->snd_cwnd_cnt + 1; // 计数器

	if ((flag & (FLAG_ANY_PROGRESS | FLAG_DSACKING_ACK)) ||
		(tcp_is_reno(tp) && !(flag & FLAG_NOT_DUP))) {
		tp->snd_cwnd_cnt = decr & 1;                    // 因为此处只可能是0,1三个值,这样的操作其实就是切换值,
		                                                // 例如现在是第一个ACK,即之前的snd_cwnd_cnt=0,decr=1,那么1&1=1,
		                                                // 将snd_cwnd_cnt赋值为1;第二个ACK到来,decr=2,则2&1=0,
		                                                // 相当于又将snd_cwnd_cnt初始化为0,因为两个ACK就需要处理一次。
		decr >>= 1;   // 除以2,是判断是第一个ACK,还是第二个;第一个的话值=0,下面不会执行,是2的话=1,下面一句会执行

		if (decr && tp->snd_cwnd > tcp_cwnd_min(sk))    // 如果是第二个ACK && 比最小的门限值还大一点,那么还需要减小cwnd
			tp->snd_cwnd -= decr;                       // 减小一个

		tp->snd_cwnd = min(tp->snd_cwnd, tcp_packets_in_flight(tp) + 1);  // 用于微调,和外面的数据包数量比较
		tp->snd_cwnd_stamp = tcp_time_stamp;            // 改变时间戳
	}
}

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是不是在窗口内,不在则不可以发送
}