TCP的TSO处理(一)(四)

2014-11-24 09:53:38 · 作者: · 浏览: 1
should_defer (struct sock *sk, struct sk_buff *skb)
{
struct tcp_sock *tp = tcp_sk(sk);
const struct inet_connection_sock *icsk = inet_csk(sk);
u32 in_flight, send_win, cong_win, limit;
int win_divisor;

/* 如果此skb包含结束标志,则马上发送*/
if (TCP_SKB_CB(skb)->flags & TCPHDR_FIN)
goto send_now;

/* 如果此时不处于Open态,则马上发送*/
if (icsk->icsk_ca_state != TCP_CA_Open)
goto send_now;

/* Defer for less than two clock ticks.
* 上个skb被延迟了,且超过现在1ms以上,则不再延迟。
* 也就是说,TSO延迟不能超过2ms!
*/
if (tp->tso_deferred && (((u32)jiffies <<1) >> 1) - (tp->tso_deferred >> 1) > 1)
goto send_now;

in_flight = tcp_packets_in_flight(tp);
/* 如果此数据段不用分片,或者受到拥塞窗口的限制不能发包,则报错*/
BUG_ON(tcp_skb_pcount(skb) <= 1 || (tp->snd_cwnd <= in_flight));
/* 通告窗口的剩余大小*/
send_win = tcp_wnd_end(tp) - TCP_SKB_CB(skb)->seq;
/* 拥塞窗口的剩余大小*/
cong_win = (tp->snd_cwnd - in_flight) * tp->mss_cache;
/* 取其小者作为最终的发送限制*/
limit = min(send_win, cong_win);

/*If a full-sized TSO skb can be sent, do it.
* 一般来说是64KB
*/
if (limit >= sk->sk_gso_max_size)
goto send_now;

/* Middle in queue won't get any more data, full sendable already */
if ((skb != tcp_write_queue_tail(sk)) && (limit >= skb->len))
goto send_now;

win_divisor = ACCESS_ONCE(sysctl_tcp_tso_win_divisor);
if (win_divisor) {
/* 一个RTT内允许发送的最大字节数*/
u32 chunk = min(tp->snd_wnd, tp->snd_cwnd * tp->mss_cache);
chunk /= win_divisor; /* 单个TSO段可消耗的发送量*/

/* If at least some fraction of a window is available, just use it. */
if (limit >= chunk)
goto send_now;
} else {
/* Different approach, try not to defer past a single ACK.
* Receiver should ACK every other full sized frame, so if we have space for
* more than 3 frames then send now.
*/
if (limit > tcp_max_burst(tp) * tp->mss_cache)
goto send_now;
}

/* OK, it looks like it is advisable to defer. */
tp->tso_deferred = 1 | (jiffies << 1); /* 记录此次defer的时间戳*/

return 1;

send_now:
tp->tso_deferred = 0;
return 0;
}

/* Returns end sequence number of the receiver's advertised window */
static inline u32 tcp_wnd_end (const struct tcp_sock *tp)
{
/* snd_wnd的单位为字节*/
return tp->snd_una + tp->snd_wnd;
}
tcp_tso_win_divisor:单个TSO段可消耗拥塞窗口的比例,默认值为3。

符合以下任意条件,不会TSO延迟,可马上发送:
(1) 数据包带有FIN标志。传输快结束了,不宜延迟。
(2) 发送方不处于Open拥塞状态。处于异常状态时,不宜延迟。
(3) 上一次skb被延迟了,且距离现在大于等于2ms。延迟不能超过2ms。
(4) min(send_win, cong_win) > full-sized TSO skb。允许发送的数据量超过TSO一次能处理的最大值,没必要再defer。
(5) skb处于发送队列中间,且允许整个skb一起发送。处于发送队列中间的skb不能再获得新的数据,没必要再defer。
(6) tcp_tso_win_divisor有设置时,limit > 单个TSO段可消耗的数据量,即min(snd_wnd, snd_cwnd * mss_cache) / tcp_tso_win_divisor。
(7) tcp_tso_win_divisor没有设置时,limit > tcp_max_burst(tp) * mss_cache,一般是3个数据包。

条件4、5、6/7,都是limit > 某个阈值,就可以马上发送。这个因为通过这几个条件,可以确定此时发送是受到应用程序的限制,而不是
通告窗口或者拥塞窗口。在应用程序发送的数据量很少的情况下,不宜采用TSO Nagle,因为这会影响此类应用。
我们注意到tcp_is_cwnd_limited()中的注释说:
" This is the inverse of cwnd check in tcp_tso_should_defer",所以可以认为在tcp_tso_should_defer()中包含判断
tcp_is_not_cwnd_limited (或者tcp_is_application_limited) 的条件。

符合以下所有条件,才会进行TSO延迟:
(1) 数据包不带有FIN标志。
(2) 发送方处于Open拥塞状态。
(3) 距离上一次延迟的时间在2ms以内。
(4) 允许发送的数据量小于sk_gso_max_size。
(5) skb处于发送队列末尾,或者skb不能整个发送出去。
(6) tcp_tso_win_divisor有设置时,允许发送的数据量不大于单个TSO段可消耗的。
(7) tcp_tso_win_divisor没有设置时,允许发送的数据量不大于3个包