187 lines
7.2 KiB
C
187 lines
7.2 KiB
C
/// @file scheduler_algorithm.c
|
|
/// @brief Round Robin algorithm.
|
|
/// @copyright (c) 2014-2022 This file is distributed under the MIT License.
|
|
/// See LICENSE.md for details.
|
|
|
|
// Include the kernel log levels.
|
|
#include "sys/kernel_levels.h"
|
|
/// Change the header.
|
|
#define __DEBUG_HEADER__ "[SCHALG]"
|
|
/// Set the log level.
|
|
#define __DEBUG_LEVEL__ LOGLEVEL_NOTICE
|
|
|
|
#include "hardware/timer.h"
|
|
#include "process/prio.h"
|
|
#include "io/debug.h"
|
|
#include "assert.h"
|
|
#include "klib/list_head.h"
|
|
#include "process/wait.h"
|
|
#include "process/scheduler.h"
|
|
|
|
/// @brief Updates task execution statistics.
|
|
/// @param task the task to update.
|
|
static void __update_task_statistics(task_struct *task);
|
|
|
|
/// @brief Checks if the given task is actually a periodic task.
|
|
/// @param task the task to check.
|
|
/// @return true if the task is periodic, false otherwise.
|
|
static inline bool_t __is_periodic_task(task_struct *task)
|
|
{
|
|
// Check if the task is a periodic one and it is not under analysis.
|
|
return task->se.is_periodic && !task->se.is_under_analysis;
|
|
}
|
|
|
|
/// @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 task, return it; no need to do anything.
|
|
if (list_head_size(&runqueue->curr->run_list) <= 1) {
|
|
return runqueue->curr;
|
|
}
|
|
// Search for the next task (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.
|
|
if (it == &runqueue->queue)
|
|
continue;
|
|
// Get the current entry.
|
|
task_struct *entry = list_entry(it, task_struct, run_list);
|
|
// We consider only runnable processes
|
|
if (entry->state != TASK_RUNNING)
|
|
continue;
|
|
// If entry is a periodic task, and we were asked to skip periodic tasks, skip it.
|
|
if (__is_periodic_task(entry) && skip_periodic)
|
|
continue;
|
|
// We have our next entry.
|
|
return entry;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/// @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);
|
|
}
|
|
|
|
/// @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);
|
|
}
|
|
|
|
/// @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);
|
|
}
|
|
|
|
/// @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);
|
|
}
|
|
|
|
/// @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);
|
|
}
|
|
|
|
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.
|
|
task->se.exec_runtime = timer_get_ticks() - task->se.exec_start;
|
|
update_process_profiling_timer(task);
|
|
|
|
// 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 (!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.
|
|
double factor = ((double)NICE_0_LOAD) / ((double)weight);
|
|
// Weight the delta_exec with the multiplicative factor.
|
|
task->se.exec_runtime = (int)(((double)task->se.exec_runtime) * factor);
|
|
}
|
|
// Update vruntime of the current task.
|
|
task->se.vruntime += task->se.exec_runtime;
|
|
}
|
|
} |