From 503f176e001ddf15e6e32bc912cf10b5764bc23b Mon Sep 17 00:00:00 2001 From: Alex Auvolat Date: Wed, 4 Mar 2015 11:51:30 +0100 Subject: Process exiting & thread termination. IMPORTANT NOTE FOLLOWS. Threads may now communicate via wait_on(void* ressource) and resume_on (void* ressource). Previous pause() is replaced by wait_on(current_thread) and resume(thread) by resume_on(thread). wait_on(x) may return false, indicating that the reason for returning is NOT that resume_on(x) was called but something else happenned. Typically false indicates that the curent thread is being killed and must terminate its kernel-land processing as soon as possible. --- src/common/include/hashtbl.h | 5 ++ src/common/libalgo/hashtbl.c | 13 +++++ src/kernel/core/idt.c | 15 ++++++ src/kernel/core/thread.c | 110 +++++++++++++++++++++++++++++++++++-------- src/kernel/core/worker.c | 9 ++-- src/kernel/dev/pciide.c | 33 ++++++++----- src/kernel/include/process.h | 18 ++++--- src/kernel/include/thread.h | 11 +++-- src/kernel/user/process.c | 105 +++++++++++++++++++++++++---------------- src/kernel/user/syscall.c | 2 +- 10 files changed, 237 insertions(+), 84 deletions(-) diff --git a/src/common/include/hashtbl.h b/src/common/include/hashtbl.h index 818cfa0..b9c7178 100644 --- a/src/common/include/hashtbl.h +++ b/src/common/include/hashtbl.h @@ -25,6 +25,11 @@ void hashtbl_remove(hashtbl_t* ht, const void* key); void* hashtbl_find(hashtbl_t* ht, const void* key); // null when not found void hashtbl_iter(hashtbl_t* ht, kv_iter_fun_t f); +// hashtbl_change is particular : +// - it does NOT call malloc and uses the existing hashtbl cell +// - it does NOT call the on_release fun on the previous element +bool hashtbl_change(hashtbl_t* ht, void* key, void* v); + size_t hashtbl_count(hashtbl_t* ht); /* vim: set ts=4 sw=4 tw=0 noet :*/ diff --git a/src/common/libalgo/hashtbl.c b/src/common/libalgo/hashtbl.c index f490632..f34c6d4 100644 --- a/src/common/libalgo/hashtbl.c +++ b/src/common/libalgo/hashtbl.c @@ -160,4 +160,17 @@ size_t hashtbl_count(hashtbl_t* ht) { return ht->nitems; } +bool hashtbl_change(hashtbl_t* ht, void* key, void* newval) { + size_t slot = SLOT_OF_HASH(ht->hf(key), ht->size); + + for (hashtbl_item_t *i = ht->items[slot]; i != 0; i = i->next) { + if (ht->ef(i->key, key)) { + i->val = newval; + return true; + } + } + + return false; +} + /* vim: set ts=4 sw=4 tw=0 noet :*/ diff --git a/src/kernel/core/idt.c b/src/kernel/core/idt.c index 5c77502..fcf8074 100644 --- a/src/kernel/core/idt.c +++ b/src/kernel/core/idt.c @@ -103,6 +103,11 @@ void idt_ex_handler(registers_t *regs) { current_thread->user_ex_handler(regs); } } + + // maybe exit + if (current_thread != 0 && regs->eip < K_HIGHHALF_ADDR && current_thread->must_exit) { + exit(); + } } /* Called in interrupt.s when an IRQ fires (interrupt 32 to 47) */ @@ -120,11 +125,21 @@ void idt_irq_handler(registers_t *regs) { } exit_critical(st); + + // maybe exit + if (current_thread != 0 && regs->eip < K_HIGHHALF_ADDR && current_thread->must_exit) { + exit(); + } } /* Caled in interrupt.s when a syscall is called */ void idt_syscall_handler(registers_t *regs) { syscall_handler(regs); + + // maybe exit + if (current_thread != 0 && regs->eip < K_HIGHHALF_ADDR && current_thread->must_exit) { + exit(); + } } /* For internal use only. Sets up an entry of the IDT with given parameters. */ diff --git a/src/kernel/core/thread.c b/src/kernel/core/thread.c index 63523af..144a905 100644 --- a/src/kernel/core/thread.c +++ b/src/kernel/core/thread.c @@ -4,6 +4,8 @@ #include #include +#include + #include #include #include @@ -15,6 +17,9 @@ void resume_context(saved_context_t *ctx); thread_t *current_thread = 0; +static hashtbl_t *waiters = 0; // threads waiting on a ressource +STATIC_MUTEX(waiters_mutex); + // ====================== // // THE PROGRAMMABLE TIMER // // ====================== // @@ -180,9 +185,12 @@ thread_t *new_thread(entry_t entry, void* data) { *(--t->ctx.esp) = 0; // push invalid return address (the run_thread function never returns) t->ctx.eip = (void(*)())run_thread; - t->state = T_STATE_PAUSED; + t->state = T_STATE_LOADING; t->last_ran = 0; + t->waiting_on = 0; + t->must_exit = false; + t->current_pd_d = get_kernel_pagedir(); t->critical_level = CL_USER; @@ -195,6 +203,8 @@ thread_t *new_thread(entry_t entry, void* data) { } static void delete_thread(thread_t *t) { + dbg_printf("Deleting thread 0x%p\n", t); + if (t->proc != 0) process_thread_deleted(t); @@ -213,13 +223,17 @@ static void irq0_handler(registers_t *regs) { } } void threading_setup(entry_t cont, void* arg) { + waiters = create_hashtbl(id_key_eq_fun, id_hash_fun, 0); + ASSERT(waiters != 0); + set_pit_frequency(TASK_SWITCH_FREQUENCY); idt_set_irq_handler(IRQ0, irq0_handler); thread_t *t = new_thread(cont, arg); ASSERT(t != 0); - resume_thread(t); + start_thread(t); + exit_critical(CL_USER); run_scheduler(); // never returns @@ -230,27 +244,65 @@ void threading_setup(entry_t cont, void* arg) { // TASK STATE MANIPULATION // // ======================= // +void start_thread(thread_t *t) { + ASSERT(t->state == T_STATE_LOADING); + + t->state = T_STATE_RUNNING; + + { int st = enter_critical(CL_NOINT); + + enqueue_thread(t, false); + + exit_critical(st); } +} + void yield() { ASSERT(current_thread != 0 && current_thread->critical_level != CL_EXCL); save_context_and_enter_scheduler(¤t_thread->ctx); } -void pause() { +bool wait_on(void* x) { ASSERT(current_thread != 0 && current_thread->critical_level != CL_EXCL); + mutex_lock(&waiters_mutex); + + void* prev_th = hashtbl_find(waiters, x); + if (prev_th == 0) { + bool add_ok = hashtbl_add(waiters, x, (void*)1); + if (!add_ok) return false; // should not happen to often, I hope + } else if (prev_th != (void*)1) { + mutex_unlock(&waiters_mutex); + return false; + } + + int st = enter_critical(CL_NOSWITCH); + + if (current_thread->must_exit) return false; + + current_thread->waiting_on = x; + + ASSERT(hashtbl_change(waiters, x, current_thread)); + mutex_unlock(&waiters_mutex); + current_thread->state = T_STATE_PAUSED; save_context_and_enter_scheduler(¤t_thread->ctx); + + exit_critical(st); + + if (current_thread->must_exit) return false; + return true; } void usleep(int usecs) { - void sleeper_resume(void* t) { - thread_t *thread = (thread_t*)t; - resume_thread(thread); - } if (current_thread == 0) return; - bool ok = worker_push_in(usecs, sleeper_resume, current_thread); - if (ok) pause(); + + void resume_on_v(void* x) { + resume_on(x); + } + bool ok = worker_push_in(usecs, resume_on_v, current_thread); + + if (ok) wait_on(current_thread); } void exit() { @@ -263,6 +315,8 @@ void exit() { // (it may switch before adding the delete_thread task), but once the task is added // no other switch may happen, therefore this thread will not get re-enqueued + dbg_printf("Thread 0x%p exiting.\n", current_thread); + worker_push(delete_thread_v, current_thread); current_thread->state = T_STATE_FINISHED; @@ -272,20 +326,29 @@ void exit() { ASSERT(false); } -bool resume_thread(thread_t *thread) { - bool ret = false; +bool resume_on(void* x) { + thread_t *thread; + + { mutex_lock(&waiters_mutex); - int st = enter_critical(CL_NOINT); + thread = hashtbl_find(waiters, x); + hashtbl_change(waiters, x, (void*)1); - if (thread->state == T_STATE_PAUSED) { - ret = true; + mutex_unlock(&waiters_mutex); } + + if (thread == 0 || thread == (void*)1) return false; + + { int st = enter_critical(CL_NOINT); + + ASSERT(thread->state == T_STATE_PAUSED); thread->state = T_STATE_RUNNING; + thread->waiting_on = 0; + enqueue_thread(thread, false); - } - exit_critical(st); + exit_critical(st); } - return ret; + return true; } void kill_thread(thread_t *thread) { @@ -293,9 +356,16 @@ void kill_thread(thread_t *thread) { int st = enter_critical(CL_NOSWITCH); - thread->state = T_STATE_FINISHED; - remove_thread_from_queue(thread); - delete_thread(thread); + thread->must_exit = true; + + int i = 0; + while (thread->state != T_STATE_FINISHED) { + if (thread->state == T_STATE_PAUSED) { + resume_on(thread->waiting_on); + } + yield(); + if (i++ > 100) dbg_printf("Thread 0x%p must be killed but will not exit.\n", thread); + } exit_critical(st); } diff --git a/src/kernel/core/worker.c b/src/kernel/core/worker.c index e50085a..c3fa04d 100644 --- a/src/kernel/core/worker.c +++ b/src/kernel/core/worker.c @@ -36,7 +36,10 @@ void start_workers(int n) { nworkers = n; for (int i = 0; i < n; i++) { workers[i] = new_thread(worker_thread, 0); - dbg_printf("New worker thread: 0x%p\n", workers[i]); + if (workers[i] != 0) { + dbg_printf("New worker thread: 0x%p\n", workers[i]); + start_thread(workers[i]); + } } } @@ -59,7 +62,7 @@ void worker_thread(void* x) { t->fun(t->data); free(t); } else { - pause(); + ASSERT(wait_on(current_thread)); } } } @@ -89,7 +92,7 @@ void worker_notify_time(int usecs) { time += usecs; if (next_task_time <= time) { for (int i = 0; i < nworkers; i++) { - if (resume_thread(workers[i])) break; + if (resume_on(workers[i])) break; } } } diff --git a/src/kernel/dev/pciide.c b/src/kernel/dev/pciide.c index 05c0b28..c357190 100644 --- a/src/kernel/dev/pciide.c +++ b/src/kernel/dev/pciide.c @@ -144,6 +144,8 @@ static uint8_t ide_print_error(ide_controller_t *c, int drive, uint8_t err) { } else if (err == 4) { dbg_printf("- Write Protected\n "); err = 8; + } else if (err == 5) { + dbg_printf("- Interrupted\n "); } dbg_printf("- [%s %s] %s\n", (const char *[]){"Primary", "Secondary"}[c->devices[drive].channel], @@ -167,7 +169,7 @@ void irq14_handler(registers_t *regs) { if (wait_irq14) { thread_t *t = wait_irq14; wait_irq14 = 0; - resume_thread(t); + resume_on(t); } } @@ -175,7 +177,7 @@ void irq15_handler(registers_t *regs) { if (wait_irq15) { thread_t *t = wait_irq15; wait_irq15 = 0; - resume_thread(t); + resume_on(t); } } @@ -183,7 +185,7 @@ void pciirq_handler(int pci_id) { if (wait_pciirq) { thread_t *t = wait_pciirq; wait_pciirq = 0; - resume_thread(t); + resume_on(t); } } @@ -201,22 +203,26 @@ static void ide_prewait_irq(ide_controller_t *c, int channel) { } } -static void ide_wait_irq(ide_controller_t *c, int channel) { +static bool ide_wait_irq(ide_controller_t *c, int channel) { + bool ret = true; + int st = enter_critical(CL_NOINT); int irq = c->channels[channel].irq; if (irq == 14) { - if (wait_irq14) pause(); + if (wait_irq14) ret = wait_on(current_thread); mutex_unlock(&on_irq14); } else if (irq == 15) { - if (wait_irq15) pause(); + if (wait_irq15) ret = wait_on(current_thread); mutex_unlock(&on_irq15); } else { - if (wait_pciirq) pause(); + if (wait_pciirq) ret = wait_on(current_thread); mutex_unlock(&on_pciirq); } exit_critical(st); + + return ret; } // ===================================== // @@ -352,7 +358,7 @@ static uint8_t ide_ata_access(ide_controller_t *c, int direction, } } - return 0; // Easy, isn't it? + return 0; } @@ -413,7 +419,7 @@ static uint8_t ide_atapi_read(ide_controller_t *c, uint8_t drive, uint32_t lba, // (IX): Receiving Data: for (int i = 0; i < numsects; i++) { - ide_wait_irq(c, channel); // Wait for an IRQ. + if (!ide_wait_irq(c, channel)) goto must_terminate; // Wait for an IRQ. ide_prewait_irq(c, channel); err = ide_polling(c, channel, 1); if (err) return err; // Polling and return if error. @@ -423,12 +429,17 @@ static uint8_t ide_atapi_read(ide_controller_t *c, uint8_t drive, uint32_t lba, } // (X): Waiting for an IRQ: - ide_wait_irq(c, channel); + if (!ide_wait_irq(c, channel)) goto must_terminate; // (XI): Waiting for BSY & DRQ to clear: while (ide_read(c, channel, ATA_REG_STATUS) & (ATA_SR_BSY | ATA_SR_DRQ)); - return 0; // Easy, ... Isn't it? + return 0; + +must_terminate: + // TODO : end communication with device... + dbg_printf("TODO (pciide may be stuck)\n"); + return 5; } static uint8_t ide_read_sectors(ide_controller_t *c, uint8_t drive, diff --git a/src/kernel/include/process.h b/src/kernel/include/process.h index 1b4012f..def3397 100644 --- a/src/kernel/include/process.h +++ b/src/kernel/include/process.h @@ -56,23 +56,29 @@ typedef struct process { struct process *parent; struct process *next_child; struct process *children; + + mutex_t lock; } process_t; typedef void* proc_entry_t; // ---- Process creation, deletion, waiting, etc. -// Simple semantics : when a process exits, all its ressources are freed immediately -// except for the process_t that remains attached to the parent process until it does -// a wait() and acknowleges the process' ending -// When a process exits, all the children are orphaned and nobody can wait on them anymore, -// which is a bad thing : a user process must always wait for all its children ! +// Simple semantics : +// - When a process exits, all its ressources are freed immediately +// except for the process_t that remains attached to the parent process until it does +// a wait() and acknowleges the process' ending +// - When a process exits, if it has living children then they are terminated too. (a process +// is seen as a "virtual machine", and spawning sub-process is seen as "sharing some ressources +// it was allocated to create a new VM", therefore removing the ressource allocated to the +// parent implies removing the ressource from its children too) process_t *current_process(); process_t *new_process(process_t *parent); bool start_process(process_t *p, proc_entry_t entry); // maps a region for user stack -void process_exit(process_t *p, int status, int exit_code); // exit current process +void process_exit(process_t *p, int status, int exit_code); // exit specified process (must not be current process) +void current_process_exit(int status, int exit_code); bool process_new_thread(process_t *p, proc_entry_t entry, void* sp); void process_thread_deleted(thread_t *t); // called by threading code when a thread exits diff --git a/src/kernel/include/thread.h b/src/kernel/include/thread.h index 163045d..2d385ea 100644 --- a/src/kernel/include/thread.h +++ b/src/kernel/include/thread.h @@ -5,6 +5,7 @@ #include #include +#define T_STATE_LOADING 0 #define T_STATE_RUNNING 1 #define T_STATE_PAUSED 2 #define T_STATE_FINISHED 3 @@ -34,21 +35,25 @@ typedef struct thread { struct thread *next_in_queue; struct thread *next_in_proc; + + void* waiting_on; + bool must_exit; } thread_t; typedef void (*entry_t)(void*); void threading_setup(entry_t cont, void* data); // never returns -thread_t *new_thread(entry_t entry, void* data); // thread is PAUSED, and must be resume_thread'ed +thread_t *new_thread(entry_t entry, void* data); // thread is PAUSED, and must be started with start_thread +void start_thread(thread_t *t); extern thread_t *current_thread; void yield(); -void pause(); void exit(); void usleep(int usecs); +bool wait_on(void* x); // true : resumed normally, false : resumed because thread was killed, or someone else already waiting -bool resume_thread(thread_t *thread); +bool resume_on(void* x); void kill_thread(thread_t *thread); // cannot be called for current thread // Kernel critical sections diff --git a/src/kernel/user/process.c b/src/kernel/user/process.c index a003143..4514218 100644 --- a/src/kernel/user/process.c +++ b/src/kernel/user/process.c @@ -50,10 +50,26 @@ process_t *new_process(process_t *parent) { proc->last_ran = 0; proc->regions = 0; proc->threads = 0; - proc->pid = (next_pid++); + proc->parent = parent; + proc->children = 0; + proc->next_child = 0; + proc->next_fd = 1; + proc->pid = (next_pid++); proc->status = PS_LOADING; + proc->exit_code = 0; + + proc->lock = MUTEX_UNLOCKED; + + if (parent != 0) { + mutex_lock(&parent->lock); + + proc->next_child = parent->children; + parent->children = proc; + + mutex_unlock(&parent->lock); + } return proc; @@ -111,14 +127,14 @@ bool process_new_thread(process_t *p, proc_entry_t entry, void* sp) { th->proc = p; th->user_ex_handler = proc_user_exception; - { int st = enter_critical(CL_NOSWITCH); // it's a bit complicated to use mutexes on process_t + { mutex_lock(&p->lock); th->next_in_proc = p->threads; p->threads = th; - exit_critical(st); } + mutex_unlock(&p->lock); } - resume_thread(th); + start_thread(th); return true; } @@ -138,41 +154,59 @@ bool start_process(process_t *p, void* entry) { return true; } -void process_exit(process_t *p, int status, int exit_code) { - // --- Make sure we are not running in a thread we are about to kill - +void current_process_exit(int status, int exit_code) { void process_exit_v(void* args) { exit_data_t *d = (exit_data_t*)args; process_exit(d->proc, d->status, d->exit_code); free(d); } - if (current_process() == p) { - exit_data_t *d = (exit_data_t*)malloc(sizeof(exit_data_t)); + exit_data_t *d = (exit_data_t*)malloc(sizeof(exit_data_t)); + + d->proc = current_process();; + d->status = status; + d->exit_code = exit_code; - d->proc = p; - d->status = status; - d->exit_code = exit_code; + worker_push(process_exit_v, d); + exit(); +} + +void process_exit(process_t *p, int status, int exit_code) { + // --- Make sure we are not running in a thread we are about to kill + ASSERT(current_process() != p); - worker_push(process_exit_v, d); - pause(); + // ---- Check we are not killing init process + if (p->parent == 0) { + PANIC("Attempted to exit init process!"); } // ---- Now we can do the actual cleanup - int st = enter_critical(CL_NOSWITCH); + mutex_lock(&p->lock); + ASSERT(p->status == PS_RUNNING || p->status == PS_LOADING); p->status = status; p->exit_code = exit_code; + // neutralize the process while (p->threads != 0) { thread_t *t = p->threads; p->threads = p->threads->next_in_proc; - t->proc = 0; + t->proc = 0; // we don't want process_thread_deleted to be called kill_thread(t); } + // terminate all the children as well and free associated process_t structures + while (p->children != 0) { + process_t *ch = p->children; + p->children = ch->next_child; + + if (ch->status == PS_RUNNING || ch->status == PS_LOADING) + process_exit(ch, PS_KILLED, 0); + free(ch); + } + // release file descriptors void release_fd(void* a, void* fd) { unref_file((fs_handle_t*)fd); @@ -202,29 +236,20 @@ void process_exit(process_t *p, int status, int exit_code) { delete_pagedir(p->pd); p->pd = 0; - // orphan children - while (p->children != 0) { - process_t *c = p->children; - p->children = c->next_child; - - c->parent = 0; - c->next_child = 0; - // TODO : if c was terminated, free it or what ? - } - - if (p->parent == 0) { - free(p); - } else { - // TODO : notify parent + // notify parent + process_t *par = p->parent; + if (par->status == PS_RUNNING) { + // TODO: notify that child is exited } - exit_critical(st); + mutex_unlock(&p->lock); } void process_thread_deleted(thread_t *t) { - int st = enter_critical(CL_NOSWITCH); - process_t *p = t->proc; + + mutex_lock(&p->lock); + if (p->threads == t) { p->threads = t->next_in_proc; } else { @@ -236,10 +261,10 @@ void process_thread_deleted(thread_t *t) { } } + mutex_unlock(&p->lock); + if (p->threads == 0 && p->status == PS_RUNNING) process_exit(p, PS_FINISHED, 0); - - exit_critical(st); } @@ -457,7 +482,7 @@ bool munmap(process_t *proc, void* addr) { static void proc_user_exception(registers_t *regs) { dbg_printf("Usermode exception in user process : exiting.\n"); dbg_dump_registers(regs); - process_exit(current_process(), PS_FAILURE, FAIL_EXCEPTION); + current_process_exit(PS_FAILURE, FAIL_EXCEPTION); } static void proc_usermem_pf(void* p, registers_t *regs, void* addr) { process_t *proc = (process_t*)p; @@ -466,13 +491,13 @@ static void proc_usermem_pf(void* p, registers_t *regs, void* addr) { if (r == 0) { dbg_printf("Segmentation fault in process %d (0x%p : not mapped) : exiting.\n", proc->pid, addr); dbg_dump_registers(regs); - process_exit(current_process(), PS_FAILURE, (addr < (void*)PAGE_SIZE ? FAIL_ZEROPTR : FAIL_SEGFAULT)); + current_process_exit(PS_FAILURE, (addr < (void*)PAGE_SIZE ? FAIL_ZEROPTR : FAIL_SEGFAULT)); } bool wr = ((regs->err_code & PF_WRITE_BIT) != 0); if (wr && !(r->mode & MM_WRITE)) { dbg_printf("Segmentation fault in process %d (0x%p : not allowed to write) : exiting.\n", proc->pid, addr); - process_exit(current_process(), PS_FAILURE, (addr < (void*)PAGE_SIZE ? FAIL_ZEROPTR : FAIL_SEGFAULT)); + current_process_exit(PS_FAILURE, (addr < (void*)PAGE_SIZE ? FAIL_ZEROPTR : FAIL_SEGFAULT)); } bool pr = ((regs->err_code & PF_PRESENT_BIT) != 0); @@ -506,7 +531,7 @@ void probe_for_read(const void* addr, size_t len) { if (r == 0 || addr + len > r->addr + r->size || !(r->mode & MM_READ)) { dbg_printf("Access violation on read at 0x%p len 0x%p in process %d : exiting.\n", addr, len, proc->pid); - process_exit(current_process(), PS_FAILURE, FAIL_SC_SEGFAULT); + current_process_exit(PS_FAILURE, FAIL_SC_SEGFAULT); } } @@ -516,7 +541,7 @@ void probe_for_write(const void* addr, size_t len) { if (r == 0 || addr + len > r->addr + r->size || !(r->mode & MM_WRITE)) { dbg_printf("Access violation on write at 0x%p len 0x%p in process %d : exiting.\n", addr, len, proc->pid); - process_exit(current_process(), PS_FAILURE, FAIL_SC_SEGFAULT); + current_process_exit(PS_FAILURE, FAIL_SC_SEGFAULT); } } diff --git a/src/kernel/user/syscall.c b/src/kernel/user/syscall.c index d9131b2..73b3341 100644 --- a/src/kernel/user/syscall.c +++ b/src/kernel/user/syscall.c @@ -34,7 +34,7 @@ static char* sc_copy_string(uint32_t s, uint32_t slen) { static uint32_t exit_sc(sc_args_t args) { dbg_printf("Proc %d exit with code %d\n", current_process()->pid, args.a); - process_exit(current_process(), PS_FINISHED, args.a); + current_process_exit(PS_FINISHED, args.a); ASSERT(false); return 0; } -- cgit v1.2.3