2011-12-11 03:34:10 +04:00
|
|
|
/* vim: tabstop=4 shiftwidth=4 noexpandtab
|
|
|
|
*
|
|
|
|
* Processes
|
2011-12-14 12:37:43 +04:00
|
|
|
*
|
|
|
|
* Internal format format for a process and functions to spawn
|
|
|
|
* new processes and manage the process tree.
|
2011-12-11 03:34:10 +04:00
|
|
|
*/
|
2011-12-08 06:59:08 +04:00
|
|
|
#include <system.h>
|
|
|
|
#include <process.h>
|
|
|
|
#include <tree.h>
|
|
|
|
#include <list.h>
|
|
|
|
|
2011-12-14 12:37:43 +04:00
|
|
|
tree_t * process_tree; /* Parent->Children tree */
|
|
|
|
list_t * process_queue; /* Ready queue */
|
2011-12-16 03:21:28 +04:00
|
|
|
list_t * reap_queue; /* Processes to reap */
|
2011-12-09 01:25:48 +04:00
|
|
|
volatile process_t * current_process = NULL;
|
2011-12-08 06:59:08 +04:00
|
|
|
|
2011-12-16 07:08:48 +04:00
|
|
|
static uint8_t volatile ready_lock;
|
|
|
|
static uint8_t volatile reap_lock;
|
|
|
|
static uint8_t volatile tree_lock;
|
|
|
|
|
2011-12-14 12:37:43 +04:00
|
|
|
/* Default process name string */
|
2011-12-08 06:59:08 +04:00
|
|
|
char * default_name = "[unnamed]";
|
|
|
|
|
2011-12-14 12:37:43 +04:00
|
|
|
/*
|
|
|
|
* Initialize the process tree and ready queue.
|
|
|
|
*/
|
2011-12-08 06:59:08 +04:00
|
|
|
void initialize_process_tree() {
|
|
|
|
process_tree = tree_create();
|
|
|
|
process_queue = list_create();
|
2011-12-16 03:21:28 +04:00
|
|
|
reap_queue = list_create();
|
2011-12-08 06:59:08 +04:00
|
|
|
}
|
|
|
|
|
2011-12-14 12:37:43 +04:00
|
|
|
/*
|
|
|
|
* Recursively print a process node to the console.
|
|
|
|
*
|
|
|
|
* @param node Node to print.
|
|
|
|
* @param height Current depth in the tree.
|
|
|
|
*/
|
2011-12-08 06:59:08 +04:00
|
|
|
void debug_print_process_tree_node(tree_node_t * node, size_t height) {
|
2011-12-14 12:37:43 +04:00
|
|
|
/* End recursion on a blank entry */
|
2011-12-08 06:59:08 +04:00
|
|
|
if (!node) return;
|
2011-12-14 12:37:43 +04:00
|
|
|
/* Indent output */
|
2011-12-08 06:59:08 +04:00
|
|
|
for (uint32_t i = 0; i < height; ++i) { kprintf(" "); }
|
2011-12-14 12:37:43 +04:00
|
|
|
/* Get the current process */
|
2011-12-08 06:59:08 +04:00
|
|
|
process_t * proc = (process_t *)node->value;
|
2011-12-14 12:37:43 +04:00
|
|
|
/* Print the process name */
|
2011-12-08 06:59:08 +04:00
|
|
|
kprintf("[%d] %s", proc->id, proc->name);
|
|
|
|
if (proc->description) {
|
2011-12-14 12:37:43 +04:00
|
|
|
/* And, if it has one, its description */
|
2011-12-08 06:59:08 +04:00
|
|
|
kprintf(" %s", proc->description);
|
|
|
|
}
|
2011-12-16 07:08:48 +04:00
|
|
|
if (proc->finished) {
|
|
|
|
kprintf(" [zombie]");
|
|
|
|
}
|
2011-12-14 12:37:43 +04:00
|
|
|
/* Linefeed */
|
2011-12-08 06:59:08 +04:00
|
|
|
kprintf("\n");
|
|
|
|
foreach(child, node->children) {
|
2011-12-14 12:37:43 +04:00
|
|
|
/* Recursively print the children */
|
2011-12-08 06:59:08 +04:00
|
|
|
debug_print_process_tree_node(child->value, height + 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-12-14 12:37:43 +04:00
|
|
|
/*
|
|
|
|
* Print the process tree to the console.
|
|
|
|
*/
|
2011-12-08 06:59:08 +04:00
|
|
|
void debug_print_process_tree() {
|
|
|
|
debug_print_process_tree_node(process_tree->root, 0);
|
|
|
|
}
|
|
|
|
|
2011-12-14 12:37:43 +04:00
|
|
|
/*
|
|
|
|
* Retreive the next ready process.
|
|
|
|
* XXX: POPs from the ready queue!
|
|
|
|
*
|
|
|
|
* @return A pointer to the next process in the queue.
|
|
|
|
*/
|
2011-12-08 06:59:08 +04:00
|
|
|
process_t * next_ready_process() {
|
2011-12-16 07:08:48 +04:00
|
|
|
spin_lock(&ready_lock);
|
2011-12-09 01:25:48 +04:00
|
|
|
node_t * np = list_dequeue(process_queue);
|
2011-12-16 07:08:48 +04:00
|
|
|
spin_unlock(&ready_lock);
|
2011-12-09 01:25:48 +04:00
|
|
|
assert(np && "Ready queue is empty.");
|
2011-12-08 06:59:08 +04:00
|
|
|
process_t * next = np->value;
|
|
|
|
free(np);
|
|
|
|
return next;
|
|
|
|
}
|
|
|
|
|
2011-12-16 03:21:28 +04:00
|
|
|
process_t * next_reapable_process() {
|
2011-12-16 07:08:48 +04:00
|
|
|
spin_lock(&reap_lock);
|
2011-12-16 03:21:28 +04:00
|
|
|
node_t * np = list_dequeue(reap_queue);
|
2011-12-16 07:08:48 +04:00
|
|
|
spin_unlock(&reap_lock);
|
|
|
|
if (!np) { return NULL; }
|
2011-12-16 03:21:28 +04:00
|
|
|
process_t * next = np->value;
|
|
|
|
free(np);
|
|
|
|
return next;
|
|
|
|
}
|
|
|
|
|
2011-12-14 12:37:43 +04:00
|
|
|
/*
|
|
|
|
* Reinsert a process into the ready queue.
|
|
|
|
*
|
|
|
|
* @param proc Process to reinsert
|
|
|
|
*/
|
2011-12-08 06:59:08 +04:00
|
|
|
void make_process_ready(process_t * proc) {
|
2011-12-16 07:08:48 +04:00
|
|
|
spin_lock(&ready_lock);
|
2011-12-08 06:59:08 +04:00
|
|
|
list_insert(process_queue, (void *)proc);
|
2011-12-16 07:08:48 +04:00
|
|
|
spin_unlock(&ready_lock);
|
2011-12-08 06:59:08 +04:00
|
|
|
}
|
|
|
|
|
2011-12-16 03:21:28 +04:00
|
|
|
void make_process_reapable(process_t * proc) {
|
2011-12-16 07:08:48 +04:00
|
|
|
spin_lock(&reap_lock);
|
2011-12-16 03:21:28 +04:00
|
|
|
list_insert(reap_queue, (void *)proc);
|
2011-12-16 07:08:48 +04:00
|
|
|
spin_unlock(&reap_lock);
|
2011-12-16 03:21:28 +04:00
|
|
|
}
|
|
|
|
|
2011-12-14 12:37:43 +04:00
|
|
|
/*
|
|
|
|
* Delete a process from the process tree
|
|
|
|
*
|
|
|
|
* @param proc Process to find and remove.
|
|
|
|
*/
|
2011-12-08 06:59:08 +04:00
|
|
|
void delete_process(process_t * proc) {
|
|
|
|
tree_node_t * entry = proc->tree_entry;
|
2011-12-14 12:37:43 +04:00
|
|
|
|
|
|
|
/* The process must exist in the tree, or the client is at fault */
|
2011-12-08 06:59:08 +04:00
|
|
|
assert(entry && "Attempted to remove a process without a ps-tree entry.");
|
2011-12-14 12:37:43 +04:00
|
|
|
|
|
|
|
/* We can not remove the root, which is an error anyway */
|
2011-12-08 06:59:08 +04:00
|
|
|
assert((entry != process_tree->root) && "Attempted to kill init.");
|
2011-12-09 01:25:48 +04:00
|
|
|
|
2011-12-14 12:37:43 +04:00
|
|
|
/* Remove the entry. */
|
2011-12-16 07:08:48 +04:00
|
|
|
spin_lock(&tree_lock);
|
2011-12-08 06:59:08 +04:00
|
|
|
tree_remove(process_tree, entry);
|
2011-12-16 07:08:48 +04:00
|
|
|
spin_unlock(&tree_lock);
|
2011-12-08 06:59:08 +04:00
|
|
|
|
2011-12-16 07:08:48 +04:00
|
|
|
free(proc);
|
2011-12-08 06:59:08 +04:00
|
|
|
}
|
|
|
|
|
2011-12-14 12:37:43 +04:00
|
|
|
/*
|
|
|
|
* Spawn the initial process.
|
|
|
|
*
|
|
|
|
* @return A pointer to the new initial process entry
|
|
|
|
*/
|
2011-12-09 01:25:48 +04:00
|
|
|
process_t * spawn_init() {
|
2011-12-14 12:37:43 +04:00
|
|
|
/* We can only do this once. */
|
2011-12-09 01:25:48 +04:00
|
|
|
assert((!process_tree->root) && "Tried to regenerate init!");
|
|
|
|
|
2011-12-14 12:37:43 +04:00
|
|
|
/* Allocate space for a new process */
|
2011-12-08 06:59:08 +04:00
|
|
|
process_t * init = malloc(sizeof(process_t));
|
2011-12-14 12:37:43 +04:00
|
|
|
/* Set it as the root process */
|
2011-12-08 06:59:08 +04:00
|
|
|
tree_set_root(process_tree, (void *)init);
|
2011-12-14 12:37:43 +04:00
|
|
|
/* Set its tree entry pointer so we can keep track
|
|
|
|
* of the process' entry in the process tree. */
|
2011-12-08 06:59:08 +04:00
|
|
|
init->tree_entry = process_tree->root;
|
2011-12-14 12:37:43 +04:00
|
|
|
init->id = 1; /* Init is PID 1 */
|
|
|
|
init->name = "init"; /* Um, duh. */
|
|
|
|
init->user = 0; /* UID 0 */
|
|
|
|
init->group = 0; /* Task group 0 */
|
|
|
|
init->status = 0; /* Run status */
|
|
|
|
init->fds.length = 0; /* Initialize the file descriptors */
|
2011-12-08 06:59:08 +04:00
|
|
|
init->fds.capacity = 4;
|
|
|
|
init->fds.entries = malloc(sizeof(fs_node_t *) * init->fds.capacity);
|
2011-12-14 12:37:43 +04:00
|
|
|
|
|
|
|
/* Set the working directory */
|
2011-12-09 01:25:48 +04:00
|
|
|
init->wd_node = clone_fs(fs_root);
|
2011-12-08 06:59:08 +04:00
|
|
|
init->wd_name = "/";
|
|
|
|
|
2011-12-14 12:37:43 +04:00
|
|
|
/* Heap and stack pointers (and actuals) */
|
2011-12-09 01:25:48 +04:00
|
|
|
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;
|
|
|
|
|
2011-12-14 12:37:43 +04:00
|
|
|
/* Process is not finished */
|
2011-12-09 01:25:48 +04:00
|
|
|
init->finished = 0;
|
|
|
|
|
2011-12-14 12:37:43 +04:00
|
|
|
/* What the hey, let's also set the description on this one */
|
2011-12-09 01:25:48 +04:00
|
|
|
init->description = "[init]";
|
2011-12-08 06:59:08 +04:00
|
|
|
return init;
|
|
|
|
}
|
|
|
|
|
2011-12-14 12:37:43 +04:00
|
|
|
/*
|
|
|
|
* Get the next available PID
|
|
|
|
*
|
|
|
|
* @return A usable PID for a new process.
|
|
|
|
*/
|
2011-12-08 06:59:08 +04:00
|
|
|
pid_t get_next_pid() {
|
2011-12-14 12:37:43 +04:00
|
|
|
/* Terribly naïve, I know, but it works for now */
|
2011-12-08 06:59:08 +04:00
|
|
|
static pid_t next = 2;
|
|
|
|
return (next++);
|
|
|
|
}
|
|
|
|
|
2011-12-14 12:37:43 +04:00
|
|
|
/*
|
|
|
|
* Disown a process from its parent.
|
|
|
|
*/
|
2011-12-08 06:59:08 +04:00
|
|
|
void process_disown(process_t * proc) {
|
|
|
|
assert(process_tree->root && "No init, has the process tree been initialized?");
|
2011-12-09 01:25:48 +04:00
|
|
|
|
2011-12-14 12:37:43 +04:00
|
|
|
/* Find the process in the tree */
|
2011-12-08 06:59:08 +04:00
|
|
|
tree_node_t * entry = proc->tree_entry;
|
2011-12-14 12:37:43 +04:00
|
|
|
/* Break it of from its current parent */
|
2011-12-16 07:08:48 +04:00
|
|
|
spin_lock(&tree_lock);
|
2011-12-08 06:59:08 +04:00
|
|
|
tree_break_off(process_tree, entry);
|
2011-12-14 12:37:43 +04:00
|
|
|
/* And insert it back elsewhere */
|
2011-12-08 06:59:08 +04:00
|
|
|
tree_node_insert_child_node(process_tree, process_tree->root, entry);
|
2011-12-16 07:08:48 +04:00
|
|
|
spin_unlock(&tree_lock);
|
2011-12-08 06:59:08 +04:00
|
|
|
}
|
|
|
|
|
2011-12-14 12:37:43 +04:00
|
|
|
/*
|
|
|
|
* Spawn a new process.
|
|
|
|
*
|
|
|
|
* @param parent The parent process to spawn the new one off of.
|
|
|
|
* @return A pointer to the new process.
|
|
|
|
*/
|
2011-12-09 01:25:48 +04:00
|
|
|
process_t * spawn_process(volatile process_t * parent) {
|
|
|
|
assert(process_tree->root && "Attempted to spawn a process without init.");
|
|
|
|
|
2011-12-14 12:37:43 +04:00
|
|
|
/* Allocate a new process */
|
2011-12-08 06:59:08 +04:00
|
|
|
process_t * proc = malloc(sizeof(process_t));
|
2011-12-14 12:37:43 +04:00
|
|
|
proc->id = get_next_pid(); /* Set its PID */
|
|
|
|
proc->name = default_name; /* Use the default name */
|
|
|
|
proc->description = NULL; /* No description */
|
2011-12-09 01:25:48 +04:00
|
|
|
|
2011-12-14 12:37:43 +04:00
|
|
|
/* Zero out the ESP/EBP/EIP */
|
2011-12-09 01:25:48 +04:00
|
|
|
proc->thread.esp = 0;
|
|
|
|
proc->thread.ebp = 0;
|
|
|
|
proc->thread.eip = 0;
|
|
|
|
|
2011-12-14 12:37:43 +04:00
|
|
|
/* Set the process image information from the parent */
|
2011-12-09 01:25:48 +04:00
|
|
|
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;
|
|
|
|
proc->image.stack = kvmalloc(KERNEL_STACK_SIZE) + KERNEL_STACK_SIZE;
|
2011-12-14 02:06:45 +04:00
|
|
|
proc->image.user_stack = parent->image.user_stack;
|
2011-12-09 01:25:48 +04:00
|
|
|
|
2011-12-16 03:21:28 +04:00
|
|
|
assert(proc->image.stack && "Failed to allocate kernel stack for new process.");
|
|
|
|
|
2011-12-14 12:37:43 +04:00
|
|
|
/* Clone the file descriptors from the original process */
|
2011-12-09 01:25:48 +04:00
|
|
|
proc->fds.length = parent->fds.length;
|
|
|
|
proc->fds.capacity = parent->fds.capacity;
|
|
|
|
proc->fds.entries = malloc(sizeof(fs_node_t *) * proc->fds.capacity);
|
2011-12-16 03:21:28 +04:00
|
|
|
assert(proc->fds.entries && "Failed to allocate file descriptor table for new process.");
|
2011-12-09 01:25:48 +04:00
|
|
|
for (uint32_t i = 0; i < parent->fds.length; ++i) {
|
|
|
|
proc->fds.entries[i] = clone_fs(parent->fds.entries[i]);
|
|
|
|
}
|
|
|
|
|
2011-12-14 12:37:43 +04:00
|
|
|
/* As well as the working directory */
|
2011-12-09 01:25:48 +04:00
|
|
|
proc->wd_node = clone_fs(parent->wd_node);
|
|
|
|
proc->wd_name = malloc((strlen(parent->wd_name) + 1) * sizeof(char));
|
2011-12-16 03:21:28 +04:00
|
|
|
assert(proc->wd_name && "Failed to allocate cwd string for new process.");
|
2011-12-09 01:25:48 +04:00
|
|
|
memcpy(proc->wd_name, parent->wd_name, strlen(parent->wd_name) + 1);
|
|
|
|
|
2011-12-14 12:37:43 +04:00
|
|
|
/* Zero out the process status */
|
2011-12-09 01:25:48 +04:00
|
|
|
proc->status = 0;
|
|
|
|
proc->finished = 0;
|
|
|
|
|
2011-12-14 12:37:43 +04:00
|
|
|
/* Insert the process into the process tree as a child
|
|
|
|
* of the parent process. */
|
2011-12-08 06:59:08 +04:00
|
|
|
tree_node_t * entry = tree_node_create(proc);
|
2011-12-16 03:21:28 +04:00
|
|
|
assert(entry && "Failed to allocate a process tree node for new process.");
|
2011-12-08 06:59:08 +04:00
|
|
|
proc->tree_entry = entry;
|
2011-12-16 07:08:48 +04:00
|
|
|
spin_lock(&tree_lock);
|
2011-12-08 06:59:08 +04:00
|
|
|
tree_node_insert_child_node(process_tree, parent->tree_entry, entry);
|
2011-12-16 07:08:48 +04:00
|
|
|
spin_unlock(&tree_lock);
|
2011-12-09 01:25:48 +04:00
|
|
|
|
2011-12-14 12:37:43 +04:00
|
|
|
/* Return the new process */
|
2011-12-08 06:59:08 +04:00
|
|
|
return proc;
|
|
|
|
}
|
|
|
|
|
|
|
|
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");
|
2011-12-09 01:25:48 +04:00
|
|
|
|
2011-12-16 07:08:48 +04:00
|
|
|
spin_lock(&tree_lock);
|
2011-12-08 06:59:08 +04:00
|
|
|
tree_node_t * entry = tree_find(process_tree,&pid,process_compare);
|
2011-12-16 07:08:48 +04:00
|
|
|
spin_unlock(&tree_lock);
|
2011-12-08 06:59:08 +04:00
|
|
|
if (entry) {
|
|
|
|
return (process_t *)entry->value;
|
|
|
|
} else {
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-12-14 12:37:43 +04:00
|
|
|
/*
|
|
|
|
* 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
|
|
|
|
*/
|
2011-12-08 06:59:08 +04:00
|
|
|
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;
|
|
|
|
}
|
2011-12-09 01:25:48 +04:00
|
|
|
|
2011-12-12 12:17:14 +04:00
|
|
|
/*
|
|
|
|
* Wake up a sleeping process
|
2011-12-14 12:37:43 +04:00
|
|
|
*
|
|
|
|
* @param process Process to wake up
|
|
|
|
* @param caller Who woke it up
|
|
|
|
* @return Don't know yet, but I think it should return something.
|
2011-12-12 12:17:14 +04:00
|
|
|
*/
|
|
|
|
int process_wake(process_t * process, process_t * caller) {
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2011-12-14 12:37:43 +04:00
|
|
|
/*
|
|
|
|
* Set the directory for a process.
|
|
|
|
*
|
|
|
|
* @param proc Process to set the directory for.
|
|
|
|
* @param directory Directory to set.
|
|
|
|
*/
|
2011-12-09 01:25:48 +04:00
|
|
|
void set_process_environment(process_t * proc, page_directory_t * directory) {
|
|
|
|
assert(proc);
|
|
|
|
assert(directory);
|
|
|
|
|
|
|
|
proc->thread.page_directory = directory;
|
|
|
|
}
|
|
|
|
|
2011-12-14 12:37:43 +04:00
|
|
|
/*
|
|
|
|
* Are there any processes available in the queue?
|
|
|
|
* (Queue not empty)
|
|
|
|
*
|
|
|
|
* @return 1 if there are processes available, 0 otherwise
|
|
|
|
*/
|
2011-12-09 01:25:48 +04:00
|
|
|
uint8_t process_available() {
|
|
|
|
return (process_queue->head != NULL);
|
|
|
|
}
|
|
|
|
|
2011-12-16 03:21:28 +04:00
|
|
|
uint8_t should_reap() {
|
|
|
|
return (reap_queue->head != NULL);
|
|
|
|
}
|
|
|
|
|
2011-12-14 12:37:43 +04:00
|
|
|
/*
|
|
|
|
* 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
|
|
|
|
*/
|
2011-12-09 01:25:48 +04:00
|
|
|
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;
|
|
|
|
}
|
2012-01-25 05:06:07 +04:00
|
|
|
|
|
|
|
/*
|
|
|
|
* 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;
|
|
|
|
}
|