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
| /* This routine writes packets to the network. It advances the
* send_head. This happens as incoming acks open up the remote
* window for us.
*
* LARGESEND note: !tcp_urg_mode is overkill, only frames between
* snd_up-64k-mss .. snd_up cannot be large. However, taking into
* account rare use of URG, this is not a big flaw.
*
* Returns 1, if no segments are in flight and we have queued segments, but
* cannot send anything now because of SWS or another problem.
*/
static int tcp_write_xmit(struct sock *sk, unsigned int mss_now, int nonagle,
int push_one, gfp_t gfp)
{
struct tcp_sock *tp = tcp_sk(sk);
struct sk_buff *skb;
unsigned int tso_segs, sent_pkts;
int cwnd_quota;
int result;
/* sent_pkts用来统计函数中已发送报文总数。*/
sent_pkts = 0;
/* 检查是不是只发送一个skb buffer,即push one */
if (!push_one) {
/* 如果要发送多个skb,则需要检测MTU。
* 这时会检测MTU,希望MTU可以比之前的大,提高发送效率。
*/
/* Do MTU probing. */
result = tcp_mtu_probe(sk);
if (!result) {
return 0;
} else if (result > 0) {
sent_pkts = 1;
}
}
while ((skb = tcp_send_head(sk))) {
unsigned int limit;
/* 设置有关TSO的信息,包括GSO类型,GSO分段的大小等等。
* 这些信息是准备给软件TSO分段使用的。
* 如果网络设备不支持TSO,但又使用了TSO功能,
* 则报文在提交给网络设备之前,需进行软分段,即由代码实现TSO分段。
*/
tso_segs = tcp_init_tso_segs(sk, skb, mss_now);
BUG_ON(!tso_segs);
/* 检查congestion windows, 可以发送几个segment */
/* 检测拥塞窗口的大小,如果为0,则说明拥塞窗口已满,目前不能发送。
* 拿拥塞窗口和正在网络上传输的包数目相比,如果拥塞窗口还大,
* 则返回拥塞窗口减掉正在网络上传输的包数目剩下的大小。
* 该函数目的是判断正在网络上传输的包数目是否超过拥塞窗口,
* 如果超过了,则不发送。
*/
cwnd_quota = tcp_cwnd_test(tp, skb);
if (!cwnd_quota)
break;
/* 检测当前报文是否完全处于发送窗口内,如果是则可以发送,否则不能发送 */
if (unlikely(!tcp_snd_wnd_test(tp, skb, mss_now)))
break;
/* tso_segs=1表示无需tso分段 */
if (tso_segs == 1) {
/* 根据nagle算法,计算是否需要发送数据 */
if (unlikely(!tcp_nagle_test(tp, skb, mss_now,
(tcp_skb_is_last(sk, skb) ?
nonagle : TCP_NAGLE_PUSH))))
break;
} else {
/* 当不止一个skb时,通过TSO计算是否需要延时发送 */
/* 如果需要TSO分段,则检测该报文是否应该延时发送。
* tcp_tso_should_defer()用来检测GSO段是否需要延时发送。
* 在段中有FIN标志,或者不处于open拥塞状态,或者TSO段延时超过2个时钟滴答,
* 或者拥塞窗口和发送窗口的最小值大于64K或三倍的当前有效MSS,在这些情况下会立即发送,
* 而其他情况下会延时发送,这样主要是为了减少软GSO分段的次数,以提高性能。
*/
if (!push_one && tcp_tso_should_defer(sk, skb))
break;
}
limit = mss_now;
/* 在TSO分片大于1的情况下,且TCP不是URG模式。通过MSS计算发送数据的limit
* 以发送窗口和拥塞窗口的最小值作为分段段长*/
*/
if (tso_segs > 1 && !tcp_urg_mode(tp))
limit = tcp_mss_split_point(sk, skb, mss_now,
cwnd_quota);
/* 当skb的长度大于限制时,需要调用tso_fragment分片,如果分段失败则暂不发送 */
if (skb->len > limit &&
unlikely(tso_fragment(sk, skb, limit, mss_now)))
break;
/* 以上6行:根据条件,可能需要对SKB中的报文进行分段处理,分段的报文包括两种:
* 一种是普通的用MSS分段的报文,另一种则是TSO分段的报文。
* 能否发送报文主要取决于两个条件:一是报文需完全在发送窗口中,而是拥塞窗口未满。
* 第一种报文,应该不会再分段了,因为在tcp_sendmsg()中创建报文的SKB时已经根据MSS处理了,
* 而第二种报文,则一般情况下都会大于MSS,因为通过TSO分段的段有可能大于拥塞窗口的剩余空间,
* 如果是这样,就需要以发送窗口和拥塞窗口的最小值作为段长对报文再次分段。
*/
/* 更新tcp的时间戳,记录此报文发送的时间 */
TCP_SKB_CB(skb)->when = tcp_time_stamp;
if (unlikely(tcp_transmit_skb(sk, skb, 1, gfp)))
break;
/* Advance the send_head. This one is sent out.
* This call will increment packets_out.
*/
/* 更新统计,并启动重传计时器 */
/* 调用tcp_event_new_data_sent()-->tcp_advance_send_head()更新sk_send_head,
* 即取发送队列中的下一个SKB。同时更新snd_nxt,即等待发送的下一个TCP段的序号,
* 然后统计发出但未得到确认的数据报个数。最后如果发送该报文前没有需要确认的报文,
* 则复位重传定时器,对本次发送的报文做重传超时计时。
*/
tcp_event_new_data_sent(sk, skb);
/* 更新struct tcp_sock中的snd_sml字段。snd_sml表示最近发送的小包(小于MSS的段)的最后一个字节序号,
* 在发送成功后,如果报文小于MSS,即更新该字段,主要用来判断是否启动nagle算法
*/
tcp_minshall_update(tp, mss_now, skb);
sent_pkts++;
if (push_one)
break;
}
/* 如果本次有数据发送,则对TCP拥塞窗口进行检查确认。*/
if (likely(sent_pkts)) {
tcp_cwnd_validate(sk);
return 0;
}
/*
* 如果本次没有数据发送,则根据已发送但未确认的报文数packets_out和sk_send_head返回,
* packets_out不为零或sk_send_head为空都视为有数据发出,因此返回成功。
*/
return !tp->packets_out && tcp_send_head(sk);
}
|