Update MentOs code to the latest development version.

This commit is contained in:
Enrico Fraccaroli
2021-10-04 11:44:02 +02:00
parent 6fd063984a
commit b01eccca2e
484 changed files with 42358 additions and 20285 deletions
+447 -257
View File
@@ -1,308 +1,498 @@
/// MentOS, The Mentoring Operating system project
/// @file process.c
/// @brief Process data structures and functions.
/// @copyright (c) 2019 This file is distributed under the MIT License.
/// @copyright (c) 2014-2021 This file is distributed under the MIT License.
/// See LICENSE.md for details.
#include "kernel_levels.h"
//#ifndef __DEBUG_LEVEL__
//#define __DEBUG_LEVEL__ LOGLEVEL_DEBUG
//#endif
#include <errno.h>
#include "process.h"
#include "prio.h"
#include "init.h"
#include "panic.h"
#include "kheap.h"
#include "debug.h"
#include "unistd.h"
#include "string.h"
#include "list_head.h"
#include "stdatomic.h"
#include "scheduler.h"
#include "assert.h"
#include "libgen.h"
#include "string.h"
#include "timer.h"
#include "fcntl.h"
#include "panic.h"
#include "debug.h"
#include "wait.h"
#include "prio.h"
#include "vfs.h"
#include "elf.h"
#define PUSH_ON_STACK(stack, type, item) \
*((type *)(stack -= sizeof(type))) = item
/// Cache for creating the task structs.
static kmem_cache_t *task_struct_cache;
/// @brief The task_struct of the init process.
static task_struct *init_proc;
void exit_handler()
static inline int __push_args_on_stack(uintptr_t *esp, char *args[], char ***argsptr)
{
exit(1);
kernel_panic("I should not be here.\n");
int argc = 0;
char *args_ptr[256];
// Count the number of arguments.
while (args[argc] != NULL) {
++argc;
}
// Prepare args with space for the terminating NULL.
for (int i = argc - 1; i >= 0; --i) {
for (int j = strlen(args[i]); j >= 0; --j) {
PUSH_ARG((*esp), char, args[i][j]);
}
args_ptr[i] = (char *)(*esp);
}
// Push terminating NULL.
PUSH_ARG((*esp), char *, (char *)NULL);
// Push array of pointers to the arguments.
for (int i = argc - 1; i >= 0; --i) {
PUSH_ARG((*esp), char *, args_ptr[i]);
}
(*argsptr) = (char **)(*esp);
return argc;
}
task_struct *create_init_process()
static int __reset_process(task_struct *task)
{
dbg_print("Building init process...\n");
// Create a new task_struct.
init_proc = kmalloc(sizeof(task_struct));
// TODO: process is IN USER SPACE! it should be in KERNEL SPACE!
memset(init_proc, 0, sizeof(task_struct));
// Set the id of the process.
init_proc->pid = get_new_pid();
// Set the name of the process.
strcpy(init_proc->name, "init");
// Set the statistics of the process.
init_proc->se.prio = DEFAULT_PRIO;
init_proc->se.start_runtime = 0;
init_proc->se.exec_start = 0;
init_proc->se.sum_exec_runtime = 0;
init_proc->se.vruntime = 0;
// Initialize the list_head.
list_head_init(&init_proc->run_list);
// Initialize the children list_head.
list_head_init(&init_proc->children);
// Initialize the sibling list_head.
list_head_init(&init_proc->sibling);
// Create a new stack segment.
init_proc->mm = create_process_image(DEFAULT_STACK_SIZE);
char *stack = (char *)init_proc->mm->start_stack;
// Clean stack space.
memset(stack, 0, DEFAULT_STACK_SIZE);
// Set the base address of the stack.
char *ebp = (char *)(stack + DEFAULT_STACK_SIZE);
// Create a pointer to keep track of the top of the stack.
char *esp = ebp;
// Set exit_handler as terminating function for init.
PUSH_ON_STACK(esp, uintptr_t, (uintptr_t)&exit_handler);
// Set the top address of the stack.
init_proc->thread.useresp = (uintptr_t)esp;
// Set the base address of the stack.
init_proc->thread.ebp = (uintptr_t)ebp;
// Set the program counter.
init_proc->thread.eip = (uintptr_t)&main_init;
// Enable the interrupts.
init_proc->thread.eflags = init_proc->thread.eflags | EFLAG_IF;
// Clear the current working directory.
memset(init_proc->cwd, '\0', PATH_MAX);
// Set the state of the process as running.
init_proc->state = TASK_RUNNING;
// Active the current process.
enqueue_task(init_proc);
pr_debug("__reset_process(%p `%s`)\n", task, task->name);
// Create a new stack segment.
task->mm = create_blank_process_image(DEFAULT_STACK_SIZE);
if (task->mm == NULL) {
pr_err("Failed to initialize process mm structure.\n");
return 0;
}
dbg_print("--------------------------------------------------\n");
dbg_print("- %s process (PID: %d, eflags: %d)\n", init_proc->name,
init_proc->pid, init_proc->thread.eflags);
dbg_print("\tStack: [0x%p - 0x%p]\n", init_proc->mm->start_stack,
init_proc->mm->start_stack + DEFAULT_STACK_SIZE);
dbg_print("\tebp: 0x%p\n", init_proc->thread.ebp);
dbg_print("\tesp: 0x%p\n", init_proc->thread.useresp);
dbg_print("\teip: 0x%p\n", init_proc->thread.eip);
dbg_print("--------------------------------------------------\n");
// Save the current page directory.
page_directory_t *crtdir = paging_get_current_directory();
// FIXME: Now to clear the stack a pgdir switch is made, it should be a kernel mmapping.
paging_switch_directory_va(task->mm->pgd);
return init_proc;
// Clean stack space.
memset((char *)task->mm->start_stack, 0, DEFAULT_STACK_SIZE);
// Set the base address of the stack.
task->thread.regs.ebp = (uintptr_t)(task->mm->start_stack + DEFAULT_STACK_SIZE);
// Set the top address of the stack.
task->thread.regs.useresp = task->thread.regs.ebp;
// Enable the interrupts.
task->thread.regs.eflags = task->thread.regs.eflags | EFLAG_IF;
// Restore previous pgdir
paging_switch_directory(crtdir);
return 1;
}
char *get_current_dir_name()
static int __load_executable(const char *path, task_struct *task, uint32_t *entry)
{
task_struct *current_process = kernel_get_current_process();
if (current_process != NULL) {
return strdup(current_process->cwd);
}
return kstrdup("/");
pr_debug("__load_executable(`%s`, %p `%s`, %p)\n", path, task, task->name, entry);
vfs_file_t *file = vfs_open(path, O_RDONLY, 0);
if (file == NULL) {
pr_err("Cannot find executable!\n");
return 0;
}
// Check that the file is actually an executable before destroying the `mm`.
if (!elf_check_file_type(file, ET_EXEC)) {
pr_err("This is not a valid ELF executable `%s`!\n", path);
return 0;
}
// FIXME: When threads will be implemented
// they should share the mm, so the destroy_process_image must be called
// only when all the threads are terminated. This can be accomplished by using
// an internal counter on the mm.
if (task->mm)
destroy_process_image(task->mm);
// Return code variable.
int ret = 0;
// Recreate the memory of the process.
if (__reset_process(task)) {
// Load the elf file, check if 0 is returned and print the error.
if (!(ret = elf_load_file(task, file, entry))) {
pr_err("Failed to load ELF file `%s`!\n", path);
}
}
// Close the file.
vfs_close(file);
return ret;
}
void sys_getcwd(char *path, size_t size)
static inline int __count_args_bytes(char **args)
{
task_struct *current_process = kernel_get_current_process();
if ((current_process != NULL) && (path != NULL)) {
strncpy(path, current_process->cwd, size);
}
int argc = 0;
int bytes = 0;
// Count the number of arguments.
while (args[argc] != NULL) {
++argc;
}
for (int i = 0; i < argc; i++) {
bytes += strlen(args[i]) + 1;
}
return bytes + (argc + 1 /* The NULL terminator */) * sizeof(char *);
}
static inline task_struct *__alloc_task(task_struct *source, task_struct *parent, const char *name)
{
// Create a new task_struct.
task_struct *proc = kmem_cache_alloc(task_struct_cache, GFP_KERNEL);
// Clear the memory.
memset(proc, 0, sizeof(task_struct));
// Set the id of the process.
proc->pid = scheduler_getpid();
// Set the state of the process as running.
proc->state = TASK_RUNNING;
// Set the current opened file descriptors and the maximum number of file descriptors.
if (source)
vfs_dup_task(proc, source);
else
vfs_init_task(proc);
// Set the pointer to process's parent.
proc->parent = parent;
// Initialize the list_head.
list_head_init(&proc->run_list);
// Initialize the children list_head.
list_head_init(&proc->children);
// Initialize the sibling list_head.
list_head_init(&proc->sibling);
// If we have a parent, set the sibling child relation.
if (parent) {
// Set the new_process as child of current.
list_head_add_tail(&proc->sibling, &parent->children);
}
if (source)
memcpy(&proc->thread, &source->thread, sizeof(thread_struct_t));
// Set the statistics of the process.
proc->uid = 0;
proc->sid = 0;
proc->gid = 0;
proc->se.prio = DEFAULT_PRIO;
proc->se.start_runtime = timer_get_ticks();
proc->se.exec_start = timer_get_ticks();
proc->se.exec_runtime = 0;
proc->se.sum_exec_runtime = 0;
proc->se.vruntime = 0;
proc->se.period = 0;
proc->se.deadline = 0;
proc->se.arrivaltime = timer_get_ticks();
proc->se.executed = false;
proc->se.is_periodic = false;
proc->se.is_under_analysis = false;
proc->se.next_period = 0;
proc->se.worst_case_exec = 0;
proc->se.utilization_factor = 0;
// Initialize the exit code of the process.
proc->exit_code = 0;
// Copy the name.
if (name)
strcpy(proc->name, name);
// Do not touch the task's segments.
proc->mm = NULL;
// Initialize the error number.
proc->error_no = 0;
// Initialize the current working directory.
if (source)
strcpy(proc->cwd, source->cwd);
else
strcpy(proc->cwd, "/");
// Clear the signal handler.
memset(&proc->sighand, 0x00, sizeof(sighand_t));
spinlock_init(&proc->sighand.siglock);
atomic_set(&proc->sighand.count, 0);
for (int i = 0; i < NSIG; ++i) {
proc->sighand.action[i].sa_handler = SIG_DFL;
sigemptyset(&proc->sighand.action[i].sa_mask);
proc->sighand.action[i].sa_flags = 0;
}
// Clear the masks.
sigemptyset(&proc->blocked);
sigemptyset(&proc->real_blocked);
sigemptyset(&proc->saved_sigmask);
// Initialzie the data structure storing the pending signals.
list_head_init(&proc->pending.list);
sigemptyset(&proc->pending.signal);
// Initalize real_timer for intervals
proc->real_timer = NULL;
return proc;
}
int init_tasking()
{
if ((task_struct_cache = KMEM_CREATE(task_struct)) == NULL) {
return 0;
}
return 1;
}
task_struct *process_create_init(const char *path)
{
pr_debug("Building init process...\n");
// Allocate the memory for the process.
init_proc = __alloc_task(NULL, NULL, "init");
// == INITIALIZE `/proc/video` ============================================
// Check that the fd_list is initialized.
assert(init_proc->fd_list && "File descriptor list not initialized.");
assert((init_proc->max_fd > 3) && "File descriptor list cannot contain the standard IOs.");
// Create STDIN descriptor.
vfs_file_t *stdin = vfs_open("/proc/video", O_RDONLY, 0);
stdin->count++;
init_proc->fd_list[STDIN_FILENO].file_struct = stdin;
init_proc->fd_list[STDIN_FILENO].flags_mask = O_RDONLY;
pr_debug("`/proc/video` stdin : %p\n", stdin);
// Create STDOUT descriptor.
vfs_file_t *stdout = vfs_open("/proc/video", O_WRONLY, 0);
stdout->count++;
init_proc->fd_list[STDOUT_FILENO].file_struct = stdout;
init_proc->fd_list[STDOUT_FILENO].flags_mask = O_WRONLY;
pr_debug("`/proc/video` stdout : %p\n", stdout);
// Create STDERR descriptor.
vfs_file_t *stderr = vfs_open("/proc/video", O_WRONLY, 0);
stderr->count++;
init_proc->fd_list[STDERR_FILENO].file_struct = stderr;
init_proc->fd_list[STDERR_FILENO].flags_mask = O_WRONLY;
pr_debug("`/proc/video` stderr : %p\n", stderr);
// ------------------------------------------------------------------------
// == INITIALIZE TASK MEMORY ==============================================
// Load the executable.
if (!__load_executable(path, init_proc, &init_proc->thread.regs.eip)) {
pr_err("Entry for init: %d\n", init_proc->thread.regs.eip);
kernel_panic("Init not valid (%d)!");
}
// ------------------------------------------------------------------------
// == INITIALIZE PROGRAM ARGUMENTS ========================================
// Save the current page directory.
page_directory_t *crtdir = paging_get_current_directory();
// Switch to init page directory.
paging_switch_directory_va(init_proc->mm->pgd);
// Prepare argv and envp for the init process.
char **argv_ptr, **envp_ptr;
int argc = 1;
static char *argv[] = {
"/bin/init",
(char *)NULL
};
static char *envp[] = {
(char *)NULL
};
// Save where the arguments start.
init_proc->mm->arg_start = init_proc->thread.regs.useresp;
// Push the arguments on the stack.
__push_args_on_stack(&init_proc->thread.regs.useresp, argv, &argv_ptr);
// Save where the arguments end.
init_proc->mm->arg_end = init_proc->thread.regs.useresp;
// Save where the environmental variables start.
init_proc->mm->env_start = init_proc->thread.regs.useresp;
// Push the environment on the stack.
__push_args_on_stack(&init_proc->thread.regs.useresp, envp, &envp_ptr);
// Save where the environmental variables end.
init_proc->mm->env_end = init_proc->thread.regs.useresp;
// Push the `main` arguments on the stack (argc, argv, envp).
PUSH_ARG(init_proc->thread.regs.useresp, char **, envp_ptr);
PUSH_ARG(init_proc->thread.regs.useresp, char **, argv_ptr);
PUSH_ARG(init_proc->thread.regs.useresp, int, argc);
// Restore previous pgdir
paging_switch_directory(crtdir);
// ------------------------------------------------------------------------
// Active the current process.
scheduler_enqueue_task(init_proc);
pr_debug("Executing '%s' (pid: %d)...\n", init_proc->name, init_proc->pid);
return init_proc;
}
char *sys_getcwd(char *buf, size_t size)
{
task_struct *current_process = scheduler_get_current_process();
if ((current_process != NULL) && (buf != NULL)) {
strncpy(buf, current_process->cwd, size);
return buf;
}
return (char *)-EACCES;
}
void sys_chdir(char const *path)
{
task_struct *current_process = kernel_get_current_process();
if ((current_process != NULL) && (path != NULL)) {
strcpy(current_process->cwd, path);
}
task_struct *current_process = scheduler_get_current_process();
if ((current_process != NULL) && (path != NULL)) {
char absolute_path[PATH_MAX];
realpath(path, absolute_path);
strcpy(current_process->cwd, absolute_path);
}
}
pid_t sys_vfork(pt_regs *r)
void sys_fchdir(int fd)
{
task_struct *current = kernel_get_current_process();
if (current == NULL) {
kernel_panic("There is no current process!");
}
dbg_print("Forking '%s'(%d) process...\n", current->name, current->pid);
// Create a new task_struct.
// TODO: process is IN USER SPACE! it should be in KERNEL SPACE!
task_struct *new_process = kmalloc(sizeof(task_struct));
// TODO: this is NOT a deep copy. should a deep copy be used here?
memcpy(new_process, current, sizeof(task_struct));
// Set the id of the process.
new_process->pid = get_new_pid();
// Set the statistics of the process.
new_process->se.prio = DEFAULT_PRIO;
new_process->se.start_runtime = 0;
new_process->se.exec_start = 0;
new_process->se.sum_exec_runtime = 0;
// TODO: vruntime should be the scheduled highest values so far.
new_process->se.vruntime = current->se.vruntime;
// Create a new stack segment.
new_process->mm = create_process_image(DEFAULT_STACK_SIZE);
char *stack = (char *)new_process->mm->start_stack;
// Copy the father's stack.
memcpy((char *)new_process->mm->start_stack,
(char *)current->mm->start_stack, DEFAULT_STACK_SIZE);
// Set the base address of the stack.
char *ebp = stack + DEFAULT_STACK_SIZE; // TODO: da controllare
// Create a pointer to keep track of the top of the stack.
char *esp = stack + (r->useresp - current->mm->start_stack);
// Set the top address of the stack.
new_process->thread.useresp = (uintptr_t)esp;
// Set the base address of the stack.
new_process->thread.ebp = (uintptr_t)ebp;
// Set the program counter.
new_process->thread.eip = r->eip;
// Set the base registers.
new_process->thread.eax = 0;
new_process->thread.ebx = r->ebx;
new_process->thread.ecx = r->ecx;
new_process->thread.edx = r->edx;
// Enable the interrupts.
new_process->thread.eflags = new_process->thread.eflags | EFLAG_IF;
// Set the state of the process as running.
new_process->state = TASK_RUNNING;
// Set current as parent for the new process
new_process->parent = current;
// Initialize the list_head.
list_head_init(&new_process->run_list);
// Initialize the children list_head.
list_head_init(&new_process->children);
// Initialize the children list_head.
list_head_init(&new_process->sibling);
// Set the new_process as child of current.
list_head_add_tail(&current->children, &new_process->sibling);
// Active the new process.
enqueue_task(new_process);
dbg_print("--------------------------------------------------\n");
dbg_print("- %s process (PID: %d, eflags: %d)\n", new_process->name,
new_process->pid, new_process->thread.eflags);
dbg_print("\teip : 0x%p\n", new_process->thread.eip);
dbg_print("\tebp : 0x%p\n", new_process->thread.ebp);
dbg_print("\tesp : 0x%p\n", new_process->thread.useresp);
dbg_print("\tStack : 0x%p\n", new_process->mm->start_stack);
dbg_print("\tRunList: 0x%p\n", &new_process->run_list);
dbg_print("--------------------------------------------------\n");
dbg_print("Fork of '%s' (child pid: %d) process completed.\n",
current->name, current->pid);
// Return PID of child process to parent.
return new_process->pid;
// Get the current task.
task_struct *task = scheduler_get_current_process();
// Check the current FD.
if (fd >= 0 && fd < task->max_fd) {
// Get the file descriptor.
vfs_file_descriptor_t *vfd = &task->fd_list[fd];
// Check the file.
if (vfd->file_struct != NULL) {
char absolute_path[PATH_MAX];
realpath(vfd->file_struct->name, absolute_path);
strcpy(task->cwd, absolute_path);
}
}
}
static inline int push_args_on_stack(uintptr_t *esp, char *args[],
char ***argsptr)
pid_t sys_fork(pt_regs *f)
{
int argc = 0;
char *args_ptr[256];
// Count the number of arguments.
while (args[argc] != NULL) {
++argc;
}
// Push terminating NULL.
PUSH_ON_STACK((*esp), char *, (char *)NULL);
// Prepare args with space for the terminating NULL.
for (int i = argc - 1; i >= 0; --i) {
for (int j = strlen(args[i]); j >= 0; --j) {
PUSH_ON_STACK((*esp), char, args[i][j]);
}
args_ptr[i] = (char *)(*esp);
}
// Push terminating NULL.
PUSH_ON_STACK((*esp), char *, (char *)NULL);
// Push array of pointers to the arguments.
for (int i = argc - 1; i >= 0; --i) {
PUSH_ON_STACK((*esp), char *, args_ptr[i]);
}
(*argsptr) = (char **)(*esp);
task_struct *current = scheduler_get_current_process();
if (current == NULL)
kernel_panic("There is no current process!");
return argc;
pr_debug("Forking '%s' (pid: %d)...\n", current->name, current->pid);
// Update current process registers, they should be equal
// to the ones of the child process, except for eax.
scheduler_store_context(f, current);
// Allocate the memory for the process.
task_struct *proc = __alloc_task(current, current, current->name);
// Copy the father's stack, memory, heap etc... to the child process
proc->mm = clone_process_image(current->mm);
// Set the eax as 0, to indicate the child process
proc->thread.regs.eax = 0;
// Enable the interrupts.
proc->thread.regs.eflags = proc->thread.regs.eflags | EFLAG_IF;
// Copy session and group id of the parent into the child
proc->sid = current->sid;
proc->gid = current->gid;
// Active the new process.
scheduler_enqueue_task(proc);
pr_debug("Forked '%s' (pid: %d, gid: %d, sid: %d)...\n", proc->name, proc->pid, proc->gid, proc->sid);
// Return PID of child process to parent.
return proc->pid;
}
int sys_execve(pt_regs *r)
int sys_execve(pt_regs *f)
{
char **argv, **_argv, **envp, **_envp;
// Check the current process.
task_struct *current = kernel_get_current_process();
if (current == NULL) {
kernel_panic("There is no current process!");
}
// Check the current process.
task_struct *current = scheduler_get_current_process();
if (current == NULL)
kernel_panic("There is no current process!");
// Get the filename.
uintptr_t *filename = (uintptr_t *)r->ebx;
if (filename == NULL) {
return -1;
}
char **origin_argv, **saved_argv, **final_argv;
char **origin_envp, **saved_envp, **final_envp;
char name_buffer[NAME_MAX];
// Get the arguments.
argv = (char **)r->ecx;
// Get the environment.
envp = (char **)r->edx;
// Get the filename.
char *filename = (char *)f->ebx;
if (filename == NULL) {
pr_err("Received NULL filename.\n");
return -1;
}
// Get the arguments
origin_argv = (char **)f->ecx;
// Get the environment.
origin_envp = (char **)f->edx;
// Check the argument, the environment, and that at least the name is provided.
if (origin_argv == NULL) {
pr_err("sys_execve failed: must provide argv.\n");
return -1;
}
if (origin_argv[0] == NULL) {
pr_err("sys_execve failed: must provide the name.\n");
return -1;
}
if (origin_envp == NULL) {
pr_err("sys_execve failed: must provide the environment.\n");
return -1;
}
// Check the argument and that at least the name is provided.
if ((argv == NULL) || (argv[0] == NULL)) {
return -1;
}
// Save the name of the process.
strcpy(name_buffer, origin_argv[0]);
// Check that the environment is provided.
if (envp == NULL) {
kernel_panic("You must provide at least an empty list for envp!");
}
// == COPY PROGRAM ARGUMENTS ==============================================
// Copy argv and envp to kernel memory, because all the old process memory will be discarded.
int argv_bytes = __count_args_bytes(origin_argv);
int envp_bytes = __count_args_bytes(origin_envp);
if ((argv_bytes < 0) || (envp_bytes < 0)) {
pr_err("Failed to count required memory to store arguments and environment (%d + %d).\n",
argv_bytes, envp_bytes);
return -1;
}
void *args_mem = kmalloc(argv_bytes + envp_bytes);
if (!args_mem) {
pr_err("Failed to allocate memory for arguments and environment %d (%d + %d).\n",
argv_bytes + envp_bytes, argv_bytes, envp_bytes);
return -1;
}
// Copy the arguments.
uint32_t args_mem_ptr = (uint32_t)args_mem + (argv_bytes + envp_bytes);
__push_args_on_stack(&args_mem_ptr, origin_argv, &saved_argv);
__push_args_on_stack(&args_mem_ptr, origin_envp, &saved_envp);
// Check the memory pointer.
assert(args_mem_ptr == (uint32_t)args_mem);
// ------------------------------------------------------------------------
// Set the name.
strcpy(current->name, argv[0]);
// == INITIALIZE TASK MEMORY ==============================================
if (!__load_executable(filename, current, &current->thread.regs.eip)) {
pr_err("Failed to load executable!\n");
// Free the temporary args memory.
kfree(args_mem);
return -1;
}
// ------------------------------------------------------------------------
// Set the top address of the stack.
current->thread.useresp = (uintptr_t)current->thread.ebp;
// == INITIALIZE PROGRAM ARGUMENTS ========================================
// Save the current page directory.
page_directory_t *crtdir = paging_get_current_directory();
// Set the program counter.
current->thread.eip = (uintptr_t)filename;
// Change the page directory to point to the newly created process
paging_switch_directory_va(current->mm->pgd);
int argc = push_args_on_stack(&current->thread.useresp, argv, &_argv);
push_args_on_stack(&current->thread.useresp, envp, &_envp);
// Save where the arguments start.
current->mm->arg_start = current->thread.regs.useresp;
// Push the arguments on the stack.
int argc = __push_args_on_stack(&current->thread.regs.useresp, saved_argv, &final_argv);
// Save where the arguments end, and the env starts.
current->mm->env_start = current->mm->arg_end = current->thread.regs.useresp;
// Push the environment on the stack.
int envc = __push_args_on_stack(&current->thread.regs.useresp, saved_envp, &final_envp);
// Save where the environmental variables end.
current->mm->env_end = current->thread.regs.useresp;
// Push the `main` arguments on the stack (argc, argv, envp).
PUSH_ARG(current->thread.regs.useresp, char **, final_envp);
PUSH_ARG(current->thread.regs.useresp, char **, final_argv);
PUSH_ARG(current->thread.regs.useresp, int, argc);
PUSH_ON_STACK(current->thread.useresp, char **, _envp);
PUSH_ON_STACK(current->thread.useresp, char **, _argv);
PUSH_ON_STACK(current->thread.useresp, int, argc);
PUSH_ON_STACK(current->thread.useresp, uintptr_t, (uintptr_t)exit_handler);
// Restore previous pgdir
paging_switch_directory(crtdir);
// ------------------------------------------------------------------------
// dbg_print("_ARGV:0x%09x {\n", _argv);
// for (int i = 0; _argv[i] != NULL; ++i) {
// dbg_print("\t[%d][0x%09x]%s\n", i, _argv[i], _argv[i]);
// }
// dbg_print("}\n");
//
// if (_envp != NULL) {
// dbg_print("_ENVP:0x%09x {\n", _envp);
// for (int i = 0; _envp[i] != NULL; ++i) {
// dbg_print("\t[%d][0x%09x]%s\n", i, _envp[i], _envp[i]);
// }
// dbg_print("}\n");
// }
// Change the name of the process.
strcpy(current->name, name_buffer);
// Perform the switch to the new process.
do_switch(current, r);
// Free the temporary args memory.
kfree(args_mem);
dbg_print("Executing '0x%p' for process %d with %d arguments (0x%p)...\n",
filename, current->pid, argc, argv);
// Perform the switch to the new process.
scheduler_restore_context(current, f);
return 0;
pr_debug("Executing '%s' (pid: %d)...\n", current->name, current->pid);
return 0;
}
+587 -289
View File
@@ -1,9 +1,15 @@
/// MentOS, The Mentoring Operating system project
/// @file scheduler.c
/// @brief Scheduler structures and functions.
/// @copyright (c) 2019 This file is distributed under the MIT License.
/// @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 "vfs.h"
#include "scheduler.h"
#include "tss.h"
#include "fpu.h"
@@ -12,11 +18,13 @@
#include "kheap.h"
#include "panic.h"
#include "debug.h"
#include "clock.h"
#include "time.h"
#include "errno.h"
#include "rbtree.h"
#include "stdlib.h"
#include "list_head.h"
#include "paging.h"
#include "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).
@@ -27,357 +35,647 @@ extern void enter_userspace(uintptr_t location, uintptr_t stack);
/// The list of processes.
runqueue_t runqueue;
uint32_t get_new_pid(void)
void scheduler_initialize()
{
/// The current unused PID.
static unsigned long int tid = 1;
// Return the pid and increment.
return tid++;
// 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;
}
task_struct *kernel_get_current_process()
uint32_t scheduler_getpid(void)
{
return runqueue.curr;
/// The current unused PID.
static unsigned long int tid = 1;
// Return the pid and increment.
return tid++;
}
task_struct *kernel_get_running_process(pid_t pid)
task_struct *scheduler_get_current_process()
{
list_head *it;
list_for_each (it, &runqueue.queue) {
task_struct *entry = list_entry(it, task_struct, run_list);
if (entry != NULL) {
if (entry->pid == pid) {
return entry;
}
}
}
return NULL;
return runqueue.curr;
}
size_t kernel_get_active_processes()
time_t scheduler_get_maximum_vruntime()
{
return runqueue.num_active;
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;
}
void kernel_initialize_scheduler()
size_t scheduler_get_active_processes()
{
// 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;
return runqueue.num_active;
}
void enqueue_task(task_struct *process)
task_struct *scheduler_get_running_process(pid_t pid)
{
// 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;
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 dequeue_task(task_struct *process)
void scheduler_enqueue_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 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 kernel_schedule(pt_regs *f)
void scheduler_dequeue_task(task_struct *process)
{
// Check if there is a running process.
if (runqueue.curr == NULL) {
return;
}
//==== Update Statistics ===================================================
time_t delta_exec = get_millisecond() - runqueue.curr->se.exec_start;
// dbg_print("[%3d] %d = %d - %d\n", runqueue.curr->pid, delta_exec,
// get_millisecond(), runqueue.curr->se.exec_start);
// set the sum_exec_runtime
runqueue.curr->se.sum_exec_runtime += delta_exec;
//==========================================================================
//==== Handle Zombies ======================================================
task_struct *next_process = NULL;
if (runqueue.curr->state == EXIT_ZOMBIE) {
// 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_process = list_entry(nNode, task_struct, run_list);
// Remove the zombie task.
dequeue_task(runqueue.curr);
} else {
//==== Scheduling ======================================================
// Pointer to the next process to be executed.
next_process = pick_next_task(&runqueue, delta_exec);
//======================================================================
}
//==========================================================================
// Print, for debugging purpose, data about the current process.
if (runqueue.num_active > 2) {
dbg_print("PID:%3d, PRIO:%3d, VRUNTIME:%9d, SUM_EXEC:%9d\n",
next_process->pid, next_process->se.prio,
next_process->se.vruntime, next_process->se.sum_exec_runtime);
}
//==== Context switch ======================================================
// Update the context of the current process.
update_context(f, runqueue.curr);
// Check if the next and current processes are different.
if (next_process != runqueue.curr) {
// Copy into Kernel stack the next process's context.
do_switch(next_process, f);
runqueue.curr->se.sum_exec_runtime = get_millisecond();
// Update the last context switch time of the next process.
next_process->se.exec_start = get_millisecond();
}
//==========================================================================
// Update the start execution time if it is executed for the first time
if (next_process->se.start_runtime == 0)
next_process->se.start_runtime = get_millisecond();
// 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 update_context(pt_regs *f, task_struct *process)
void scheduler_run(pt_regs *f)
{
// Store the registers.
process->thread.gs = f->gs;
process->thread.fs = f->fs;
process->thread.es = f->es;
process->thread.ds = f->ds;
process->thread.edi = f->edi;
process->thread.esi = f->esi;
process->thread.ebp = f->ebp;
process->thread.ebx = f->ebx;
process->thread.edx = f->edx;
process->thread.ecx = f->ecx;
process->thread.eax = f->eax;
process->thread.eip = f->eip;
process->thread.eflags = f->eflags;
process->thread.useresp = f->useresp;
// TODO: Check if the following registers should be saved.
// process->thread.cs = f->cs;
// process->thread.ss = f->ss;
// Store the FPU.
switch_fpu();
// 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 do_switch(task_struct *process, pt_regs *f)
void scheduler_store_context(pt_regs *f, task_struct *process)
{
// Switch to the next process.
runqueue.curr = process;
// Restore the registers.
f->gs = process->thread.gs;
f->fs = process->thread.fs;
f->es = process->thread.es;
f->ds = process->thread.ds;
f->edi = process->thread.edi;
f->esi = process->thread.esi;
f->ebp = process->thread.ebp;
f->ebx = process->thread.ebx;
f->edx = process->thread.edx;
f->ecx = process->thread.ecx;
f->eax = process->thread.eax;
f->eip = process->thread.eip;
f->eflags = process->thread.eflags;
f->useresp = process->thread.useresp;
// TODO: Check if the following registers should be restored.
// f->cs = process->thread.cs;
// f->ss = process->thread.ss;
// Restore the FPU.
unswitch_fpu();
// Store the registers.
process->thread.regs = *f;
}
int set_user_nice(task_struct *p, long nice)
void scheduler_restore_context(task_struct *process, pt_regs *f)
{
if (PRIO_TO_NICE(p->se.prio) != nice && nice >= MIN_NICE &&
nice <= MAX_NICE) {
p->se.prio = NICE_TO_PRIO(nice);
}
return PRIO_TO_NICE(p->se.prio);
// 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 enter_user_jmp(uintptr_t location, uintptr_t stack)
void scheduler_enter_user_jmp(uintptr_t location, uintptr_t stack)
{
// Reset stack pointer for kernel.
tss_set_stack(0x10, initial_esp);
// Reset stack pointer for kernel.
tss_set_stack(0x10, initial_esp);
// update start execution time.
runqueue.curr->se.start_runtime = get_millisecond();
// update start execution time.
runqueue.curr->se.start_runtime = timer_get_ticks();
// last context switch time.
runqueue.curr->se.exec_start = get_millisecond();
// last context switch time.
runqueue.curr->se.exec_start = timer_get_ticks();
// Jump in location.
enter_userspace(location, stack);
// 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!");
}
// 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;
// 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;
}
// 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;
// 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!");
}
// 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;
}
if (increment < -40) {
increment = -40;
}
if (increment > 40) {
increment = 40;
}
int newNice = PRIO_TO_NICE(runqueue.curr->se.prio) + increment;
dbg_print("New nice value would be : %d\n", newNice);
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 (newNice < MIN_NICE) {
newNice = MIN_NICE;
}
if (newNice > MAX_NICE) {
newNice = MAX_NICE;
}
int actualNice = set_user_nice(runqueue.curr, newNice);
dbg_print("Actual new nice value is: %d\n", actualNice);
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);
return actualNice;
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!");
}
// 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
/* For now we do not support waiting for processes inside the given
* process group (pid < -1).
*/
if ((pid < -1) || (pid == 0)) {
errno = ESRCH;
return (-1);
}
if (pid == runqueue.curr->pid) {
errno = ECHILD;
return (-1);
}
if (options != 0 && options != WNOHANG) {
errno = EINVAL;
return (-1);
}
if (status == NULL) {
errno = EFAULT;
return (-1);
}
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.
(*status) = entry->state; //TODO: da rivedere
// Remove entry from children of parent.
list_head_del(&entry->sibling);
// Delete the task_struct.
kfree(entry);
dbg_print("Freeing memory of process %d.\n", ppid);
return ppid;
}
return 0;
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 current task.
if (runqueue.curr == NULL) {
kernel_panic("There is no current process!");
}
task_struct *init_proc = kernel_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;
// If it has children, then init process has to take care of them.
if (!list_head_empty(&runqueue.curr->children)) {
dbg_print("Moving children of %s(%d) to init(%d): {\n",
runqueue.curr->name, runqueue.curr->pid, init_proc->pid);
// TODO: Try to plug the list of children instead of iterating.
list_head *it;
list_for_each (it, &runqueue.curr->children) {
task_struct *entry = list_entry(it, task_struct, sibling);
dbg_print(" [%d] %s\n", entry->pid, entry->name);
it = entry->sibling.prev;
list_head_del(&entry->sibling);
list_head_add_tail(&init_proc->children, &entry->sibling);
entry->parent = init_proc;
}
dbg_print("}\n");
dbg_print("Listing children of init(%d): {\n", init_proc->pid);
list_for_each (it, &init_proc->children) {
task_struct *entry = list_entry(it, task_struct, sibling);
dbg_print(" [%d] %s\n", entry->pid, entry->name);
}
dbg_print("}\n");
}
// Free the space occupied by the stack.
destroy_process_image(runqueue.curr->mm);
// Debugging message.
dbg_print("Process %d exited with value %d\n", runqueue.curr->pid,
exit_code);
// 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;
}
+106 -54
View File
@@ -1,73 +1,125 @@
/// MentOS, The Mentoring Operating system project
/// @file scheduler_algorithm.c
/// @brief Round Robin algorithm.
/// @date Mar 2019.
/// @copyright (c) 2014-2021 This file is distributed under the MIT License.
/// See LICENSE.md for details.
#include "timer.h"
#include "prio.h"
#include "debug.h"
#include "assert.h"
#include "list_head.h"
#include "wait.h"
#include "scheduler.h"
#define GET_WEIGHT(prio) prio_to_weight[USER_PRIO((prio))]
#define NICE_0_LOAD GET_WEIGHT(DEFAULT_PRIO)
task_struct *pick_next_task(runqueue_t *runqueue, time_t delta_exec)
static inline task_struct *scheduler_rr(runqueue_t *runqueue, bool_t skip_periodic)
{
// Pointer to the next task to schedule.
task_struct *next = NULL;
// If there is just one process, 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.
task_struct *next = NULL, *entry = NULL;
// Search for the next process (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.
if (it == &runqueue->queue)
continue;
// Get the current entry.
entry = list_entry(it, task_struct, run_list);
// We consider only runnable processes
if (entry->state != TASK_RUNNING)
continue;
// 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 && skip_periodic && !entry->se.is_under_analysis)
continue;
// We have our next entry.
next = entry;
break;
}
return next;
}
static inline task_struct *scheduler_priority(runqueue_t *runqueue, bool_t skip_periodic)
{
return scheduler_rr(runqueue, skip_periodic);
}
static inline task_struct *scheduler_cfs(runqueue_t *runqueue, bool_t skip_periodic)
{
return scheduler_rr(runqueue, skip_periodic);
}
static inline task_struct *scheduler_aedf(runqueue_t *runqueue)
{
return scheduler_rr(runqueue, false);
}
static inline task_struct *scheduler_edf(runqueue_t *runqueue)
{
return scheduler_rr(runqueue, false);
}
static inline task_struct *scheduler_rm(runqueue_t *runqueue)
{
return scheduler_rr(runqueue, false);
}
task_struct *scheduler_pick_next_task(runqueue_t *runqueue)
{
// 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);
// set the sum_exec_runtime.
runqueue->curr->se.sum_exec_runtime += runqueue->curr->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 (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.
runqueue->curr->se.exec_runtime = (int)(((double)runqueue->curr->se.exec_runtime) * factor);
}
// Update vruntime of the current process.
runqueue->curr->se.vruntime += runqueue->curr->se.exec_runtime;
}
// Pointer to the next task to schedule.
task_struct *next = NULL;
#if defined(SCHEDULER_RR)
//==== Implementatin of the Round-Robin Scheduling algorithm ============
//=======================================================================
next = scheduler_rr(runqueue, false);
#elif defined(SCHEDULER_PRIORITY)
//==== Implementatin of the Priority Scheduling algorithm ===============
// get the first element of the list
next = list_entry(/*...*/);
// Get its static priority.
time_t min = /*...*/
list_head *it;
// Inter over the runqueue to find the task with the smallest priority value
list_for_each (it, &runqueue->queue) {
task_struct *entry = list_entry(/*...*/);
// Check entry has a lower priority
if (/*...*/) {
/*...*/
}
}
//=======================================================================
next = scheduler_priority(runqueue, false);
#elif defined(SCHEDULER_CFS)
//==== Implementatin of the Completely Fair Scheduling ==================
// Get the weight of the current process.
// (use GET_WEIGHT macro!)
int weight = /*...*/
if (weight != NICE_0_LOAD) {
// get the multiplicative factor for its delta_exec.
double factor = /*...*/
// weight the delta_exec with the multiplicative factor.
delta_exec = // ...
}
// Update vruntime of the current process.
// ...
// Inter over the runqueue to find the task with the smallest vruntime value
// ...
//========================================================================
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. Have you implemented a scheduling algorithm?");
assert(next && "No valid task selected by the scheduling algorithm.");
return next;
// Update the last context switch time of the next process.
next->se.exec_start = timer_get_ticks();
return next;
}
@@ -1,23 +1,38 @@
; MentOS, The Mentoring Operating system project
; @file user.asm
; @brief
; @copyright (c) 2019 This file is distributed under the MIT License.
; @copyright (c) 2014-2021 This file is distributed under the MIT License.
; See LICENSE.md for details.
; Enter userspace (ring3) (from Ring 0, namely Kernel)
; Usage: enter_userspace(uintptr_t location, uintptr_t stack);
; On stack
; | stack |
; | location |
; | return address |
; | EBP | EBP
; | stack | [ebp + 0x0C] ARG1
; | location | [ebp + 0x08] ARG0
; | return address | [ebp + 0x04]
; | EBP | [ebp + 0x00]
; | SS |
; | ESP |
; | EFLAGS |
; | CS |
; | EIP |
; We can use the following macros to access the arguments ONLY AFTER 0x23 is
; pushed onto the stack, in fact, the first argument is after 0x08 because
; we just pushed first `ebp` and then `0x23`.
%define ARG0 [ebp + 0x08] ; Argument 0
%define ARG1 [ebp + 0x0C] ; Argument 1
%define ARG2 [ebp + 0x10] ; Argument 2
%define ARG3 [ebp + 0x14] ; Argument 3
%define ARG4 [ebp + 0x18] ; Argument 4
; -----------------------------------------------------------------------------
; SECTION (text)
; -----------------------------------------------------------------------------
section .text
global enter_userspace ; Allows the C code to call enter_userspace(...).
enter_userspace:
push ebp ; Save current ebp
@@ -45,38 +60,28 @@ enter_userspace:
;---------------------------------------------------------------------------
;==== (ESP) Stack address ==================================================
mov eax, [ebp + 0xC] ; get uintptr_t stack
mov eax, ARG1 ; get uintptr_t stack
push eax ; push process's stack address on Kernel's stack
;---------------------------------------------------------------------------
;==== (EFLAGS) =============================================================
pushf ; push EFLAGS into Kernel's stack
pop eax ; pop EFLAGS into eax
or eax, 0x200 ; enable interrupt ?request ring3
or eax, 0x200 ; enable interrupt
push eax ; push new EFLAGS on Kernel's stack
;---------------------------------------------------------------------------
;==== (CS) Code Segment ====================================================
push 0x1B ;
push 0x1b ;
;---------------------------------------------------------------------------
;==== (EIP) Entry point ====================================================
mov eax, [ebp + 0x8] ; get uintptr_t location
mov eax, ARG0 ; get uintptr_t location
push eax ; push uintptr_t location on Kernel's stack
;---------------------------------------------------------------------------
iret ; interrupt return
pop ebp
ret
; WE SHOULD NOT STILL BE HERE! :(
;==== Reset segment selector ===============================================
mov ax, 0x10
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
;---------------------------------------------------------------------------
add esp, 0x14 ; reset stack pointer (20 bytes)
pop ebp ; reset value of ebp
ret ; return to kernel code
; WE SHOULD NOT STILL BE HERE! :(p
+42
View File
@@ -0,0 +1,42 @@
/// MentOS, The Mentoring Operating system project
/// @file wait.c
/// @brief wait functions.
/// @copyright (c) 2014-2021 This file is distributed under the MIT License.
/// See LICENSE.md for details.
/// Change the header.
#define __DEBUG_HEADER__ "[WAIT ]"
#include "wait.h"
static inline void __add_wait_queue(wait_queue_head_t *head, wait_queue_entry_t *wq)
{
list_head_add_tail(&wq->task_list, &head->task_list);
}
static inline void __remove_wait_queue(wait_queue_head_t *head, wait_queue_entry_t *wq)
{
list_head_del(&wq->task_list);
}
void init_waitqueue_entry(wait_queue_entry_t *wq, struct task_struct *task)
{
wq->flags = 0;
wq->task = task;
wq->func = default_wake_function;
}
void add_wait_queue(wait_queue_head_t *head, wait_queue_entry_t *wq)
{
wq->flags &= ~WQ_FLAG_EXCLUSIVE;
spinlock_lock(&head->lock);
__add_wait_queue(head, wq);
spinlock_unlock(&head->lock);
}
void remove_wait_queue(wait_queue_head_t *head, wait_queue_entry_t *wq)
{
spinlock_lock(&head->lock);
__remove_wait_queue(head, wq);
spinlock_unlock(&head->lock);
}