mcs: update refills based on spec
This is a list of fixes that came up while working on the verification spec for the mcs changes. - trigger a timer tick if we are unable to split a refill due to the refill list being full. - make refill_ordered more useful - pull the thread out of the scheduler before updating it - simplify refill logic at verifications request - Add unused to refill_sum - Don't refill_split_check if consumed is empty - sched_control: fix double increment bug - sched-control: charge before reconfiguring ksCurSC - Charge round robin threads differently Sporadic server refill rules do not behave correctly for round robin threads, instead, change the logic. Round robin threads have 2 refills: current and next.
This commit is contained in:
parent
798fb76ded
commit
9253704d2c
8 changed files with 248 additions and 176 deletions
|
|
@ -53,6 +53,11 @@ static inline word_t refill_size(sched_context_t *sc)
|
|||
return sc->scRefillTail + 1u + (sc->scRefillMax - sc->scRefillHead);
|
||||
}
|
||||
|
||||
static inline bool_t refill_full(sched_context_t *sc)
|
||||
{
|
||||
return refill_size(sc) == sc->scRefillMax;
|
||||
}
|
||||
|
||||
static inline bool_t refill_single(sched_context_t *sc)
|
||||
{
|
||||
return sc->scRefillHead == sc->scRefillTail;
|
||||
|
|
@ -101,9 +106,8 @@ void refill_update(sched_context_t *sc, ticks_t new_period, ticks_t new_budget,
|
|||
* `used` amount from its current replenishment without
|
||||
* depleting the budget, i.e refill_expired returns false.
|
||||
*
|
||||
* return any uncharged usage.
|
||||
*/
|
||||
ticks_t refill_budget_check(sched_context_t *sc, ticks_t used);
|
||||
void refill_budget_check(sched_context_t *sc, ticks_t used, ticks_t capacity);
|
||||
|
||||
/*
|
||||
* Charge a scheduling context `used` amount from its
|
||||
|
|
|
|||
|
|
@ -86,28 +86,42 @@ static inline bool_t isHighestPrio(word_t dom, prio_t prio)
|
|||
}
|
||||
|
||||
#ifdef CONFIG_KERNEL_MCS
|
||||
static inline bool_t PURE isRoundRobin(sched_context_t *sc)
|
||||
{
|
||||
return sc->scPeriod == 0;
|
||||
}
|
||||
|
||||
static inline bool_t isCurDomainExpired(void)
|
||||
{
|
||||
return CONFIG_NUM_DOMAINS > 1 &&
|
||||
ksDomainTime < (NODE_STATE(ksConsumed) + getKernelWcetTicks());
|
||||
ksDomainTime < (NODE_STATE(ksConsumed) + MIN_BUDGET);
|
||||
}
|
||||
|
||||
static inline void commitTime(void)
|
||||
{
|
||||
|
||||
if (likely(NODE_STATE(ksConsumed) > 0 && (NODE_STATE(ksCurThread) != NODE_STATE(ksIdleThread)))) {
|
||||
if (likely(NODE_STATE(ksConsumed) > 0)) {
|
||||
/* if this function is called the head refil must be sufficient to
|
||||
* charge ksConsumed */
|
||||
assert(refill_sufficient(NODE_STATE(ksCurSC), NODE_STATE(ksConsumed)));
|
||||
/* and it must be ready to use */
|
||||
assert(refill_ready(NODE_STATE(ksCurSC)));
|
||||
refill_split_check(NODE_STATE(ksCurSC), NODE_STATE(ksConsumed));
|
||||
|
||||
if (isRoundRobin(NODE_STATE(ksCurSC))) {
|
||||
/* for round robin threads, there are only two refills: the HEAD, which is what
|
||||
* we are consuming, and the tail, which is what we have consumed */
|
||||
assert(refill_size(NODE_STATE(ksCurSC)) == MIN_REFILLS);
|
||||
REFILL_HEAD(NODE_STATE(ksCurSC)).rAmount -= NODE_STATE(ksConsumed);
|
||||
REFILL_TAIL(NODE_STATE(ksCurSC)).rAmount += NODE_STATE(ksConsumed);
|
||||
} else {
|
||||
refill_split_check(NODE_STATE(ksCurSC), NODE_STATE(ksConsumed));
|
||||
}
|
||||
assert(refill_sufficient(NODE_STATE(ksCurSC), 0));
|
||||
assert(refill_ready(NODE_STATE(ksCurSC)));
|
||||
}
|
||||
if (CONFIG_NUM_DOMAINS > 1) {
|
||||
if (unlikely(ksDomainTime < NODE_STATE(ksConsumed))) {
|
||||
ksDomainTime = 0;
|
||||
} else {
|
||||
ksDomainTime -= NODE_STATE(ksConsumed);
|
||||
}
|
||||
assert(ksDomainTime > NODE_STATE(ksConsumed));
|
||||
assert(ksDomainTime - NODE_STATE(ksConsumed) >= MIN_BUDGET);
|
||||
ksDomainTime -= NODE_STATE(ksConsumed);
|
||||
}
|
||||
|
||||
NODE_STATE(ksConsumed) = 0llu;
|
||||
|
|
@ -171,6 +185,9 @@ static inline void updateRestartPC(tcb_t *tcb)
|
|||
*/
|
||||
void endTimeslice(void);
|
||||
|
||||
/* called when a thread has used up its head refill */
|
||||
void chargeBudget(ticks_t capacity, ticks_t consumed);
|
||||
|
||||
/* Update the kernels timestamp and stores in ksCurTime.
|
||||
* The difference between the previous kernel timestamp and the one just read
|
||||
* is stored in ksConsumed.
|
||||
|
|
@ -199,31 +216,23 @@ static inline bool_t checkBudget(void)
|
|||
/* currently running thread must have available capacity */
|
||||
assert(refill_ready(NODE_STATE(ksCurSC)));
|
||||
|
||||
if (unlikely(NODE_STATE(ksCurThread) == NODE_STATE(ksIdleThread))) {
|
||||
ticks_t capacity = refill_capacity(NODE_STATE(ksCurSC), NODE_STATE(ksConsumed));
|
||||
/* if the budget isn't enough, the timeslice for this SC is over. For
|
||||
* round robin threads this is sufficient, however for periodic threads
|
||||
* we also need to check there is space to schedule the replenishment - if the refill
|
||||
* is full then the timeslice is also over as the rest of the budget is forfeit. */
|
||||
if (likely(capacity >= MIN_BUDGET && (isRoundRobin(NODE_STATE(ksCurSC)) ||
|
||||
!refill_full(NODE_STATE(ksCurSC))))) {
|
||||
if (unlikely(isCurDomainExpired())) {
|
||||
commitTime();
|
||||
rescheduleRequired();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
ticks_t capacity = refill_capacity(NODE_STATE(ksCurSC), NODE_STATE(ksConsumed));
|
||||
if (unlikely(capacity < MIN_BUDGET)) {
|
||||
if (capacity == 0) {
|
||||
NODE_STATE(ksConsumed) = refill_budget_check(NODE_STATE(ksCurSC), NODE_STATE(ksConsumed));
|
||||
}
|
||||
if (NODE_STATE(ksConsumed) > 0) {
|
||||
refill_split_check(NODE_STATE(ksCurSC), NODE_STATE(ksConsumed));
|
||||
}
|
||||
NODE_STATE(ksConsumed) = 0;
|
||||
NODE_STATE(ksCurTime) += 1llu;
|
||||
if (likely(isRunnable(NODE_STATE(ksCurThread)))) {
|
||||
endTimeslice();
|
||||
rescheduleRequired();
|
||||
}
|
||||
return false;
|
||||
} else if (unlikely(isCurDomainExpired())) {
|
||||
commitTime();
|
||||
rescheduleRequired();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
chargeBudget(capacity, NODE_STATE(ksConsumed));
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Everything checkBudget does, but also set the thread
|
||||
|
|
|
|||
|
|
@ -42,7 +42,6 @@ exception_t handleInterruptEntry(void)
|
|||
irq = getActiveIRQ();
|
||||
#ifdef CONFIG_KERNEL_MCS
|
||||
if (SMP_TERNARY(clh_is_self_in_queue(), 1)) {
|
||||
assert(irq != irq_remote_call_ipi);
|
||||
updateTimestamp();
|
||||
checkBudget();
|
||||
}
|
||||
|
|
@ -60,7 +59,6 @@ exception_t handleInterruptEntry(void)
|
|||
|
||||
#ifdef CONFIG_KERNEL_MCS
|
||||
if (SMP_TERNARY(clh_is_self_in_queue(), 1)) {
|
||||
assert(irq != irq_remote_call_ipi);
|
||||
#endif
|
||||
schedule();
|
||||
activateThread();
|
||||
|
|
@ -534,14 +532,8 @@ static inline void mcsIRQ(irq_t irq)
|
|||
static void handleYield(void)
|
||||
{
|
||||
#ifdef CONFIG_KERNEL_MCS
|
||||
/* checkBudgetRestart should have failed if we got here */
|
||||
assert(refill_sufficient(NODE_STATE(ksCurSC), NODE_STATE(ksConsumed)));
|
||||
/* Yield the current remaining budget */
|
||||
refill_budget_check(NODE_STATE(ksCurSC), REFILL_HEAD(NODE_STATE(ksCurSC)).rAmount);
|
||||
/* we just charged all of the time to the yielding thread */
|
||||
NODE_STATE(ksConsumed) = 0;
|
||||
endTimeslice();
|
||||
rescheduleRequired();
|
||||
chargeBudget(0, REFILL_HEAD(NODE_STATE(ksCurSC)).rAmount);
|
||||
#else
|
||||
tcbSchedDequeue(NODE_STATE(ksCurThread));
|
||||
SCHED_APPEND_CURRENT_TCB;
|
||||
|
|
|
|||
|
|
@ -73,16 +73,21 @@ static UNUSED bool_t refill_ordered(sched_context_t *sc)
|
|||
word_t next = refill_next(sc, sc->scRefillHead);
|
||||
|
||||
while (current != sc->scRefillTail) {
|
||||
assert(REFILL_INDEX(sc, current).rTime <= REFILL_INDEX(sc, next).rTime);
|
||||
if (!(REFILL_INDEX(sc, current).rTime <= REFILL_INDEX(sc, next).rTime)) {
|
||||
refill_print(sc);
|
||||
return false;
|
||||
}
|
||||
current = next;
|
||||
next = refill_next(sc, current);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#define REFILL_SANITY_START(sc) ticks_t _sum = refill_sum(sc); refill_ordered(sc);
|
||||
#define REFILL_SANITY_START(sc) ticks_t _sum = refill_sum(sc); assert(refill_ordered(sc));
|
||||
#define REFILL_SANITY_CHECK(sc, budget) \
|
||||
do { \
|
||||
assert(refill_sum(sc) == budget); refill_ordered(sc); \
|
||||
assert(refill_sum(sc) == budget); assert(refill_ordered(sc)); \
|
||||
} while (0)
|
||||
|
||||
#define REFILL_SANITY_END(sc) \
|
||||
|
|
@ -96,7 +101,7 @@ static UNUSED bool_t refill_ordered(sched_context_t *sc)
|
|||
#endif /* CONFIG_DEBUG_BUILD */
|
||||
|
||||
/* compute the sum of a refill queue */
|
||||
static ticks_t refill_sum(sched_context_t *sc)
|
||||
static UNUSED ticks_t refill_sum(sched_context_t *sc)
|
||||
{
|
||||
ticks_t sum = REFILL_HEAD(sc).rAmount;
|
||||
word_t current = sc->scRefillHead;
|
||||
|
|
@ -128,8 +133,6 @@ static inline refill_t refill_pop_head(sched_context_t *sc)
|
|||
/* add item to tail of refill queue */
|
||||
static inline void refill_add_tail(sched_context_t *sc, refill_t refill)
|
||||
{
|
||||
/* cannot add an empty refill */
|
||||
assert(refill.rAmount != 0);
|
||||
/* cannot add beyond queue size */
|
||||
assert(refill_size(sc) < sc->scRefillMax);
|
||||
|
||||
|
|
@ -141,6 +144,16 @@ static inline void refill_add_tail(sched_context_t *sc, refill_t refill)
|
|||
assert(new_tail < sc->scRefillMax);
|
||||
}
|
||||
|
||||
static inline void maybe_add_empty_tail(sched_context_t *sc)
|
||||
{
|
||||
if (isRoundRobin(sc)) {
|
||||
/* add an empty refill - we track the used up time here */
|
||||
refill_t empty_tail = { .rTime = NODE_STATE(ksCurTime)};
|
||||
refill_add_tail(sc, empty_tail);
|
||||
assert(refill_size(sc) == MIN_REFILLS);
|
||||
}
|
||||
}
|
||||
|
||||
void refill_new(sched_context_t *sc, word_t max_refills, ticks_t budget, ticks_t period)
|
||||
{
|
||||
sc->scPeriod = period;
|
||||
|
|
@ -152,6 +165,7 @@ void refill_new(sched_context_t *sc, word_t max_refills, ticks_t budget, ticks_t
|
|||
REFILL_HEAD(sc).rAmount = budget;
|
||||
/* budget can be used from now */
|
||||
REFILL_HEAD(sc).rTime = NODE_STATE(ksCurTime);
|
||||
maybe_add_empty_tail(sc);
|
||||
REFILL_SANITY_CHECK(sc, budget);
|
||||
}
|
||||
|
||||
|
|
@ -161,90 +175,78 @@ void refill_update(sched_context_t *sc, ticks_t new_period, ticks_t new_budget,
|
|||
/* refill must be initialised in order to be updated - otherwise refill_new should be used */
|
||||
assert(sc->scRefillMax > 0);
|
||||
|
||||
/* figure out how much budget is available */
|
||||
ticks_t total_budget = refill_sum(sc);
|
||||
REFILL_SANITY_CHECK(sc, total_budget);
|
||||
/* this is called on an active thread. We want to preserve the sliding window constraint -
|
||||
* so over new_period, new_budget should not be exceeded even temporarily */
|
||||
|
||||
/* first deal with a difference in max refills - merge
|
||||
* any refills that exceed the new max */
|
||||
while (new_max_refills < refill_size(sc)) {
|
||||
/* merge refills */
|
||||
|
||||
assert(!refill_single(sc));
|
||||
refill_t refill = refill_pop_head(sc);
|
||||
REFILL_HEAD(sc).rAmount += refill.rAmount;
|
||||
}
|
||||
|
||||
REFILL_SANITY_CHECK(sc, total_budget);
|
||||
|
||||
/* move anything in the list that is beyond the old max */
|
||||
if (sc->scRefillMax > new_max_refills) {
|
||||
word_t curr = sc->scRefillHead;
|
||||
for (curr = sc->scRefillHead; curr < sc->scRefillMax; curr++) {
|
||||
word_t diff = sc->scRefillMax - new_max_refills;
|
||||
REFILL_INDEX(sc, curr - diff) = REFILL_INDEX(sc, curr);
|
||||
}
|
||||
}
|
||||
/* move the head refill to the start of the list - it's ok as we're going to truncate the
|
||||
* list to size 1 - and this way we can't be in an invalid list position once new_max_refills
|
||||
* is updated */
|
||||
REFILL_INDEX(sc, 0) = REFILL_HEAD(sc);
|
||||
sc->scRefillHead = 0;
|
||||
/* truncate refill list to size 1 */
|
||||
sc->scRefillTail = sc->scRefillHead;
|
||||
/* update max refills */
|
||||
sc->scRefillMax = new_max_refills;
|
||||
|
||||
/* now deal with the period change - update each refill by the difference in period */
|
||||
word_t current = refill_next(sc, sc->scRefillHead);
|
||||
while (current != sc->scRefillTail) {
|
||||
/* adjust the period of each refill by new one (except the head) */
|
||||
REFILL_INDEX(sc, current).rTime += (new_period - sc->scPeriod);
|
||||
current = refill_next(sc, current);
|
||||
}
|
||||
/* update period */
|
||||
sc->scPeriod = new_period;
|
||||
REFILL_SANITY_CHECK(sc, total_budget);
|
||||
|
||||
/* now deal with the new budget */
|
||||
if (new_budget > total_budget) {
|
||||
/* if the budget has increased, just add it to the last refill */
|
||||
REFILL_TAIL(sc).rAmount += (new_budget - total_budget);
|
||||
} else {
|
||||
/* if the budget has decreased, iterate through from head to
|
||||
* tail until the amount decreased has been removed from the refill
|
||||
* buffer */
|
||||
ticks_t remove = total_budget - new_budget;
|
||||
while (remove >= REFILL_HEAD(sc).rAmount) {
|
||||
assert(!refill_single(sc));
|
||||
refill_t old_head = refill_pop_head(sc);
|
||||
remove -= old_head.rAmount;
|
||||
}
|
||||
REFILL_HEAD(sc).rAmount -= remove;
|
||||
if (REFILL_HEAD(sc).rAmount < MIN_BUDGET) {
|
||||
assert(!refill_single(sc));
|
||||
refill_t old_head = refill_pop_head(sc);
|
||||
REFILL_HEAD(sc).rAmount += old_head.rAmount;
|
||||
}
|
||||
if (refill_ready(sc)) {
|
||||
REFILL_HEAD(sc).rTime = NODE_STATE(ksCurTime);
|
||||
}
|
||||
|
||||
if (REFILL_HEAD(sc).rAmount >= new_budget) {
|
||||
/* if the heads budget exceeds the new budget just trim it */
|
||||
REFILL_HEAD(sc).rAmount = new_budget;
|
||||
maybe_add_empty_tail(sc);
|
||||
} else {
|
||||
/* otherwise schedule the rest for the next period */
|
||||
refill_t new = { .rAmount = (new_budget - REFILL_HEAD(sc).rAmount),
|
||||
.rTime = REFILL_HEAD(sc).rTime + new_period
|
||||
};
|
||||
refill_add_tail(sc, new);
|
||||
}
|
||||
|
||||
/* merge any overlapping refills */
|
||||
refill_unblock_check(sc);
|
||||
REFILL_SANITY_CHECK(sc, new_budget);
|
||||
}
|
||||
|
||||
ticks_t refill_budget_check(sched_context_t *sc, ticks_t usage)
|
||||
static inline void schedule_used(sched_context_t *sc, refill_t new)
|
||||
{
|
||||
/* schedule the used amount */
|
||||
if (new.rAmount < MIN_BUDGET && !refill_single(sc)) {
|
||||
/* used amount is to small - merge with last and delay */
|
||||
REFILL_TAIL(sc).rAmount += new.rAmount;
|
||||
REFILL_TAIL(sc).rTime = MAX(new.rTime, REFILL_TAIL(sc).rTime);
|
||||
} else if (new.rTime <= REFILL_TAIL(sc).rTime) {
|
||||
REFILL_TAIL(sc).rAmount += new.rAmount;
|
||||
} else {
|
||||
refill_add_tail(sc, new);
|
||||
}
|
||||
}
|
||||
|
||||
void refill_budget_check(sched_context_t *sc, ticks_t usage, ticks_t capacity)
|
||||
{
|
||||
/* this function should only be called when the sc is out of budget */
|
||||
assert(refill_capacity(sc, usage) == 0);
|
||||
assert(capacity < MIN_BUDGET || refill_full(sc));
|
||||
assert(sc->scPeriod > 0);
|
||||
REFILL_SANITY_START(sc);
|
||||
|
||||
while (REFILL_HEAD(sc).rAmount <= usage) {
|
||||
/* exhaust and schedule replenishment */
|
||||
usage -= REFILL_HEAD(sc).rAmount;
|
||||
if (refill_single(sc)) {
|
||||
/* update in place */
|
||||
REFILL_HEAD(sc).rTime += sc->scPeriod;
|
||||
} else {
|
||||
refill_t old_head = refill_pop_head(sc);
|
||||
old_head.rTime = old_head.rTime + sc->scPeriod;
|
||||
refill_add_tail(sc, old_head);
|
||||
if (capacity == 0) {
|
||||
while (REFILL_HEAD(sc).rAmount <= usage) {
|
||||
/* exhaust and schedule replenishment */
|
||||
usage -= REFILL_HEAD(sc).rAmount;
|
||||
if (refill_single(sc)) {
|
||||
/* update in place */
|
||||
REFILL_HEAD(sc).rTime += sc->scPeriod;
|
||||
} else {
|
||||
refill_t old_head = refill_pop_head(sc);
|
||||
old_head.rTime = old_head.rTime + sc->scPeriod;
|
||||
schedule_used(sc, old_head);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* budget overrun */
|
||||
if (usage > 0 && sc->scPeriod > 0) {
|
||||
if (usage > 0) {
|
||||
/* budget reduced when calculating capacity */
|
||||
/* due to overrun delay next replenishment */
|
||||
REFILL_HEAD(sc).rTime += usage;
|
||||
|
|
@ -255,13 +257,25 @@ ticks_t refill_budget_check(sched_context_t *sc, ticks_t usage)
|
|||
|
||||
refill_t refill = refill_pop_head(sc);
|
||||
REFILL_HEAD(sc).rAmount += refill.rAmount;
|
||||
REFILL_HEAD(sc).rTime = refill.rTime;
|
||||
}
|
||||
}
|
||||
|
||||
REFILL_SANITY_END(sc);
|
||||
capacity = refill_capacity(sc, usage);
|
||||
if (capacity > 0 && refill_ready(sc)) {
|
||||
refill_split_check(sc, usage);
|
||||
}
|
||||
|
||||
/* return any usage we haven't dealt with */
|
||||
return usage;
|
||||
/* ensure the refill head is sufficient, such that when we wake in awaken,
|
||||
* there is enough budget to run */
|
||||
while (REFILL_HEAD(NODE_STATE(ksCurSC)).rAmount < MIN_BUDGET) {
|
||||
refill_t refill = refill_pop_head(sc);
|
||||
REFILL_HEAD(sc).rAmount += refill.rAmount;
|
||||
/* this loop is guaranteed to terminate as the sum of
|
||||
* rAmount in a refill must be >= MIN_BUDGET */
|
||||
}
|
||||
|
||||
REFILL_SANITY_END(sc);
|
||||
}
|
||||
|
||||
void refill_split_check(sched_context_t *sc, ticks_t usage)
|
||||
|
|
@ -272,40 +286,50 @@ void refill_split_check(sched_context_t *sc, ticks_t usage)
|
|||
* time has been used */
|
||||
assert(usage > 0);
|
||||
assert(usage <= REFILL_HEAD(sc).rAmount);
|
||||
assert(sc->scPeriod > 0);
|
||||
|
||||
REFILL_SANITY_START(sc);
|
||||
|
||||
/* first deal with the remaining budget of the current replenishment */
|
||||
ticks_t remnant = REFILL_HEAD(sc).rAmount - usage;
|
||||
if (remnant < MIN_BUDGET && refill_single(sc)) {
|
||||
/* delay entire replenishment - can't merge, nothing to merge with */
|
||||
REFILL_HEAD(sc).rTime += sc->scPeriod;
|
||||
REFILL_SANITY_END(sc);
|
||||
return;
|
||||
}
|
||||
|
||||
if (refill_size(sc) == sc->scRefillMax || remnant < MIN_BUDGET) {
|
||||
assert(!refill_single(sc));
|
||||
/* merge remnant with next replenishment - either it's too small
|
||||
* or we're out of space */
|
||||
refill_pop_head(sc);
|
||||
REFILL_HEAD(sc).rAmount += remnant;
|
||||
} else {
|
||||
assert(remnant >= MIN_BUDGET);
|
||||
/* split the head refill */
|
||||
REFILL_HEAD(sc).rAmount = remnant;
|
||||
}
|
||||
|
||||
/* schedule the used amount */
|
||||
/* set up a new replenishment structure */
|
||||
refill_t new = (refill_t) {
|
||||
.rAmount = usage, .rTime = REFILL_HEAD(sc).rTime + sc->scPeriod
|
||||
};
|
||||
refill_add_tail(sc, new);
|
||||
|
||||
if (refill_size(sc) == sc->scRefillMax || remnant < MIN_BUDGET) {
|
||||
/* merge remnant with next replenishment - either it's too small
|
||||
* or we're out of space */
|
||||
if (refill_single(sc)) {
|
||||
/* update inplace */
|
||||
new.rAmount += remnant;
|
||||
REFILL_HEAD(sc) = new;
|
||||
} else {
|
||||
refill_pop_head(sc);
|
||||
REFILL_HEAD(sc).rAmount += remnant;
|
||||
schedule_used(sc, new);
|
||||
}
|
||||
assert(refill_ordered(sc));
|
||||
} else {
|
||||
/* leave remnant as reduced replenishment */
|
||||
assert(remnant >= MIN_BUDGET);
|
||||
/* split the head refill */
|
||||
REFILL_HEAD(sc).rAmount = remnant;
|
||||
schedule_used(sc, new);
|
||||
}
|
||||
|
||||
REFILL_SANITY_END(sc);
|
||||
}
|
||||
|
||||
void refill_unblock_check(sched_context_t *sc)
|
||||
{
|
||||
|
||||
if (isRoundRobin(sc)) {
|
||||
/* nothing to do */
|
||||
return;
|
||||
}
|
||||
|
||||
/* advance earliest activation time to now */
|
||||
REFILL_SANITY_START(sc);
|
||||
if (refill_ready(sc)) {
|
||||
|
|
@ -323,14 +347,7 @@ void refill_unblock_check(sched_context_t *sc)
|
|||
}
|
||||
}
|
||||
|
||||
/* it's possible that a refill is not bigger than min budget (if a task
|
||||
* uses less than min budget, it will still be scheduled for refill), if
|
||||
* so merge with the next refill, as it's not enough to schedule the task. */
|
||||
if (!refill_sufficient(sc, 0)) {
|
||||
assert(!refill_single(sc));
|
||||
refill_t insufficient = refill_pop_head(sc);
|
||||
REFILL_HEAD(sc).rAmount += insufficient.rAmount;
|
||||
}
|
||||
assert(refill_sufficient(sc, 0));
|
||||
}
|
||||
REFILL_SANITY_END(sc);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ static inline bool_t PURE isSchedulable(const tcb_t *thread)
|
|||
{
|
||||
return isRunnable(thread) &&
|
||||
thread->tcbSchedContext != NULL &&
|
||||
thread->tcbSchedContext->scRefillMax > 0 &&
|
||||
!thread_state_get_tcbInReleaseQueue(thread->tcbState);
|
||||
}
|
||||
#else
|
||||
|
|
@ -114,8 +115,9 @@ void restart(tcb_t *target)
|
|||
cancelIPC(target);
|
||||
#ifdef CONFIG_KERNEL_MCS
|
||||
setThreadState(target, ThreadState_Restart);
|
||||
if (likely(target->tcbSchedContext != NULL)) {
|
||||
schedContext_resume(target->tcbSchedContext);
|
||||
schedContext_resume(target->tcbSchedContext);
|
||||
if (isSchedulable(target)) {
|
||||
possibleSwitchTo(target);
|
||||
}
|
||||
#else
|
||||
setupReplyMaster(target);
|
||||
|
|
@ -330,9 +332,8 @@ static void switchSchedContext(void)
|
|||
}
|
||||
|
||||
/* if a thread doesn't have enough budget, it should not be in the scheduler */
|
||||
if (!refill_ready(NODE_STATE(ksCurSC)) || !refill_sufficient(NODE_STATE(ksCurSC), 0)) {
|
||||
assert(!thread_state_get_tcbQueued(NODE_STATE(ksCurSC)->scTcb->tcbState));
|
||||
}
|
||||
assert((refill_ready(NODE_STATE(ksCurSC)) && refill_sufficient(NODE_STATE(ksCurSC), 0))
|
||||
|| !thread_state_get_tcbQueued(NODE_STATE(ksCurSC)->scTcb->tcbState));
|
||||
|
||||
NODE_STATE(ksCurSC) = NODE_STATE(ksCurThread)->tcbSchedContext;
|
||||
}
|
||||
|
|
@ -578,8 +579,31 @@ void setNextInterrupt(void)
|
|||
setDeadline(next_interrupt - getTimerPrecision());
|
||||
}
|
||||
|
||||
void chargeBudget(ticks_t capacity, ticks_t consumed)
|
||||
{
|
||||
|
||||
if (isRoundRobin(NODE_STATE(ksCurSC))) {
|
||||
assert(refill_size(NODE_STATE(ksCurSC)) == MIN_REFILLS);
|
||||
REFILL_HEAD(NODE_STATE(ksCurSC)).rAmount += REFILL_TAIL(NODE_STATE(ksCurSC)).rAmount;
|
||||
REFILL_TAIL(NODE_STATE(ksCurSC)).rAmount = 0;
|
||||
} else {
|
||||
refill_budget_check(NODE_STATE(ksCurSC), consumed, capacity);
|
||||
}
|
||||
|
||||
assert(REFILL_HEAD(NODE_STATE(ksCurSC)).rAmount >= MIN_BUDGET);
|
||||
NODE_STATE(ksConsumed) = 0;
|
||||
if (likely(isRunnable(NODE_STATE(ksCurThread)))) {
|
||||
endTimeslice();
|
||||
rescheduleRequired();
|
||||
}
|
||||
}
|
||||
|
||||
void endTimeslice(void)
|
||||
{
|
||||
if (unlikely(NODE_STATE(ksCurThread) == NODE_STATE(ksIdleThread))) {
|
||||
return;
|
||||
}
|
||||
|
||||
assert(isRunnable(NODE_STATE(ksCurSC->scTcb)));
|
||||
if (refill_ready(NODE_STATE(ksCurSC)) && refill_sufficient(NODE_STATE(ksCurSC), 0)) {
|
||||
/* apply round robin */
|
||||
|
|
@ -645,15 +669,17 @@ void awaken(void)
|
|||
{
|
||||
while (unlikely(NODE_STATE(ksReleaseHead) != NULL && refill_ready(NODE_STATE(ksReleaseHead)->tcbSchedContext))) {
|
||||
tcb_t *awakened = tcbReleaseDequeue();
|
||||
/* the currently running thread cannot have just woken up */
|
||||
assert(awakened != NODE_STATE(ksCurThread));
|
||||
/* round robin threads should not be in the release queue */
|
||||
assert(!isRoundRobin(awakened->tcbSchedContext));
|
||||
/* threads should wake up on the correct core */
|
||||
SMP_COND_STATEMENT(assert(awakened->tcbAffinity == getCurrentCPUIndex()));
|
||||
refill_unblock_check(awakened->tcbSchedContext);
|
||||
if (unlikely(!refill_ready(awakened->tcbSchedContext))) {
|
||||
tcbReleaseEnqueue(awakened);
|
||||
} else {
|
||||
assert(refill_sufficient(awakened->tcbSchedContext, 0));
|
||||
tcbSchedAppend(awakened);
|
||||
possibleSwitchTo(awakened);
|
||||
}
|
||||
/* threads HEAD refill should always be > MIN_BUDGET */
|
||||
assert(refill_sufficient(awakened->tcbSchedContext, 0));
|
||||
possibleSwitchTo(awakened);
|
||||
/* changed head of release queue -> need to reprogram */
|
||||
NODE_STATE(ksReprogram) = true;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -135,10 +135,21 @@ static exception_t invokeSchedContext_Unbind(sched_context_t *sc)
|
|||
return EXCEPTION_NONE;
|
||||
}
|
||||
|
||||
#ifdef ENABLE_SMP_SUPPORT
|
||||
static inline void maybeStallSC(sched_context_t *sc)
|
||||
{
|
||||
if (sc->scTcb) {
|
||||
remoteTCBStall(sc->scTcb);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
exception_t decodeSchedContextInvocation(word_t label, cap_t cap, extra_caps_t extraCaps)
|
||||
{
|
||||
sched_context_t *sc = SC_PTR(cap_sched_context_cap_get_capSCPtr(cap));
|
||||
|
||||
SMP_COND_STATEMENT((maybeStallSC(sc));)
|
||||
|
||||
switch (label) {
|
||||
case SchedContextBind:
|
||||
return decodeSchedContext_Bind(sc, extraCaps);
|
||||
|
|
@ -160,7 +171,6 @@ void schedContext_resume(sched_context_t *sc)
|
|||
assert(!sc || sc->scTcb != NULL);
|
||||
if (likely(sc) && isSchedulable(sc->scTcb)) {
|
||||
assert(sc->scTcb != NULL);
|
||||
refill_unblock_check(sc);
|
||||
|
||||
if (isRunnable(sc->scTcb) && sc->scRefillMax > 0) {
|
||||
if (!(refill_ready(sc) && refill_sufficient(sc, 0))) {
|
||||
|
|
@ -179,10 +189,10 @@ void schedContext_bindTCB(sched_context_t *sc, tcb_t *tcb)
|
|||
tcb->tcbSchedContext = sc;
|
||||
sc->scTcb = tcb;
|
||||
|
||||
#if CONFIG_MAX_NUM_NODES > 1
|
||||
#ifdef ENABLE_SMP_SUPPORT
|
||||
if (tcb->tcbAffinity != sc->scCore) {
|
||||
if (isSchedulable(tcb)) {
|
||||
SMP_COND_STATEMENT(remoteTCBStall(tcb));
|
||||
remoteTCBStall(tcb);
|
||||
tcbSchedDequeue(tcb);
|
||||
}
|
||||
migrateTCB(tcb, sc->scCore);
|
||||
|
|
|
|||
|
|
@ -19,22 +19,35 @@
|
|||
static exception_t invokeSchedControl_Configure(sched_context_t *target, word_t core, ticks_t budget, ticks_t period,
|
||||
word_t max_refills)
|
||||
{
|
||||
/* don't modify parameters of tcb while it is in a sorted queue */
|
||||
|
||||
if (target->scTcb) {
|
||||
/* possibly stall a remote core */
|
||||
SMP_COND_STATEMENT(remoteTCBStall(target->scTcb));
|
||||
/* remove from scheduler */
|
||||
tcbReleaseRemove(target->scTcb);
|
||||
tcbSchedDequeue(target->scTcb);
|
||||
/* bill the current consumed amount before adjusting the params */
|
||||
if (NODE_STATE_ON_CORE(ksCurSC, target->scCore) == target) {
|
||||
ticks_t capacity = refill_capacity(target, NODE_STATE_ON_CORE(ksConsumed, target->scCore));
|
||||
if (checkBudget()) {
|
||||
commitTime();
|
||||
} else {
|
||||
chargeBudget(capacity, NODE_STATE_ON_CORE(ksConsumed, target->scCore));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (budget == period) {
|
||||
/* this is a cool hack: for round robin, we set the
|
||||
* period to 0, which means that the budget will always be ready to be refilled
|
||||
* and the code doesn't need special casing
|
||||
* and avoids some special casing.
|
||||
*/
|
||||
period = 0;
|
||||
max_refills = MIN_REFILLS;
|
||||
}
|
||||
|
||||
if (core == target->scCore && target->scRefillMax > 0 && target->scTcb && isRunnable(target->scTcb)) {
|
||||
if (SMP_COND_STATEMENT(core == target->scCore &&) target->scRefillMax > 0 && target->scTcb
|
||||
&& isRunnable(target->scTcb)) {
|
||||
/* the scheduling context is active - it can be used, so
|
||||
* we need to preserve the bandwidth */
|
||||
refill_update(target, period, budget, max_refills);
|
||||
|
|
@ -42,19 +55,21 @@ static exception_t invokeSchedControl_Configure(sched_context_t *target, word_t
|
|||
/* the scheduling context isn't active - it's budget is not being used, so
|
||||
* we can just populate the parameters from now */
|
||||
refill_new(target, max_refills, budget, period);
|
||||
|
||||
if (core != target->scCore && target->scTcb) {
|
||||
/* if the core changed and the SC has a tcb, the SC is getting
|
||||
* budget - so migrate it */
|
||||
target->scCore = core;
|
||||
SMP_COND_STATEMENT(Arch_migrateTCB(target->scTcb));
|
||||
SMP_COND_STATEMENT(target->scTcb->tcbAffinity = target->scCore;)
|
||||
#ifdef ENABLE_SMP_SUPPORT
|
||||
target->scCore = core;
|
||||
if (target->scTcb && target->scTcb->tcbAffinity != target->scCore) {
|
||||
migrateTCB(target->scTcb, target->scCore);
|
||||
}
|
||||
#endif /* ENABLE_SMP_SUPPORT */
|
||||
}
|
||||
|
||||
if (target->scTcb && isRunnable(target->scTcb) && target->scRefillMax > 0) {
|
||||
if (target->scTcb && target->scRefillMax > 0) {
|
||||
schedContext_resume(target);
|
||||
possibleSwitchTo(target->scTcb);
|
||||
if (target->scTcb == NODE_STATE(ksCurThread)) {
|
||||
rescheduleRequired();
|
||||
} else if (isRunnable(target->scTcb)) {
|
||||
possibleSwitchTo(target->scTcb);
|
||||
}
|
||||
}
|
||||
|
||||
return EXCEPTION_NONE;
|
||||
|
|
@ -76,7 +91,7 @@ static exception_t decodeSchedControl_Configure(word_t length, cap_t cap, extra_
|
|||
|
||||
time_t budget_us = mode_parseTimeArg(0, buffer);
|
||||
time_t period_us = mode_parseTimeArg(TIME_ARG_SIZE, buffer);
|
||||
word_t max_refills = MIN_REFILLS + getSyscallArg(TIME_ARG_SIZE * 2, buffer);
|
||||
word_t max_refills = getSyscallArg(TIME_ARG_SIZE * 2, buffer);
|
||||
|
||||
cap_t targetCap = extraCaps.excaprefs[0]->cap;
|
||||
if (unlikely(cap_get_capType(targetCap) != cap_sched_context_cap)) {
|
||||
|
|
|
|||
|
|
@ -88,7 +88,6 @@ void tcbSchedEnqueue(tcb_t *tcb)
|
|||
#ifdef CONFIG_KERNEL_MCS
|
||||
assert(isSchedulable(tcb));
|
||||
assert(refill_sufficient(tcb->tcbSchedContext, 0));
|
||||
assert(refill_ready(tcb->tcbSchedContext));
|
||||
#endif
|
||||
|
||||
if (!thread_state_get_tcbQueued(tcb->tcbState)) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue