/** * \file * \brief Kernel scheduling policy: Rate-Based Earliest Deadline (RBED) * * The algorithm is described in the paper "Dynamic Integrated * Scheduling of Hard Real-Time, Soft Real-Time and Non-Real-Time * Processes" by Scott A. Brandt of UC Santa Cruz. * * Note that while in the paper real number arithmetic is used on some * variables, we employ fixed-point integer arithmetic within #SPECTRUM * in these cases. */ /* * Copyright (c) 2007, 2008, 2009, 2010, 2013, ETH Zurich. * All rights reserved. * * This file is distributed under the terms in the attached LICENSE file. * If you do not find this file, copies can be found by writing to: * ETH Zurich D-INFK, Universitaetstrasse 6, CH-8092 Zurich. Attn: Systems Group. */ /** * Implementation Notes * * real-time tasks: * the behaviour of real-time tasks is characterized by four parameters: wcet * (worst case execution time), period, (relative) deadline, and * release_time. Besides release_time, the values of these parameters are * not changed by the scheduler. RT tasks are considered to be periodic. Note * that the interpretation of the parameters is a little different than the * original RBED paper. * * ->release_time is the time that the task is ready to be scheduled. RT tasks * with ->release_time in the future are not effectively considered to be on * the runqueue and are ignored by the scheduler. In order to meet its * deadline the task needs to be scheduled no later than ->release_time + * deadline - wcet. EDF guarantees this property, as long as the utilization * rate is <= 1. * * The execution of an rt task for a particular period ends either when: (a) * the task runs outs of budget (->etime >= ->wcet), or (b) the task yields * using scheduler_yield(). When this happens the task's ->etime is reset to * 0, while ->release_time is increased by ->period. Note that * scheduler_remove() does not finalize the current period of the task. Also, * note that depending on ->period, there might be a case that an rt task is * not executed, even if the CPU is idle. * * best-effort tasks: * for best-effort tasks the scheduler is responsible to assigns proper values * the RT parameters. Also, To prioritize between BE tasks, the scheduler uses * ->weight. */ #include #ifndef SCHEDULER_SIMULATOR # include # include # include # include # include // update_sched_timer # include #include #endif #define SPECTRUM 1000000 /** * Minimum resource rate reserved for best-effort processes, in #SPECTRUM. * We set this to 10%. */ #define BETA (SPECTRUM / 10) // queue_tail has to be global, as its used from assembly // this is always kept in sync with kcb_current->queue_tail struct dcb *queue_tail = NULL; /// Last (currently) scheduled task, for accounting purposes static struct dcb *lastdisp = NULL; /** * \brief Returns whether dcb is in scheduling queue. * \param dcb Pointer to DCB to check. * \return True if in queue, false otherwise. */ static inline bool in_queue(struct dcb *dcb) { return dcb->next != NULL || kcb_current->queue_tail == dcb; } static inline unsigned int u_target(struct dcb *dcb) { return (dcb->wcet * SPECTRUM) / dcb->period; } static inline unsigned int u_actual_srt(struct dcb *dcb) { if(u_target(dcb) != 0) { return MIN(u_target(dcb), (1 - BETA - kcb_current->u_hrt) / (kcb_current->u_srt / u_target(dcb))); } else { return 0; } } static inline systime_t deadline(struct dcb *dcb) { return dcb->release_time + dcb->deadline; } static void queue_insert(struct dcb *dcb) { // Empty queue case if(kcb_current->queue_head == NULL) { assert(kcb_current->queue_tail == NULL); kcb_current->queue_head = kcb_current->queue_tail = queue_tail = dcb; return; } /* Insert into priority queue (this is doing EDF). We insert at * the tail of a train of tasks with equal deadlines, as well as * equal release times for best-effort tasks, so that trains of * best-effort tasks with equal deadlines (and those released at * the same time) get scheduled in a round-robin fashion. The * release time equality check is important, as best-effort tasks * have lazily allocated deadlines. In some circumstances (like * when another task blocks), this might otherwise cause a wrong * yielding behavior when old deadlines are encountered. */ struct dcb *prev = NULL; for(struct dcb *i = kcb_current->queue_head; i != NULL; prev = i, i = i->next) { // Skip over equal, smaller release times if best-effort if(dcb->type == TASK_TYPE_BEST_EFFORT && dcb->release_time >= i->release_time) { continue; } // Skip over equal deadlines if(deadline(dcb) >= deadline(i)) { continue; } dcb->next = i; if(prev == NULL) { // Insert before head kcb_current->queue_head = dcb; } else { // Insert inside queue prev->next = dcb; } return; } // Insert after queue tail kcb_current->queue_tail->next = dcb; kcb_current->queue_tail = queue_tail = dcb; } /** * \brief Remove 'dcb' from scheduler ring. * * Removes dispatcher 'dcb' from the scheduler ring. If it was not in * the ring, this function is a no-op. The postcondition for this * function is that dcb is not in the ring. * * \param dcb Pointer to DCB to remove. */ static void queue_remove(struct dcb *dcb) { // No-op if not in scheduler ring if(!in_queue(dcb)) { return; } if(dcb == kcb_current->queue_head) { kcb_current->queue_head = dcb->next; if(kcb_current->queue_head == NULL) { kcb_current->queue_tail = queue_tail = NULL; } goto out; } for(struct dcb *i = kcb_current->queue_head; i != NULL; i = i->next) { if(i->next == dcb) { i->next = i->next->next; if(kcb_current->queue_tail == dcb) { kcb_current->queue_tail = queue_tail = i; } break; } } out: dcb->next = NULL; } #if 0 /** * \brief (Re-)Sort the scheduler priority queue. */ static void queue_sort(void) { start_over: for(struct dcb *i = queue_head; i != NULL && i->next != NULL; i = i->next) { if(deadline(i) > deadline(i->next)) { // Gotta re-sort queue_remove(i); queue_insert(i); goto start_over; } } } static void queue_reset(void) { for(struct dcb *i = queue_head; i != NULL; i = i->next) { if(i->type == TASK_TYPE_BEST_EFFORT) { i->release_time = kernel_now; } } } #endif /** * \brief Allocates resources for tasks. * * \param dcb Pointer to dcb to allocate resources for. * * \return u_actual for 'dcb' in percent. */ static unsigned int do_resource_allocation(struct dcb *dcb) { unsigned int u_actual; switch(dcb->type) { case TASK_TYPE_HARD_REALTIME: u_actual = u_target(dcb); break; case TASK_TYPE_SOFT_REALTIME: u_actual = u_actual_srt(dcb); break; case TASK_TYPE_BEST_EFFORT: assert(kcb_current->w_be > 0 && kcb_current->n_be > 0); assert(dcb->weight < UINT_MAX / SPECTRUM); u_actual = (MAX(BETA, SPECTRUM - kcb_current->u_hrt - kcb_current->u_srt) * dcb->weight) / kcb_current->w_be; dcb->deadline = dcb->period = kcb_current->n_be * kernel_timeslice; break; default: panic("Unknown task type %d!", dcb->type); break; } return u_actual; } #if 0 // XXX: Don't understand this yet static void adjust_weights(void) { // No runnable best-effort tasks have a positive weight if(w_be == 0) { // Re-assign weights for(struct dcb *i = queue_head; i != NULL; i = i->next) { if(i->type != TASK_TYPE_BEST_EFFORT) { continue; } i->weight = 1; w_be++; } } } #endif static void set_best_effort_wcet(struct dcb *dcb) { unsigned int u_actual = do_resource_allocation(dcb); systime_t wcet_undiv = (kcb_current->n_be * kernel_timeslice * u_actual); // Assert we are never overloaded assert(kcb_current->u_hrt + kcb_current->u_srt + u_actual <= SPECTRUM); // Divide with proper rounding dcb->wcet = (wcet_undiv + SPECTRUM / 2) / SPECTRUM; } /** * \brief Scheduler policy. * * \return Next DCB to schedule or NULL if wait for interrupts. */ struct dcb *schedule(void) { struct dcb *todisp; systime_t now = systime_now(); // Assert we are never overloaded assert(kcb_current->u_hrt + kcb_current->u_srt + BETA <= SPECTRUM); // Update executed time of last dispatched task if(lastdisp != NULL) { assert(lastdisp->last_dispatch <= now); if(lastdisp->release_time <= now) { lastdisp->etime += now - MAX(lastdisp->last_dispatch, lastdisp->release_time); } } start_over: todisp = kcb_current->queue_head; #ifndef SCHEDULER_SIMULATOR #define PRINT_NAME(d) \ do { \ if (!(d) || !(d)->disp) { \ debug(SUBSYS_DISPATCH, "todisp == NULL\n"); \ break; \ } \ struct dispatcher_shared_generic *dst = \ get_dispatcher_shared_generic(d->disp); \ debug(SUBSYS_DISPATCH, "looking at '%s', release_time=%lu, kernel_now=%zu\n", \ dst->name, d->release_time, now); \ }while(0) #else #define PRINT_NAME(d) do{}while(0) #endif // Skip over all tasks released in the future, they're technically not // in the schedule yet. We just have them to reduce book-keeping. while(todisp != NULL && todisp->release_time > now) { PRINT_NAME(todisp); todisp = todisp->next; } #undef PRINT_NAME // nothing to dispatch if(todisp == NULL) { #ifndef SCHEDULER_SIMULATOR debug(SUBSYS_DISPATCH, "schedule: no dcb runnable\n"); #endif lastdisp = NULL; return NULL; } // Lazy resource allocation for best-effort processes if(todisp->type == TASK_TYPE_BEST_EFFORT) { set_best_effort_wcet(todisp); /* We might've shortened the deadline into the past (eg. when * another BE task was removed while we already ran well into * our timeslice). In that case we need to re-release. */ if(deadline(todisp) < now) { todisp->release_time = now; } } // Assert we never miss a hard deadline if(todisp->type == TASK_TYPE_HARD_REALTIME && now > deadline(todisp)) { panic("Missed hard deadline: now = %zu, deadline = %lu", now, deadline(todisp)); assert(false && "HRT task missed a dead line!"); } // Deadline's can't be in the past (or EDF wouldn't work properly) assert(deadline(todisp) >= now); // Dispatch first guy in schedule if not over budget if(todisp->etime < todisp->wcet) { todisp->last_dispatch = now; // If nothing changed, run whatever ran last (task might have // yielded to another), unless it is blocked if(lastdisp == todisp && dcb_current != NULL && in_queue(dcb_current)) { /* trace_event(TRACE_SUBSYS_KERNEL, TRACE_EVENT_KERNEL_SCHED_CURRENT, */ /* (uint32_t)(lvaddr_t)dcb_current & 0xFFFFFFFF); */ return dcb_current; } /* trace_event(TRACE_SUBSYS_KERNEL, TRACE_EVENT_KERNEL_SCHED_SCHEDULE, */ /* (uint32_t)(lvaddr_t)todisp & 0xFFFFFFFF); */ // Remember who we run next lastdisp = todisp; #ifdef CONFIG_ONESHOT_TIMER // we might be able to do better than that... // (e.g., check if there is only one task in the queue) update_sched_timer(now + (todisp->wcet - todisp->etime)); #endif return todisp; } /* we selected a task that is over budget. do the necessary bookkeeping, put * it back on the queue and re-select a task */ // Best-effort task consumed WCET // XXX: Don't understand this yet #if 0 if(todisp->type == TASK_TYPE_BEST_EFFORT) { w_be -= todisp->weight; todisp->weight = 0; adjust_weights(); } #endif // Update periodic task and re-sort into run-queue struct dcb *dcb = todisp; queue_remove(todisp); if(dcb->type != TASK_TYPE_BEST_EFFORT) { if(now > dcb->release_time) { dcb->release_time += dcb->period; } } else { dcb->release_time = now; } dcb->etime = 0; queue_insert(dcb); lastdisp = NULL; goto start_over; } void schedule_now(struct dcb *dcb) { systime_t now = systime_now(); if (dcb->release_time >= now) { dcb->release_time = now; } dcb->deadline = 1; } void make_runnable(struct dcb *dcb) { systime_t now = systime_now(); // No-Op if already in schedule if(in_queue(dcb)) { return; } trace_event(TRACE_SUBSYS_KERNEL, TRACE_EVENT_KERNEL_SCHED_MAKE_RUNNABLE, (uint32_t)(lvaddr_t)dcb & 0xFFFFFFFF); // Keep counters up to date switch(dcb->type) { case TASK_TYPE_BEST_EFFORT: if(dcb->weight == 0) { dcb->weight = 1; } else { // XXX: Don't understand this yet #if 0 // Give blocked processes a boost dcb->weight = dcb->weight / 2 + 6; #endif } kcb_current->w_be += dcb->weight; kcb_current->n_be++; dcb->deadline = dcb->period = kcb_current->n_be * kernel_timeslice; dcb->release_time = now; /* queue_sort(); */ break; case TASK_TYPE_SOFT_REALTIME: panic("Unimplemented!"); break; case TASK_TYPE_HARD_REALTIME: kcb_current->u_hrt += u_target(dcb); break; default: panic("Unknown task type %d", dcb->type); break; } // Never overload the scheduler if(kcb_current->u_hrt + kcb_current->u_srt + BETA > SPECTRUM) { panic("RBED scheduler overload (loaded %d%%)!", (kcb_current->u_hrt + kcb_current->u_srt + BETA) / (SPECTRUM / 100)); } if(dcb->release_time < now) { panic("Released in the past! now = %zu, release_time = %lu\n", now, dcb->release_time); } /* assert(dcb->release_time >= kernel_now); */ dcb->etime = 0; queue_insert(dcb); } /** * \brief Remove 'dcb' from scheduler ring. * * Removes dispatcher 'dcb' from the scheduler ring. If it was not in * the ring, this function is a no-op. The postcondition for this * function is that dcb is not in the ring. * * \param dcb Pointer to DCB to remove. */ void scheduler_remove(struct dcb *dcb) { // No-Op if not in schedule if(!in_queue(dcb)) { return; } queue_remove(dcb); trace_event(TRACE_SUBSYS_KERNEL, TRACE_EVENT_KERNEL_SCHED_REMOVE, (uint32_t)(lvaddr_t)dcb & 0xFFFFFFFF); // Update counters switch(dcb->type) { case TASK_TYPE_BEST_EFFORT: kcb_current->w_be -= dcb->weight; kcb_current->n_be--; /* queue_sort(); */ /* adjust_weights(); */ break; case TASK_TYPE_SOFT_REALTIME: panic("Unimplemented!"); break; case TASK_TYPE_HARD_REALTIME: kcb_current->u_hrt -= u_target(dcb); break; } } /** * \brief Yield 'dcb' for the rest of the current timeslice. * * Re-sorts 'dcb' into the scheduler queue with its release time increased by * the timeslice period. It is an error to yield a dispatcher not in the * scheduler queue. * * \param dcb Pointer to DCB to remove. */ void scheduler_yield(struct dcb *dcb) { systime_t now = systime_now(); // For tasks not running yet, yield is a no-op if(!in_queue(dcb) || dcb->release_time > now) { return; } /* trace_event(TRACE_SUBSYS_KERNEL, TRACE_EVENT_KERNEL_SCHED_YIELD, */ /* (uint32_t)(lvaddr_t)dcb & 0xFFFFFFFF); */ queue_remove(dcb); switch(dcb->type) { case TASK_TYPE_HARD_REALTIME: case TASK_TYPE_SOFT_REALTIME: dcb->release_time += dcb->period; break; case TASK_TYPE_BEST_EFFORT: // Shuffle us around one time dcb->release_time = now; break; } dcb->etime = 0; lastdisp = NULL; // Don't account for us anymore queue_insert(dcb); } #ifndef SCHEDULER_SIMULATOR void scheduler_reset_time(void) { trace_event(TRACE_SUBSYS_KERNEL, TRACE_EVENT_KERNEL_TIMER_SYNC, 0); // XXX: Currently, we just re-release everything now struct kcb *k = kcb_current; do { printk(LOG_NOTE, "clearing kcb %p\n", k); for(struct dcb *i = k->queue_head; i != NULL; i = i->next) { i->release_time = 0; i->etime = 0; i->last_dispatch = 0; } k = k->next; }while(k && k!=kcb_current); // Forget all accounting information lastdisp = NULL; } void scheduler_convert(void) { enum sched_state from = kcb_current->sched; switch (from) { case SCHED_RBED: // do nothing break; case SCHED_RR: { // initialize RBED fields // make all tasks best effort struct dcb *tmp = NULL; printf("kcb_current: %p\n", kcb_current); printf("kcb_current->ring_current: %p\n", kcb_current->ring_current); printf("kcb_current->ring_current->prev: %p\n", kcb_current->ring_current->prev); struct dcb *i = kcb_current->ring_current; do { printf("converting %p\n", i); i->type = TASK_TYPE_BEST_EFFORT; tmp = i->next; i->next = i->prev = NULL; make_runnable(i); i = tmp; } while (i != kcb_current->ring_current); for (i = kcb_current->queue_head; i; i=i->next) { printf("%p (-> %p)\n", i, i->next); } break; } default: printf("don't know how to convert %d to RBED state\n", from); break; } } void scheduler_restore_state(void) { // clear time slices scheduler_reset_time(); } #endif