首页 > 代码库 > TCP源码分析--tcp_write_xmit

TCP源码分析--tcp_write_xmit

版本: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);
}

参考此处