a lot of cleanup, added pmp

master
Anton Lydike 3 years ago
parent e69cfa8a5a
commit bb13cbeca5

@ -1,23 +1,9 @@
# small makefile for compiling the kernel # small makefile for compiling the kernel
# kernel lib dir
KLIBDIR=kinclude
# object file dir
ODIR=obj
# target dir
TARGET=out
GCC_PREF=riscv32-unknown-elf-
CC=$(GCC_PREF)gcc
OBJDUMP=$(GCC_PREF)objdump
CFLAGS=-I$(KLIBDIR) -MD -mcmodel=medany -Wall -Wextra -pedantic-errors -Wno-builtin-declaration-mismatch
KERNEL_CFLAGS=-nostdlib -T linker.ld
ARCH = rv32im
### Build configuration: ### Build configuration:
# Define the maximum number of running processes # process and binary count
PROCESS_COUNT = 8 PROCESS_COUNT = 8
PACKAGED_BINARY_COUNT = 8
# Define the maximum number of binaries packaged with the kernel # Define the maximum number of binaries packaged with the kernel
PACKAGED_BINARY_COUNT = 4 PACKAGED_BINARY_COUNT = 4
@ -25,17 +11,34 @@ PACKAGED_BINARY_COUNT = 4
# Comment this out if you don't have any text IO device memory mapped # Comment this out if you don't have any text IO device memory mapped
CFLAGS += -DTEXT_IO_ADDR=0xff0000 -DTEXT_IO_BUFLEN=64 CFLAGS += -DTEXT_IO_ADDR=0xff0000 -DTEXT_IO_BUFLEN=64
# Uncomment these to build with only the rv32i standard # If you want to build without any extension, you can uncomment the next line
#CFLAGS += -D__risc_no_ext=1 #CFLAGS += -D__risc_no_ext=1
#ARCH = rv32i # also change this to represent your target RISC-V architecture and extensions
ARCH = rv32im
# Configure if mtime is memory-mapped or inside a CSR: # Configure if mtime is memory-mapped or inside a CSR:
# replace 0xFF11FF22FF33 with the correct address # replace 0xFF11FF22FF33 with the correct address
#CFLAGS += -DTIMECMP_IN_MEMORY=1 -DTIMECMP_MEM_ADDR=0xFF11FF22 #CFLAGS += -DTIMECMP_IN_MEMORY=1 -DTIMECMP_MEM_ADDR=0xFF11FF22
# Set this to the first out-of-bounds memory address
END_OF_USABLE_MEM=0xff0000
### End configuration ### End configuration
CFLAGS += -march=$(ARCH) -DPROCESS_COUNT=$(PROCESS_COUNT) -DNUM_BINARIES=$(PACKAGED_BINARY_COUNT)
# kernel lib dir
KLIBDIR=kinclude
# object file dir
ODIR=obj
# target dir
TARGET=out
GCC_PREF=riscv32-unknown-elf-
CC=$(GCC_PREF)gcc
OBJDUMP=$(GCC_PREF)objdump
CFLAGS+=-I$(KLIBDIR) -MD -mcmodel=medany -Wall -Wextra -pedantic-errors -Wno-builtin-declaration-mismatch -march=$(ARCH)
KERNEL_CFLAGS=-nostdlib -T linker.ld
CFLAGS+=-DPROCESS_COUNT=$(PROCESS_COUNT) -DPACKAGED_BINARY_COUNT=$(PACKAGED_BINARY_COUNT) -DEND_OF_USABLE_MEM=$(END_OF_USABLE_MEM)
# dependencies that need to be built: # dependencies that need to be built:
_DEPS = ecall.c csr.c sched.c io.c malloc.c _DEPS = ecall.c csr.c sched.c io.c malloc.c

