diff --git a/kernel.c b/kernel.c index 017bc81..1bbaaae 100644 --- a/kernel.c +++ b/kernel.c @@ -54,7 +54,10 @@ void read_binary_table() // create a new process for each binary found // it should have around 4kb stack - create_new_process(binary_table+i, 1<<12); + optional_pcbptr res = create_new_process(binary_table+i, 1<<12); + if (has_error(res)) { + dbgln("Error creating initial process!", 31); + } } } diff --git a/kernel.h b/kernel.h index 3c4a2cd..0fcb60e 100644 --- a/kernel.h +++ b/kernel.h @@ -4,12 +4,11 @@ #define true 1 #define false 0 -#define XLEN 32 // 32 bit system -#define PROCESS_COUNT 32 // number of concurrent processes -#define NUM_BINARIES 16 // number of binaries loaded simultaneously +#define PROCESS_COUNT 8 // number of concurrent processes +#define NUM_BINARIES 4 // number of binaries loaded simultaneously // scheduler settings -#define TIME_SLICE_LEN 100 // number of cpu time ticks per slice +#define TIME_SLICE_LEN 10 // number of cpu time ticks per slice // init function extern __attribute__((__noreturn__)) void init(); diff --git a/kinclude/boot.S b/kinclude/boot.S index 5ed7767..4f2be0d 100644 --- a/kinclude/boot.S +++ b/kinclude/boot.S @@ -45,7 +45,8 @@ _start: jal init // halt machine after returning from init - csrwi CSR_HALT, 1 + li t0, -1 + csrw CSR_HALT, t0 1: j 1b @@ -162,4 +163,13 @@ memset: addi a1, a1, 32 blt a1, a2, 1b ret -#endif \ No newline at end of file +#endif + +// this is where instantiated threads return to once they are finished +.section .thread_fini +.global thread_finalizer +thread_finalizer: +// just a simple exit syscall + nop + li a7, 5 + ecall \ No newline at end of file diff --git a/kinclude/ecall.c b/kinclude/ecall.c index d01ec4f..4c1b91d 100644 --- a/kinclude/ecall.c +++ b/kinclude/ecall.c @@ -16,11 +16,16 @@ ecall_handler ecall_table[ECALL_TABLE_LEN] = { 0 }; int ecall_handle_spawn_thread(int* args_ptr, ProcessControlBlock* pcb) { - void* entry = (void*) args_ptr[0]; - void* args = (void*) args_ptr[1]; + void* entry = (void*) args_ptr[0]; // a0 + void* args = (void*) args_ptr[1]; // a1 + //int flags = args_ptr[2]; // a2 + optional_pcbptr pcb_or_err = create_new_thread(pcb, entry, args, 1<<14); + + if (has_error(pcb_or_err)) + return pcb_or_err.error; + return EINVAL; -// void* entry, void* args } int ecall_handle_sleep(int* args, ProcessControlBlock* pcb) @@ -38,13 +43,32 @@ int ecall_handle_sleep(int* args, ProcessControlBlock* pcb) int ecall_handle_join(int* args, ProcessControlBlock* pcb) { - return EINVAL; + int pid = args[0]; // a0 + + ProcessControlBlock* target = process_from_pid(pid); + + if (target == NULL) + return ESRCH; + + if (target->status == PROC_DEAD) + return target->exit_code; + + pcb->status = PROC_WAIT_PROC; + pcb->waiting_for_process = target; + + int timeout = args[1]; + if (timeout <= 0) + return 0; + + unsigned int len = (unsigned int) timeout; + pcb->asleep_until = read_time() + len; + + return 0; } int ecall_handle_kill(int* args, ProcessControlBlock* pcb) { return EINVAL; - } int ecall_handle_exit(int* args, ProcessControlBlock* pcb) @@ -52,6 +76,8 @@ int ecall_handle_exit(int* args, ProcessControlBlock* pcb) pcb->status = PROC_DEAD; pcb->exit_code = *args; + dbgln("exit", 4); + char msg[34] = "process exited with code "; itoa(pcb->pid, &msg[8], 10); @@ -59,28 +85,36 @@ int ecall_handle_exit(int* args, ProcessControlBlock* pcb) dbgln(msg, 34); + // recursively kill all child processes + kill_child_processes(pcb); + return 0; } #pragma GCC diagnostic pop -void trap_handle_ecall() { - { - - mark_ecall_entry(); - }; +static void print_num(int num) { + char buff[16]; + char* end = itoa(num, buff, 10); + dbgln(buff, (int)(end - buff)); +} + +void trap_handle_ecall() { + mark_ecall_entry(); ProcessControlBlock* pcb = get_current_process(); int *regs = pcb->regs; - int code = regs[16]; // code is inside a7 + int code = regs[REG_A0 + 7]; // code is inside a7 + + dbgln("ecall:", 6); + print_num(code); // check if the code is too large/small or if the handler is zero if (code < 0 || code > ECALL_TABLE_LEN || ecall_table[code] == 0) { - regs[9] = ENOCODE; - __asm__("ebreak"); + regs[REG_A0] = ENOCODE; } else { // get the corresponding ecall handler ecall_handler handler = ecall_table[code]; - regs[9] = handler(®s[9], pcb); + regs[REG_A0] = handler(®s[REG_A0], pcb); } // increment pc of this process @@ -99,8 +133,8 @@ void trap_handle(int interrupt_bit, int code, int mtval) case 5: case 6: case 7: - scheduler_run_next(); - break; + scheduler_run_next(); + break; default: // impossible HALT(12); @@ -131,7 +165,7 @@ void trap_handle(int interrupt_bit, int code, int mtval) HALT(13); } } - HALT(1); + HALT(14); __builtin_unreachable(); } @@ -146,6 +180,6 @@ void init_ecall_table() void handle_exception(int ecode, int mtval) { - dbgln("Trap encountered!", 17); + dbgln("exception encountered!", 17); __asm__("ebreak"); } diff --git a/kinclude/ecall.h b/kinclude/ecall.h index 51c1aeb..e5e03a2 100644 --- a/kinclude/ecall.h +++ b/kinclude/ecall.h @@ -15,7 +15,7 @@ enum ecall_codes { ECALL_EXIT = 5, }; -#define ECALL_TABLE_LEN 16 +#define ECALL_TABLE_LEN 8 // initializer for ecall lookup table void init_ecall_table(); diff --git a/kinclude/io.c b/kinclude/io.c index 32f2fd2..021d1eb 100644 --- a/kinclude/io.c +++ b/kinclude/io.c @@ -44,13 +44,24 @@ char* itoa (int value, char* str, int base) value *= -1; } - int num; + int digits = 0; + int num = 0; + // reverse number + do { + num = num * base; + num += value % base; + value = value / base; + digits++; + } while (value > 0); + + value = num; do { num = value % base; value = value / base; *str++ = alpha[num]; + digits--; } - while (value > 0); + while (digits > 0); return str; } diff --git a/kinclude/ktypes.h b/kinclude/ktypes.h index 54302f3..0a065e5 100644 --- a/kinclude/ktypes.h +++ b/kinclude/ktypes.h @@ -1,15 +1,18 @@ #ifndef H_ktypes #define H_ktypes +#define NULL ((void*) 0) + /* * Error codes */ enum error_code { - ENOCODE = 1, // invalid syscall code - EINVAL = 2, // invalid argument value - ENOMEM = 3, // not enough memory - ENOBUFS = 4, // no space left in buffer + ENOCODE = 1, // invalid syscall code + EINVAL = 2, // invalid argument value + ENOMEM = 3, // not enough memory + ENOBUFS = 4, // no space left in buffer + ESRCH = 5, // no such process }; /* @@ -37,6 +40,24 @@ struct ProcessControlBlock { ProcessControlBlock* waiting_for_process; struct loaded_binary* binary; unsigned long long int asleep_until; + // parent + ProcessControlBlock* parent; +}; + +enum pcb_struct_registers { + REG_RA = 0, + REG_SP = 1, + REG_GP = 2, + REG_TP = 3, + REG_T0 = 4, + REG_T1 = 5, + REG_t2 = 6, + REG_FP = 7, + REG_S0 = 7, + REG_S1 = 8, + REG_A0 = 9, + REG_S2 = 17, + REG_T3 = 27 }; diff --git a/kinclude/sched.c b/kinclude/sched.c index 9dde2f8..bf22623 100644 --- a/kinclude/sched.c +++ b/kinclude/sched.c @@ -4,10 +4,37 @@ #include "io.h" #include "malloc.h" +// use memset provided in boot.S +extern void memset(int, void*, void*); +// this is the address where threads return to +extern int thread_finalizer; + +static void print_num(int num) { + char buff[16]; + char* end = itoa(num, buff, 10); + dbgln(buff, (int)(end - buff)); +} + +static void print_processes() { + char line[12] = "============"; + char alive[5] = "alive"; + char dead[4] = "dead"; + for (int i = 0; i < PROCESS_COUNT; i++) { + ProcessControlBlock* pcb = processes + i; + print_num(pcb->pid); + if (pcb->status == PROC_DEAD) { + dbgln(dead, 4); + } else { + dbgln(alive, 5); + } + print_num(i); + dbgln(line, 12); + } +} // scheduling data: ProcessControlBlock processes[PROCESS_COUNT]; -ProcessControlBlock* current_process; +ProcessControlBlock* current_process = NULL; unsigned long long int scheduling_interrupted_start; unsigned long long int next_interrupt_scheduled_for; int next_process_id = 1; @@ -15,10 +42,6 @@ int next_process_id = 1; void scheduler_run_next () { current_process = scheduler_select_free(); - char msg[30] = "scheduling "; - char* end = itoa(current_process->pid, &msg[11], 10); - dbgln(msg, ((int) end) - ((int) msg)); - // set up timer interrupt set_next_interrupt(); scheduler_switch_to(current_process); @@ -29,6 +52,7 @@ void scheduler_try_return_to(ProcessControlBlock* pcb) if (pcb->status != PROC_RDY) { scheduler_run_next(); } else { + dbgln("returning to process...", 23); current_process = pcb; // add time spent in ecall handler to the processes time slice next_interrupt_scheduled_for = next_interrupt_scheduled_for + (read_time() - scheduling_interrupted_start); @@ -43,12 +67,18 @@ ProcessControlBlock* scheduler_select_free() int i; int timeout_available = false; // note if a timeout is available + if (current_process == NULL) + current_process = processes + PROCESS_COUNT - 1; + while (true) { mtime = read_time(); + int i = 1; + ProcessControlBlock* pcb = current_process + 1; + if (pcb > processes + PROCESS_COUNT) + pcb = processes; - for (i=0; i < PROCESS_COUNT; i++) { - ProcessControlBlock* pcb = processes + i; - if (pcb->status == PROC_RDY && pcb != current_process) + while (pcb != current_process) { + if (pcb->status == PROC_RDY) return pcb; if (pcb->status == PROC_WAIT_SLEEP) { @@ -68,7 +98,11 @@ ProcessControlBlock* scheduler_select_free() timeout_available = true; } } + pcb++; + if (pcb > processes + PROCESS_COUNT) + pcb = processes; } + if (current_process->status == PROC_RDY) { return current_process; } @@ -127,13 +161,13 @@ void scheduler_switch_to(ProcessControlBlock* pcb) __builtin_unreachable(); } -int scheduler_index_from_pid(int pid) +ProcessControlBlock* process_from_pid(int pid) { for (int i = 0; i < PROCESS_COUNT; i++) { if (processes[i].pid == pid) - return i; + return processes + i; } - return -1; + return NULL; } int* get_current_process_registers() @@ -168,17 +202,19 @@ optional_pcbptr find_available_pcb_slot() { return (optional_pcbptr) { .error = ENOBUFS }; pcb = processes + index; } + index++; + return (optional_pcbptr) { .value = pcb }; } -int create_new_process(loaded_binary* bin, int stack_size) +optional_pcbptr create_new_process(loaded_binary* bin, int stack_size) { // try to get a position in the processes list optional_pcbptr slot_or_err = find_available_pcb_slot(); // if that failed, we cannot creat a new process if (has_error(slot_or_err)) { dbgln("No more process structs!", 24); - return slot_or_err.error; + return slot_or_err; } // allocate stack for the new process @@ -186,7 +222,7 @@ int create_new_process(loaded_binary* bin, int stack_size) // if that failed, we also can't create a new process if (has_error(stack_top_or_err)) { dbgln("Error while allocating stack for process", 40); - return stack_top_or_err.error; + return (optional_pcbptr) { .error = stack_top_or_err.error }; } ProcessControlBlock* pcb = slot_or_err.value; @@ -199,11 +235,75 @@ int create_new_process(loaded_binary* bin, int stack_size) pcb->pid = pid; pcb->pc = bin->entrypoint; pcb->binary = bin; + pcb->parent = NULL; + pcb->asleep_until = 0; + // zero out registers + memset(0, pcb->regs, pcb->regs + 31); // load stack top into stack pointer register - pcb->regs[1] = (int) stack_top_or_err.value; + pcb->regs[REG_SP] = (int) stack_top_or_err.value; // load pid into a0 register - pcb->regs[9] = pid; + pcb->regs[REG_A0] = pid; dbgln("Created new process!", 20); + return (optional_pcbptr) { .value = pcb }; +} + +optional_pcbptr create_new_thread(ProcessControlBlock* parent, void* entrypoint, void* args, int stack_size) +{ + // try to get a position in the processes list + optional_pcbptr slot_or_err = find_available_pcb_slot(); + // if that failed, we cannot creat a new process + if (has_error(slot_or_err)) { + dbgln("No more process structs!", 24); + return slot_or_err; + } + + // allocate stack for the new process + optional_voidptr stack_top_or_err = malloc_stack(stack_size); // allocate 4Kib stack + // if that failed, we also can't create a new process + if (has_error(stack_top_or_err)) { + dbgln("Error while allocating stack for thread", 39); + return (optional_pcbptr) { .error = stack_top_or_err.error }; + } + + ProcessControlBlock* pcb = slot_or_err.value; + + // determine next pid + int pid = next_process_id++; + + // mark process as ready + pcb->status = PROC_RDY; + pcb->pid = pid; + pcb->pc = (int) entrypoint; + pcb->binary = parent->binary; + pcb->parent = parent; + pcb->asleep_until = 0; + // zero out registers + memset(0, pcb->regs, pcb->regs + 31); + // set return address to global thread finalizer + pcb->regs[REG_RA] = (int) &thread_finalizer; + // load stack top into stack pointer register + pcb->regs[REG_SP] = (int) stack_top_or_err.value; + // copy global pointer from parent + pcb->regs[REG_GP] = parent->regs[REG_GP]; + // load args pointer into a0 register + pcb->regs[REG_A0] = (int) args; + + dbgln("Created new thread!", 19); + + return (optional_pcbptr) { .value = pcb }; +} + +void kill_child_processes(ProcessControlBlock* pcb) +{ + for (int i = 0; i < PROCESS_COUNT; i++) { + ProcessControlBlock* proc = processes + i; + if (proc->parent != pcb) + continue; + + proc->status = PROC_DEAD; + proc->exit_code = -9; // set arbitrary exit code + kill_child_processes(proc); + } } diff --git a/kinclude/sched.h b/kinclude/sched.h index a4937fe..50366ab 100644 --- a/kinclude/sched.h +++ b/kinclude/sched.h @@ -9,16 +9,16 @@ extern ProcessControlBlock processes[PROCESS_COUNT]; // scheduler methods ProcessControlBlock* scheduler_select_free(); -int scheduler_create_process(int binid); void set_next_interrupt(); void __attribute__((noreturn)) scheduler_run_next(); void __attribute__((noreturn)) scheduler_try_return_to(ProcessControlBlock*); void __attribute__((noreturn)) scheduler_switch_to(ProcessControlBlock*); -int scheduler_index_from_pid(int pid); +ProcessControlBlock* process_from_pid(int pid); int* get_current_process_registers(); ProcessControlBlock* get_current_process(); void mark_ecall_entry(); -int create_new_process(loaded_binary* bin, int stack_size); - +optional_pcbptr create_new_process(loaded_binary*, int); +optional_pcbptr create_new_thread(ProcessControlBlock*, void*, void*, int); +void kill_child_processes(ProcessControlBlock* pcb); #endif \ No newline at end of file diff --git a/linker.ld b/linker.ld index acd54b5..cfb9805 100644 --- a/linker.ld +++ b/linker.ld @@ -237,4 +237,9 @@ SECTIONS /* End of uninitialized data segment (used by syscalls.c for heap) */ PROVIDE( end = . ); _end = ALIGN(8); + + .thread_fini : + { + *(.thread_fini) + } } diff --git a/package.py b/package.py index 7b57bc5..331e709 100644 --- a/package.py +++ b/package.py @@ -14,7 +14,7 @@ INCLUDE_THESE_SECTIONS = set(( '.text', '.stack', '.bss', '.sdata', '.rdata', '.rodata' '.sbss', '.data', '.stack', '.init', '.fini', '.preinit_array', '.init_array', - '.fini_array', '.rodata' + '.fini_array', '.rodata', '.thread_fini' )) # these sections are empty, so we don't want to read the elf here EMPTY_SECTIONS = set((