/// MentOS, The Mentoring Operating system project /// @file scheduler.c /// @brief Scheduler structures and functions. /// @copyright (c) 2014-2021 This file is distributed under the MIT License. /// See LICENSE.md for details. /// Change the header. #define __DEBUG_HEADER__ "[SCHED ]" #include "assert.h" #include "strerror.h" #include "fs/vfs.h" #include "process/scheduler.h" #include "descriptor_tables/tss.h" #include "devices/fpu.h" #include "process/prio.h" #include "process/wait.h" #include "mem/kheap.h" #include "system/panic.h" #include "misc/debug.h" #include "time.h" #include "sys/errno.h" #include "klib/list_head.h" #include "mem/paging.h" #include "hardware/timer.h" #include "math.h" #include "stdio.h" /// @brief Assembly function setting the kernel stack to jump into /// location in Ring 3 mode (USER mode). /// @param location The location where to jump. /// @param stack The stack to use. extern void enter_userspace(uintptr_t location, uintptr_t stack); /// The list of processes. runqueue_t runqueue; void scheduler_initialize() { // Initialize the runqueue list of tasks. list_head_init(&runqueue.queue); // Reset the current task. runqueue.curr = NULL; // Reset the number of active tasks. runqueue.num_active = 0; } uint32_t scheduler_getpid(void) { /// The current unused PID. static unsigned long int tid = 1; // Return the pid and increment. return tid++; } task_struct *scheduler_get_current_process() { return runqueue.curr; } time_t scheduler_get_maximum_vruntime() { time_t vruntime = 0; task_struct *entry; list_for_each_decl(it, &runqueue.queue) { // Check if we reached the head of list_head, and skip it. if (it == &runqueue.queue) continue; // Get the current entry. entry = list_entry(it, task_struct, run_list); // Skip the process 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 && !entry->se.is_under_analysis) continue; if (entry->se.vruntime > vruntime) vruntime = entry->se.vruntime; } return vruntime; } size_t scheduler_get_active_processes() { return runqueue.num_active; } task_struct *scheduler_get_running_process(pid_t pid) { task_struct *entry; list_for_each_decl(it, &runqueue.queue) { entry = list_entry(it, task_struct, run_list); if (entry->pid == pid) return entry; } return NULL; } void scheduler_enqueue_task(task_struct *process) { // If current_process is NULL, then process is the current process. if (runqueue.curr == NULL) { runqueue.curr = process; } // Add the new process at the end. list_head_add_tail(&process->run_list, &runqueue.queue); // Increment the number of active processes. ++runqueue.num_active; } void scheduler_dequeue_task(task_struct *process) { // Delete the process from the list of running processes. list_head_del(&process->run_list); // Decrement the number of active processes. --runqueue.num_active; if (process->se.is_periodic) runqueue.num_periodic--; } void scheduler_run(pt_regs *f) { // Check if there is a running process. if (runqueue.curr == NULL) return; task_struct *next = NULL; // Update the context of the current process. scheduler_store_context(f, runqueue.curr); // We check the existence of pending signals every time we finish // handling an interrupt or an exception. if (!do_signal(f)) { #if 1 if (runqueue.curr->state == EXIT_ZOMBIE) { //==== Handle Zombies ================================================= //pr_debug("Handle zombie %d\n", runqueue.curr->pid); // get the next process after the current one list_head *nNode = runqueue.curr->run_list.next; // check if we reached the head of list_head if (nNode == &runqueue.queue) { nNode = nNode->next; } // get the task_struct next = list_entry(nNode, task_struct, run_list); // Remove the zombie task. scheduler_dequeue_task(runqueue.curr); assert(next && "No valid task selected after removing ZOMBIE."); //===================================================================== } else { #endif //==== Scheduling ===================================================== // If we are currently executing a periodic process, and this process // has yet to complete, keep executing it. #ifdef SCHEDULER_EDF if (runqueue.curr->se.is_periodic) if (!runqueue.curr->se.executed) return; #endif // Pointer to the next process to be executed. next = scheduler_pick_next_task(&runqueue); //===================================================================== } // Check if the next and current processes are different. if (next != runqueue.curr) { // Copy into Kernel stack the next process's context. scheduler_restore_context(next, f); } } //========================================================================== } void scheduler_store_context(pt_regs *f, task_struct *process) { // Store the registers. process->thread.regs = *f; } void scheduler_restore_context(task_struct *process, pt_regs *f) { // Switch to the next process. runqueue.curr = process; // Restore the registers. *f = process->thread.regs; // TODO: Explain paging switch (ring 0 doesn't need page switching) // Switch to process page directory paging_switch_directory_va(process->mm->pgd); } void scheduler_enter_user_jmp(uintptr_t location, uintptr_t stack) { // Reset stack pointer for kernel. tss_set_stack(0x10, initial_esp); // update start execution time. runqueue.curr->se.start_runtime = timer_get_ticks(); // last context switch time. runqueue.curr->se.exec_start = timer_get_ticks(); // Jump in location. enter_userspace(location, stack); } /// @brief Awakens a sleeping process. /// @param process The process that should be awakened /// @param mode The type of wait (TASK_INTERRUPTIBLE or TASK_UNINTERRUPTIBLE). /// @param sync Specifies if the wakeup should be synchronous. /// @return 1 on success, 0 on failure. static inline int try_to_wake_up(task_struct *process, int mode, int sync) { // Only tasks in the state TASK_UNINTERRUPTIBLE can be woke up if (process->state == TASK_UNINTERRUPTIBLE || process->state == TASK_STOPPED) { //TODO: Recalc task priority process->state = TASK_RUNNING; return 1; } return 0; } int default_wake_function(wait_queue_entry_t *wait, unsigned mode, int sync) { task_struct *p = wait->task; return try_to_wake_up(p, mode, sync); } wait_queue_entry_t *sleep_on(wait_queue_head_t *wq) { // Save the sleeping process registers state task_struct *sleeping_task = scheduler_get_current_process(); #if 0 pt_regs* f = get_current_interrupt_stack_frame(); scheduler_store_context(f, sleeping_task); // Select next process in the runqueue as the current, restore it's context, // we assume that the first process is init wich does not sleep (I hope). // This is necessary to make the scheduler_run() in syscall_handler work. task_struct *next = list_entry(runqueue.queue.next, task_struct, run_list); assert((next != sleeping_task) && "The next selected process in the runqueue is the sleeping process"); scheduler_restore_context(next, f); #endif // Stops task from runqueue making it unrunnable sleeping_task->state = TASK_UNINTERRUPTIBLE; // Add sleeping process to sleep wait queue wait_queue_entry_t *wait_entry = kmalloc(sizeof(struct wait_queue_entry_t)); init_waitqueue_entry(wait_entry, sleeping_task); add_wait_queue(wq, wait_entry); return wait_entry; } int is_orphaned_pgrp(pid_t gid) { pid_t sid = 0; // Obtain SID of the group from a member list_head *it; list_for_each (it, &runqueue.queue) { task_struct *task = list_entry(it, task_struct, run_list); if (task->gid == gid) { sid = task->sid; break; } } // Check if the process leader of the session is alive list_for_each (it, &runqueue.queue) { task_struct *task = list_entry(it, task_struct, run_list); if (task->pid == sid) { return 0; } } return 1; } pid_t sys_getpid() { // Get the current task. if (runqueue.curr == NULL) { kernel_panic("There is no current process!"); } // Return the process identifer of the process. return runqueue.curr->pid; } pid_t sys_getsid(pid_t pid) { //If pid == 0 return SID of the calling process if (pid == 0) { if (runqueue.curr == NULL) { kernel_panic("There is no current process!"); } // Return the session identifer of the process. return runqueue.curr->sid; } //If != 0 get SID of the specified process list_head *it; list_for_each (it, &runqueue.queue) { task_struct *task = list_entry(it, task_struct, run_list); if (task->pid == pid) { if(runqueue.curr->sid != task->sid) return -EPERM; return task->sid; } } return -ESRCH; } pid_t sys_setsid() { task_struct *task = runqueue.curr; if (task == NULL) { kernel_panic("There is no current process!"); } if (task->sid == task->pid) { pr_debug("Process %d is already a session leader.", task->pid); return -EPERM; } task->sid = task->pid; task->gid = task->pid; return task->sid; } pid_t sys_getgid() { task_struct *curr = runqueue.curr; if (curr == NULL) { kernel_panic("There is no current process!"); } return curr->gid; } int sys_setgid(pid_t gid) { task_struct *curr = runqueue.curr; if (curr == NULL) { kernel_panic("There is no current process!"); } if (curr->gid == curr->pid) pr_debug("Process %d is already a session leader.", task->pid); curr->gid = curr->pid; return 0; } pid_t sys_getppid() { // Get the current task. if (runqueue.curr == NULL) { kernel_panic("There is no current process!"); } if (runqueue.curr->parent == NULL) { return 0; } // Return the parent process identifer of the process. return runqueue.curr->parent->pid; } int sys_nice(int increment) { // Get the current task. if (runqueue.curr == NULL) { kernel_panic("There is no current process!"); } if (increment < -40) { increment = -40; } if (increment > 40) { increment = 40; } int newNice = PRIO_TO_NICE(runqueue.curr->se.prio) + increment; pr_debug("New nice value would be : %d\n", newNice); if (newNice < MIN_NICE) { newNice = MIN_NICE; } if (newNice > MAX_NICE) { newNice = MAX_NICE; } if (PRIO_TO_NICE(runqueue.curr->se.prio) != newNice && newNice >= MIN_NICE && newNice <= MAX_NICE) { runqueue.curr->se.prio = NICE_TO_PRIO(newNice); } int actualNice = PRIO_TO_NICE(runqueue.curr->se.prio); pr_debug("Actual new nice value is: %d\n", actualNice); return actualNice; } pid_t sys_waitpid(pid_t pid, int *status, int options) { // Get the current task. if (runqueue.curr == NULL) { kernel_panic("There is no current process!"); } /* For now we do not support waiting for processes inside the given * process group (pid < -1). */ if ((pid < -1) || (pid == 0)) { return -ESRCH; } if (pid == runqueue.curr->pid) { return -ECHILD; } if (options != 0 && options != WNOHANG) { return -EINVAL; } #if 0 if (status == NULL) { return -EFAULT; } #endif if (list_head_empty(&runqueue.curr->children)) { return -ECHILD; } list_head *it; list_for_each (it, &runqueue.curr->children) { task_struct *entry = list_entry(it, task_struct, sibling); if (entry == NULL) { continue; } if (entry->state != EXIT_ZOMBIE) { continue; } if ((pid > 1) && (entry->pid != pid)) { continue; } // Save the pid to return. pid_t ppid = entry->pid; // Save the state (TODO: Improve status set). if (status) (*status) = entry->state; // Finalize the VFS structures. vfs_destroy_task(entry); // Remove entry from children of parent. list_head_del(&entry->sibling); // Remove entry from the scheduling queue. scheduler_dequeue_task(entry); // Delete the task_struct. kmem_cache_free(entry); pr_debug("Process %d is freeing memory of process %d.\n", runqueue.curr->pid, ppid); return ppid; } return 0; } void sys_exit(int exit_code) { // Get the current task. if (runqueue.curr == NULL) { kernel_panic("There is no current process!"); } // Get the process. task_struct *init_proc = scheduler_get_running_process(1); if (runqueue.curr == init_proc) { kernel_panic("Init process cannot call sys_exit!"); } // Set the termination code of the process. runqueue.curr->exit_code = (exit_code << 8) & 0xFF00; // Set the state of the process to zombie. runqueue.curr->state = EXIT_ZOMBIE; // Send a SIGCHLD to the parent process. if (runqueue.curr->parent) { int ret = sys_kill(runqueue.curr->parent->pid, SIGCHLD); if (ret == -1) { printf("[%d] %5d failed sending signal %d : %s\n", ret, runqueue.curr->parent->pid, SIGCHLD, strerror(errno)); } } // If it has children, then init process has to take care of them. if (!list_head_empty(&runqueue.curr->children)) { pr_debug("Moving children of %s(%d) to init(%d): {\n", runqueue.curr->name, runqueue.curr->pid, init_proc->pid); // Change the parent. pr_debug("Moving children (%d): {\n", init_proc->pid); list_for_each_decl(it, &runqueue.curr->children) { task_struct *entry = list_entry(it, task_struct, sibling); pr_debug(" [%d] %s\n", entry->pid, entry->name); entry->parent = init_proc; } pr_debug("}\n"); // Plug the list of children. list_head_merge(&init_proc->children, &runqueue.curr->children); // Print the list of children. pr_debug("New list of init children (%d): {\n", init_proc->pid); list_for_each_decl(it, &init_proc->children) { task_struct *entry = list_entry(it, task_struct, sibling); pr_debug(" [%d] %s\n", entry->pid, entry->name); } pr_debug("}\n"); } // Free the space occupied by the stack. destroy_process_image(runqueue.curr->mm); // Debugging message. pr_debug("Process %d exited with value %d\n", runqueue.curr->pid, exit_code); } int sys_sched_setparam(pid_t pid, const sched_param_t *param) { list_head *it; // Iter over the runqueue to find the task list_for_each (it, &runqueue.queue) { task_struct *entry = list_entry(it, task_struct, run_list); if (entry->pid == pid) { if (!entry->se.is_periodic && param->is_periodic) runqueue.num_periodic++; else if (entry->se.is_periodic && !param->is_periodic) runqueue.num_periodic--; // Sets the parameters from param to the "se" struct parameters. entry->se.prio = param->sched_priority; entry->se.period = param->period; entry->se.arrivaltime = param->arrivaltime; entry->se.is_periodic = param->is_periodic; entry->se.deadline = timer_get_ticks() + param->deadline; entry->se.next_period = timer_get_ticks(); entry->se.is_under_analysis = true; entry->se.executed = false; return 1; } } return -1; } int sys_sched_getparam(pid_t pid, sched_param_t *param) { list_head *it; // Iter over the runqueue to find the task list_for_each (it, &runqueue.queue) { task_struct *entry = list_entry(it, task_struct, run_list); if (entry->pid == pid) { //Sets the parameters from the "se" struct to param param->sched_priority = entry->se.prio; param->period = entry->se.period; param->deadline = entry->se.deadline; param->arrivaltime = entry->se.arrivaltime; return 1; } } return -1; } /// @brief Performs the response time analysis for the current list /// of periodic processes. /// @return 1 if scheduling periodic processes is feasable, 0 otherwise. static int __response_time_analysis() { task_struct *entry, *previous; time_t r, previous_r = 0; list_for_each_decl(it, &runqueue.queue) { entry = list_entry(it, task_struct, run_list); if (entry->se.is_periodic) { // Put r equal to worst case exec because is the first point in time that the task could possibly complete r = entry->se.worst_case_exec; previous_r = 0; // The analysis can be completed either missing the deadline or reaching a fixed point while (r < entry->se.deadline && r != previous_r) { previous_r = r; r = entry->se.worst_case_exec; list_for_each_decl(it2, &runqueue.queue) { previous = list_entry(it2, task_struct, run_list); // Check the interferences of higher priority processes if (previous->se.is_periodic && previous->se.period < entry->se.period) { r += (int)ceil((double)previous_r / (double)previous->se.period) * previous->se.worst_case_exec; pr_debug("%d += (%.2f / %.2f) * %d\n", r, (double)previous_r, (double)previous->se.period, previous->se.worst_case_exec); pr_debug("Response Time Analysis -> [%s]vs[%s] R = %d\n\n", entry->name, previous->name, r); } } } // Feasibility of scheduler is guaranteed if and only if response time analysis is lower than deadline. if (r > entry->se.deadline) return 1; } } return 0; } int sys_waitperiod() { if (runqueue.curr) { if (runqueue.curr->se.is_periodic) { // Update the Worst Case Execution Time (WCET). time_t wcet = timer_get_ticks() - runqueue.curr->se.exec_start; if (runqueue.curr->se.worst_case_exec < wcet) runqueue.curr->se.worst_case_exec = wcet; // Update thye utilization factor. runqueue.curr->se.utilization_factor = ((double)runqueue.curr->se.worst_case_exec / (double)runqueue.curr->se.period); // If the task is under analysis, we need to test if the process can be // placed with the other periodic tasks. if (runqueue.curr->se.is_under_analysis) { runqueue.curr->se.worst_case_exec = runqueue.curr->se.sum_exec_runtime; bool_t is_not_schedulable = false; #if defined(SCHEDULER_EDF) double u = 0; task_struct *entry; list_for_each_decl(it, &runqueue.queue) { entry = list_entry(it, task_struct, run_list); // Sum the utilization factor of all periodic tasks. if (entry->se.is_periodic) u += entry->se.utilization_factor; } if (u > 1) { is_not_schedulable = true; } pr_debug("utilization factor = %f\n", u); #elif defined(SCHEDULER_RM) // Calculating least upper bound of utilization factor. // For large amount of processes ulub asymptotically should reach ln(2). double ulub = (runqueue.num_periodic * (pow(2, (1.0 / runqueue.num_periodic)) - 1)); double u = 0; task_struct *entry; list_for_each_decl(it, &runqueue.queue) { entry = list_entry(it, task_struct, run_list); // Sum the utilization factor of all periodic tasks. if (entry->se.is_periodic) u += entry->se.utilization_factor; } // If the sum of utilization factor is bounded between ulub and 1 we need to calculate // the response time analysis for each process. if (u > 1) { is_not_schedulable = true; } else if (u <= ulub) is_not_schedulable = false; else is_not_schedulable = __response_time_analysis(); #endif // If it is not schedulable, we need to tell it to the process. if (is_not_schedulable) return -ENOTSCHEDULABLE; // Otherwise, it is schedulable. runqueue.curr->se.is_under_analysis = false; // The task has been executed as non-periodic process so that his deadline is not been updated // by the scheduling algorithm of periodic tasks. We need to update it manually. runqueue.curr->se.next_period = timer_get_ticks(); runqueue.curr->se.deadline = timer_get_ticks() + runqueue.curr->se.period; } if (timer_get_ticks() > runqueue.curr->se.deadline) pr_warning("%d > %d Missing deadline...\n", timer_get_ticks(), runqueue.curr->se.deadline); // Tell the scheduler that we have executed the periodic process. runqueue.curr->se.executed = true; } else pr_warning("An aperiodic task is calling `waitperiod`, ignoring...\n"); return 0; } return -ESRCH; }