@ -4,20 +4,26 @@
#include "sched.h" #include "sched.h"
#include "io.h" #include "io.h"
#include "malloc.h" #include "malloc.h"
#include "csr.h"
void read_binary_table(); void read_binary_table();
void setup_mem_protection();
extern struct process_control_block processes[PROCESS_COUNT];
// this array is populated when the memory image is built, therefore it should // this array is populated when the memory image is built, therefore it should
// resign in a section which is not overwritten with zeros on startup // resign in a section which is not overwritten with zeros on startup
loaded_binary binary_table[NUM_BINARIES] __attribute__ ((section(".data"))); loaded_binary binary_table[PACKAGED_BINARY_COUNT] __attribute__ ((section(".data")));
// access the memset function defined in boot.S
extern void memset(unsigned int, void*, void*); extern void memset(unsigned int, void*, void*);
// access linker symbols:
extern byte* _end, _ftext;
extern void init() extern void init()
{ {
dbgln("Kernel started!", 15); dbgln("Kernel started!", 15);
// setup phsycial memory protection
setup_mem_protection();
// initialize scheduler // initialize scheduler
scheudler_init(); scheudler_init();
// initialize tabel for associating ecall codes with their handlers // initialize tabel for associating ecall codes with their handlers
@ -33,13 +39,13 @@ void read_binary_table()
{ {
char msg[28] = "found bin with id 0 at pos 0"; char msg[28] = "found bin with id 0 at pos 0";
malloc_info info = { struct malloc_info info = {
.allocate_memory_end = (void*) 0xFF0000, .allocate_memory_end = (void*) END_OF_USABLE_MEM,
.allocate_memory_start = (void*) 0 .allocate_memory_start = (void*) 0
}; };
// calculate the end of loaded binaries // calculate the end of loaded binaries
for (int i = 0; i < NUM_BINARIES; i++) { for (int i = 0; i < PACKAGED_BINARY_COUNT; i++) {
if (binary_table[i].binid == 0) if (binary_table[i].binid == 0)
break; break;
@ -56,15 +62,34 @@ void read_binary_table()
// initialize malloc // initialize malloc
malloc_init(&info); malloc_init(&info);
for (int i = 0; i < NUM_BINARIES; i++) { for (int i = 0; i < PACKAGED_BINARY_COUNT; i++) {
if (binary_table[i].binid == 0) if (binary_table[i].binid == 0)
break; break;
// create a new process for each binary found // create a new process for each binary found
// it should have around 4kb stack // it should have around 4kb stack
optional_pcbptr res = create_new_process(binary_table + i, 1 << 12); optional_pcbptr res = create_new_process(binary_table + i);
if (has_error(res)) { if (has_error(res)) {
dbgln("Error creating initial process!", 31); dbgln("Error creating initial process!", 31);
} }
} }
} }
void setup_mem_protection()
{
// this pmp config uses Top-of-Range mode - read more in the privileged spec p.49
// we disallow all access to 0x0-0x100 from user and machine mode
// and all access to 0x100-kernel_end from user mode
// to do this, we must first calculate the kernel bin length
uint32 kernel_bin_len = ((uint32) &_end) - ((uint32) &_ftext);
CSR_WRITE(CSR_PMPADDR, 0x100);
CSR_WRITE(CSR_PMPADDR + 1, 0x100 + kernel_bin_len);
// this contains two pmp configs:
// fields: L A RWX
// pmpcfg0: 0b1_00_01_000 <- disallow RWX from U and M mode
// pmpcfg1: 0b0_00_01_000 <- disallow RWX from U mode
// the resulint number is 0b0000100010001000, hex 0x888
CSR_WRITE(CSR_PMPCFG, 0x888);
}

@ -1,3 +1,4 @@
#include "ktypes.h"
#include "csr.h" #include "csr.h"
#ifdef TIMECMP_IN_MEMORY #ifdef TIMECMP_IN_MEMORY
@ -6,10 +7,10 @@
#error "You set TIMECMP_IN_MEMORY but did not provide a memory addres in TIMECMP_MEM_ADDR!" #error "You set TIMECMP_IN_MEMORY but did not provide a memory addres in TIMECMP_MEM_ADDR!"
#endif #endif
void write_mtimecmp(unsigned long long int mtimecmp) void write_mtimecmp(uint64 mtimecmp)
{ {
unsigned int lo = mtimecmp & 0xffffffff; uint32 lo = mtimecmp & 0xffffffff;
unsigned int hi = mtimecmp >> 32; uint32 hi = mtimecmp >> 32;
__asm__ volatile ( __asm__ volatile (
"li t0, %0\n" "li t0, %0\n"
@ -21,10 +22,10 @@ void write_mtimecmp(unsigned long long int mtimecmp)
#else #else
void write_mtimecmp(unsigned long long int mtimecmp) void write_mtimecmp(uint64 mtimecmp)
{ {
unsigned int lower = mtimecmp & 0xffffffff; uint32 lower = mtimecmp & 0xffffffff;
unsigned int higher = mtimecmp >> 32; uint32 higher = mtimecmp >> 32;
__asm__ volatile ( __asm__ volatile (
"csrw %0, %2\n" "csrw %0, %2\n"

@ -16,6 +16,9 @@
#define CSR_MTIMECMP 0x780 // mtimecmp register for timer interrupts #define CSR_MTIMECMP 0x780 // mtimecmp register for timer interrupts
#define CSR_MTIMECMPH 0x781 // mtimecmph register for timer interrupts #define CSR_MTIMECMPH 0x781 // mtimecmph register for timer interrupts
#define CSR_PMPCFG 0x3A0 // start of physical memory protection config
#define CSR_PMPADDR 0x3B0 // start of pmp addresses
#ifndef CSR_HALT #ifndef CSR_HALT
#define CSR_HALT 0x789 // writing nonzero value here will halt the cpu #define CSR_HALT 0x789 // writing nonzero value here will halt the cpu
#endif #endif
@ -35,9 +38,9 @@
__asm__ ("csrw %0, %1" :: "I"(CSR_HALT), "I"(code)); \ __asm__ ("csrw %0, %1" :: "I"(CSR_HALT), "I"(code)); \
} }
void write_mtimecmp(unsigned long long int mtimecmp); void write_mtimecmp(uint64 mtimecmp);
inline __attribute__((always_inline)) unsigned long long int read_time() inline __attribute__((always_inline)) uint64 read_time()
{ {
unsigned int lower, higher; unsigned int lower, higher;
@ -47,7 +50,7 @@ inline __attribute__((always_inline)) unsigned long long int read_time()
: "=r"(lower), "=r"(higher) : "=r"(lower), "=r"(higher)
: "i"(CSR_TIME), "i"(CSR_TIMEH) : "i"(CSR_TIME), "i"(CSR_TIMEH)
); );
return (unsigned long long) higher << 32 | lower; return (uint64) higher << 32 | lower;
} }
#endif #endif

@ -16,12 +16,15 @@ ecall_handler ecall_table[ECALL_TABLE_LEN] = { 0 };
optional_int ecall_handle_spawn_thread(int* args_ptr, struct process_control_block* pcb) optional_int ecall_handle_spawn_thread(int* args_ptr, struct process_control_block* pcb)
{ {
// args_ptr is a pointer to the pcb->regs field, starting at a0.
// args_ptr[0] is a0, args_ptr[1] is a1, etc.
void* entry = (void*) args_ptr[0]; // a0 void* entry = (void*) args_ptr[0]; // a0
void* args = (void*) args_ptr[1]; // a1 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); // create a new thread
optional_pcbptr pcb_or_err = create_new_thread(pcb, entry, args);
// if an error occured, pass it along to the process
if (has_error(pcb_or_err)) if (has_error(pcb_or_err))
return (optional_int) { .error = pcb_or_err.error }; return (optional_int) { .error = pcb_or_err.error };
@ -30,12 +33,15 @@ optional_int ecall_handle_spawn_thread(int* args_ptr, struct process_control_blo
optional_int ecall_handle_sleep(int* args, struct process_control_block* pcb) optional_int ecall_handle_sleep(int* args, struct process_control_block* pcb)
{ {
// read len from a0
int len = args[0]; int len = args[0];
// only allow sleeping for positive intervals
if (len < 0) { if (len < 0) {
return (optional_int) { .error = EINVAL }; return (optional_int) { .error = EINVAL };
} }
// if a positive interval is given, calculate the wakeup time
if (len > 0) { if (len > 0) {
pcb->asleep_until = read_time() + len; pcb->asleep_until = read_time() + len;
pcb->status = PROC_WAIT_SLEEP; pcb->status = PROC_WAIT_SLEEP;
@ -46,28 +52,35 @@ optional_int ecall_handle_sleep(int* args, struct process_control_block* pcb)
optional_int ecall_handle_join(int* args, struct process_control_block* pcb) optional_int ecall_handle_join(int* args, struct process_control_block* pcb)
{ {
int pid = args[0]; // a0 int pid = args[0]; // read pid from processes a0 register
// find the referenced process
struct process_control_block* target = process_from_pid(pid); struct process_control_block* target = process_from_pid(pid);
if (target == NULL) if (target == NULL)
return (optional_int) { .error = ESRCH }; return (optional_int) { .error = ESRCH };
// if the process is dead, join can return immediately
if (target->status == PROC_DEAD) if (target->status == PROC_DEAD)
return (optional_int) { .value = target->exit_code }; return (optional_int) { .value = target->exit_code };
// mark the current process as waiting for the target process
pcb->status = PROC_WAIT_PROC; pcb->status = PROC_WAIT_PROC;
pcb->waiting_for_process = target; pcb->waiting_for_process = target;
// check if a valid timeout was passed in register a1
int timeout = args[1]; int timeout = args[1];
if (timeout <= 0) if (timeout <= 0)
return (optional_int) { .value = 0 }; return (optional_int) { .value = 0 };
// set the asleep_until field
unsigned int len = (unsigned int) timeout; unsigned int len = (unsigned int) timeout;
pcb->asleep_until = read_time() + len; pcb->asleep_until = read_time() + len;
// here we can return whatever value we want, as it is overwritten when
// the process is awoken again
return (optional_int) { .value = 0 }; return (optional_int) { .value = 0 };
} }
@ -100,6 +113,7 @@ optional_int ecall_handle_exit(int* args, struct process_control_block* pcb)
pcb->status = PROC_DEAD; pcb->status = PROC_DEAD;
pcb->exit_code = *args; pcb->exit_code = *args;
// print a message if debugging is enabled
if (DEBUGGING) { if (DEBUGGING) {
char msg[34] = "process exited with code "; char msg[34] = "process exited with code ";
@ -119,10 +133,12 @@ optional_int ecall_handle_exit(int* args, struct process_control_block* pcb)
void trap_handle_ecall() void trap_handle_ecall()
{ {
// save current clock so we don't waste too much process time
mark_ecall_entry(); mark_ecall_entry();
// get the current process
struct process_control_block* pcb = get_current_process(); struct process_control_block* pcb = get_current_process();
int *regs = pcb->regs; int *regs = pcb->regs;
int code = regs[REG_A0 + 7]; // code is stored inside a7 int code = regs[REG_A0 + 7]; // syscall code is stored inside a7
// check if the code is too large/small or if the handler is zero // check if the code is too large/small or if the handler is zero
if (code < 0 || code > ECALL_TABLE_LEN || ecall_table[code] == NULL) { if (code < 0 || code > ECALL_TABLE_LEN || ecall_table[code] == NULL) {
@ -154,7 +170,7 @@ void trap_handle(int interrupt_bit, int code, int mtval)
scheduler_run_next(); scheduler_run_next();
break; break;
default: default:
// impossible // any other interrupt is not supported currently
HALT(12); HALT(12);
break; break;
} }
@ -187,6 +203,8 @@ void trap_handle(int interrupt_bit, int code, int mtval)
__builtin_unreachable(); __builtin_unreachable();
} }
// this writes the function pointers to the ecall table
// it's called from the kernels init() function
void init_ecall_table() void init_ecall_table()
{ {
ecall_table[ECALL_SPAWN] = ecall_handle_spawn_thread; ecall_table[ECALL_SPAWN] = ecall_handle_spawn_thread;
@ -196,6 +214,8 @@ void init_ecall_table()
ecall_table[ECALL_EXIT] = ecall_handle_exit; ecall_table[ECALL_EXIT] = ecall_handle_exit;
} }
// this exception handler is crude and just kills off any process who
// causes an exception.
void handle_exception(int ecode, int mtval) void handle_exception(int ecode, int mtval)
{ {
// kill off offending process // kill off offending process

@ -1,23 +1,33 @@
#include "io.h" #include "io.h"
// this file should only be used for debugging purposes. Most functions here
// could easily corrupt kernel memory if used with untrusted input.
// ALWAYS check your buffer lengths!
#ifdef TEXT_IO_ADDR #ifdef TEXT_IO_ADDR
// this function writes a string to the TEXT_IO buffer
// it adds a newline at the end and splits the passed string into smaller chunks
// if it is larger than TEXT_IO_BUFLEN.
void dbgln(char* text, int len) void dbgln(char* text, int len)
{ {
// if the passed text is longer than TEXT_IO_BUFLEN, print it in chunks
while (len > TEXT_IO_BUFLEN) { while (len > TEXT_IO_BUFLEN) {
dbgln(text, TEXT_IO_BUFLEN); dbgln(text, TEXT_IO_BUFLEN);
text += TEXT_IO_BUFLEN; text += TEXT_IO_BUFLEN;
len -= TEXT_IO_BUFLEN; len -= TEXT_IO_BUFLEN;
} }
// this is the address of the textIO
char* ioaddr = (char*) TEXT_IO_ADDR + 4; char* ioaddr = (char*) TEXT_IO_ADDR + 4;
// write message bytewise to buffer (this could be implemented faster)
for (int i = 0; i < len; i++) { for (int i = 0; i < len; i++) {
if (*text == 0) if (*text == 0)
break; break;
*ioaddr++ = *text++; *ioaddr++ = *text++;
} }
// add a newline
if (len < TEXT_IO_BUFLEN) if (len < TEXT_IO_BUFLEN)
*ioaddr = '\n'; *ioaddr = '\n';
@ -28,13 +38,16 @@ void dbgln(char* text, int len)
/* alphabet for itoa */ /* alphabet for itoa */
char alpha[16] = "0123456789abcdef"; char alpha[16] = "0123456789abcdef";
// convert int to str
char* itoa(int value, char* str, int base) char* itoa(int value, char* str, int base)
{ {
// fail on unknown base
if (base > 16 || base < 2) { if (base > 16 || base < 2) {
*str++ = '?'; *str++ = '?';
return str; return str;
} }
// handle negative numbers
if (value < 0) { if (value < 0) {
*str++ = '-'; *str++ = '-';
value *= -1; value *= -1;
@ -51,6 +64,7 @@ char* itoa(int value, char* str, int base)
digits++; digits++;
} while (value > 0); } while (value > 0);
// write reversed number to the buffer
value = num; value = num;
do { do {
num = value % base; num = value % base;

@ -1,8 +1,14 @@
#ifndef H_ktypes #ifndef H_ktypes
#define H_ktypes #define H_ktypes
// define the nullpointer
#define NULL ((void*) 0) #define NULL ((void*) 0)
// types with explicit bit-widths
typedef unsigned int uint32;
typedef unsigned long long uint64;
typedef unsigned char byte;
/* /*
* Error codes * Error codes
*/ */

@ -5,7 +5,7 @@
#include "io.h" #include "io.h"
// information about the systems memory layout is stored here // information about the systems memory layout is stored here
static malloc_info global_malloc_info = { 0 }; static struct malloc_info global_malloc_info = { 0 };
// this pointer points to the end of unused memory // this pointer points to the end of unused memory
static void* allocate_memory_end; static void* allocate_memory_end;
// this stack holds currently unused program stacks // this stack holds currently unused program stacks
@ -22,8 +22,11 @@ void stash_stack(void* stack_top)
} }
} }
void malloc_init(malloc_info* given_info) // this function is called by the kernels init() function after it parsed the
// list of loaded binaries and calculated the available memory for general allocation
void malloc_init(struct malloc_info* given_info)
{ {
// save the passed info
global_malloc_info = *given_info; global_malloc_info = *given_info;
allocate_memory_end = given_info->allocate_memory_end; allocate_memory_end = given_info->allocate_memory_end;
allocate_memory_end = (void*) (((int) allocate_memory_end) - PROCESS_COUNT); allocate_memory_end = (void*) (((int) allocate_memory_end) - PROCESS_COUNT);
@ -53,6 +56,8 @@ optional_voidptr malloc_stack()
return (optional_voidptr) { .value = stack_top }; return (optional_voidptr) { .value = stack_top };
} }
// a stack is not returned to the general memory pool (since EMBARK has no such
// pool) but rather just added to the pool of free process stacks.
void free_stack(void* stack_top) void free_stack(void* stack_top)
{ {
stash_stack(stack_top); stash_stack(stack_top);

@ -3,14 +3,14 @@
#include "ktypes.h" #include "ktypes.h"
typedef struct malloc_info { struct malloc_info {
void* allocate_memory_end; void* allocate_memory_end;
void* allocate_memory_start; void* allocate_memory_start;
} malloc_info; };
optional_voidptr malloc_stack(); optional_voidptr malloc_stack();
void free_stack(void* stack_top); void free_stack(void* stack_top);
void malloc_init(malloc_info* info); void malloc_init(struct malloc_info* info);
#endif #endif

@ -15,8 +15,8 @@ struct process_control_block processes[PROCESS_COUNT];
// pointer to the currently scheduled process // pointer to the currently scheduled process
struct process_control_block* current_process = NULL; struct process_control_block* current_process = NULL;
// timer variables to add kernel time back to the processes time slice // timer variables to add kernel time back to the processes time slice
unsigned long long int scheduling_interrupted_start; uint64 scheduling_interrupted_start;
unsigned long long int next_interrupt_scheduled_for; uint64 next_interrupt_scheduled_for;
// this counter generates process ids // this counter generates process ids
int next_process_id = 1; int next_process_id = 1;
@ -60,7 +60,7 @@ void scheduler_try_return_to(struct process_control_block* pcb)
// select a new process to run next // select a new process to run next
struct process_control_block* scheduler_select_free() struct process_control_block* scheduler_select_free()
{ {
unsigned long long int mtime; uint64 mtime;
int timeout_available = 0; // note if a timeout is available int timeout_available = 0; // note if a timeout is available
while (1) { while (1) {
@ -122,6 +122,7 @@ struct process_control_block* scheduler_select_free()
} }
} }
// performs the context switch from kernel to userspace mode
void scheduler_switch_to(struct process_control_block* pcb) void scheduler_switch_to(struct process_control_block* pcb)
{ {
CSR_WRITE(CSR_MEPC, pcb->pc); CSR_WRITE(CSR_MEPC, pcb->pc);
@ -167,6 +168,7 @@ void scheduler_switch_to(struct process_control_block* pcb)
__builtin_unreachable(); __builtin_unreachable();
} }
// get a PCB from a pid
struct process_control_block* process_from_pid(int pid) struct process_control_block* process_from_pid(int pid)
{ {
for (int i = 0; i < PROCESS_COUNT; i++) { for (int i = 0; i < PROCESS_COUNT; i++) {
@ -186,6 +188,7 @@ struct process_control_block* get_current_process()
return current_process; return current_process;
} }
// this method sets up the mtimecmp register to trigger the next timer interrupt
void set_next_interrupt() void set_next_interrupt()
{ {
next_interrupt_scheduled_for = read_time() + TIME_SLICE_LEN; next_interrupt_scheduled_for = read_time() + TIME_SLICE_LEN;
@ -197,14 +200,20 @@ void mark_ecall_entry()
scheduling_interrupted_start = read_time(); scheduling_interrupted_start = read_time();
} }
// this function selects an unused entry in the processes list
// it tries to select slots which have been unused the longest
optional_pcbptr find_available_pcb_slot() optional_pcbptr find_available_pcb_slot()
{ {
// this method loops over the process list using an index which persists
// over multiple function calls, wrapping when it reaches the end. This
// makes free space selection relatively fair.
static int index = 0; static int index = 0;
int start_index = index; int start_index = index;
struct process_control_block* pcb = processes + index; struct process_control_block* pcb = processes + index;
while (pcb->status != PROC_DEAD) { while (pcb->status != PROC_DEAD) {
index = (index + 1) % PROCESS_COUNT; index = (index + 1) % PROCESS_COUNT;
// if we iterated over the whole list and found nothing, we have no space left!
if (index == start_index) if (index == start_index)
return (optional_pcbptr) { .error = ENOBUFS }; return (optional_pcbptr) { .error = ENOBUFS };
pcb = processes + index; pcb = processes + index;
@ -214,9 +223,9 @@ optional_pcbptr find_available_pcb_slot()
return (optional_pcbptr) { .value = pcb }; return (optional_pcbptr) { .value = pcb };
} }
optional_pcbptr create_new_process(loaded_binary* bin, int stack_size) optional_pcbptr create_new_process(loaded_binary* bin)
{ {
// try to get a position in the processes list // try to get an unused entry in the processes list
optional_pcbptr slot_or_err = find_available_pcb_slot(); optional_pcbptr slot_or_err = find_available_pcb_slot();
// if that failed, we cannot creat a new process // if that failed, we cannot creat a new process
@ -226,7 +235,7 @@ optional_pcbptr create_new_process(loaded_binary* bin, int stack_size)
} }
// allocate stack for the new process // allocate stack for the new process
optional_voidptr stack_top_or_err = malloc_stack(stack_size); // allocate 4Kib stack optional_voidptr stack_top_or_err = malloc_stack(); // allocate stack
// if that failed, we also can't create a new process // if that failed, we also can't create a new process
if (has_error(stack_top_or_err)) { if (has_error(stack_top_or_err)) {
@ -259,9 +268,9 @@ optional_pcbptr create_new_process(loaded_binary* bin, int stack_size)
return (optional_pcbptr) { .value = pcb }; return (optional_pcbptr) { .value = pcb };
} }
optional_pcbptr create_new_thread(struct process_control_block* parent, void* entrypoint, void* args, int stack_size) optional_pcbptr create_new_thread(struct process_control_block* parent, void* entrypoint, void* args)
{ {
// try to get a position in the processes list // try to get an unused entry in the processes list
optional_pcbptr slot_or_err = find_available_pcb_slot(); optional_pcbptr slot_or_err = find_available_pcb_slot();
// if that failed, we cannot creat a new process // if that failed, we cannot creat a new process
@ -271,7 +280,7 @@ optional_pcbptr create_new_thread(struct process_control_block* parent, void* en
} }
// allocate stack for the new process // allocate stack for the new process
optional_voidptr stack_top_or_err = malloc_stack(stack_size); // allocate 4Kib stack optional_voidptr stack_top_or_err = malloc_stack(); // allocate stack
// if that failed, we also can't create a new process // if that failed, we also can't create a new process
if (has_error(stack_top_or_err)) { if (has_error(stack_top_or_err)) {

@ -20,8 +20,8 @@ struct process_control_block* get_current_process();
void mark_ecall_entry(); void mark_ecall_entry();
// process creation / destruction // process creation / destruction
optional_pcbptr create_new_process(loaded_binary*, int); optional_pcbptr create_new_process(loaded_binary*);
optional_pcbptr create_new_thread(struct process_control_block*, void*, void*, int); optional_pcbptr create_new_thread(struct process_control_block*, void*, void*);
void destroy_process(struct process_control_block* pcb); void destroy_process(struct process_control_block* pcb);
void kill_child_processes(struct process_control_block* pcb); void kill_child_processes(struct process_control_block* pcb);
#endif #endif

@ -14,17 +14,8 @@ SECTOR_SIZE = 512
# start address # start address
MEM_START = 0x100 MEM_START = 0x100
# this is the name of the global variable holding the list of loaded binaries # address where the userspace binaries should be located (-1 to start directly after the kernel)
KERNEL_BINARY_TABLE = 'binary_table' USR_BIN_START = -1
# loaded_binary struct size (4 integers)
KERNEL_BINARY_TABLE_ENTRY_SIZE = 4 * 4
# overwrite this function to generate the entries for the loaded binary list
def create_loaded_bin_struct(binid: int, entrypoint: int, start: int, end: int):
"""
Creates the binary data to populate the KERNEL_BINARY_TABLE structs
"""
return b''.join(num.to_bytes(4, 'little') for num in (binid, entrypoint, start, end))
## end of config ## end of config
@ -40,7 +31,23 @@ EMPTY_SECTIONS = set((
'.bss', '.sbss', '.stack' '.bss', '.sbss', '.stack'
)) ))
# this is the name of the global variable holding the list of loaded binaries
KERNEL_BINARY_TABLE = 'binary_table'
# loaded_binary struct size (4 integers)
KERNEL_BINARY_TABLE_ENTRY_SIZE = 4 * 4
# overwrite this function to generate the entries for the loaded binary list
def create_loaded_bin_struct(binid: int, entrypoint: int, start: int, end: int):
"""
Creates the binary data to populate the KERNEL_BINARY_TABLE structs
"""
return b''.join(num.to_bytes(4, 'little') for num in (binid, entrypoint, start, end))
def overlaps(p1, l1, p2, l2) -> bool: def overlaps(p1, l1, p2, l2) -> bool:
"""
check if the intervals (p1, p1+l1) and (p2, p2+l2) overlap
"""
return (p1 <= p2 and p1 + l1 > p2) or (p2 <= p1 and p2 + l2 > p1) return (p1 <= p2 and p1 + l1 > p2) or (p2 <= p1 and p2 + l2 > p1)
class Section: class Section:
@ -202,6 +209,9 @@ def package(kernel: str, binaries: List[str], out: str):
img.putBin(kernel) img.putBin(kernel)
if USR_BIN_START > 0:
img.seek(USR_BIN_START)
binid = 0 binid = 0
for bin_name in binaries: for bin_name in binaries:
img.align(8) # align to eight bytes img.align(8) # align to eight bytes

@ -11,5 +11,5 @@ spawn:
thread: thread:
$(CC) $(CFLAGS) -o threads threads.c $(CC) $(CFLAGS) -o threads threads.c
all: simple spawn threads all: simple spawn thread

@ -110,22 +110,6 @@ char* itoa(int value, char* str, int base)
return str; return str;
} }
void wrap_main()
{
dbgln("start", 5);
register int code asm ("s1") = main();
dbgln("end", 3);
__asm__ __volatile__ (
"mv a0, s1\n"
"li a7, 5\n"
"ecall\n"
"ebreak\n"
);
}
void _start() void _start()
{ {
__asm__ __volatile__ ( __asm__ __volatile__ (
@ -134,6 +118,10 @@ void _start()
" la gp, _gp\n" " la gp, _gp\n"
".option pop\n" ".option pop\n"
); );
__asm__ __volatile__ (
"mv a0, %0\n"
"li a7, 5\n"
"ecall\n" :: "r"(main())
);
wrap_main();
} }

@ -13,6 +13,7 @@ int main()
return 1; return 1;
} }
struct optional_int t2 = spawn(thread, &arg2); struct optional_int t2 = spawn(thread, &arg2);
if (has_error(t2)) { if (has_error(t2)) {
__asm__ ("ebreak"); __asm__ ("ebreak");
return 2; return 2;
@ -36,6 +37,7 @@ int thread(void* args)
{ {
// read value // read value
int arg = *((int*) args); int arg = *((int*) args);
//char buff[64] = "sleeping for "; //char buff[64] = "sleeping for ";
//char* end = itoa(arg, &buff[13], 10); //char* end = itoa(arg, &buff[13], 10);
@ -118,12 +120,10 @@ void _start()
" la gp, _gp\n" " la gp, _gp\n"
".option pop\n" ".option pop\n"
); );
main();
register int code asm ("s1") = main();
__asm__ __volatile__ ( __asm__ __volatile__ (
"mv a0, s1\n"
"li a7, 5\n" "li a7, 5\n"
"ecall\n" "ecall\n"
"ebreak\n"
); );
__builtin_unreachable();
} }

@ -26,7 +26,11 @@ char* itoa(int value, char* str, int base);
int thread(void* args); int thread(void* args);
__attribute__((naked)) struct optional_int spawn(int (*target)(void*), void* args) { // ignore unused parameter errors only for these functions
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-parameter"
__attribute__((naked)) struct optional_int spawn(int (*target)(void*), void* args)
{
__asm__ ( __asm__ (
"li a7, 1\n" "li a7, 1\n"
"ecall\n" "ecall\n"
@ -35,7 +39,8 @@ __attribute__((naked)) struct optional_int spawn(int (*target)(void*), void* arg
__builtin_unreachable(); __builtin_unreachable();
} }
__attribute__((naked)) struct optional_int join(int pid, int timeout) { __attribute__((naked)) struct optional_int join(int pid, int timeout)
{
__asm__ ( __asm__ (
"li a7, 3\n" "li a7, 3\n"
"ecall\n" "ecall\n"
@ -45,7 +50,8 @@ __attribute__((naked)) struct optional_int join(int pid, int timeout) {
} }
__attribute__((naked)) struct optional_int sleep(int timeout) { __attribute__((naked)) struct optional_int sleep(int timeout)
{
__asm__ ( __asm__ (
"li a7, 2\n" "li a7, 2\n"
"ecall\n" "ecall\n"
@ -53,3 +59,4 @@ __attribute__((naked)) struct optional_int sleep(int timeout) {
); );
__builtin_unreachable(); __builtin_unreachable();
} }
#pragma GCC diagnostic pop

Loading…
Cancel
Save