diff --git a/kernel/include/isr.h b/kernel/include/isr.h index 4684542..aff983d 100644 --- a/kernel/include/isr.h +++ b/kernel/include/isr.h @@ -6,8 +6,8 @@ // Interrupt frame (ie. what's on the stack on interrupt) struct isr_int_frame { // Pushed by OS - uint32_t esp, ebx, ecx, edx, esi, edi, ebp; - uint32_t ds, es, fs, gs, eax; + uint32_t gs, fs, es, ds; + uint32_t edi, esi, ebp, esp, ebx, edx, ecx, eax; uint32_t interrupt, error; // Pushed by CPU @@ -20,6 +20,7 @@ void isr_register(uint8_t i, isr_handler_ptr handler); int isr_handle_interrupt(struct isr_int_frame iframe); // Assembly definitions (isr.s) +extern void isr_return(void); extern void* isr_redirect_table[48]; // ISR Table extern void isr128(); // int 0x80 - syscall diff --git a/kernel/include/paging.h b/kernel/include/paging.h index cdf9b77..40ee8de 100644 --- a/kernel/include/paging.h +++ b/kernel/include/paging.h @@ -31,7 +31,7 @@ void pag_register_temp_frame(uint32_t target); uint32_t pag_temp_frame(void); void pag_map(uint32_t vaddr, uint32_t paddr, uint32_t flags); uint32_t pag_umap(uint32_t vaddr); -void* pag_virt_to_phys(uint32_t vaddr); +uint32_t pag_virt_to_phys(uint32_t vaddr); uint32_t* pag_get_pagedir(); void pag_set_pagedir(uint32_t* pagedir); void pag_sync_pagedir(); diff --git a/kernel/include/pmm.h b/kernel/include/pmm.h index e1f8f95..27d3225 100644 --- a/kernel/include/pmm.h +++ b/kernel/include/pmm.h @@ -7,7 +7,7 @@ extern bitmap_t pmm_bitmap; int pmm_init(void); -void* pmm_alloc(void); -void pmm_free(void* page); +uint32_t pmm_alloc(void); +void pmm_free(uint32_t page); #endif diff --git a/kernel/include/task.h b/kernel/include/task.h new file mode 100644 index 0000000..4183618 --- /dev/null +++ b/kernel/include/task.h @@ -0,0 +1,72 @@ +#ifndef _TASK_H +#define _TASK_H + +#include + +#define TASK_KERNEL 0 // Main kernel task ID + +enum { + TASK_STATE_DEAD, + TASK_STATE_IDLE, + TASK_STATE_READY, +}; + +/* + * Each task has an ID. This is used as a reference to the task. + * A task has it's own paging setup, so it must have it's own pagedir. + * Tasks have state. These states are defined as follows: + * TASK_STATE_DEAD - This task has been killed + * TASK_STATE_READY - This task is ready for scheduling + * TASK_STATE_IDLE - This task doesn't need processor time + * Each task has it's own kernel stack to push registers and such to when + * context switching. To switch to a task, you load kstack_pointer into ESP and + * pop the state. + * The kstack_bottom is loaded into ESP using the TSS when switching from user + * to kernel space on interrupts. + * Tasks are stored in a linked list, so it must have a reference to the next + * task. + * + * WARN: Be careful when modifing this struct, it's contents are accessed from + * task.s which uses hardcoded offsets. + */ +struct task { + size_t id; + + size_t kstack_pointer; + size_t kstack_bottom; + + uint32_t* pagedir; + uint8_t state; + int kernel_task; + + size_t heap_start; + size_t heap_end; + + struct task* next; +}; + +// Describes contents of the kstack when task switching +struct task_switch_ctx { + uint32_t edi; + uint32_t esi; + uint32_t ebx; + uint32_t ebp; + uint32_t eip; +}; + +extern struct task* task_first; +extern struct task* task_current; + +int task_init(void); +void task_create(size_t id, uint32_t eip, int kernel_task, uint32_t* pagedir, int argc, char** argv); +void task_kill(size_t id); +void task_set_user_heap(struct task* task, size_t heap_end); +struct task* task_get(size_t id); +uint8_t task_get_state(size_t id); +void task_set_state(size_t id, uint8_t state); +size_t task_create_id(void); + +// Defined in task.s +extern void task_switch(struct task* old, struct task* new); + +#endif diff --git a/kernel/src/isr.s b/kernel/src/isr.s index 512f204..f1141e1 100644 --- a/kernel/src/isr.s +++ b/kernel/src/isr.s @@ -3,37 +3,41 @@ isr_common: ;; Push registers to the int_frame_t struct - push eax - push gs - push fs - push es + + pushad + ;; push eax + ;; push ecx + ;; push edx + ;; push ebx + ;; push esp + ;; push ebp + ;; push esi + ;; push edi push ds - push ebp - push edi - push esi - push edx - push ecx + push es + push fs + push gs + + ;; Load kernel data segment push ebx - push esp + mov bx, 0x10 + mov ds, bx + mov es, bx + mov fs, bx + mov gs, bx + pop ebx [extern isr_handle_interrupt] call isr_handle_interrupt -isr_return: - add esp, 4 ; Keep ESP - pop ebx - pop ecx - pop edx - pop esi - pop edi - pop ebp - pop ds - pop es - pop fs +[global isr_return] +isr_return: ; Also used by task switching pop gs - add esp, 4 ; Keep EAX - add esp, 8 ; Pop interupt number and error code - - iretd ; Pop CS, EIP & EFLAGS (Also SS & ESP if it's from userspace) + pop fs + pop es + pop ds + popad + add esp, 8 ; Pop interupt number and error code + iretd ; Pop CS, EIP & EFLAGS (Also SS & ESP if it's from userspace) %macro ISR_WITH_ERRNO 1 isr%1: diff --git a/kernel/src/kernel.c b/kernel/src/kernel.c index 871a2c5..4ffd116 100644 --- a/kernel/src/kernel.c +++ b/kernel/src/kernel.c @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -31,6 +32,7 @@ void kmain(void* ptr, uint32_t magic) { tty_puts("kern.\n\n"); if (timer_init() != 0) panic("Failed to initialize timer."); + if (task_init() != 0) panic("Failed to initialize tasking."); // TODO: Scheduler // TODO: Syscalls diff --git a/kernel/src/paging.c b/kernel/src/paging.c index a6d2ada..93b8bfd 100644 --- a/kernel/src/paging.c +++ b/kernel/src/paging.c @@ -68,7 +68,7 @@ void pag_map(uint32_t vaddr, uint32_t paddr, uint32_t flags) { // Allocate a pagetab if it didn't exist if (!(pagedir[pdi] & PAGE_FLAG_PRESENT)) { - uint32_t tabaddr = pmm_bitmap.ready ? (uint32_t)pmm_alloc() : pag_temp_frame(); + uint32_t tabaddr = pmm_bitmap.ready ? pmm_alloc() : pag_temp_frame(); pagedir[pdi] = tabaddr | PAGE_FLAG_PRESENT | PAGE_FLAG_WRITE | PAGE_FLAG_OWNER | flags; @@ -125,14 +125,14 @@ uint32_t pag_umap(uint32_t vaddr) { uint32_t pde = pagedir[pdi]; if (remove && pde & PAGE_FLAG_OWNER) { uint32_t tabaddr = PHYS_ADDR(pde); - pmm_free((void*)tabaddr); + pmm_free(tabaddr); pagedir[pdi] = 0; } invalidate(vaddr); // Free the page frame - if (pte & PAGE_FLAG_OWNER) pmm_free((void*)PHYS_ADDR(pte)); + if (pte & PAGE_FLAG_OWNER) pmm_free(PHYS_ADDR(pte)); if (prevpd != NULL) { pag_sync_pagedir(); // Sync it to other pagedirs @@ -142,13 +142,13 @@ uint32_t pag_umap(uint32_t vaddr) { return pte; } -void* pag_virt_to_phys(uint32_t vaddr) { +uint32_t pag_virt_to_phys(uint32_t vaddr) { uint32_t pdi = vaddr >> 22; uint32_t pti = vaddr >> 12 & 0x3FF; uint32_t* pagetab = PAGETAB(pdi); - return (void*)((pagetab[pti] & ~0xFFF) + ((uint32_t)vaddr & 0xFFF)); + return (pagetab[pti] & ~0xFFF) + ((uint32_t)vaddr & 0xFFF); } uint32_t* pag_get_pagedir() { @@ -224,11 +224,11 @@ void pag_free_pagedir(uint32_t* pagedir) { for (size_t j = 0; j < 1024; j++) { uint32_t pte = pagetab[j]; - if (pte & PAGE_FLAG_OWNER) pmm_free((void*)PHYS_ADDR(pte)); + if (pte & PAGE_FLAG_OWNER) pmm_free(PHYS_ADDR(pte)); } memset(pagetab, 0, PAGE_SIZE); - if (pde & PAGE_FLAG_OWNER) pmm_free((void*)PHYS_ADDR(pde)); + if (pde & PAGE_FLAG_OWNER) pmm_free(PHYS_ADDR(pde)); pd[i] = 0; } diff --git a/kernel/src/pmm.c b/kernel/src/pmm.c index bb8f6b2..de0d316 100644 --- a/kernel/src/pmm.c +++ b/kernel/src/pmm.c @@ -83,19 +83,19 @@ int pmm_init(void) { return 0; } -void* pmm_alloc(void) { - if (pmm_bitmap.blocks - pmm_bitmap.used == 0) return NULL; +uint32_t pmm_alloc(void) { + if (pmm_bitmap.blocks - pmm_bitmap.used == 0) return 0; size_t base = bmap_get_area(&pmm_bitmap, 1, 0); - if (base == BMAP_NOT_FOUND) return NULL; + if (base == BMAP_NOT_FOUND) return 0; bmap_set(&pmm_bitmap, base, 1); - return bmap_ptr(&pmm_bitmap, base); + return (uint32_t)bmap_ptr(&pmm_bitmap, base); } -void pmm_free(void* page) { - size_t base = (size_t)page / BLOCK_SIZE; +void pmm_free(uint32_t page) { + size_t base = page / BLOCK_SIZE; bmap_set_area(&pmm_bitmap, base, 1, 0); bmap_set(&pmm_bitmap, 0, 1); } diff --git a/kernel/src/task.c b/kernel/src/task.c new file mode 100644 index 0000000..139e313 --- /dev/null +++ b/kernel/src/task.c @@ -0,0 +1,247 @@ +#include +#include +#include +#include +#include +#include +#include + +#include + +struct task* task_first; +struct task* task_dummy; +struct task* task_current; + +int task_init(void) { + task_first = kzalloc(sizeof(*task_first)); + task_dummy = kzalloc(sizeof(*task_dummy)); + if (task_first == NULL || task_dummy == NULL) return 1; + + + // The first task is what's running right now @~@ + task_current = task_first; + task_current->id = TASK_KERNEL; + task_current->state = TASK_STATE_READY; + task_current->pagedir = pag_get_pagedir(); + task_current->kernel_task = 1; + + debugf("[task] Multitasking initiated.\n"); + + return 0; +} + +void task_create(size_t id, uint32_t eip, int kernel_task, uint32_t* pagedir, int argc, char** argv) { + __asm__ volatile ("cli"); // Make sure we don't get interrupted + + uint32_t kstack = (uint32_t)kmalloc(0x1000 - 16) + (0x1000 - 16); + uint8_t* kesp = (uint8_t*)kstack; + + kesp -= sizeof(struct isr_int_frame); + struct isr_int_frame* trap = (struct isr_int_frame*)kesp; + memset(trap, 0, sizeof(*trap)); + + uint16_t codesel = kernel_task ? GDT_KERNEL_CODE : GDT_USER_CODE; + uint16_t datasel = kernel_task ? GDT_KERNEL_DATA : GDT_USER_DATA; + + trap->cs = codesel; + trap->ds = datasel; + trap->es = datasel; + trap->fs = datasel; + trap->gs = datasel; + + trap->user_ss = datasel; + trap->user_esp = USER_STACK; + + trap->eflags = 0x200; // Enable interrupts + trap->eip = eip; + + kesp -= sizeof(struct isr_int_frame); + struct task_switch_ctx* ctx = (struct task_switch_ctx*)kesp; + ctx->edi = 0; + ctx->esi = 0; + ctx->ebx = 0; + ctx->ebp = 0; + + ctx->eip = (uint32_t)isr_return; + + struct task* current = task_first; + while (current != NULL) { + if (current->next == NULL) break; + current = current->next; + } + + struct task* new = kzalloc(sizeof(*new)); + current->next = new; + + new->kstack_bottom = kstack; + new->kstack_pointer = (size_t)kesp; + new->id = id; + new->kernel_task = kernel_task; + new->state = TASK_STATE_READY; + new->pagedir = pagedir; + new->heap_start = USER_HEAP; + new->heap_end = USER_HEAP; + + // We don't need to setup a stack frame + if (kernel_task) { + __asm__ volatile ("sti"); + return; + } + + void* old_pagedir = pag_get_pagedir(); + pag_set_pagedir(new->pagedir); + + // Setup argc, argv & environ + size_t arg_len = 0; + for (int i = 0; i < argc; i++) arg_len += strlen(argv[i]) + 1; + size_t page_count = dceil(4 + (argc * 4) + 10 + arg_len, PAGE_SIZE); + + for (size_t i = 0; i < page_count; i++) { + uint32_t paddr = pmm_alloc(); + if (paddr == 0) { // Not enough memory + // TODO: Do pmm_free() for each page + kfree(new); + kfree((void*)kstack); + pag_set_pagedir(old_pagedir); + return; + } + pag_map(USER_STACK + i * PAGE_SIZE, (uint32_t)paddr, 0); + } + + int* argc_ptr = (int*)USER_STACK; + char** argv_ptr = (char**)(USER_STACK + 4); + *argc_ptr = argc; + + size_t cur = 0; + for (int i = 0; i < argc; i++) { + size_t len = strlen(argv[i]); + char* ptr = (char*)((size_t)argv_ptr + argc * 4 + 4 + cur); + + memcpy(ptr, argv[i], len); + ptr[len] = '\0'; + + argv_ptr[i] = ptr; + cur += len + 1; + } + + pag_set_pagedir(old_pagedir); +} + +void task_kill(size_t id) { + __asm__ volatile ("cli"); + + struct task* current = task_first; + while (current != NULL) { + if (current->next && current->next->id == id) break; + current = current->next; + } + + struct task* task = current->next; + if (task == NULL || task->state == TASK_STATE_DEAD) return; + current->next = task->next; + + void* kstack = (void*)(task->kstack_bottom + 0x1000 - 16); + kfree(kstack); + + uint32_t* old_pagedir = pag_get_pagedir(); + pag_set_pagedir(task->pagedir); + size_t heap_start = dceil(task->heap_start, PAGE_SIZE); + size_t heap_end = dceil(task->heap_end, PAGE_SIZE); + + if (heap_end > heap_start) { + size_t num = heap_end - heap_start; + + for (size_t i = 0; i < num; i++) { + uint32_t vaddr = heap_start * PAGE_SIZE + i * PAGE_SIZE; + uint32_t paddr = pag_virt_to_phys(vaddr); + pmm_free(paddr); + } + } + + pag_set_pagedir(old_pagedir); + pag_free_pagedir(task->pagedir); + + // Workaround so we don't free a task we're in + if (task_current == task) { + memset(task_dummy, 0, sizeof(*task_dummy)); + task_current = task_dummy; + } + kfree(task); + + // schedule(); + __asm__ volatile ("sti"); +} + +void task_set_user_heap(struct task* task, size_t heap_end) { + if (heap_end <= task->heap_start) { + debugf("[task] Task %d tried to shrink heap bellow 0.\n", task->id); + task_kill(task->id); + return; + } + + int old_page_top = dceil(task->heap_end, PAGE_SIZE); + int new_page_top = dceil(heap_end, PAGE_SIZE); + + if (new_page_top == old_page_top) return; + else if (new_page_top > old_page_top) { // Grow + int num = new_page_top - old_page_top; + + for (size_t i = 0; i < num; i++) { + uint32_t paddr = (uint32_t)pmm_alloc(); + if (paddr == 0) { + debugf("[task] Task %d tried to grow heap too much.\n", task->id); + task_kill(task->id); + } + uint32_t vaddr = old_page_top * PAGE_SIZE + i * PAGE_SIZE; + pag_map(vaddr, paddr, PAGE_FLAG_USER); + memset((void*)vaddr, 0, PAGE_SIZE); + } + } else { // Shrink + int num = old_page_top - new_page_top; + + for (size_t i = 0; i < num; i++) { + uint32_t vaddr = old_page_top * PAGE_SIZE + i * PAGE_SIZE; + uint32_t paddr = pag_virt_to_phys(vaddr); + pmm_free(paddr); + pag_umap(vaddr); + } + } + + task->heap_end = heap_end; +} + +struct task* task_get(size_t id) { + struct task* find = task_first; + while (find != NULL) { + if (find->id == id) break; + find = find->next; + } + + return find; +} + +uint8_t task_get_state(size_t id) { + struct task* t = task_get(id); + if (t == NULL) return TASK_STATE_DEAD; + return t->state; +} + +void task_set_state(size_t id, uint8_t state) { + struct task* t = task_get(id); + if (t == NULL) return; + t->state = state; +} + +size_t task_create_id(void) { + struct task* current = task_first; + + size_t max = current->id; + current = current->next; + while (current != NULL) { + if (max < current->id) max = current->id; + current = current->next; + } + + return max + 1; +} + diff --git a/kernel/src/task.s b/kernel/src/task.s new file mode 100644 index 0000000..850ff29 --- /dev/null +++ b/kernel/src/task.s @@ -0,0 +1,35 @@ +bits 32 + +;; Prototype: +;; void switch_context(struct task* old, struct task* new); +;; Description: +;; Pushes register state onto the old tasks kstack and pops the new state. +global task_switch +task_switch: + mov eax, [esp + 4] ; eax = struct task* old + mov edx, [esp + 8] ; ebx = struct task* nex + + ;; Push registers that weren't pushed by the CDECL call + push ebp + push ebx + push esi + push edi + + ;; Swap the stack pointers + mov [eax + 4], esp ; old->kstack_pointer = ESP + mov esp, [edx + 4] ; ESP = new->kstack_pointer + + ;; Change the pagedir + mov eax, [edx + 12] ; EAX = new->pagedir + sub eax, 0xC0000000 ; Convert to physical address + mov cr3, eax + + ;; Pop register that weren't pushed by the CDECL call + pop edi + pop esi + pop ebx + pop ebp + + ;; This return will pop task_switch_ctx.eip + ret +