639 lines
17 KiB
C
639 lines
17 KiB
C
/* vim: tabstop=4 shiftwidth=4 noexpandtab
|
|
*
|
|
* Processes
|
|
*
|
|
* Internal format format for a process and functions to spawn
|
|
* new processes and manage the process tree.
|
|
*/
|
|
#include <system.h>
|
|
#include <process.h>
|
|
#include <tree.h>
|
|
#include <list.h>
|
|
#include <logging.h>
|
|
#include <shm.h>
|
|
#include <printf.h>
|
|
|
|
tree_t * process_tree; /* Parent->Children tree */
|
|
list_t * process_list; /* Flat storage */
|
|
list_t * process_queue; /* Ready queue */
|
|
list_t * reap_queue; /* Processes to reap */
|
|
list_t * sleep_queue;
|
|
list_t * recently_reaped;
|
|
volatile process_t * current_process = NULL;
|
|
process_t * kernel_idle_task = NULL;
|
|
|
|
static uint8_t volatile reap_lock = 0;
|
|
static uint8_t volatile tree_lock = 0;
|
|
static uint8_t volatile process_queue_lock = 0;
|
|
static uint8_t volatile wait_lock_tmp = 0;
|
|
static uint8_t volatile sleep_lock = 0;
|
|
|
|
/* Default process name string */
|
|
char * default_name = "[unnamed]";
|
|
|
|
/*
|
|
* Initialize the process tree and ready queue.
|
|
*/
|
|
void initialize_process_tree(void) {
|
|
process_tree = tree_create();
|
|
process_list = list_create();
|
|
process_queue = list_create();
|
|
reap_queue = list_create();
|
|
sleep_queue = list_create();
|
|
recently_reaped = list_create();
|
|
}
|
|
|
|
/*
|
|
* Recursively print a process node to the console.
|
|
*
|
|
* @param node Node to print.
|
|
* @param height Current depth in the tree.
|
|
*/
|
|
void debug_print_process_tree_node(tree_node_t * node, size_t height) {
|
|
/* End recursion on a blank entry */
|
|
if (!node) return;
|
|
char * tmp = malloc(512);
|
|
memset(tmp, 0, 512);
|
|
char * c = tmp;
|
|
/* Indent output */
|
|
for (uint32_t i = 0; i < height; ++i) {
|
|
c += sprintf(c, " ");
|
|
}
|
|
/* Get the current process */
|
|
process_t * proc = (process_t *)node->value;
|
|
/* Print the process name */
|
|
c += sprintf(c, "%d.%d %s", proc->group ? proc->group : proc->id, proc->id, proc->name);
|
|
if (proc->description) {
|
|
/* And, if it has one, its description */
|
|
c += sprintf(c, " %s", proc->description);
|
|
}
|
|
if (proc->finished) {
|
|
c += sprintf(c, " [zombie]");
|
|
}
|
|
/* Linefeed */
|
|
debug_print(NOTICE, "%s", tmp);
|
|
free(tmp);
|
|
foreach(child, node->children) {
|
|
/* Recursively print the children */
|
|
debug_print_process_tree_node(child->value, height + 1);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Print the process tree to the console.
|
|
*/
|
|
void debug_print_process_tree(void) {
|
|
debug_print_process_tree_node(process_tree->root, 0);
|
|
}
|
|
|
|
/*
|
|
* Retreive the next ready process.
|
|
* XXX: POPs from the ready queue!
|
|
*
|
|
* @return A pointer to the next process in the queue.
|
|
*/
|
|
process_t * next_ready_process(void) {
|
|
if (!process_available()) {
|
|
return kernel_idle_task;
|
|
}
|
|
node_t * np = list_dequeue(process_queue);
|
|
assert(np && "Ready queue is empty.");
|
|
process_t * next = np->value;
|
|
return next;
|
|
}
|
|
|
|
process_t * next_reapable_process(void) {
|
|
spin_lock(&reap_lock);
|
|
node_t * np = list_dequeue(reap_queue);
|
|
spin_unlock(&reap_lock);
|
|
if (!np) { return NULL; }
|
|
process_t * next = np->value;
|
|
free(np);
|
|
return next;
|
|
}
|
|
|
|
/*
|
|
* Reinsert a process into the ready queue.
|
|
*
|
|
* @param proc Process to reinsert
|
|
*/
|
|
void make_process_ready(process_t * proc) {
|
|
if (proc->sleep_node.owner != NULL) {
|
|
if (proc->sleep_node.owner == sleep_queue) {
|
|
/* XXX can't wake from timed sleep */
|
|
assert(proc->timed_sleep_node);
|
|
spin_lock(&sleep_lock);
|
|
list_delete(sleep_queue, proc->timed_sleep_node);
|
|
spin_unlock(&sleep_lock);
|
|
proc->sleep_node.owner = NULL;
|
|
free(proc->timed_sleep_node->value);
|
|
} else {
|
|
spin_lock(&wait_lock_tmp);
|
|
list_delete((list_t*)proc->sleep_node.owner, &proc->sleep_node);
|
|
spin_unlock(&wait_lock_tmp);
|
|
}
|
|
}
|
|
spin_lock(&process_queue_lock);
|
|
list_append(process_queue, &proc->sched_node);
|
|
spin_unlock(&process_queue_lock);
|
|
}
|
|
|
|
void make_process_reapable(process_t * proc) {
|
|
delete_process(proc);
|
|
spin_lock(&reap_lock);
|
|
list_insert(reap_queue, (void *)proc);
|
|
spin_unlock(&reap_lock);
|
|
}
|
|
|
|
void set_reaped(process_t * proc) {
|
|
spin_lock(&reap_lock);
|
|
list_insert(recently_reaped, (void *)proc);
|
|
spin_unlock(&reap_lock);
|
|
}
|
|
|
|
/*
|
|
* Delete a process from the process tree
|
|
*
|
|
* @param proc Process to find and remove.
|
|
*/
|
|
void delete_process(process_t * proc) {
|
|
tree_node_t * entry = proc->tree_entry;
|
|
|
|
/* The process must exist in the tree, or the client is at fault */
|
|
if (!entry) return;
|
|
|
|
/* We can not remove the root, which is an error anyway */
|
|
assert((entry != process_tree->root) && "Attempted to kill init.");
|
|
|
|
/* Remove the entry. */
|
|
spin_lock(&tree_lock);
|
|
tree_remove(process_tree, entry);
|
|
list_delete(process_list, list_find(process_list, proc));
|
|
spin_unlock(&tree_lock);
|
|
}
|
|
|
|
static void _kidle(void) {
|
|
while (1) {
|
|
IRQ_RES;
|
|
PAUSE;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Spawn the idle "process".
|
|
*/
|
|
process_t * spawn_kidle(void) {
|
|
process_t * idle = malloc(sizeof(process_t));
|
|
memset(idle, 0x00, sizeof(process_t));
|
|
idle->id = -1;
|
|
idle->name = strdup("[kidle]");
|
|
idle->is_tasklet = 1;
|
|
|
|
idle->image.stack = (uintptr_t)malloc(KERNEL_STACK_SIZE) + KERNEL_STACK_SIZE;
|
|
idle->thread.eip = (uintptr_t)&_kidle;
|
|
idle->thread.esp = idle->image.stack;
|
|
idle->thread.ebp = idle->image.stack;
|
|
|
|
idle->started = 1;
|
|
idle->running = 1;
|
|
idle->wait_queue = list_create();
|
|
idle->shm_mappings = list_create();
|
|
idle->signal_queue = list_create();
|
|
|
|
set_process_environment(idle, current_directory);
|
|
return idle;
|
|
}
|
|
|
|
/*
|
|
* Spawn the initial process.
|
|
*
|
|
* @return A pointer to the new initial process entry
|
|
*/
|
|
process_t * spawn_init(void) {
|
|
/* We can only do this once. */
|
|
assert((!process_tree->root) && "Tried to regenerate init!");
|
|
|
|
/* Allocate space for a new process */
|
|
process_t * init = malloc(sizeof(process_t));
|
|
/* Set it as the root process */
|
|
tree_set_root(process_tree, (void *)init);
|
|
/* Set its tree entry pointer so we can keep track
|
|
* of the process' entry in the process tree. */
|
|
init->tree_entry = process_tree->root;
|
|
init->id = 1; /* Init is PID 1 */
|
|
init->group = 0;
|
|
init->name = strdup("init"); /* Um, duh. */
|
|
init->cmdline = NULL;
|
|
init->user = 0; /* UID 0 */
|
|
init->mask = 022; /* umask */
|
|
init->group = 0; /* Task group 0 */
|
|
init->status = 0; /* Run status */
|
|
init->fds = malloc(sizeof(fd_table_t));
|
|
init->fds->refs = 1;
|
|
init->fds->length = 0; /* Initialize the file descriptors */
|
|
init->fds->capacity = 4;
|
|
init->fds->entries = malloc(sizeof(fs_node_t *) * init->fds->capacity);
|
|
|
|
/* Set the working directory */
|
|
init->wd_node = clone_fs(fs_root);
|
|
init->wd_name = strdup("/");
|
|
|
|
/* Heap and stack pointers (and actuals) */
|
|
init->image.entry = 0;
|
|
init->image.heap = 0;
|
|
init->image.heap_actual = 0;
|
|
init->image.stack = initial_esp + 1;
|
|
init->image.user_stack = 0;
|
|
init->image.size = 0;
|
|
init->image.shm_heap = SHM_START; /* Yeah, a bit of a hack. */
|
|
|
|
/* Process is not finished */
|
|
init->finished = 0;
|
|
init->started = 1;
|
|
init->running = 1;
|
|
init->wait_queue = list_create();
|
|
init->shm_mappings = list_create();
|
|
init->signal_queue = list_create();
|
|
init->signal_kstack = NULL; /* None yet initialized */
|
|
|
|
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->is_tasklet = 0;
|
|
|
|
set_process_environment(init, current_directory);
|
|
|
|
/* What the hey, let's also set the description on this one */
|
|
init->description = strdup("[init]");
|
|
list_insert(process_list, (void *)init);
|
|
|
|
return init;
|
|
}
|
|
|
|
/*
|
|
* Get the next available PID
|
|
*
|
|
* @return A usable PID for a new process.
|
|
*/
|
|
pid_t get_next_pid(void) {
|
|
/* Terribly naïve, I know, but it works for now */
|
|
static pid_t next = 2;
|
|
return (next++);
|
|
}
|
|
|
|
/*
|
|
* Disown a process from its parent.
|
|
*/
|
|
void process_disown(process_t * proc) {
|
|
assert(process_tree->root && "No init, has the process tree been initialized?");
|
|
|
|
/* Find the process in the tree */
|
|
tree_node_t * entry = proc->tree_entry;
|
|
/* Break it of from its current parent */
|
|
spin_lock(&tree_lock);
|
|
tree_break_off(process_tree, entry);
|
|
/* And insert it back elsewhere */
|
|
tree_node_insert_child_node(process_tree, process_tree->root, entry);
|
|
spin_unlock(&tree_lock);
|
|
}
|
|
|
|
/*
|
|
* Spawn a new process.
|
|
*
|
|
* @param parent The parent process to spawn the new one off of.
|
|
* @return A pointer to the new process.
|
|
*/
|
|
process_t * spawn_process(volatile process_t * parent) {
|
|
assert(process_tree->root && "Attempted to spawn a process without init.");
|
|
|
|
/* Allocate a new process */
|
|
debug_print(INFO," process_t {");
|
|
process_t * proc = malloc(sizeof(process_t));
|
|
debug_print(INFO," }");
|
|
proc->id = get_next_pid(); /* Set its PID */
|
|
proc->group = proc->id; /* Set the GID */
|
|
proc->name = strdup(default_name); /* Use the default name */
|
|
proc->description = NULL; /* No description */
|
|
proc->cmdline = parent->cmdline;
|
|
|
|
/* Copy permissions */
|
|
proc->user = parent->user;
|
|
proc->mask = parent->mask;
|
|
|
|
/* XXX this is wrong? */
|
|
proc->group = parent->group;
|
|
|
|
/* Zero out the ESP/EBP/EIP */
|
|
proc->thread.esp = 0;
|
|
proc->thread.ebp = 0;
|
|
proc->thread.eip = 0;
|
|
proc->thread.fpu_enabled = 0;
|
|
|
|
/* Set the process image information from the parent */
|
|
proc->image.entry = parent->image.entry;
|
|
proc->image.heap = parent->image.heap;
|
|
proc->image.heap_actual = parent->image.heap_actual;
|
|
proc->image.size = parent->image.size;
|
|
debug_print(INFO," stack {");
|
|
proc->image.stack = (uintptr_t)malloc(KERNEL_STACK_SIZE) + KERNEL_STACK_SIZE;
|
|
debug_print(INFO," }");
|
|
proc->image.user_stack = parent->image.user_stack;
|
|
proc->image.shm_heap = SHM_START; /* Yeah, a bit of a hack. */
|
|
|
|
assert(proc->image.stack && "Failed to allocate kernel stack for new process.");
|
|
|
|
/* Clone the file descriptors from the original process */
|
|
proc->fds = malloc(sizeof(fd_table_t));
|
|
proc->fds->refs = 1;
|
|
proc->fds->length = parent->fds->length;
|
|
proc->fds->capacity = parent->fds->capacity;
|
|
debug_print(INFO," fds / files {");
|
|
proc->fds->entries = malloc(sizeof(fs_node_t *) * proc->fds->capacity);
|
|
assert(proc->fds->entries && "Failed to allocate file descriptor table for new process.");
|
|
debug_print(INFO," ---");
|
|
for (uint32_t i = 0; i < parent->fds->length; ++i) {
|
|
proc->fds->entries[i] = clone_fs(parent->fds->entries[i]);
|
|
}
|
|
debug_print(INFO," }");
|
|
|
|
/* As well as the working directory */
|
|
proc->wd_node = clone_fs(parent->wd_node);
|
|
proc->wd_name = strdup(parent->wd_name);
|
|
|
|
/* Zero out the process status */
|
|
proc->status = 0;
|
|
proc->finished = 0;
|
|
proc->started = 0;
|
|
proc->running = 0;
|
|
memset(proc->signals.functions, 0x00, sizeof(uintptr_t) * NUMSIGNALS);
|
|
proc->wait_queue = list_create();
|
|
proc->shm_mappings = list_create();
|
|
proc->signal_queue = list_create();
|
|
proc->signal_kstack = NULL; /* None yet initialized */
|
|
|
|
proc->sched_node.prev = NULL;
|
|
proc->sched_node.next = NULL;
|
|
proc->sched_node.value = proc;
|
|
|
|
proc->sleep_node.prev = NULL;
|
|
proc->sleep_node.next = NULL;
|
|
proc->sleep_node.value = proc;
|
|
|
|
proc->timed_sleep_node = NULL;
|
|
|
|
proc->is_tasklet = 0;
|
|
|
|
/* Insert the process into the process tree as a child
|
|
* of the parent process. */
|
|
tree_node_t * entry = tree_node_create(proc);
|
|
assert(entry && "Failed to allocate a process tree node for new process.");
|
|
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 the new process */
|
|
return proc;
|
|
}
|
|
|
|
process_t * find_reaped_process(pid_t pid) {
|
|
foreach(node, recently_reaped) {
|
|
process_t * proc = node->value;
|
|
if (proc && proc->id == pid) return proc;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
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) {
|
|
assert((pid > 0) && "Tried to retreive a process with PID < 0");
|
|
|
|
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;
|
|
} else {
|
|
return find_reaped_process(pid);
|
|
}
|
|
}
|
|
|
|
process_t * process_get_first_child_rec(tree_node_t * node, process_t * target) {
|
|
if (!node) return NULL;
|
|
process_t * proc = (process_t *)node->value;
|
|
if (proc == target) {
|
|
foreach(child, node->children) {
|
|
process_t * cproc = (process_t *)((tree_node_t *)child->value)->value;
|
|
return cproc;
|
|
}
|
|
return NULL;
|
|
}
|
|
foreach(child, node->children) {
|
|
/* Recursively print the children */
|
|
process_t * out = process_get_first_child_rec(child->value, target);
|
|
if (out) return out;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
process_t * process_get_first_child(process_t * process) {
|
|
spin_lock(&tree_lock);
|
|
process_t * result = process_get_first_child_rec(process_tree->root, process);
|
|
spin_unlock(&tree_lock);
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* Wait for children.
|
|
*
|
|
* @param process Process doing the waiting.
|
|
* @param pid PID to wait for
|
|
* @param status [out] Where to put the status conditions of the waited-for process
|
|
* @param options Options (unused)
|
|
* @return A pointer to the process that broke the wait
|
|
*/
|
|
process_t * process_wait(process_t * process, pid_t pid, int * status, int options) {
|
|
/* `options` is ignored */
|
|
if (pid == -1) {
|
|
/* wait for any child process */
|
|
} else if (pid < 0) {
|
|
/* wait for any porcess whose ->group == processes[abs(pid)]->group */
|
|
} else if (pid == 0) {
|
|
/* wait for any process whose ->group == process->group */
|
|
} else {
|
|
/* wait for processes[pid] */
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Wake up a sleeping process
|
|
*
|
|
* @param process Process to wake up
|
|
* @param caller Who woke it up
|
|
* @return Don't know yet, but I think it should return something.
|
|
*/
|
|
int process_wake(process_t * process, process_t * caller) {
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Set the directory for a process.
|
|
*
|
|
* @param proc Process to set the directory for.
|
|
* @param directory Directory to set.
|
|
*/
|
|
void set_process_environment(process_t * proc, page_directory_t * directory) {
|
|
assert(proc);
|
|
assert(directory);
|
|
|
|
proc->thread.page_directory = directory;
|
|
}
|
|
|
|
/*
|
|
* Are there any processes available in the queue?
|
|
* (Queue not empty)
|
|
*
|
|
* @return 1 if there are processes available, 0 otherwise
|
|
*/
|
|
uint8_t process_available(void) {
|
|
return (process_queue->head != NULL);
|
|
}
|
|
|
|
uint8_t should_reap(void) {
|
|
return (reap_queue->head != NULL);
|
|
}
|
|
|
|
/*
|
|
* Append a file descriptor to a process.
|
|
*
|
|
* @param proc Process to append to
|
|
* @param node The VFS node
|
|
* @return The actual fd, for use in userspace
|
|
*/
|
|
uint32_t process_append_fd(process_t * proc, fs_node_t * node) {
|
|
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->entries[proc->fds->length] = node;
|
|
proc->fds->length++;
|
|
return proc->fds->length-1;
|
|
}
|
|
|
|
/*
|
|
* dup2() -> Move the file pointed to by `s(ou)rc(e)` into
|
|
* the slot pointed to be `dest(ination)`.
|
|
*
|
|
* @param proc Process to do this for
|
|
* @param src Source file descriptor
|
|
* @param dest Destination file descriptor
|
|
* @return The destination file descriptor, -1 on failure
|
|
*/
|
|
uint32_t process_move_fd(process_t * proc, int src, int dest) {
|
|
if ((size_t)src > proc->fds->length || (size_t)dest > proc->fds->length) {
|
|
return -1;
|
|
}
|
|
#if 0
|
|
if (proc->fds->entries[dest] != proc->fds->entries[src]) {
|
|
close_fs(proc->fds->entries[src]);
|
|
}
|
|
#endif
|
|
proc->fds->entries[dest] = proc->fds->entries[src];
|
|
return dest;
|
|
}
|
|
|
|
int wakeup_queue(list_t * queue) {
|
|
int awoken_processes = 0;
|
|
while (queue->length > 0) {
|
|
spin_lock(&wait_lock_tmp);
|
|
node_t * node = list_pop(queue);
|
|
spin_unlock(&wait_lock_tmp);
|
|
if (!((process_t *)node->value)->finished) {
|
|
make_process_ready(node->value);
|
|
}
|
|
awoken_processes++;
|
|
}
|
|
return awoken_processes;
|
|
}
|
|
|
|
|
|
int sleep_on(list_t * queue) {
|
|
if (current_process->sleep_node.owner) {
|
|
/* uh, we can't sleep right now, we're marked as ready */
|
|
switch_task(0);
|
|
return 0;
|
|
}
|
|
spin_lock(&wait_lock_tmp);
|
|
list_append(queue, (node_t *)¤t_process->sleep_node);
|
|
spin_unlock(&wait_lock_tmp);
|
|
switch_task(0);
|
|
return 0;
|
|
}
|
|
|
|
int process_is_ready(process_t * proc) {
|
|
return (proc->sched_node.owner != NULL);
|
|
}
|
|
|
|
|
|
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))) {
|
|
process_t * process = proc->process;
|
|
process->sleep_node.owner = NULL;
|
|
process->timed_sleep_node = NULL;
|
|
if (!process_is_ready(process)) {
|
|
make_process_ready(process);
|
|
}
|
|
free(proc);
|
|
free(list_dequeue(sleep_queue));
|
|
if (sleep_queue->length) {
|
|
proc = ((sleeper_t *)sleep_queue->head->value);
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
spin_unlock(&sleep_lock);
|
|
}
|
|
|
|
void sleep_until(process_t * process, unsigned long seconds, unsigned long subseconds) {
|
|
if (current_process->sleep_node.owner) {
|
|
/* Can't sleep, sleeping already */
|
|
return;
|
|
}
|
|
process->sleep_node.owner = sleep_queue;
|
|
spin_lock(&sleep_lock);
|
|
node_t * before = NULL;
|
|
foreach(node, sleep_queue) {
|
|
sleeper_t * candidate = ((sleeper_t *)node->value);
|
|
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;
|
|
process->timed_sleep_node = list_insert_after(sleep_queue, before, proc);
|
|
spin_unlock(&sleep_lock);
|
|
}
|