/// @file process.c /// @brief Process data structures and functions. /// @copyright (c) 2014-2021 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__ "[PROC ]" // Set the log level. #define __DEBUG_LEVEL__ LOGLEVEL_NOTICE #include "process/process.h" #include "process/scheduler.h" #include "assert.h" #include "libgen.h" #include "string.h" #include "hardware/timer.h" #include "sys/errno.h" #include "fcntl.h" #include "system/panic.h" #include "io/debug.h" #include "process/wait.h" #include "process/prio.h" #include "fs/vfs.h" #include "elf/elf.h" #include "klib/stack_helper.h" /// 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; /// @brief Counts the number of arguments. /// @param args the array of arguments, it must be NULL terminated. /// @return the number of arguments. static inline int __count_args(char **args) { int argc = 0; while (args[argc] != NULL) ++argc; return argc; } /// @brief Counts the bytes occupied by the arguments. /// @param args the array of arguments, it must be NULL terminated. /// @return the bytes occupied by the arguments. static inline int __count_args_bytes(char **args) { // Count the number of arguments. int argc = __count_args(args); // Count the number of characters. int nchar = 0; for (int i = 0; i < argc; i++) { nchar += strlen(args[i]) + 1; } return nchar + (argc + 1 /* The NULL terminator */) * sizeof(char *); } /// @brief Pushes the arguments on the stack. /// @param stack pointer to the stack location. /// @param args the list of arguments. /// @return the final position of the stack, where the list of pushed arguments is stored. static inline char **__push_args_on_stack(uintptr_t *stack, char *args[]) { // Count the number of arguments. int argc = __count_args(args); // Prepare args with space for the terminating NULL. char *args_location[256]; for (int i = argc - 1; i >= 0; --i) { for (int j = strlen(args[i]); j >= 0; --j) { PUSH_VALUE_ON_STACK(*stack, args[i][j]); } args_location[i] = (char *)(*stack); } // Push terminating NULL. PUSH_VALUE_ON_STACK(*stack, (char *)NULL); // Push array of pointers to the arguments. for (int i = argc - 1; i >= 0; --i) { PUSH_VALUE_ON_STACK(*stack, args_location[i]); } return (char **)(*stack); } static int __reset_process(task_struct *task) { 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; } // 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); // 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; } static int __load_executable(const char *path, task_struct *task, uint32_t *entry) { 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; } 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->gid = 0; proc->sid = 0; proc->pgid = 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. argv_ptr = __push_args_on_stack(&init_proc->thread.regs.useresp, argv); // 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. envp_ptr = __push_args_on_stack(&init_proc->thread.regs.useresp, envp); // 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_VALUE_ON_STACK(init_proc->thread.regs.useresp, envp_ptr); PUSH_VALUE_ON_STACK(init_proc->thread.regs.useresp, argv_ptr); PUSH_VALUE_ON_STACK(init_proc->thread.regs.useresp, 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 = scheduler_get_current_process(); if ((current_process != NULL) && (path != NULL)) { char absolute_path[PATH_MAX]; realpath(path, absolute_path); // Check that the directory exists. vfs_file_t *dir = vfs_open(absolute_path, O_RDONLY | O_DIRECTORY, S_IXUSR); if (dir != NULL) { pr_debug("Success `%s` -> `%s` -> `%s`\n", path, absolute_path, dir->name); strcpy(current_process->cwd, absolute_path); vfs_close(dir); } else { pr_debug("Failed `%s` -> `%s` -> `NULL`\n", path, absolute_path); } } } void sys_fchdir(int fd) { // 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); } } } pid_t sys_fork(pt_regs *f) { task_struct *current = scheduler_get_current_process(); if (current == NULL) kernel_panic("There is no current process!"); 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->pgid = current->pgid; proc->uid = current->uid; proc->gid = current->gid; // Active the new process. scheduler_enqueue_task(proc); pr_debug("Forked '%s' (pid: %d, gid: %d, sid: %d, pgid: %d)...\n", proc->name, proc->pid, proc->gid, proc->sid, proc->pgid); // Return PID of child process to parent. return proc->pid; } int sys_execve(pt_regs *f) { // Check the current process. task_struct *current = scheduler_get_current_process(); if (current == NULL) kernel_panic("There is no current process!"); char **origin_argv, **saved_argv, **final_argv; char **origin_envp, **saved_envp, **final_envp; char name_buffer[NAME_MAX]; // 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; } // Save the name of the process. strcpy(name_buffer, origin_argv[0]); // == COPY PROGRAM ARGUMENTS ============================================== // Copy argv and envp to kernel memory, because all the old process memory will be discarded. int argc = __count_args(origin_argv); int argv_bytes = __count_args_bytes(origin_argv); int envc = __count_args(origin_envp); 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); saved_argv = __push_args_on_stack(&args_mem_ptr, origin_argv); saved_envp = __push_args_on_stack(&args_mem_ptr, origin_envp); // Check the memory pointer. assert(args_mem_ptr == (uint32_t)args_mem); // ------------------------------------------------------------------------ // == INITIALIZE TASK MEMORY ============================================== if (!__load_executable(filename, current, ¤t->thread.regs.eip)) { pr_err("Failed to load executable!\n"); // Free the temporary args memory. kfree(args_mem); return -1; } // ------------------------------------------------------------------------ // == INITIALIZE PROGRAM ARGUMENTS ======================================== // Save the current page directory. page_directory_t *crtdir = paging_get_current_directory(); // Change the page directory to point to the newly created process paging_switch_directory_va(current->mm->pgd); // Save where the arguments start. current->mm->arg_start = current->thread.regs.useresp; // Push the arguments on the stack. final_argv = __push_args_on_stack(¤t->thread.regs.useresp, saved_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. final_envp = __push_args_on_stack(¤t->thread.regs.useresp, saved_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_VALUE_ON_STACK(current->thread.regs.useresp, final_envp); PUSH_VALUE_ON_STACK(current->thread.regs.useresp, final_argv); PUSH_VALUE_ON_STACK(current->thread.regs.useresp, argc); // Restore previous pgdir paging_switch_directory(crtdir); // ------------------------------------------------------------------------ // Change the name of the process. strcpy(current->name, name_buffer); // Free the temporary args memory. kfree(args_mem); // Perform the switch to the new process. scheduler_restore_context(current, f); pr_debug("Executing '%s' (pid: %d)...\n", current->name, current->pid); return 0; }