struct qfq_aggregate { struct hlist_node next; /* Link for the slot list. */
u64 S, F; /* flow timestamps (exact) */
/* group we belong to. In principle we would need the index, *whichislog_2(lmax/weight),butweneverreferenceit *directly,onlythegroup.
*/ struct qfq_group *grp;
/* these are copied from the flowset. */
u32 class_weight; /* Weight of each class in this aggregate. */ /* Max pkt size for the classes in this aggregate, DRR quantum. */ int lmax;
u32 inv_w; /* ONE_FP/(sum of weights of classes in aggr.). */
u32 budgetmax; /* Max budget for this aggregate. */
u32 initial_budget, budget; /* Initial and current budget. */
int num_classes; /* Number of classes in this aggr. */ struct list_head active; /* DRR queue of active classes. */
struct hlist_node nonfull_next; /* See nonfull_aggs in qfq_sched. */
};
struct qfq_group {
u64 S, F; /* group timestamps (approx). */ unsignedint slot_shift; /* Slot shift. */ unsignedint index; /* Group index. */ unsignedint front; /* Index of the front slot. */ unsignedlong full_slots; /* non-empty slots */
/* Array of RR lists of active aggregates. */ struct hlist_head slots[QFQ_MAX_SLOTS];
};
u64 oldV, V; /* Precise virtual times. */ struct qfq_aggregate *in_serv_agg; /* Aggregate being served. */
u32 wsum; /* weight sum */
u32 iwsum; /* inverse weight sum */
unsignedlong bitmaps[QFQ_MAX_STATE]; /* Group bitmaps. */ struct qfq_group groups[QFQ_MAX_INDEX + 1]; /* The groups. */
u32 min_slot_shift; /* Index of the group-0 bit in the bitmaps. */
u32 max_agg_classes; /* Max number of classes per aggr. */ struct hlist_head nonfull_aggs; /* Aggs with room for more classes. */
};
/* Update aggregate as a function of the new number of classes. */ staticvoid qfq_update_agg(struct qfq_sched *q, struct qfq_aggregate *agg, int new_num_classes)
{
u32 new_agg_weight;
if (new_num_classes == q->max_agg_classes)
hlist_del_init(&agg->nonfull_next);
if (agg->num_classes > new_num_classes &&
new_num_classes == q->max_agg_classes - 1) /* agg no more full */
hlist_add_head(&agg->nonfull_next, &q->nonfull_aggs);
/* The next assignment may let *agg->initial_budget>agg->budgetmax *hold,wewilltakeitintoaccountincharge_actual_service().
*/
agg->budgetmax = new_num_classes * agg->lmax;
new_agg_weight = agg->class_weight * new_num_classes;
agg->inv_w = ONE_FP/new_agg_weight;
if (agg->grp == NULL) { int i = qfq_calc_index(agg->inv_w, agg->budgetmax,
q->min_slot_shift);
agg->grp = &q->groups[i];
}
if (q->in_serv_agg == agg)
q->in_serv_agg = qfq_choose_next_agg(q);
kfree(agg);
}
/* Deschedule class from within its parent aggregate. */ staticvoid qfq_deactivate_class(struct qfq_sched *q, struct qfq_class *cl)
{ struct qfq_aggregate *agg = cl->agg;
list_del_init(&cl->alist); /* remove from RR queue of the aggregate */ if (list_empty(&agg->active)) /* agg is now inactive */
qfq_deactivate_agg(q, agg);
}
/* Remove class from its parent aggregate. */ staticvoid qfq_rm_from_agg(struct qfq_sched *q, struct qfq_class *cl)
{ struct qfq_aggregate *agg = cl->agg;
cl->agg = NULL; if (agg->num_classes == 1) { /* agg being emptied, destroy it */
qfq_destroy_agg(q, agg); return;
}
qfq_update_agg(q, agg, agg->num_classes-1);
}
/* Deschedule class and remove it from its parent aggregate. */ staticvoid qfq_deact_rm_from_agg(struct qfq_sched *q, struct qfq_class *cl)
{ if (cl->qdisc->q.qlen > 0) /* class is active */
qfq_deactivate_class(q, cl);
qfq_rm_from_agg(q, cl);
}
/* Move class to a new aggregate, matching the new class weight and/or lmax */ staticint qfq_change_agg(struct Qdisc *sch, struct qfq_class *cl, u32 weight,
u32 lmax)
{ struct qfq_sched *q = qdisc_priv(sch); struct qfq_aggregate *new_agg;
/* 'lmax' can range from [QFQ_MIN_LMAX, pktlen + stab overhead] */ if (lmax > QFQ_MAX_LMAX) return -EINVAL;
if (tb[TCA_QFQ_LMAX]) {
lmax = nla_get_u32(tb[TCA_QFQ_LMAX]);
} else { /* MTU size is user controlled */
lmax = psched_mtu(qdisc_dev(sch)); if (lmax < QFQ_MIN_LMAX || lmax > QFQ_MAX_LMAX) {
NL_SET_ERR_MSG_MOD(extack, "MTU size out of bounds for qfq"); return -EINVAL;
}
}
inv_w = ONE_FP / weight;
weight = ONE_FP / inv_w;
if (cl != NULL) {
sch_tree_lock(sch);
old_weight = cl->agg->class_weight;
old_lmax = cl->agg->lmax;
sch_tree_unlock(sch); if (lmax == old_lmax && weight == old_weight) return0; /* nothing to change */
}
delta_w = weight - (cl ? old_weight : 0);
if (q->wsum + delta_w > QFQ_MAX_WSUM) {
NL_SET_ERR_MSG_FMT_MOD(extack, "total weight out of range (%d + %u)",
delta_w, q->wsum); return -EINVAL;
}
if (cl != NULL) { /* modify existing class */ if (tca[TCA_RATE]) {
err = gen_replace_estimator(&cl->bstats, NULL,
&cl->rate_est,
NULL, true,
tca[TCA_RATE]); if (err) return err;
}
existing = true; goto set_change_agg;
}
/* create and init new class */
cl = kzalloc(sizeof(struct qfq_class), GFP_KERNEL); if (cl == NULL) return -ENOBUFS;
if (TC_H_MAJ(skb->priority ^ sch->handle) == 0) {
pr_debug("qfq_classify: found %d\n", skb->priority);
cl = qfq_find_class(sch, skb->priority); if (cl != NULL) return cl;
}
*qerr = NET_XMIT_SUCCESS | __NET_XMIT_BYPASS;
fl = rcu_dereference_bh(q->filter_list);
result = tcf_classify(skb, NULL, fl, &res, false); if (result >= 0) { #ifdef CONFIG_NET_CLS_ACT switch (result) { case TC_ACT_QUEUED: case TC_ACT_STOLEN: case TC_ACT_TRAP:
*qerr = NET_XMIT_SUCCESS | __NET_XMIT_STOLEN;
fallthrough; case TC_ACT_SHOT: return NULL;
} #endif
cl = (struct qfq_class *)res.class; if (cl == NULL)
cl = qfq_find_class(sch, res.classid); return cl;
}
return NULL;
}
/* Generic comparison function, handling wraparound. */ staticinlineint qfq_gt(u64 a, u64 b)
{ return (s64)(a - b) > 0;
}
/* Round a precise timestamp to its slotted value. */ staticinline u64 qfq_round_down(u64 ts, unsignedint shift)
{ return ts & ~((1ULL << shift) - 1);
}
/* return the pointer to the group with lowest index in the bitmap */ staticinlinestruct qfq_group *qfq_ffs(struct qfq_sched *q, unsignedlong bitmap)
{ int index = __ffs(bitmap); return &q->groups[index];
} /* Calculate a mask to mimic what would be ffs_from(). */ staticinlineunsignedlong mask_from(unsignedlong bitmap, int from)
{ return bitmap & ~((1UL << from) - 1);
}
/* *ThestatecomputationreliesonER=0,IR=1,EB=2,IB=3 *Firstcomputeeligibilitycomparinggrp->S,q->V, *thencheckifsomeoneisblockingusandpossiblyaddEB
*/ staticint qfq_calc_state(struct qfq_sched *q, conststruct qfq_group *grp)
{ /* if S > V we are not eligible */ unsignedint state = qfq_gt(grp->S, q->V); unsignedlong mask = mask_from(q->bitmaps[ER], grp->index); struct qfq_group *next;
if (mask) {
next = qfq_ffs(q, mask); if (qfq_gt(grp->F, next->F))
state |= EB;
}
ineligible = q->bitmaps[IR] | q->bitmaps[IB]; if (ineligible) { if (!q->bitmaps[ER]) {
grp = qfq_ffs(q, ineligible); if (qfq_gt(grp->S, q->V))
q->V = grp->S;
}
qfq_make_eligible(q);
}
}
/* Dequeue head packet of the head class in the DRR queue of the aggregate. */ staticstruct sk_buff *agg_dequeue(struct qfq_aggregate *agg, struct qfq_class *cl, unsignedint len)
{ struct sk_buff *skb = qdisc_dequeue_peeked(cl->qdisc);
if (!skb) return NULL;
cl->deficit -= (int) len;
if (cl->qdisc->q.qlen == 0) /* no more packets, remove from list */
list_del_init(&cl->alist); elseif (cl->deficit < qdisc_peek_len(cl->qdisc)) {
cl->deficit += agg->lmax;
list_move_tail(&cl->alist, &agg->active);
}
/* Update F according to the actual service received by the aggregate. */ staticinlinevoid charge_actual_service(struct qfq_aggregate *agg)
{ /* Compute the service received by the aggregate, taking into *accountthat,afterdecreasingthenumberofclassesin *agg,itmayhappenthat *agg->initial_budget-agg->budget>agg->bugdetmax
*/
u32 service_received = min(agg->budgetmax,
agg->initial_budget - agg->budget);
/* Assign a reasonable start time for a new aggregate in group i. *Admissiblevaluesfor\hat(F)aremultiplesof\sigma_i *nogreaterthanV+\sigma_i.Largervaluesmeanthat *wehadawraparoundsoweconsiderthetimestamptobestale. * *IfFisnotstaleandF>=VthenwesetS=F. *OtherwiseweshouldassignS=V,butthismayviolate *theorderinginEB(see[2]).So,ifwehavegroupsinER, *setStotheF_jofthefirstgroupjwhichwouldbeblockingus. *WeareguaranteednottomoveSbackwardbecause *otherwiseourgroupiwouldstillbeblocked.
*/ staticvoid qfq_update_start(struct qfq_sched *q, struct qfq_aggregate *agg)
{ unsignedlong mask;
u64 limit, roundedF; int slot_shift = agg->grp->slot_shift;
if (!qfq_gt(agg->F, q->V) || qfq_gt(roundedF, limit)) { /* timestamp was stale */
mask = mask_from(q->bitmaps[ER], agg->grp->index); if (mask) { struct qfq_group *next = qfq_ffs(q, mask); if (qfq_gt(roundedF, next->F)) { if (qfq_gt(limit, next->F))
agg->S = next->F; else/* preserve timestamp correctness */
agg->S = limit; return;
}
}
agg->S = q->V;
} else/* timestamp is not stale */
agg->S = agg->F;
}
/* Update the timestamps of agg before scheduling/rescheduling it for *service.Inparticular,assigntoagg->Fitsmaximumpossible *value,i.e.,thevirtualfinishtimewithwhichtheaggregate *shouldbelabeledifitusedallitsbudgetonceinservice.
*/ staticinlinevoid
qfq_update_agg_ts(struct qfq_sched *q, struct qfq_aggregate *agg, enum update_reason reason)
{ if (reason != requeue)
qfq_update_start(q, agg); else/* just charge agg for the service received */
agg->S = agg->F;
/* If lmax is lowered, through qfq_change_class, for a class *owningpendingpacketswithlargersizethanthenewvalue *oflmax,thenthefollowingconditionmayhold.
*/ if (unlikely(in_serv_agg->budget < len))
in_serv_agg->budget = 0; else
in_serv_agg->budget -= len;
q->V += (u64)len * q->iwsum;
pr_debug("qfq dequeue: len %u F %lld now %lld\n",
len, (unsignedlonglong) in_serv_agg->F,
(unsignedlonglong) q->V);
/* agg starts to be served, remove it from schedule */
qfq_front_slot_remove(grp);
new_front_agg = qfq_slot_scan(grp);
if (new_front_agg == NULL) /* group is now inactive, remove from ER */
__clear_bit(grp->index, &q->bitmaps[ER]); else {
u64 roundedS = qfq_round_down(new_front_agg->S,
grp->slot_shift); unsignedint s;
agg = cl->agg; /* if the class is active, then done here */ if (cl_is_active(cl)) { if (unlikely(skb == cl->qdisc->ops->peek(cl->qdisc)) &&
list_first_entry(&agg->active, struct qfq_class, alist)
== cl && cl->deficit < len)
list_move_tail(&cl->alist, &agg->active);
return err;
}
/* schedule class for service within the aggregate */
cl->deficit = agg->lmax;
list_add_tail(&cl->alist, &agg->active);
if (list_first_entry(&agg->active, struct qfq_class, alist) != cl ||
q->in_serv_agg == agg) return err; /* non-empty or in service, nothing else to do */
pr_debug("qfq enqueue: new state %d %#lx S %lld F %lld V %lld\n",
s, q->bitmaps[s],
(unsignedlonglong) agg->S,
(unsignedlonglong) agg->F,
(unsignedlonglong) q->V);
/* Update agg ts and schedule agg for service */ staticvoid qfq_activate_agg(struct qfq_sched *q, struct qfq_aggregate *agg, enum update_reason reason)
{
agg->initial_budget = agg->budget = agg->budgetmax; /* recharge budg. */
qfq_update_agg_ts(q, agg, reason); if (q->in_serv_agg == NULL) { /* no aggr. in service or scheduled */
q->in_serv_agg = agg; /* start serving this aggregate */ /* update V: to be in service, agg must be eligible */
q->oldV = q->V = agg->S;
} elseif (agg != q->in_serv_agg)
qfq_schedule_agg(q, agg);
}
for (i = 0; i < q->clhash.hashsize; i++) {
hlist_for_each_entry(cl, &q->clhash.hash[i], common.hnode) { if (cl->qdisc->q.qlen > 0)
qfq_deactivate_class(q, cl);
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.