100字范文,内容丰富有趣,生活中的好帮手!
100字范文 > Linux Fair Queue Packet Scheduler (FQ)公平队列报文调度器

Linux Fair Queue Packet Scheduler (FQ)公平队列报文调度器

时间:2018-11-06 07:05:39

相关推荐

Linux Fair Queue Packet Scheduler (FQ)公平队列报文调度器

文章目录

简介重要接口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;elseflow->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;elsep = &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计算得到。公平体现在分数上,使用分数保证了上层设置的速度。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。