1424 lines
44 KiB
C
1424 lines
44 KiB
C
/**
|
|
* @file kernel/sys/process.c
|
|
* @brief Task switch and thread scheduling.
|
|
*
|
|
* Implements the primary scheduling primitives for the kernel.
|
|
*
|
|
* Generally, what the kernel refers to as a "process" is an individual thread.
|
|
* The POSIX concept of a "process" is represented in Misaka as a collection of
|
|
* threads and their shared paging, signal, and file descriptor tables.
|
|
*
|
|
* Kernel threads are also "processes", referred to as "tasklets".
|
|
*
|
|
* Misaka allows nested kernel preemption, and task switching involves saving
|
|
* kernel state in a manner similar to setjmp/longjmp, as well as saving the
|
|
* outer context in the case of a nested task switch.
|
|
*
|
|
* @copyright This file is part of ToaruOS and is released under the terms
|
|
* of the NCSA / University of Illinois License - see LICENSE.md
|
|
* @author 2011-2021 K. Lange
|
|
* @author 2012 Markus Schober
|
|
* @author 2015 Dale Weiler
|
|
*/
|
|
#include <errno.h>
|
|
#include <kernel/assert.h>
|
|
#include <kernel/process.h>
|
|
#include <kernel/printf.h>
|
|
#include <kernel/string.h>
|
|
#include <kernel/vfs.h>
|
|
#include <kernel/spinlock.h>
|
|
#include <kernel/tree.h>
|
|
#include <kernel/list.h>
|
|
#include <kernel/mmu.h>
|
|
#include <kernel/shm.h>
|
|
#include <kernel/signal.h>
|
|
#include <kernel/time.h>
|
|
#include <kernel/misc.h>
|
|
#include <kernel/syscall.h>
|
|
#include <sys/wait.h>
|
|
#include <sys/signal_defs.h>
|
|
|
|
/* FIXME: This only needs the size of the regs struct... */
|
|
#include <kernel/arch/x86_64/regs.h>
|
|
|
|
tree_t * process_tree; /* Stores the parent-child process relationships; the root of this graph is 'init'. */
|
|
list_t * process_list; /* Stores all existing processes. Mostly used for sanity checking or for places where iterating over all processes is useful. */
|
|
list_t * process_queue; /* Scheduler ready queue. This the round-robin source. The head is the next process to run. */
|
|
list_t * sleep_queue; /* Ordered list of processes waiting to be awoken by timeouts. The head is the earliest thread to awaken. */
|
|
list_t * reap_queue; /* Processes that could not be cleaned up and need to be deleted. */
|
|
|
|
struct ProcessorLocal processor_local_data[32] = {0};
|
|
int processor_count = 1;
|
|
|
|
/* The following locks protect access to the process tree, scheduler queue,
|
|
* sleeping, and the very special wait queue... */
|
|
static spin_lock_t tree_lock = { 0 };
|
|
static spin_lock_t process_queue_lock = { 0 };
|
|
static spin_lock_t wait_lock_tmp = { 0 };
|
|
static spin_lock_t sleep_lock = { 0 };
|
|
static spin_lock_t reap_lock = { 0 };
|
|
|
|
void update_process_times(int includeSystem) {
|
|
uint64_t pTime = arch_perf_timer();
|
|
if (this_core->current_process->time_in && this_core->current_process->time_in < pTime) {
|
|
this_core->current_process->time_total += pTime - this_core->current_process->time_in;
|
|
}
|
|
this_core->current_process->time_in = 0;
|
|
|
|
if (includeSystem) {
|
|
if (this_core->current_process->time_switch && this_core->current_process->time_switch < pTime) {
|
|
this_core->current_process->time_sys += pTime - this_core->current_process->time_switch;
|
|
}
|
|
this_core->current_process->time_switch = 0;
|
|
}
|
|
}
|
|
|
|
#define must_have_lock(lck) if (lck.owner != this_core->cpu_id+1) { printf("Failed lock check.\n"); arch_fatal(); }
|
|
|
|
/**
|
|
* @brief Restore the context of the next available process's kernel thread.
|
|
*
|
|
* Loads the next ready process from the scheduler queue and resumes it.
|
|
*
|
|
* If no processes are available, the local idle task will be run from the beginning
|
|
* of its function entry.
|
|
*
|
|
* If the next process in the queue has been marked as finished, it will be discard
|
|
* until a non-finished process is found.
|
|
*
|
|
* If the next process is new, it will be marked as started, and its entry point
|
|
* jumped to.
|
|
*
|
|
* For all other cases, the process's stored kernel thread state will be restored
|
|
* and execution will contain in @ref switch_task with a return value of 1.
|
|
*
|
|
* Note that switch_next does not return and should be called only when the current
|
|
* process has been properly added to a scheduling queue, or marked as awaiting cleanup,
|
|
* otherwise its return state if resumed is undefined and generally whatever the state
|
|
* was when that process last entered switch_task.
|
|
*
|
|
* @returns never.
|
|
*/
|
|
void switch_next(void) {
|
|
this_core->previous_process = this_core->current_process;
|
|
update_process_times(1);
|
|
|
|
/* Get the next available process, discarded anything in the queue
|
|
* marked as finished. */
|
|
do {
|
|
this_core->current_process = next_ready_process();
|
|
} while (this_core->current_process->flags & PROC_FLAG_FINISHED);
|
|
|
|
this_core->current_process->time_in = arch_perf_timer();
|
|
this_core->current_process->time_switch = this_core->current_process->time_in;
|
|
|
|
/* Restore paging and task switch context. */
|
|
mmu_set_directory(this_core->current_process->thread.page_directory->directory);
|
|
arch_set_kernel_stack(this_core->current_process->image.stack);
|
|
|
|
if ((this_core->current_process->flags & PROC_FLAG_FINISHED) || (!this_core->current_process->signal_queue)) {
|
|
printf("Should not have this process...\n");
|
|
if (this_core->current_process->flags & PROC_FLAG_FINISHED) printf("It is marked finished.\n");
|
|
if (!this_core->current_process->signal_queue) printf("It doesn't have a signal queue.\n");
|
|
arch_fatal();
|
|
__builtin_unreachable();
|
|
}
|
|
|
|
if (this_core->current_process->flags & PROC_FLAG_STARTED) {
|
|
/* If this process has a signal pending, we save its current context - including
|
|
* the entire kernel stack - before resuming switch_task. */
|
|
if (!this_core->current_process->signal_kstack) {
|
|
if (this_core->current_process->signal_queue->length > 0) {
|
|
this_core->current_process->signal_kstack = malloc(KERNEL_STACK_SIZE);
|
|
memcpy(this_core->current_process->signal_kstack, (void*)(this_core->current_process->image.stack - KERNEL_STACK_SIZE), KERNEL_STACK_SIZE);
|
|
memcpy((thread_t*)&this_core->current_process->signal_state, (thread_t*)&this_core->current_process->thread, sizeof(thread_t));
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Mark the process as running and started. */
|
|
__sync_or_and_fetch(&this_core->current_process->flags, PROC_FLAG_STARTED);
|
|
|
|
/* Jump to next */
|
|
arch_restore_context(&this_core->current_process->thread);
|
|
__builtin_unreachable();
|
|
}
|
|
|
|
extern void * _ret_from_preempt_source;
|
|
|
|
/**
|
|
* @brief Yield the processor to the next available task.
|
|
*
|
|
* Yields the current process, allowing the next to run. Can be called both as
|
|
* part of general preemption or from blocking tasks; in the latter case,
|
|
* the process should be added to a scheduler queue to be awakoen later when the
|
|
* blocking operation is completed and @p reschedule should be set to 0.
|
|
*
|
|
* @param reschedule Non-zero if this process should be added to the ready queue.
|
|
*/
|
|
void switch_task(uint8_t reschedule) {
|
|
|
|
/* switch_task() called but the scheduler isn't enabled? Resume... this is probably a bug. */
|
|
if (!this_core->current_process) return;
|
|
|
|
if (this_core->current_process == this_core->kernel_idle_task && __builtin_return_address(0) != &_ret_from_preempt_source) {
|
|
printf("Context switch from kernel_idle_task triggered from somewhere other than pre-emption source. Halting.\n");
|
|
printf("This generally means that a driver responding to interrupts has attempted to yield in its interrupt context.\n");
|
|
printf("Ensure that all device drivers which respond to interrupts do so with non-blocking data structures.\n");
|
|
printf(" Return address of switch_task: %p\n", __builtin_return_address(0));
|
|
arch_fatal();
|
|
}
|
|
|
|
/* If a process got to switch_task but was not marked as running, it must be exiting and we don't
|
|
* want to waste time saving context for it. Also, kidle is always resumed from the top of its
|
|
* loop function, so we don't save any context for it either. */
|
|
if (!(this_core->current_process->flags & PROC_FLAG_RUNNING) || (this_core->current_process == this_core->kernel_idle_task)) {
|
|
switch_next();
|
|
return;
|
|
}
|
|
|
|
arch_save_floating((process_t*)this_core->current_process);
|
|
|
|
/* 'setjmp' - save the execution context. When this call returns '1' we are back
|
|
* from a task switch and have been awoken if we were sleeping. */
|
|
if (arch_save_context(&this_core->current_process->thread) == 1) {
|
|
arch_restore_floating((process_t*)this_core->current_process);
|
|
|
|
fix_signal_stacks();
|
|
if (!(this_core->current_process->flags & PROC_FLAG_FINISHED)) {
|
|
if (this_core->current_process->signal_queue->length > 0) {
|
|
node_t * node = list_dequeue(this_core->current_process->signal_queue);
|
|
signal_t * sig = node->value;
|
|
free(node);
|
|
handle_signal((process_t*)this_core->current_process,sig);
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
/* If this is a normal yield, we reschedule.
|
|
* XXX: Is this going to work okay with SMP? I think this whole thing
|
|
* needs to be wrapped in a lock, but also what if we put the
|
|
* thread into a schedule queue previously but a different core
|
|
* picks it up before we saved the thread context or the FPU state... */
|
|
if (reschedule) {
|
|
make_process_ready((process_t*)this_core->current_process);
|
|
}
|
|
|
|
/* @ref switch_next() does not return. */
|
|
switch_next();
|
|
}
|
|
|
|
/**
|
|
* @brief Initial scheduler datastructures.
|
|
*
|
|
* Called by early system startup to allocate trees and lists
|
|
* the schedule uses to track processes.
|
|
*/
|
|
void initialize_process_tree(void) {
|
|
process_tree = tree_create();
|
|
process_list = list_create("global process list",NULL);
|
|
process_queue = list_create("global scheduler queue",NULL);
|
|
sleep_queue = list_create("global timed sleep queue",NULL);
|
|
reap_queue = list_create("processes awaiting later cleanup",NULL);
|
|
|
|
/* TODO: PID bitset? */
|
|
}
|
|
|
|
/**
|
|
* @brief Determines if a process is alive and valid.
|
|
*
|
|
* Scans @ref process_list to see if @p process is a valid
|
|
* process object or not.
|
|
*
|
|
* XXX This is horribly inefficient, and its very existence
|
|
* is likely indicative of bugs whereever it needed to
|
|
* be called...
|
|
*
|
|
* @param process Process object to check.
|
|
* @returns 1 if the process is valid, 0 if it is not.
|
|
*/
|
|
int is_valid_process(process_t * process) {
|
|
foreach(lnode, process_list) {
|
|
if (lnode->value == process) {
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @brief Allocate a new file descriptor.
|
|
*
|
|
* Adds a new entry to the file descriptor table for @p proc
|
|
* pointing to the file @p node. The file descriptor's offset
|
|
* and file modes must be set by the caller afterwards.
|
|
*
|
|
* @param proc Process whose file descriptor should be modified.
|
|
* @param node VFS object to add a reference to.
|
|
* @returns the new file descriptor index
|
|
*/
|
|
unsigned long process_append_fd(process_t * proc, fs_node_t * node) {
|
|
spin_lock(proc->fds->lock);
|
|
/* Fill gaps */
|
|
for (unsigned long i = 0; i < proc->fds->length; ++i) {
|
|
if (!proc->fds->entries[i]) {
|
|
proc->fds->entries[i] = node;
|
|
/* modes, offsets must be set by caller */
|
|
proc->fds->modes[i] = 0;
|
|
proc->fds->offsets[i] = 0;
|
|
spin_unlock(proc->fds->lock);
|
|
return i;
|
|
}
|
|
}
|
|
/* No gaps, expand */
|
|
if (proc->fds->length == proc->fds->capacity) {
|
|
proc->fds->capacity *= 2;
|
|
proc->fds->entries = realloc(proc->fds->entries, sizeof(fs_node_t *) * proc->fds->capacity);
|
|
proc->fds->modes = realloc(proc->fds->modes, sizeof(int) * proc->fds->capacity);
|
|
proc->fds->offsets = realloc(proc->fds->offsets, sizeof(uint64_t) * proc->fds->capacity);
|
|
}
|
|
proc->fds->entries[proc->fds->length] = node;
|
|
/* modes, offsets must be set by caller */
|
|
proc->fds->modes[proc->fds->length] = 0;
|
|
proc->fds->offsets[proc->fds->length] = 0;
|
|
proc->fds->length++;
|
|
spin_unlock(proc->fds->lock);
|
|
return proc->fds->length-1;
|
|
}
|
|
|
|
/**
|
|
* @brief Allocate a process identifier.
|
|
*
|
|
* Obtains the next available process identifier.
|
|
*
|
|
* FIXME This used to use a bitset in Toaru32 so it could
|
|
* handle overflow of the pid counter. We need to
|
|
* bring that back.
|
|
*/
|
|
pid_t get_next_pid(void) {
|
|
static pid_t _next_pid = 2;
|
|
return __sync_fetch_and_add(&_next_pid,1);
|
|
}
|
|
|
|
/**
|
|
* @brief The idle task.
|
|
*
|
|
* Sits in a loop forever. Scheduled whenever there is nothing
|
|
* else to do. Actually always enters from the top of the function
|
|
* whenever scheduled, as we don't both to save its state.
|
|
*/
|
|
static void _kidle(void) {
|
|
while (1) {
|
|
arch_pause();
|
|
}
|
|
}
|
|
|
|
static void _kburn(void) {
|
|
while (1) {
|
|
arch_pause();
|
|
switch_next();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Release a process's paging data.
|
|
*
|
|
* If this is a thread in a POSIX process with other
|
|
* living threads, the directory is not actually released
|
|
* but the reference count for it is decremented.
|
|
*
|
|
* XXX There's probably no reason for this to take an argument;
|
|
* we only ever free directories in two places: on exec, or
|
|
* when a thread exits, and that's always the current thread.
|
|
*/
|
|
void process_release_directory(page_directory_t * dir) {
|
|
spin_lock(dir->lock);
|
|
dir->refcount--;
|
|
if (dir->refcount < 1) {
|
|
mmu_free(dir->directory);
|
|
free(dir);
|
|
} else {
|
|
spin_unlock(dir->lock);
|
|
}
|
|
}
|
|
|
|
process_t * spawn_kidle(int bsp) {
|
|
process_t * idle = calloc(1,sizeof(process_t));
|
|
idle->id = -1;
|
|
idle->name = strdup("[kidle]");
|
|
idle->flags = PROC_FLAG_IS_TASKLET | PROC_FLAG_STARTED | PROC_FLAG_RUNNING;
|
|
idle->image.stack = (uintptr_t)valloc(KERNEL_STACK_SIZE)+ KERNEL_STACK_SIZE;
|
|
mmu_frame_allocate(
|
|
mmu_get_page(idle->image.stack - KERNEL_STACK_SIZE, 0),
|
|
MMU_FLAG_KERNEL);
|
|
|
|
/* TODO arch_initialize_context(uintptr_t) ? */
|
|
idle->thread.context.ip = bsp ? (uintptr_t)&_kidle : (uintptr_t)&_kburn;
|
|
idle->thread.context.sp = idle->image.stack;
|
|
idle->thread.context.bp = idle->image.stack;
|
|
|
|
/* FIXME Why does the idle thread have wait queues and shm mappings?
|
|
* Can we make sure these are never referenced and not allocate them? */
|
|
idle->wait_queue = list_create("process wait queue (kidle)",idle);
|
|
idle->shm_mappings = list_create("process shm mappings (kidle)",idle);
|
|
idle->signal_queue = list_create("process signal queue (kidle)",idle);
|
|
gettimeofday(&idle->start, NULL);
|
|
idle->thread.page_directory = malloc(sizeof(page_directory_t));
|
|
idle->thread.page_directory->refcount = 1;
|
|
idle->thread.page_directory->directory = mmu_clone(this_core->current_pml);
|
|
spin_init(idle->thread.page_directory->lock);
|
|
return idle;
|
|
}
|
|
|
|
process_t * spawn_init(void) {
|
|
process_t * init = calloc(1,sizeof(process_t));
|
|
tree_set_root(process_tree, (void*)init);
|
|
|
|
init->tree_entry = process_tree->root;
|
|
init->id = 1;
|
|
init->group = 0;
|
|
init->job = 1;
|
|
init->session = 1;
|
|
init->name = strdup("init");
|
|
init->cmdline = NULL;
|
|
init->user = USER_ROOT_UID;
|
|
init->real_user = USER_ROOT_UID;
|
|
init->user_group = USER_ROOT_UID;
|
|
init->real_user_group = USER_ROOT_UID;
|
|
init->mask = 022;
|
|
init->status = 0;
|
|
|
|
init->fds = malloc(sizeof(fd_table_t));
|
|
init->fds->refs = 1;
|
|
init->fds->length = 0;
|
|
init->fds->capacity = 4;
|
|
init->fds->entries = malloc(init->fds->capacity * sizeof(fs_node_t *));
|
|
init->fds->modes = malloc(init->fds->capacity * sizeof(int));
|
|
init->fds->offsets = malloc(init->fds->capacity * sizeof(uint64_t));
|
|
spin_init(init->fds->lock);
|
|
|
|
init->wd_node = clone_fs(fs_root);
|
|
init->wd_name = strdup("/");
|
|
|
|
init->image.entry = 0;
|
|
init->image.heap = 0;
|
|
init->image.stack = (uintptr_t)valloc(KERNEL_STACK_SIZE) + KERNEL_STACK_SIZE;
|
|
mmu_frame_allocate(
|
|
mmu_get_page(init->image.stack - KERNEL_STACK_SIZE, 0),
|
|
MMU_FLAG_KERNEL);
|
|
init->image.shm_heap = 0x200000000; /* That's 8GiB? That should work fine... */
|
|
|
|
init->flags = PROC_FLAG_STARTED | PROC_FLAG_RUNNING;
|
|
init->wait_queue = list_create("process wait queue (init)", init);
|
|
init->shm_mappings = list_create("process shm mapping (init)", init);
|
|
init->signal_queue = list_create("process signal queue (init)", init);
|
|
init->signal_kstack = NULL; /* Initialized later */
|
|
|
|
init->sched_node.prev = NULL;
|
|
init->sched_node.next = NULL;
|
|
init->sched_node.value = init;
|
|
|
|
init->sleep_node.prev = NULL;
|
|
init->sleep_node.next = NULL;
|
|
init->sleep_node.value = init;
|
|
|
|
init->timed_sleep_node = NULL;
|
|
|
|
init->thread.page_directory = malloc(sizeof(page_directory_t));
|
|
init->thread.page_directory->refcount = 1;
|
|
init->thread.page_directory->directory = this_core->current_pml;
|
|
spin_init(init->thread.page_directory->lock);
|
|
init->description = strdup("[init]");
|
|
list_insert(process_list, (void*)init);
|
|
|
|
return init;
|
|
}
|
|
|
|
process_t * spawn_process(volatile process_t * parent, int flags) {
|
|
process_t * proc = calloc(1,sizeof(process_t));
|
|
|
|
proc->id = get_next_pid();
|
|
proc->group = proc->id;
|
|
proc->name = strdup(parent->name);
|
|
proc->description = NULL;
|
|
proc->cmdline = parent->cmdline; /* FIXME dup it? */
|
|
|
|
proc->user = parent->user;
|
|
proc->real_user = parent->real_user;
|
|
proc->user_group = parent->user_group;
|
|
proc->real_user_group = parent->real_user_group;
|
|
proc->mask = parent->mask;
|
|
proc->job = parent->job;
|
|
proc->session = parent->session;
|
|
|
|
if (parent->supplementary_group_count) {
|
|
proc->supplementary_group_count = parent->supplementary_group_count;
|
|
proc->supplementary_group_list = malloc(sizeof(gid_t) * proc->supplementary_group_count);
|
|
for (int i = 0; i < proc->supplementary_group_count; ++i) {
|
|
proc->supplementary_group_list[i] = parent->supplementary_group_list[i];
|
|
}
|
|
}
|
|
|
|
proc->thread.context.sp = 0;
|
|
proc->thread.context.bp = 0;
|
|
proc->thread.context.ip = 0;
|
|
memcpy((void*)proc->thread.fp_regs, (void*)parent->thread.fp_regs, 512);
|
|
|
|
/* Entry is only stored for reference. */
|
|
proc->image.entry = parent->image.entry;
|
|
proc->image.heap = parent->image.heap;
|
|
proc->image.stack = (uintptr_t)valloc(KERNEL_STACK_SIZE) + KERNEL_STACK_SIZE;
|
|
mmu_frame_allocate(
|
|
mmu_get_page(proc->image.stack - KERNEL_STACK_SIZE, 0),
|
|
MMU_FLAG_KERNEL);
|
|
proc->image.shm_heap = 0x200000000; /* FIXME this should be a macro def */
|
|
|
|
if (flags & PROC_REUSE_FDS) {
|
|
spin_lock(parent->fds->lock);
|
|
proc->fds = parent->fds;
|
|
proc->fds->refs++;
|
|
spin_unlock(parent->fds->lock);
|
|
} else {
|
|
proc->fds = malloc(sizeof(fd_table_t));
|
|
spin_init(proc->fds->lock);
|
|
proc->fds->refs = 1;
|
|
spin_lock(parent->fds->lock);
|
|
proc->fds->length = parent->fds->length;
|
|
proc->fds->capacity = parent->fds->capacity;
|
|
proc->fds->entries = malloc(proc->fds->capacity * sizeof(fs_node_t *));
|
|
proc->fds->modes = malloc(proc->fds->capacity * sizeof(int));
|
|
proc->fds->offsets = malloc(proc->fds->capacity * sizeof(uint64_t));
|
|
for (uint32_t i = 0; i < parent->fds->length; ++i) {
|
|
proc->fds->entries[i] = clone_fs(parent->fds->entries[i]);
|
|
proc->fds->modes[i] = parent->fds->modes[i];
|
|
proc->fds->offsets[i] = parent->fds->offsets[i];
|
|
}
|
|
spin_unlock(parent->fds->lock);
|
|
}
|
|
|
|
proc->wd_node = clone_fs(parent->wd_node);
|
|
proc->wd_name = strdup(parent->wd_name);
|
|
|
|
proc->wait_queue = list_create("process wait queue",proc);
|
|
proc->shm_mappings = list_create("process shm mappings",proc);
|
|
proc->signal_queue = list_create("process signal queue",proc);
|
|
|
|
proc->sched_node.value = proc;
|
|
proc->sleep_node.value = proc;
|
|
|
|
gettimeofday(&proc->start, NULL);
|
|
tree_node_t * entry = tree_node_create(proc);
|
|
proc->tree_entry = entry;
|
|
|
|
spin_lock(tree_lock);
|
|
tree_node_insert_child_node(process_tree, parent->tree_entry, entry);
|
|
list_insert(process_list, (void*)proc);
|
|
spin_unlock(tree_lock);
|
|
return proc;
|
|
}
|
|
|
|
extern void tree_remove_reparent_root(tree_t * tree, tree_node_t * node);
|
|
|
|
void process_reap(process_t * proc) {
|
|
if (proc->signal_kstack) {
|
|
free(proc->signal_kstack);
|
|
}
|
|
|
|
if (proc->tracees) {
|
|
while (proc->tracees->length) {
|
|
free(list_pop(proc->tracees));
|
|
}
|
|
free(proc->tracees);
|
|
}
|
|
|
|
/* Unmark the stack bottom's fault detector */
|
|
mmu_frame_allocate(
|
|
mmu_get_page(proc->image.stack - KERNEL_STACK_SIZE, 0),
|
|
MMU_FLAG_KERNEL | MMU_FLAG_WRITABLE);
|
|
|
|
free((void *)(proc->image.stack - KERNEL_STACK_SIZE));
|
|
process_release_directory(proc->thread.page_directory);
|
|
|
|
free(proc->name);
|
|
free(proc);
|
|
}
|
|
|
|
static int process_is_owned(process_t * proc) {
|
|
for (int i = 0; i < processor_count; ++i) {
|
|
if (processor_local_data[i].previous_process == proc ||
|
|
processor_local_data[i].current_process == proc) {
|
|
return 1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void process_reap_later(process_t * proc) {
|
|
spin_lock(reap_lock);
|
|
/* See if we can delete anything */
|
|
while (reap_queue->head) {
|
|
process_t * proc = reap_queue->head->value;
|
|
if (!process_is_owned(proc)) {
|
|
free(list_dequeue(reap_queue));
|
|
process_reap(proc);
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
/* And delete this thing later */
|
|
list_insert(reap_queue, proc);
|
|
spin_unlock(reap_lock);
|
|
}
|
|
|
|
/**
|
|
* @brief Remove a process from the valid process list.
|
|
*
|
|
* Deletes a process from both the valid list and the process tree.
|
|
* Any the process has any children, they become orphaned and are
|
|
* moved under 'init', which is awoken if it was blocked on 'waitpid'.
|
|
*
|
|
* Finally, the process is freed.
|
|
*/
|
|
void process_delete(process_t * proc) {
|
|
assert(proc != this_core->current_process);
|
|
|
|
tree_node_t * entry = proc->tree_entry;
|
|
if (!entry) {
|
|
printf("Tried to delete process with no tree entry?\n");
|
|
return;
|
|
}
|
|
if (process_tree->root == entry) {
|
|
printf("Tried to delete process init...\n");
|
|
return;
|
|
}
|
|
|
|
spin_lock(tree_lock);
|
|
int has_children = entry->children->length;
|
|
tree_remove_reparent_root(process_tree, entry);
|
|
list_delete(process_list, list_find(process_list, proc));
|
|
spin_unlock(tree_lock);
|
|
|
|
if (has_children) {
|
|
/* Wake up init */
|
|
process_t * init = process_tree->root->value;
|
|
wakeup_queue(init->wait_queue);
|
|
}
|
|
|
|
// FIXME bitset_clear(&pid_set, proc->id);
|
|
proc->tree_entry = NULL;
|
|
|
|
shm_release_all(proc);
|
|
free(proc->shm_mappings);
|
|
|
|
if (proc->supplementary_group_list) {
|
|
proc->supplementary_group_count = 0;
|
|
free(proc->supplementary_group_list);
|
|
}
|
|
|
|
/* Is someone using this process? */
|
|
for (int i = 0; i < processor_count; ++i) {
|
|
if (i == this_core->cpu_id) continue;
|
|
if (processor_local_data[i].previous_process == proc ||
|
|
processor_local_data[i].current_process == proc) {
|
|
process_reap_later(proc);
|
|
return;
|
|
}
|
|
}
|
|
|
|
process_reap(proc);
|
|
}
|
|
|
|
/**
|
|
* @brief Place an available process in the ready queue.
|
|
*
|
|
* Marks a process as available for general scheduling.
|
|
* If the process was currently in a sleep queue, it is
|
|
* marked as having been interrupted and removed from its
|
|
* owning queue before being moved.
|
|
*
|
|
* The process must not otherwise have been in a scheduling
|
|
* queue before it is placed in the ready queue.
|
|
*/
|
|
void make_process_ready(volatile process_t * proc) {
|
|
if (proc->sleep_node.owner != NULL) {
|
|
spin_lock(sleep_lock);
|
|
if (proc->sleep_node.owner == sleep_queue) {
|
|
/* The sleep queue is slightly special... */
|
|
if (proc->timed_sleep_node) {
|
|
list_delete(sleep_queue, proc->timed_sleep_node);
|
|
proc->sleep_node.owner = NULL;
|
|
free(proc->timed_sleep_node->value);
|
|
}
|
|
spin_unlock(sleep_lock);
|
|
} else {
|
|
/* This was blocked on a semaphore we can interrupt. */
|
|
__sync_or_and_fetch(&proc->flags, PROC_FLAG_SLEEP_INT);
|
|
list_delete((list_t*)proc->sleep_node.owner, (node_t*)&proc->sleep_node);
|
|
spin_unlock(sleep_lock);
|
|
}
|
|
}
|
|
|
|
spin_lock(process_queue_lock);
|
|
if (proc->sched_node.owner) {
|
|
/* There's only one ready queue, so this means the process was already ready, which
|
|
* is indicative of a bug somewhere as we shouldn't be added processes to the ready
|
|
* queue multiple times. */
|
|
spin_unlock(process_queue_lock);
|
|
return;
|
|
}
|
|
|
|
list_append(process_queue, (node_t*)&proc->sched_node);
|
|
spin_unlock(process_queue_lock);
|
|
|
|
arch_wakeup_others();
|
|
}
|
|
|
|
/**
|
|
* @brief Pop the next available process from the queue.
|
|
*
|
|
* Gets the next available process from the round-robin scheduling
|
|
* queue. If there is no process to run, the idle task is returned.
|
|
*
|
|
* TODO This needs more locking for SMP...
|
|
*/
|
|
volatile process_t * next_ready_process(void) {
|
|
spin_lock(process_queue_lock);
|
|
|
|
if (!process_queue->head) {
|
|
if (process_queue->length) {
|
|
printf("Queue has a length but head is NULL\n");
|
|
arch_fatal();
|
|
}
|
|
spin_unlock(process_queue_lock);
|
|
return this_core->kernel_idle_task;
|
|
}
|
|
|
|
node_t * np = list_dequeue(process_queue);
|
|
|
|
if ((uintptr_t)np < 0xFFFFff0000000000UL || (uintptr_t)np > 0xFFFFfff000000000UL) {
|
|
printf("Suspicious pointer in queue: %#zx\n", (uintptr_t)np);
|
|
arch_fatal();
|
|
}
|
|
volatile process_t * next = np->value;
|
|
|
|
if ((next->flags & PROC_FLAG_RUNNING) && (next->owner != this_core->cpu_id)) {
|
|
/* We pulled a process too soon, switch to idle for a bit so the
|
|
* core that marked this process as ready can finish switching away from it. */
|
|
list_append(process_queue, (node_t*)&next->sched_node);
|
|
spin_unlock(process_queue_lock);
|
|
return this_core->kernel_idle_task;
|
|
}
|
|
|
|
spin_unlock(process_queue_lock);
|
|
|
|
__sync_or_and_fetch(&next->flags, PROC_FLAG_RUNNING);
|
|
next->owner = this_core->cpu_id;
|
|
|
|
return next;
|
|
}
|
|
|
|
/**
|
|
* @brief Signal a semaphore.
|
|
*
|
|
* Okay, so toaru32 used these general-purpose lists of processes
|
|
* as a sort of sempahore system, so often when you see 'queue' it
|
|
* can be read as 'semaphore' and be equally valid (outside of the
|
|
* 'ready queue', I guess). This will awaken all processes currently
|
|
* in the semaphore @p queue, unless they were marked as finished in
|
|
* which case they will be discarded.
|
|
*
|
|
* Note that these "semaphore queues" are binary semaphores - simple
|
|
* locks, but with smarter logic than the "spin_lock" primitive also
|
|
* used throughout the kernel, as that just blindly switches tasks
|
|
* until its atomic swap succeeds.
|
|
*
|
|
* @param queue The semaphore to signal
|
|
* @returns the number of processes successfully awoken
|
|
*/
|
|
int wakeup_queue(list_t * queue) {
|
|
int awoken_processes = 0;
|
|
spin_lock(wait_lock_tmp);
|
|
while (queue->length > 0) {
|
|
node_t * node = list_pop(queue);
|
|
if (!(((process_t *)node->value)->flags & PROC_FLAG_FINISHED)) {
|
|
make_process_ready(node->value);
|
|
}
|
|
awoken_processes++;
|
|
}
|
|
spin_unlock(wait_lock_tmp);
|
|
return awoken_processes;
|
|
}
|
|
|
|
/**
|
|
* @brief Signal a semaphore, exceptionally.
|
|
*
|
|
* Wake up everything in the semaphore @p queue but mark every
|
|
* waiter as having been interrupted, rather than gracefully awoken.
|
|
* Generally that means the event they were waiting for did not
|
|
* happen and may never happen.
|
|
*
|
|
* Otherwise, same semantics as @ref wakeup_queue.
|
|
*/
|
|
int wakeup_queue_interrupted(list_t * queue) {
|
|
int awoken_processes = 0;
|
|
spin_lock(wait_lock_tmp);
|
|
while (queue->length > 0) {
|
|
node_t * node = list_pop(queue);
|
|
if (!(((process_t *)node->value)->flags & PROC_FLAG_FINISHED)) {
|
|
process_t * proc = node->value;
|
|
__sync_or_and_fetch(&proc->flags, PROC_FLAG_SLEEP_INT);
|
|
make_process_ready(proc);
|
|
}
|
|
awoken_processes++;
|
|
}
|
|
spin_unlock(wait_lock_tmp);
|
|
return awoken_processes;
|
|
}
|
|
|
|
/**
|
|
* @brief Wait for a binary semaphore.
|
|
*
|
|
* Wait for an event with everyone else in @p queue.
|
|
*
|
|
* @returns 1 if the wait was interrupted (eg. the event did not occur); 0 otherwise.
|
|
*/
|
|
int sleep_on(list_t * queue) {
|
|
if (this_core->current_process->sleep_node.owner) {
|
|
switch_task(0);
|
|
return 0;
|
|
}
|
|
__sync_and_and_fetch(&this_core->current_process->flags, ~(PROC_FLAG_SLEEP_INT));
|
|
spin_lock(wait_lock_tmp);
|
|
list_append(queue, (node_t*)&this_core->current_process->sleep_node);
|
|
spin_unlock(wait_lock_tmp);
|
|
switch_task(0);
|
|
return !!(this_core->current_process->flags & PROC_FLAG_SLEEP_INT);
|
|
}
|
|
|
|
int sleep_on_unlocking(list_t * queue, spin_lock_t * release) {
|
|
__sync_and_and_fetch(&this_core->current_process->flags, ~(PROC_FLAG_SLEEP_INT));
|
|
spin_lock(wait_lock_tmp);
|
|
list_append(queue, (node_t*)&this_core->current_process->sleep_node);
|
|
spin_unlock(wait_lock_tmp);
|
|
|
|
spin_unlock(*release);
|
|
|
|
switch_task(0);
|
|
return !!(this_core->current_process->flags & PROC_FLAG_SLEEP_INT);
|
|
}
|
|
|
|
/**
|
|
* @brief Indicates whether a process is ready to be run but not currently running.
|
|
*/
|
|
int process_is_ready(process_t * proc) {
|
|
return (proc->sched_node.owner != NULL && !(proc->flags & PROC_FLAG_RUNNING));
|
|
}
|
|
|
|
int process_alert_node_locked(process_t * process, void * value);
|
|
|
|
/**
|
|
* @brief Wake up processes that were sleeping on timers.
|
|
*
|
|
* Reschedule all processes whose timed waits have expired as of
|
|
* the time indicated by @p seconds and @p subseconds. If the sleep
|
|
* was part of an fswait system call timing out, the call is marked
|
|
* as timed out before the process is rescheduled.
|
|
*/
|
|
void wakeup_sleepers(unsigned long seconds, unsigned long subseconds) {
|
|
spin_lock(sleep_lock);
|
|
if (sleep_queue->length) {
|
|
sleeper_t * proc = ((sleeper_t *)sleep_queue->head->value);
|
|
while (proc && (proc->end_tick < seconds || (proc->end_tick == seconds && proc->end_subtick <= subseconds))) {
|
|
|
|
if (proc->is_fswait) {
|
|
proc->is_fswait = -1;
|
|
process_alert_node_locked(proc->process,proc);
|
|
} else {
|
|
process_t * process = proc->process;
|
|
process->sleep_node.owner = NULL;
|
|
process->timed_sleep_node = NULL;
|
|
if (!process_is_ready(process)) {
|
|
spin_lock(wait_lock_tmp);
|
|
make_process_ready(process);
|
|
spin_unlock(wait_lock_tmp);
|
|
}
|
|
}
|
|
free(proc);
|
|
free(list_dequeue(sleep_queue));
|
|
if (sleep_queue->length) {
|
|
proc = ((sleeper_t *)sleep_queue->head->value);
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
spin_unlock(sleep_lock);
|
|
}
|
|
|
|
/**
|
|
* @brief Wait until a given time.
|
|
*
|
|
* Suspends the current process until the given time. The process may
|
|
* still be resumed by a signal or other mechanism, in which case the
|
|
* sleep will not be resumed by the kernel.
|
|
*/
|
|
void sleep_until(process_t * process, unsigned long seconds, unsigned long subseconds) {
|
|
spin_lock(sleep_lock);
|
|
if (this_core->current_process->sleep_node.owner) {
|
|
spin_unlock(sleep_lock);
|
|
/* Can't sleep, sleeping already */
|
|
return;
|
|
}
|
|
process->sleep_node.owner = sleep_queue;
|
|
|
|
node_t * before = NULL;
|
|
foreach(node, sleep_queue) {
|
|
sleeper_t * candidate = ((sleeper_t *)node->value);
|
|
if (!candidate) {
|
|
printf("null candidate?\n");
|
|
continue;
|
|
}
|
|
if (candidate->end_tick > seconds || (candidate->end_tick == seconds && candidate->end_subtick > subseconds)) {
|
|
break;
|
|
}
|
|
before = node;
|
|
}
|
|
sleeper_t * proc = malloc(sizeof(sleeper_t));
|
|
proc->process = process;
|
|
proc->end_tick = seconds;
|
|
proc->end_subtick = subseconds;
|
|
proc->is_fswait = 0;
|
|
process->timed_sleep_node = list_insert_after(sleep_queue, before, proc);
|
|
spin_unlock(sleep_lock);
|
|
}
|
|
|
|
uint8_t process_compare(void * proc_v, void * pid_v) {
|
|
pid_t pid = (*(pid_t *)pid_v);
|
|
process_t * proc = (process_t *)proc_v;
|
|
|
|
return (uint8_t)(proc->id == pid);
|
|
}
|
|
|
|
process_t * process_from_pid(pid_t pid) {
|
|
if (pid < 0) return NULL;
|
|
|
|
spin_lock(tree_lock);
|
|
tree_node_t * entry = tree_find(process_tree,&pid,process_compare);
|
|
spin_unlock(tree_lock);
|
|
if (entry) {
|
|
return (process_t *)entry->value;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
|
|
long process_move_fd(process_t * proc, long src, long dest) {
|
|
if ((size_t)src >= proc->fds->length || (dest != -1 && (size_t)dest >= proc->fds->length)) {
|
|
return -1;
|
|
}
|
|
if (dest == -1) {
|
|
dest = process_append_fd(proc, NULL);
|
|
}
|
|
if (proc->fds->entries[dest] != proc->fds->entries[src]) {
|
|
close_fs(proc->fds->entries[dest]);
|
|
proc->fds->entries[dest] = proc->fds->entries[src];
|
|
proc->fds->modes[dest] = proc->fds->modes[src];
|
|
proc->fds->offsets[dest] = proc->fds->offsets[src];
|
|
open_fs(proc->fds->entries[dest], 0);
|
|
}
|
|
return dest;
|
|
}
|
|
|
|
void tasking_start(void) {
|
|
this_core->current_process = spawn_init();
|
|
this_core->kernel_idle_task = spawn_kidle(1);
|
|
}
|
|
|
|
static int wait_candidate(volatile process_t * parent, int pid, int options, volatile process_t * proc) {
|
|
if (!proc) return 0;
|
|
|
|
if (options & WNOKERN) {
|
|
/* Skip kernel processes */
|
|
if (proc->flags & PROC_FLAG_IS_TASKLET) return 0;
|
|
}
|
|
|
|
if (pid < -1) {
|
|
if (proc->job == -pid || proc->id == -pid) return 1;
|
|
} else if (pid == 0) {
|
|
/* Matches our group ID */
|
|
if (proc->job == parent->id) return 1;
|
|
} else if (pid > 0) {
|
|
/* Specific pid */
|
|
if (proc->id == pid) return 1;
|
|
} else {
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int waitpid(int pid, int * status, int options) {
|
|
volatile process_t * volatile proc = (process_t*)this_core->current_process;
|
|
#if 0
|
|
if (proc->group) {
|
|
proc = process_from_pid(proc->group);
|
|
}
|
|
#endif
|
|
|
|
do {
|
|
volatile process_t * candidate = NULL;
|
|
int has_children = 0;
|
|
int is_parent = 0;
|
|
|
|
spin_lock(proc->wait_lock);
|
|
|
|
/* First, find out if there is anyone to reap */
|
|
foreach(node, proc->tree_entry->children) {
|
|
if (!node->value) {
|
|
continue;
|
|
}
|
|
volatile process_t * volatile child = ((tree_node_t *)node->value)->value;
|
|
|
|
if (wait_candidate(proc, pid, options, child)) {
|
|
has_children = 1;
|
|
is_parent = 1;
|
|
if (child->flags & PROC_FLAG_FINISHED) {
|
|
candidate = child;
|
|
break;
|
|
}
|
|
if ((options & WSTOPPED) && child->flags & PROC_FLAG_SUSPENDED) {
|
|
candidate = child;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!candidate && proc->tracees) {
|
|
foreach(node, proc->tracees) {
|
|
process_t * child = node->value;
|
|
if (wait_candidate(proc,pid,options,child)) {
|
|
has_children = 1;
|
|
if (child->flags & (PROC_FLAG_SUSPENDED | PROC_FLAG_FINISHED)) {
|
|
candidate = child;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!has_children) {
|
|
/* No valid children matching this description */
|
|
spin_unlock(proc->wait_lock);
|
|
return -ECHILD;
|
|
}
|
|
|
|
if (candidate) {
|
|
spin_unlock(proc->wait_lock);
|
|
if (status) {
|
|
*status = candidate->status;
|
|
}
|
|
int pid = candidate->id;
|
|
if (is_parent && (candidate->flags & PROC_FLAG_FINISHED)) {
|
|
while (*((volatile int *)&candidate->flags) & PROC_FLAG_RUNNING);
|
|
proc->time_children += candidate->time_children + candidate->time_total;
|
|
proc->time_sys_children += candidate->time_sys_children + candidate->time_sys;
|
|
process_delete((process_t*)candidate);
|
|
}
|
|
return pid;
|
|
} else {
|
|
if (options & WNOHANG) {
|
|
spin_unlock(proc->wait_lock);
|
|
return 0;
|
|
}
|
|
/* Wait */
|
|
if (sleep_on_unlocking(proc->wait_queue, &proc->wait_lock) != 0) {
|
|
return -EINTR;
|
|
}
|
|
}
|
|
} while (1);
|
|
}
|
|
|
|
int process_timeout_sleep(process_t * process, int timeout) {
|
|
unsigned long s, ss;
|
|
relative_time(0, timeout * 1000, &s, &ss);
|
|
|
|
node_t * before = NULL;
|
|
foreach(node, sleep_queue) {
|
|
sleeper_t * candidate = ((sleeper_t *)node->value);
|
|
if (candidate->end_tick > s || (candidate->end_tick == s && candidate->end_subtick > ss)) {
|
|
break;
|
|
}
|
|
before = node;
|
|
}
|
|
sleeper_t * proc = malloc(sizeof(sleeper_t));
|
|
proc->process = process;
|
|
proc->end_tick = s;
|
|
proc->end_subtick = ss;
|
|
proc->is_fswait = 1;
|
|
list_insert(((process_t *)process)->node_waits, proc);
|
|
process->timeout_node = list_insert_after(sleep_queue, before, proc);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int process_wait_nodes(process_t * process,fs_node_t * nodes[], int timeout) {
|
|
fs_node_t ** n = nodes;
|
|
int index = 0;
|
|
if (*n) {
|
|
do {
|
|
int result = selectcheck_fs(*n);
|
|
if (result < 0) {
|
|
return -1;
|
|
}
|
|
if (result == 0) {
|
|
return index;
|
|
}
|
|
n++;
|
|
index++;
|
|
} while (*n);
|
|
}
|
|
|
|
if (timeout == 0) {
|
|
return -2;
|
|
}
|
|
|
|
n = nodes;
|
|
|
|
spin_lock(sleep_lock);
|
|
spin_lock(process->sched_lock);
|
|
process->node_waits = list_create("process fswaiters",process);
|
|
if (*n) {
|
|
do {
|
|
if (selectwait_fs(*n, process) < 0) {
|
|
printf("bad selectwait?\n");
|
|
}
|
|
n++;
|
|
} while (*n);
|
|
}
|
|
|
|
if (timeout > 0) {
|
|
process_timeout_sleep(process, timeout);
|
|
} else {
|
|
process->timeout_node = NULL;
|
|
}
|
|
|
|
process->awoken_index = -1;
|
|
spin_unlock(process->sched_lock);
|
|
spin_unlock(sleep_lock);
|
|
|
|
/* Wait. */
|
|
switch_task(0);
|
|
|
|
return process->awoken_index;
|
|
}
|
|
|
|
int process_awaken_from_fswait(process_t * process, int index) {
|
|
must_have_lock(sleep_lock);
|
|
|
|
process->awoken_index = index;
|
|
list_free(process->node_waits);
|
|
free(process->node_waits);
|
|
process->node_waits = NULL;
|
|
|
|
if (process->timeout_node && process->timeout_node->owner == sleep_queue) {
|
|
sleeper_t * proc = process->timeout_node->value;
|
|
if (proc->is_fswait != -1) {
|
|
list_delete(sleep_queue, process->timeout_node);
|
|
free(process->timeout_node->value);
|
|
free(process->timeout_node);
|
|
}
|
|
}
|
|
process->timeout_node = NULL;
|
|
|
|
spin_lock(wait_lock_tmp);
|
|
make_process_ready(process);
|
|
spin_unlock(wait_lock_tmp);
|
|
spin_unlock(process->sched_lock);
|
|
return 0;
|
|
}
|
|
|
|
void process_awaken_signal(process_t * process) {
|
|
spin_lock(process->sched_lock);
|
|
if (process->node_waits) {
|
|
spin_lock(sleep_lock);
|
|
process_awaken_from_fswait(process, -1);
|
|
spin_unlock(sleep_lock);
|
|
} else {
|
|
spin_unlock(process->sched_lock);
|
|
}
|
|
}
|
|
|
|
int process_alert_node_locked(process_t * process, void * value) {
|
|
must_have_lock(sleep_lock);
|
|
|
|
if (!is_valid_process(process)) {
|
|
printf("invalid process\n");
|
|
return 0;
|
|
}
|
|
|
|
spin_lock(process->sched_lock);
|
|
|
|
if (!process->node_waits) {
|
|
spin_unlock(process->sched_lock);
|
|
return 0; /* Possibly already returned. Wait for another call. */
|
|
}
|
|
|
|
int index = 0;
|
|
foreach(node, process->node_waits) {
|
|
if (value == node->value) {
|
|
return process_awaken_from_fswait(process, index);
|
|
}
|
|
index++;
|
|
}
|
|
|
|
spin_unlock(process->sched_lock);
|
|
return -1;
|
|
}
|
|
|
|
int process_alert_node(process_t * process, void * value) {
|
|
spin_lock(sleep_lock);
|
|
int result = process_alert_node_locked(process, value);
|
|
spin_unlock(sleep_lock);
|
|
return result;
|
|
}
|
|
|
|
process_t * process_get_parent(process_t * process) {
|
|
process_t * result = NULL;
|
|
spin_lock(tree_lock);
|
|
|
|
tree_node_t * entry = process->tree_entry;
|
|
|
|
if (entry->parent) {
|
|
result = entry->parent->value;
|
|
}
|
|
|
|
spin_unlock(tree_lock);
|
|
return result;
|
|
}
|
|
|
|
void task_exit(int retval) {
|
|
this_core->current_process->status = retval;
|
|
|
|
/* free whatever we can */
|
|
list_free(this_core->current_process->wait_queue);
|
|
free(this_core->current_process->wait_queue);
|
|
list_free(this_core->current_process->signal_queue);
|
|
free(this_core->current_process->signal_queue);
|
|
free(this_core->current_process->wd_name);
|
|
if (this_core->current_process->node_waits) {
|
|
list_free(this_core->current_process->node_waits);
|
|
free(this_core->current_process->node_waits);
|
|
this_core->current_process->node_waits = NULL;
|
|
}
|
|
|
|
if (this_core->current_process->fds) {
|
|
spin_lock(this_core->current_process->fds->lock);
|
|
this_core->current_process->fds->refs--;
|
|
if (this_core->current_process->fds->refs == 0) {
|
|
for (uint32_t i = 0; i < this_core->current_process->fds->length; ++i) {
|
|
if (this_core->current_process->fds->entries[i]) {
|
|
close_fs(this_core->current_process->fds->entries[i]);
|
|
this_core->current_process->fds->entries[i] = NULL;
|
|
}
|
|
}
|
|
free(this_core->current_process->fds->entries);
|
|
free(this_core->current_process->fds->offsets);
|
|
free(this_core->current_process->fds->modes);
|
|
free(this_core->current_process->fds);
|
|
this_core->current_process->fds = NULL;
|
|
} else {
|
|
spin_unlock(this_core->current_process->fds->lock);
|
|
}
|
|
}
|
|
|
|
if (this_core->current_process->tracees) {
|
|
spin_lock(this_core->current_process->wait_lock);
|
|
while (this_core->current_process->tracees->length) {
|
|
node_t * n = list_pop(this_core->current_process->tracees);
|
|
process_t * tracee = n->value;
|
|
free(n);
|
|
if (is_valid_process(tracee)) {
|
|
tracee->tracer = 0;
|
|
__sync_and_and_fetch(&tracee->flags, ~(PROC_FLAG_TRACE_SIGNALS | PROC_FLAG_TRACE_SYSCALLS));
|
|
if (tracee->flags & PROC_FLAG_SUSPENDED) {
|
|
tracee->status = 0;
|
|
__sync_and_and_fetch(&tracee->flags, ~(PROC_FLAG_SUSPENDED));
|
|
make_process_ready(tracee);
|
|
}
|
|
}
|
|
}
|
|
spin_unlock(this_core->current_process->wait_lock);
|
|
}
|
|
|
|
update_process_times(1);
|
|
|
|
process_t * parent = process_get_parent((process_t *)this_core->current_process);
|
|
__sync_or_and_fetch(&this_core->current_process->flags, PROC_FLAG_FINISHED);
|
|
|
|
if (this_core->current_process->tracer) {
|
|
process_t * tracer = process_from_pid(this_core->current_process->tracer);
|
|
if (tracer && tracer != parent) {
|
|
spin_lock(tracer->wait_lock);
|
|
wakeup_queue(tracer->wait_queue);
|
|
spin_unlock(tracer->wait_lock);
|
|
}
|
|
}
|
|
|
|
if (parent && !(parent->flags & PROC_FLAG_FINISHED)) {
|
|
spin_lock(parent->wait_lock);
|
|
send_signal(parent->group, SIGCHLD, 1);
|
|
wakeup_queue(parent->wait_queue);
|
|
spin_unlock(parent->wait_lock);
|
|
}
|
|
|
|
switch_next();
|
|
}
|
|
|
|
#define PUSH(stack, type, item) stack -= sizeof(type); \
|
|
*((type *) stack) = item
|
|
|
|
pid_t fork(void) {
|
|
uintptr_t sp, bp;
|
|
process_t * parent = (process_t*)this_core->current_process;
|
|
union PML * directory = mmu_clone(parent->thread.page_directory->directory);
|
|
process_t * new_proc = spawn_process(parent, 0);
|
|
new_proc->thread.page_directory = malloc(sizeof(page_directory_t));
|
|
new_proc->thread.page_directory->refcount = 1;
|
|
new_proc->thread.page_directory->directory = directory;
|
|
spin_init(new_proc->thread.page_directory->lock);
|
|
|
|
struct regs r;
|
|
memcpy(&r, parent->syscall_registers, sizeof(struct regs));
|
|
sp = new_proc->image.stack;
|
|
bp = sp;
|
|
|
|
arch_syscall_return(&r, 0);
|
|
PUSH(sp, struct regs, r);
|
|
new_proc->syscall_registers = (void*)sp;
|
|
new_proc->thread.context.sp = sp;
|
|
new_proc->thread.context.bp = bp;
|
|
new_proc->thread.context.tls_base = parent->thread.context.tls_base;
|
|
new_proc->thread.context.ip = (uintptr_t)&arch_resume_user;
|
|
if (parent->flags & PROC_FLAG_IS_TASKLET) new_proc->flags |= PROC_FLAG_IS_TASKLET;
|
|
make_process_ready(new_proc);
|
|
return new_proc->id;
|
|
}
|
|
|
|
pid_t clone(uintptr_t new_stack, uintptr_t thread_func, uintptr_t arg) {
|
|
uintptr_t sp, bp;
|
|
process_t * parent = (process_t *)this_core->current_process;
|
|
process_t * new_proc = spawn_process(this_core->current_process, 1);
|
|
new_proc->thread.page_directory = this_core->current_process->thread.page_directory;
|
|
spin_lock(new_proc->thread.page_directory->lock);
|
|
new_proc->thread.page_directory->refcount++;
|
|
spin_unlock(new_proc->thread.page_directory->lock);
|
|
|
|
struct regs r;
|
|
memcpy(&r, parent->syscall_registers, sizeof(struct regs));
|
|
sp = new_proc->image.stack;
|
|
bp = sp;
|
|
|
|
/* Set the gid */
|
|
if (this_core->current_process->group) {
|
|
new_proc->group = this_core->current_process->group;
|
|
} else {
|
|
/* We are the session leader */
|
|
new_proc->group = this_core->current_process->id;
|
|
}
|
|
|
|
/* different calling convention */
|
|
r.rdi = arg;
|
|
PUSH(new_stack, uintptr_t, (uintptr_t)0xFFFFB00F);
|
|
PUSH(sp, struct regs, r);
|
|
new_proc->syscall_registers = (void*)sp;
|
|
new_proc->syscall_registers->rsp = new_stack;
|
|
new_proc->syscall_registers->rbp = new_stack;
|
|
new_proc->syscall_registers->rip = thread_func;
|
|
new_proc->thread.context.sp = sp;
|
|
new_proc->thread.context.bp = bp;
|
|
new_proc->thread.context.tls_base = this_core->current_process->thread.context.tls_base;
|
|
new_proc->thread.context.ip = (uintptr_t)&arch_resume_user;
|
|
if (parent->flags & PROC_FLAG_IS_TASKLET) new_proc->flags |= PROC_FLAG_IS_TASKLET;
|
|
make_process_ready(new_proc);
|
|
return new_proc->id;
|
|
}
|
|
|
|
process_t * spawn_worker_thread(void (*entrypoint)(void * argp), const char * name, void * argp) {
|
|
process_t * proc = calloc(1,sizeof(process_t));
|
|
|
|
proc->flags = PROC_FLAG_IS_TASKLET | PROC_FLAG_STARTED;
|
|
|
|
proc->id = get_next_pid();
|
|
proc->group = proc->id;
|
|
proc->name = strdup(name);
|
|
proc->description = NULL;
|
|
proc->cmdline = NULL;
|
|
|
|
/* Are these necessary for tasklets? Should probably all be zero. */
|
|
proc->user = 0;
|
|
proc->real_user = 0;
|
|
proc->user_group = 0;
|
|
proc->real_user_group = 0;
|
|
proc->mask = 0;
|
|
proc->job = proc->id;
|
|
proc->session = proc->id;
|
|
|
|
proc->thread.page_directory = malloc(sizeof(page_directory_t));
|
|
proc->thread.page_directory->refcount = 1;
|
|
proc->thread.page_directory->directory = mmu_clone(mmu_get_kernel_directory());
|
|
spin_init(proc->thread.page_directory->lock);
|
|
|
|
proc->image.stack = (uintptr_t)valloc(KERNEL_STACK_SIZE) + KERNEL_STACK_SIZE;
|
|
PUSH(proc->image.stack, uintptr_t, (uintptr_t)entrypoint);
|
|
PUSH(proc->image.stack, void*, argp);
|
|
|
|
proc->thread.context.sp = proc->image.stack;
|
|
proc->thread.context.bp = proc->image.stack;
|
|
proc->thread.context.ip = (uintptr_t)&arch_enter_tasklet;
|
|
|
|
|
|
proc->wait_queue = list_create("worker thread wait queue",proc);
|
|
proc->shm_mappings = list_create("worker thread shm mappings",proc);
|
|
proc->signal_queue = list_create("worker thread signal queue",proc);
|
|
|
|
proc->sched_node.value = proc;
|
|
proc->sleep_node.value = proc;
|
|
|
|
gettimeofday(&proc->start, NULL);
|
|
tree_node_t * entry = tree_node_create(proc);
|
|
proc->tree_entry = entry;
|
|
|
|
spin_lock(tree_lock);
|
|
tree_node_insert_child_node(process_tree, this_core->current_process->tree_entry, entry);
|
|
list_insert(process_list, (void*)proc);
|
|
spin_unlock(tree_lock);
|
|
|
|
make_process_ready(proc);
|
|
|
|
return proc;
|
|
}
|
|
|
|
static void update_one_process(uint64_t clock_ticks, uint64_t perf_scale, process_t * proc) {
|
|
proc->usage[3] = proc->usage[2];
|
|
proc->usage[2] = proc->usage[1];
|
|
proc->usage[1] = proc->usage[0];
|
|
proc->usage[0] = (1000 * (proc->time_total - proc->time_prev)) / (clock_ticks * perf_scale);
|
|
proc->time_prev = proc->time_total;
|
|
}
|
|
|
|
void update_process_usage(uint64_t clock_ticks, uint64_t perf_scale) {
|
|
spin_lock(tree_lock);
|
|
foreach(lnode, process_list) {
|
|
process_t * proc = lnode->value;
|
|
update_one_process(clock_ticks, perf_scale, proc);
|
|
}
|
|
spin_unlock(tree_lock);
|
|
/* Now use idle tasks to calculator processor activity? */
|
|
for (int i = 0; i < processor_count; ++i) {
|
|
process_t * proc = processor_local_data[i].kernel_idle_task;
|
|
update_one_process(clock_ticks, perf_scale, proc);
|
|
}
|
|
}
|