版本:2.6.33.4
发送端 tcp_write_xmit 函数
/* 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,则说明拥塞窗口已满,目前不能发送。
<span style="white-space:pre"> </span> * 拿拥塞窗口和正在网络上传输的包数目相比,如果拥塞窗口还大,
<span style="white-space:pre"> </span> * 则返回拥塞窗口减掉正在网络上传输的包数目剩下的大小。
<span style="white-space:pre"> </span> * 该函数目的是判断正在网络上传输的包数目是否超过拥塞窗口,
* 如果超过了,则不发送。
*/
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分段的报文。
<span style="white-space:pre"> </span> * 能否发送报文主要取决于两个条件:一是报文需完全在发送窗口中,而是拥塞窗口未满。
<span style="white-space:pre"> </span> * 第一种报文,应该不会再分段了,因为在tcp_sendmsg()中创建报文的SKB时已经根据MSS处理了,
<span style="white-space:pre"> </span> * 而第二种报文,则一般情况下都会大于MSS,因为通过TSO分段的段有可能大于拥塞窗口的剩余空间,
<span style="white-space:pre"> </span> * 如果是这样,就需要以发送窗口和拥塞窗口的最小值作为段长对报文再次分段。
<span style="white-space:pre"> </span> */
/* 更新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);
}
参考 此处