From 0c6a38e18909ec06076b7d231dfe5e41f776fbf5 Mon Sep 17 00:00:00 2001 From: Enrico Fraccaroli Date: Tue, 5 Oct 2021 10:08:49 +0200 Subject: [PATCH] Update scheduler comments and improve code readability. --- mentos/src/process/scheduler_algorithm.c | 160 +++++++++++++++-------- 1 file changed, 108 insertions(+), 52 deletions(-) diff --git a/mentos/src/process/scheduler_algorithm.c b/mentos/src/process/scheduler_algorithm.c index 9cb3994..656389d 100644 --- a/mentos/src/process/scheduler_algorithm.c +++ b/mentos/src/process/scheduler_algorithm.c @@ -12,16 +12,27 @@ #include "wait.h" #include "scheduler.h" -static inline task_struct *scheduler_rr(runqueue_t *runqueue, bool_t skip_periodic) +/// @brief Updates task execution statistics. +/// @param task the task to update. +static void __update_task_statistics(task_struct *task); + +/// @brief Employs time-sharing, giving each job a timeslice, and is also +/// preemptive since the scheduler forces the task out of the CPU once +/// the timeslice expires. +/// @param runqueue list of all processes. +/// @param skip_periodic tells the algorithm if there are periodic processes +/// in the list, and in that case it needs to skip them. +/// @return the next task on success, NULL on failure. +static inline task_struct *__scheduler_rr(runqueue_t *runqueue, bool_t skip_periodic) { - // If there is just one process, return it. + // If there is just one task, return it. if ((runqueue->curr->run_list.next == &runqueue->queue) && (runqueue->curr->run_list.prev == &runqueue->queue)) { return runqueue->curr; } - // By default, the next process is the current one. + // By default, the next task is the current one. task_struct *next = NULL, *entry = NULL; - // Search for the next process (BEWARE: We do not start from the head, so INSIDE skip the head). + // Search for the next task (BEWARE: We do not start from the head, so INSIDE skip the head). list_for_each_decl(it, &runqueue->curr->run_list) { // Check if we reached the head of list_head, and skip it. @@ -34,7 +45,7 @@ static inline task_struct *scheduler_rr(runqueue_t *runqueue, bool_t skip_period if (entry->state != TASK_RUNNING) continue; - // Skip the process if it is a periodic one, we are issued to skip + // Skip the task if it is a periodic one, we are issued to skip // periodic tasks, and the entry is not a periodic task under // analysis. if (entry->se.is_periodic && skip_periodic && !entry->se.is_under_analysis) @@ -47,79 +58,124 @@ static inline task_struct *scheduler_rr(runqueue_t *runqueue, bool_t skip_period return next; } -static inline task_struct *scheduler_priority(runqueue_t *runqueue, bool_t skip_periodic) +/// @brief Is a non-preemptive algorithm, where each task is assigned a +/// priority. Processes with highest priority are executed first, while +/// processes with same priority are executed on first-come/first-served +/// basis. Priority can be decided based on memory requirements, time +/// requirements or any other resource requirement. +/// @param runqueue list of all processes. +/// @param skip_periodic tells the algorithm if there are periodic processes +/// in the list, and in that case it needs to skip them. +/// @return the next task on success, NULL on failure. +static inline task_struct *__scheduler_priority(runqueue_t *runqueue, bool_t skip_periodic) { - return scheduler_rr(runqueue, skip_periodic); + return __scheduler_rr(runqueue, skip_periodic); } -static inline task_struct *scheduler_cfs(runqueue_t *runqueue, bool_t skip_periodic) +/// @brief It aims at giving a fair share of CPU time to processes, and +/// achieves that by associating a virtual runtime to each of them. It always +/// tries to run the task with the smallest vruntime (i.e., the task which +/// executed least so far). It always tries to split up CPU time between +/// runnable tasks as close to "ideal multitasking hardware" as possible. +/// @param runqueue list of all processes. +/// @param skip_periodic tells the algorithm if there are periodic processes +/// in the list, and in that case it needs to skip them. +/// @return the next task on success, NULL on failure. +static inline task_struct *__scheduler_cfs(runqueue_t *runqueue, bool_t skip_periodic) { - return scheduler_rr(runqueue, skip_periodic); + return __scheduler_rr(runqueue, skip_periodic); } -static inline task_struct *scheduler_aedf(runqueue_t *runqueue) +/// @brief Executes the task with the earliest absolute deadline among all +/// the ready tasks. +/// @param runqueue list of all processes. +/// @return the next task on success, NULL on failure. +static inline task_struct *__scheduler_aedf(runqueue_t *runqueue) { - return scheduler_rr(runqueue, false); + return __scheduler_rr(runqueue, false); } -static inline task_struct *scheduler_edf(runqueue_t *runqueue) +/// @brief Executes the task with the earliest absolute DEADLINE among all +/// the ready tasks. When a task was executed, and its period is starting +/// again, it must be set as 'executable again', and its deadline and next_period +/// must be updated. +/// @param runqueue list of all processes. +/// @return the next task on success, NULL on failure. +static inline task_struct *__scheduler_edf(runqueue_t *runqueue) { - return scheduler_rr(runqueue, false); + return __scheduler_rr(runqueue, false); } -static inline task_struct *scheduler_rm(runqueue_t *runqueue) +/// @brief Executes the task with the earliest next PERIOD among all the +/// ready tasks. +/// @details When a task was executed, and its period is starting again, it +/// must be set as 'executable again', and its deadline and next_period must +/// be updated. +/// @param runqueue list of all processes. +/// @return the next task on success, NULL on failure. +static inline task_struct *__scheduler_rm(runqueue_t *runqueue) { - return scheduler_rr(runqueue, false); + return __scheduler_rr(runqueue, false); } task_struct *scheduler_pick_next_task(runqueue_t *runqueue) { + // Update task statistics. + __update_task_statistics(runqueue->curr); + + // Pointer to the next task to schedule. + task_struct *next = NULL; +#if defined(SCHEDULER_RR) + next = __scheduler_rr(runqueue, false); +#elif defined(SCHEDULER_PRIORITY) + next = __scheduler_priority(runqueue, false); +#elif defined(SCHEDULER_CFS) + next = __scheduler_cfs(runqueue, false); +#elif defined(SCHEDULER_EDF) + next = __scheduler_edf(runqueue); +#elif defined(SCHEDULER_RM) + next = __scheduler_rm(runqueue); +#elif defined(SCHEDULER_AEDF) + next = __scheduler_aedf(runqueue); +#else +#error "You should enable a scheduling algorithm!" +#endif + + assert(next && "No valid task selected by the scheduling algorithm."); + + // Update the last context switch time of the next task. + next->se.exec_start = timer_get_ticks(); + + return next; +} + +static void __update_task_statistics(task_struct *task) +{ + assert(task && "Current task is not valid."); + // While periodic task is under analysis is executed with aperiodic // scheduler and can be preempted by a "true" periodic task. // We need to sum all the execution spots to calculate the WCET even // if is a more pessimistic evaluation. // Update the delta exec. - runqueue->curr->se.exec_runtime = timer_get_ticks() - runqueue->curr->se.exec_start; - update_process_profiling_timer(runqueue->curr); + task->se.exec_runtime = timer_get_ticks() - task->se.exec_start; + update_process_profiling_timer(task); - // set the sum_exec_runtime. - runqueue->curr->se.sum_exec_runtime += runqueue->curr->se.exec_runtime; + // Set the sum_exec_runtime. + task->se.sum_exec_runtime += task->se.exec_runtime; // If the task is not a periodic task we have to update the virtual runtime. - if (!runqueue->curr->se.is_periodic) { - // Get the weight of the current process. - time_t weight = GET_WEIGHT(runqueue->curr->se.prio); + if (!task->se.is_periodic) { + // Get the weight of the current task. + time_t weight = GET_WEIGHT(task->se.prio); + // If the weight is different from the default load, compute it. if (weight != NICE_0_LOAD) { - // get the multiplicative factor for its delta_exec. + // Get the multiplicative factor for its delta_exec. double factor = ((double)NICE_0_LOAD) / ((double)weight); - // weight the delta_exec with the multiplicative factor. - runqueue->curr->se.exec_runtime = (int)(((double)runqueue->curr->se.exec_runtime) * factor); + // Weight the delta_exec with the multiplicative factor. + task->se.exec_runtime = (int)(((double)task->se.exec_runtime) * factor); } - // Update vruntime of the current process. - runqueue->curr->se.vruntime += runqueue->curr->se.exec_runtime; + // Update vruntime of the current task. + task->se.vruntime += task->se.exec_runtime; } - - // Pointer to the next task to schedule. - task_struct *next = NULL; -#if defined(SCHEDULER_RR) - next = scheduler_rr(runqueue, false); -#elif defined(SCHEDULER_PRIORITY) - next = scheduler_priority(runqueue, false); -#elif defined(SCHEDULER_CFS) - next = scheduler_cfs(runqueue, false); -#elif defined(SCHEDULER_EDF) - next = scheduler_edf(runqueue); -#elif defined(SCHEDULER_RM) - next = scheduler_rm(runqueue); -#elif defined(SCHEDULER_AEDF) - next = scheduler_aedf(runqueue); -#else -#error "You should enable a scheduling algorithm!" -#endif - assert(next && "No valid task selected by the scheduling algorithm."); - - // Update the last context switch time of the next process. - next->se.exec_start = timer_get_ticks(); - - return next; -} +} \ No newline at end of file