/* 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 #include #include #include #include #include #include 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); }