static void tcp_update_pacing_rate(struct sock *sk)
{
const struct tcp_sock *tp = tcp_sk(sk);
u64 rate;
/* set sk_pacing_rate to 200 % of current rate (mss * cwnd / srtt) */
rate = (u64)tp->mss_cache * ((USEC_PER_SEC / 100) << 3);
/* current rate is (cwnd * mss) / srtt
* In Slow Start [1], set sk_pacing_rate to 200 % the current rate.
* In Congestion Avoidance phase, set it to 120 % the current rate.
*
* [1] : Normal Slow Start condition is (tp->snd_cwnd < tp->snd_ssthresh)
* If snd_cwnd >= (tp->snd_ssthresh / 2), we are approaching
* end of slow start and should slow down.
*/
if (tp->snd_cwnd < tp->snd_ssthresh / 2)
rate *= sysctl_tcp_pacing_ss_ratio;
else
rate *= sysctl_tcp_pacing_ca_ratio;
rate *= max(tp->snd_cwnd, tp->packets_out);
if (likely(tp->srtt_us))
do_div(rate, tp->srtt_us);
/* ACCESS_ONCE() is needed because sch_fq fetches sk_pacing_rate
* without any lock. We want to make sure compiler wont store
* intermediate values in this location.
*/
ACCESS_ONCE(sk->sk_pacing_rate) = min_t(u64, rate,
sk->sk_max_pacing_rate);
}
现在的内核不再需要你自己去计算pacing rate了,内核直接就支持pacing rate!因此,我不再需要那个令人恶心的tcp_pacing_recalc_delta函数了!一切更加简单了!
我的修改在于:
1.我不在针对每一个数据包的传输实施pacing,我只对正常的数据包实施pacing,这意味着重传数据包和纯ACK不会被pacing逻辑控制;struct {
struct timer_list timer;
u8 pacing;
u64 next_to_send;
} pacing;
这里就不解释了。
while ((skb = tcp_send_head(sk))) {
unsigned int limit;
u64 now = ktime_get_ns(); // 我采集了u64粒度的时间戳
tso_segs = tcp_init_tso_segs(skb, mss_now);
...
if (unlikely(!tcp_snd_wnd_test(tp, skb, mss_now)))
break;
if (sysctl_tcp_pacing && tp->pacing.pacing == 1) { // 这里的if内是我添加的代码
u32 plen;
u64 rate, len;
if (now < tp->pacing.next_to_send) {
// 如果此时不允许发送,那就预约延后的某个时间发送
tcp_pacing_reset_timer(sk, tp->pacing.next_to_send);
break;
}
// 如果设定了确定的rate,那就按照这个rate发送,这个对固定速率需求的流特别好用。更好的做法是将其安装在socket里而不是一个全局的参数!
// 然则如果安装在socket里,就必须在应用层用setsockopt来设置,需要修改应用程序,而如果是全局参数,则对应用完全透明。利弊由谁决定?!
rate = sysctl_tcp_rate ? sysctl_tcp_rate:sk->sk_pacing_rate;
rate = min(rate, sk->sk_pacing_rate);
plen = skb->len + MAX_HEADER; // 倾倒在网络中的不光有TCP数据段,还有协议头!别光想着TCP!
// 计算延后时间:时间=数据量/速度
len = (u64)plen * NSEC_PER_SEC;
if (rate)
do_div(len, rate);
// 设置下一次发送的时间为:当前时间+发送当前数据的时间
tp->pacing.next_to_send = now + len;
}
if (tso_segs == 1) {
...
void tcp_init_xmit_timers(struct sock *sk)
{
inet_csk_init_xmit_timers(sk, &tcp_write_timer, &tcp_delack_timer,
&tcp_keepalive_timer);
init_timer(&(tcp_sk(sk)->pacing.timer));
tcp_sk(sk)->pacing.timer.function = &tcp_pacing_timer;
tcp_sk(sk)->pacing.timer.data = (unsigned long) sk;
}
void tcp_pacing_reset_timer(struct sock *sk, u64 expires)
{
struct tcp_sock *tp = tcp_sk(sk);
u32 timeout = nsecs_to_jiffies(expires);
if(!sysctl_tcp_pacing || !tp->pacing.pacing)
return;
if (!mod_timer(&tp->pacing.timer, timeout))
sock_hold(sk);
}
static void tcp_pacing_timer(unsigned long data)
{
struct sock *sk = (struct sock*)data;
struct tcp_sock *tp = tcp_sk(sk);
if(!sysctl_tcp_pacing || !tp->pacing.pacing)
return;
bh_lock_sock(sk);
if (sock_owned_by_user(sk)) {
// 此时socket被用户态进程占据,比如正在recv,poll等,那么就将handler过程委托给这类用户态进程,好在用户态进程在释放socket时可以有release回调可调用!
if (!test_and_set_bit(TCP_PACING_TIMER_DEFERRED, &tcp_sk(sk)->tsq_flags))
sock_hold(sk);
goto out_unlock;
}
if (sk->sk_state == TCP_CLOSE)
goto out;
if(!sk->sk_send_head){
goto out;
}
tcp_push_pending_frames(sk);
out:
if (tcp_memory_pressure)
sk_mem_reclaim(sk);
out_unlock:
bh_unlock_sock(sk);
sock_put(sk);
}
if (flags & (1UL << TCP_PACING_TIMER_DEFERRED)) {
if(sk->sk_send_head)
tcp_push_pending_frames(sk);
__sock_put(sk);
}
左边是pacing的效果,右边是非pacing的效果,统一使用cubic算法。这个pacing对于额定速率的流媒体传输非常有用!
------------------------------------
这个版本大致可以实现pacing的效果,然而却不够,实际上的效果好坏取决于计算的精准度。以HZ250为例,每隔4毫秒会有一个时钟中断,如果用普通timer的话,RTT在4毫秒以内的pacing将会工作不正常,所以我采用了NS来计算,最终再还原到jiffies,但这仍然不够(不过对于长肥网络这个版本就够了)。所以我必须实现基于hrtimer的高精度版本。