Earliest Departure Timeモデル
バージョン4.20から導入されたEDTモデルについて。EDTの考えは前述したVan Jacobsonの「Evolving from AFAP: Teaching NICs about time」を見てもらうのが手っ取り早い。今まではNICに対して「何」を送るかしか指定しなかったが、「何」に加えて「いつ」送るかも指定すべきと言うのが基本的なアイデアだろうか。EDTの実装として、SIGCOMM17で発表されたCarousel(日本語で言うとメリーゴーランド)が例示されているけど、残念ながら現時点で実装は公開されていない。
Linuxに取り込まれているEDTパッチセットは次の8つのパッチから構成される。
72b0094f9182 tcp: switch tcp_clock_ns() to CLOCK_TAI base
2fd66ffba507 tcp: introduce tcp_skb_timestamp_us() helper
9799ccb0e984 tcp: add tcp_wstamp_ns socket field
d3edd06ea8ea tcp: provide earliest departure time in skb->tstamp
fd2bca2aa789 tcp: switch internal pacing timer to CLOCK_TAI
ab408b6dc744 tcp: switch tcp and sch_fq to new earliest departure time model
c092dd5f4a7f tcp: switch tcp_internal_pacing() to tcp_wstamp_ns
90caf67b01fa net_sched: sch_fq: remove dead code dealing with retransmits
さらに第2弾のパッチセットがこちら。
5f6188a8003d tcp: do not change tcp_wstamp_ns in tcp_mstamp_refresh
76a9ebe811fb net: extend sk_pacing_rate to unsigned long
a7a2563064e9 tcp: mitigate scheduling jitter in EDT pacing model
7baf33bdac37 net_sched: sch_fq: no longer use skb_is_tcp_pure_ack()
864e5c090749 tcp: optimize tcp internal pacing
825e1c523d50 tcp: cdg: use tcp high resolution clock cache
ちょっとだけソースを眺める。TCPコントロールブロック(tcp_sock)に追加されたtcp_wstamp_nsがキモで、これは次に送る予定のパケットの送信時刻を示している。つまりソケットごとにEDTを保持できることを意味する。
net/ipv4/tcp_output.c
static void tcp_update_skb_after_send(struct sock *sk, struct sk_buff *skb,
u64 prior_wstamp)
{
struct tcp_sock *tp = tcp_sk(sk);
if (sk->sk_pacing_status != SK_PACING_NONE) {
unsigned long rate = sk->sk_pacing_rate;
/* Original sch_fq does not pace first 10 MSS
* Note that tp->data_segs_out overflows after 2^32 packets,
* this is a minor annoyance.
*/
if (rate != ~0UL && rate && tp->data_segs_out >= 10) {
u64 len_ns = div64_ul((u64)skb->len * NSEC_PER_SEC, rate);
u64 credit = tp->tcp_wstamp_ns - prior_wstamp;
/* take into account OS jitter */
len_ns -= min_t(u64, len_ns / 2, credit);
tp->tcp_wstamp_ns += len_ns;
}
}
list_move_tail(&skb->tcp_tsorted_anchor, &tp->tsorted_sent_queue);
}
パケット送信後に呼ばれるtcp_update_skb_after_send関数にて、ペーシングが有効な場合、次に送信されるパケットサイズに応じてtcp_wstamp_nsが更新される。で、この値はsch_sqやTCP BBRから参照される。以下ではinternal TCP pacingの場合を見ていく。
static bool tcp_pacing_check(struct sock *sk)
{
struct tcp_sock *tp = tcp_sk(sk);
if (!tcp_needs_internal_pacing(sk))
return false;
if (tp->tcp_wstamp_ns <= tp->tcp_clock_cache)
return false;
if (!hrtimer_is_queued(&tp->pacing_timer)) {
hrtimer_start(&tp->pacing_timer,
ns_to_ktime(tp->tcp_wstamp_ns),
HRTIMER_MODE_ABS_PINNED_SOFT);
sock_hold(sk);
}
return true;
}
<snip>
static bool 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;
bool is_cwnd_limited = false, is_rwnd_limited = false;
u32 max_segs;
sent_pkts = 0;
tcp_mstamp_refresh(tp);
<snip>
while ((skb = tcp_send_head(sk))) {
<snip>
if (tcp_pacing_check(sk))
break;
tcp_write_xmit関数はパケット送信関数。tcp_pacing_check関数で次のパケットの送信予定時刻に達してない場合は、pacing_timerを起動して送信は行わない。
/* Note: Called under soft irq.
* We can call TCP stack right away, unless socket is owned by user.
*/
enum hrtimer_restart tcp_pace_kick(struct hrtimer *timer)
{
struct tcp_sock *tp = container_of(timer, struct tcp_sock, pacing_timer);
struct sock *sk = (struct sock *)tp;
tcp_tsq_handler(sk);
sock_put(sk);
return HRTIMER_NORESTART;
}
pacing_timerハンドラに登録されているのはtcp_pace_kick関数で、中身はTSQ (TCP Small Queues)関係の処理。TSQについては割愛するが、条件が揃えば再送処理を実行する。
この記事が気に入ったらサポートをしてみませんか?