概述
文章目录
- 简介
- 重要接口
- enqueue():
- dequeue() :
- 函数分析
- 红黑树的作用
- 什么时候会往红黑树上添加节点
- 什么时候把红黑树上的节点取下来
- 流下一次允许发包时间的计算
- 分数的作用
- 什么时候减分
- 什么时候加分
- 公平体现在哪里
- 总结
简介
FQ主要用于本地流量调节,某一个套接字上的所有报文被认为是一条流,这条流的速度可以设置在skb->sk上,如果没设置则使用公用的另一套调度。本文关注基于流的调度。
代码中会使用一个flows结构体对应一个套接字,并挂接在红黑树上。
突发避免(也叫pacing)功能:
传输层可以设置sk->sk_pacing_rate,作为这条流的发包速度,这个报文调度器会根据速度在每个包之间添加延时。
重要接口
enqueue():
在红黑树上找到这条流,如果不存在则创建一条流并添加进红黑树。添加skb到每条流的skb队列(fifo)。
dequeue() :
服务于循环中的流。
函数分析
static int fq_enqueue(struct sk_buff *skb, struct Qdisc *sch,
struct sk_buff **to_free)
{
struct fq_sched_data *q = qdisc_priv(sch);
struct fq_flow *f;
if (unlikely(sch->q.qlen >= sch->limit))
return qdisc_drop(skb, sch, to_free);
f = fq_classify(skb, q);
if (unlikely(f->qlen >= q->flow_plimit && f != &q->internal)) {
q->stat_flows_plimit++;
return qdisc_drop(skb, sch, to_free);
}
f->qlen++;
qdisc_qstats_backlog_inc(sch, skb);
if (fq_flow_is_detached(f)) {
struct sock *sk = skb->sk;
fq_flow_add_tail(&q->new_flows, f);
if (time_after(jiffies, f->age + q->flow_refill_delay))
f->credit = max_t(u32, f->credit, q->quantum);
if (sk && q->rate_enable) {
if (unlikely(smp_load_acquire(&sk->sk_pacing_status) !=
SK_PACING_FQ))
smp_store_release(&sk->sk_pacing_status,
SK_PACING_FQ);
}
q->inactive_flows--;
}
/* Note: this overwrites f->age */
flow_queue_add(f, skb);
if (unlikely(f == &q->internal)) {
q->stat_internal_packets++;
}
sch->q.qlen++;
return NET_XMIT_SUCCESS;
}
参数说明,skb就是要发送的数据包,sch就是对应的网卡设备上的队列,第三个参数不关注,这是有关锁而不能立即释放skb的需要。
简单来说,在条件允许的情况下,把skb加入对应的流。这里有个问题,为什么不直接把流挂在对应的套接字上,何必每次都去找。
static void flow_queue_add(struct fq_flow *flow, struct sk_buff *skb)
{
struct rb_node **p, *parent;
struct sk_buff *head, *aux;
fq_skb_cb(skb)->time_to_send = skb->tstamp ?: ktime_get_ns();
head = flow->head;
if (!head ||
fq_skb_cb(skb)->time_to_send >= fq_skb_cb(flow->tail)->time_to_send) {
if (!head)
flow->head = skb;
else
flow->tail->next = skb;
flow->tail = skb;
skb->next = NULL;
return;
}
p = &flow->t_root.rb_node;
parent = NULL;
while (*p) {
parent = *p;
aux = rb_to_skb(parent);
if (fq_skb_cb(skb)->time_to_send >= fq_skb_cb(aux)->time_to_send)
p = &parent->rb_right;
else
p = &parent->rb_left;
}
rb_link_node(&skb->rbnode, parent, p);
rb_insert_color(&skb->rbnode, &flow->t_root);
}
重点,设置skb的发送时间,这是由tcp自己的算好的,至于加入的是红黑树还是链表,为了便于理解,认为加入链表就好。
static struct sk_buff *fq_dequeue(struct Qdisc *sch)
{
struct fq_sched_data *q = qdisc_priv(sch);
struct fq_flow_head *head;
struct sk_buff *skb;
struct fq_flow *f;
unsigned long rate;
u32 plen;
u64 now;
if (!sch->q.qlen)
return NULL;
skb = fq_dequeue_head(sch, &q->internal);
if (skb)
goto out;
now = ktime_get_ns();
fq_check_throttled(q, now);
begin:
head = &q->new_flows;
if (!head->first) {
head = &q->old_flows;
if (!head->first) {
if (q->time_next_delayed_flow != ~0ULL)
qdisc_watchdog_schedule_ns(&q->watchdog,
q->time_next_delayed_flow);
return NULL;
}
}
f = head->first;
if (f->credit <= 0) {
f->credit += q->quantum;
head->first = f->next;
fq_flow_add_tail(&q->old_flows, f);
goto begin;
}
skb = fq_peek(f);
if (skb) {
u64 time_next_packet = max_t(u64, fq_skb_cb(skb)->time_to_send,
f->time_next_packet);
if (now < time_next_packet) {
head->first = f->next;
f->time_next_packet = time_next_packet;
fq_flow_set_throttled(q, f);
goto begin;
}
if (time_next_packet &&
(s64)(now - time_next_packet - q->ce_threshold) > 0) {
INET_ECN_set_ce(skb);
q->stat_ce_mark++;
}
}
skb = fq_dequeue_head(sch, f);
if (!skb) {
head->first = f->next;
/* force a pass through old_flows to prevent starvation */
if ((head == &q->new_flows) && q->old_flows.first) {
fq_flow_add_tail(&q->old_flows, f);
} else {
fq_flow_set_detached(f);
q->inactive_flows++;
}
goto begin;
}
prefetch(&skb->end);
plen = qdisc_pkt_len(skb);
f->credit -= plen;
if (!q->rate_enable)
goto out;
rate = q->flow_max_rate;
/* If EDT time was provided for this skb, we need to
* update f->time_next_packet only if this qdisc enforces
* a flow max rate.
*/
if (!skb->tstamp) {
if (skb->sk)
rate = min(skb->sk->sk_pacing_rate, rate);
if (rate <= q->low_rate_threshold) {
f->credit = 0;
} else {
plen = max(plen, q->quantum);
if (f->credit > 0)
goto out;
}
}
if (rate != ~0UL) {
u64 len = (u64)plen * NSEC_PER_SEC;
if (likely(rate))
len = div64_ul(len, rate);
/* Since socket rate can change later,
* clamp the delay to 1 second.
* Really, providers of too big packets should be fixed !
*/
if (unlikely(len > NSEC_PER_SEC)) {
len = NSEC_PER_SEC;
q->stat_pkts_too_long++;
}
/* Account for schedule/timers drifts.
* f->time_next_packet was set when prior packet was sent,
* and current time (@now) can be too late by tens of us.
*/
if (f->time_next_packet)
len -= min(len/2, now - f->time_next_packet);
f->time_next_packet = now + len;
}
out:
qdisc_bstats_update(sch, skb);
return skb;
}
参数说明,传入的参数是网卡队列,而不是某个流,返回的是一个skb。
大致流程没有问题,超时加该流有剩余分数即可以发包。
下面讨论几个重要的疑点:
- 红黑树的作用
- 分数的作用
- 公平体现在哪里
红黑树的作用
什么时候会往红黑树上添加节点
取下一个报文,但是该报文的发送时间还未超时,会将这个流取下,添加到红黑树上,排序根据是下一次允许发包时间。
什么时候把红黑树上的节点取下来
在函数fq_dequeue中的第一步,会顺序检查红黑树上的流,如果到了该流发包的时间,则把该流取下来,一直遍历到该某个流超时时间大于现在。
流下一次允许发包时间的计算
发完一个包之后,使用这个包的大小除以BtlBw,ns为单位,加上这个大小,就是下一次允许发包的时间。
/* Account for schedule/timers drifts.
* f->time_next_packet was set when prior packet was sent,
* and current time (@now) can be too late by tens of us.
*/
if (f->time_next_packet)
len -= min(len/2, now - f->time_next_packet);
在计算下一个超时时间时还有这么一段代码,意思是如果优先队列先发送了,那么这个队列发包就会延后了,这里给这个流加速。
分数的作用
什么时候减分
当一个包发完之后,分数需要相应地减去包大小的分数。
什么时候加分
当分数被减到0及以下之后,每次都会加一定的分数,大小是两倍的mtu。
公平体现在哪里
公平就是体现在分数,分数是以字节计算的,而不是包的个数计算的,比如,流1,每次发大包1500字节,流2,每次发小包500字节,假设流1排在流2前面,分数初始值相等,假设都为1500,每次增加1500,两者的速度也是相等的。
- 第一轮,流1,共发送1500字节,分数减到0,流2,共发送0字节,分数1500,队列->流1->流2
- 第二轮,流1,共发送1500字节,分数加到1500,流2,共发送500字节,分数1000,队列->流2->流1
- 第三轮,。。。
- 第四轮,。。。
- 第五轮,流1,共发送3000字节,分数减到0,流2,共发送1500字节,分数1500,队列->流1->流2
以上流程会一直循环下去,所以长时间来看,保证了两条流发送的总的字节数是相等的。
总结
- 发包速率由套接字自己设定的速率控制,即BBR算法中或者其他算法,设置的BtlBw计算得到。
- 公平体现在分数上,使用分数保证了上层设置的速度。
最后
以上就是幽默飞机为你收集整理的Linux Fair Queue Packet Scheduler (FQ)公平队列报文调度器的全部内容,希望文章能够帮你解决Linux Fair Queue Packet Scheduler (FQ)公平队列报文调度器所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复