From c646404cf0ff8db34d16cf43f1c1049bf78b095c Mon Sep 17 00:00:00 2001 From: Tomas Date: Tue, 6 May 2025 19:29:35 +0200 Subject: [PATCH] PREEMPTIVE MULTITASKING!!!! --- .gitignore | 1 + docs/syscalls.txt | 77 ++++++++++++++++++++++++--------- kernel/include/gdt.h | 96 ++++++++++++++++++++++++------------------ kernel/include/isr.h | 2 +- kernel/include/sched.h | 9 ++++ kernel/include/timer.h | 2 + kernel/src/gdt.c | 40 +++++++----------- kernel/src/isr.c | 3 +- kernel/src/isr.s | 4 +- kernel/src/kernel.c | 34 +++++++++++++-- kernel/src/sched.c | 26 ++++++++++++ kernel/src/task.c | 13 +++--- kernel/src/task.s | 4 +- kernel/src/timer.c | 7 ++- 14 files changed, 215 insertions(+), 103 deletions(-) create mode 100644 kernel/include/sched.h create mode 100644 kernel/src/sched.c diff --git a/.gitignore b/.gitignore index 131199e..b3910c0 100644 --- a/.gitignore +++ b/.gitignore @@ -28,4 +28,5 @@ DS_Store *.swo /.clangd /.cache +/compile_commands.json diff --git a/docs/syscalls.txt b/docs/syscalls.txt index 9fd7fc9..bf21ded 100644 --- a/docs/syscalls.txt +++ b/docs/syscalls.txt @@ -1,25 +1,64 @@ System Calls ============ -Syscall handlers are prefixed by 'sys_'. Syscalls can be called using int 0x80. -Arguments are passed in registers like this: -* EAX - Syscall number -* EBX - Argument 0 -* ECX - Argument 1 -* EDX - Argument 2 +pid_t fork(void); +Creates a child process. -If the syscall returns, the return value will be in EAX. +void exit(int status); +Terminate the calling process. + +pid_t waitpid(pid_t pid); +Wait untill a process changes state. + +int kill(pid_t, int sig); +Send a signal to a process. + +pid_t getpid(void); +Get PID of calling process. + +void sleep(size_t t); +Sleep for **t** clock ticks. + +int exec(char* path, char** argv); +Execute a program. + +int brk(void* addr); +Change data segment size. + +int open(char* path, int flags); +Open and possibly create a file. + +int close(int fd); +Close a file descriptor. + +ssize_t read(int fd, void* buf, size_t count); +Read up to **count** bytes into **buf**. + +ssize_t write(int fd, void* buf, size_t count); +Write up to **count** bytes from **buf**. + +int dup(int oldfd); +Duplicate a file descriptor. + +int pipe(int pipe[2]); +Create a pipe and return it's file descriptors in **pipe**. + +int chdir(char* path); +Change the current directory of calling process. + +int mkdir(char* path); +Create a new directory. + +int mknod(char* path, int maj, int min); +Create a new device file. + +int link(char* f1, char* f2); +Creates another filename (**f2**) for **f1**. + +int unlink(char* path); +Delete a filename. + +int fstat(int fd, struct stat* statbuf); +Return info about a file. -Syscall Table (TODO: Update this) -------------- -| NR | SYSC NAME | RETURN (EAX) | ARG0 (EBX) | ARG1 (ECX) | ARG2 (EDX) | -| 00 | reserved | - | - | - | - | -| 01 | sleep | - | uint ms | - | - | -| 02 | read | int count | int fd | char* buf | uint count | -| 03 | write | int count | int fd | char* buf | uint count | -| 04 | open | int fd | char* path | int oflag | - | -| 05 | close | int status | int fd | - | - | -| 06 | malloc | void* buf | int count | - | - | -| 07 | free | - | void* buf | - | - | -| 08 | seek | #TBD | | | | diff --git a/kernel/include/gdt.h b/kernel/include/gdt.h index bdcc815..7d6365b 100644 --- a/kernel/include/gdt.h +++ b/kernel/include/gdt.h @@ -3,6 +3,10 @@ #include +// Privilege levels +#define DPL_KERNEL 0 +#define DPL_USER 3 + /* * Segment selector explanation (why kcode is 0x08?) * @@ -26,8 +30,11 @@ * s 0 for the GDT and the index is one, because it * is the second entry in our GDT (after null desc.) */ -#define SEGMENT_SEL(_priv, _tab, _i)\ - (uint16_t)(((_i) << 3) | ((_tab) << 2) | (_priv)) +#define SEGMENT_SEL(_idx, _tab, _dpl) (\ + (((_dpl) ) & 3) | \ + (((_tab) << 2) & 4) | \ + (((_idx) << 3) ) \ + ) /* * Entries: @@ -40,21 +47,43 @@ */ #define GDT_ENTRIES 6 #define GDT_NULL SEGMENT_SEL(0, 0, 0) -#define GDT_KERNEL_CODE SEGMENT_SEL(0, 0, 1) -#define GDT_KERNEL_DATA SEGMENT_SEL(0, 0, 2) -#define GDT_USER_CODE SEGMENT_SEL(3, 0, 3) -#define GDT_USER_DATA SEGMENT_SEL(3, 0, 4) -#define GDT_TSS SEGMENT_SEL(0, 0, 5) +#define GDT_KERNEL_CODE SEGMENT_SEL(1, 0, DPL_KERNEL) +#define GDT_KERNEL_DATA SEGMENT_SEL(2, 0, DPL_KERNEL) +#define GDT_USER_CODE SEGMENT_SEL(3, 0, DPL_USER) +#define GDT_USER_DATA SEGMENT_SEL(4, 0, DPL_USER) +#define GDT_TSS SEGMENT_SEL(5, 0, DPL_KERNEL) -#define GDT_DESC(_lim, _base, _acc, _flag) (struct gdt_entry){\ - .limit = (_lim) & 0xffff,\ - .base_low = (uint16_t)(_base),\ - .base_mid = (uint8_t)((_base) >> 16),\ - .access = (_acc),\ - .flags = ((_flag & 0x0f) << 4) | (((_lim) >> 16) & 0x0f),\ - .base_high = (uint8_t)((_base) >> 24),\ +#define GDT_DESC(_lim, _base, _access, _flags) (struct gdt_entry) { \ + .base_low = (_base) & 0xFFFF, \ + .base_mid = ((_base) >> 16) & 0xFF, \ + .base_high = ((_base) >> 24) & 0xFF, \ + .limit = (_lim) & 0xFFFF, \ + .flags = ((_flags) & 0xF0) | (((_lim) >> 16) & 0x0F), \ + .access = (_access) \ } +/* + * GDT descriptor/entry explanation + * + * 0-15 low 16 bits of limit + * 15-31 low 16 bits of base + * 32-39 mid 8 bits of base + * 40-47 access byte + * 40 Accessed + * 41 Read/Write + * 42 Direction/Conforming + * 43 Executable + * 44 Descriptor type + * 45-46 DPL + * 47 Present + * 48-51 high 4 bits of limit + * 52-55 flags + * 52 Reserved + * 53 Long-mode + * 54 Size (0 - 16-bit, 1 - 32-bit) + * 55 Granularity (0 - limit in bytes, 1 - limit in pages) + * 56-63 high 8 bits of base + */ struct gdt_entry { uint16_t limit; uint16_t base_low; @@ -70,40 +99,23 @@ struct gdt_pointer { } __packed; struct tss_pointer { - uint16_t link; - uint16_t __reserved0; + uint16_t prev_task, __reserved0; uint32_t esp0; - uint16_t ss0; - uint16_t __reserved1; + uint16_t ss0, __reserved1; uint32_t esp1; - uint16_t ss1; - uint16_t __reserved2; + uint16_t ss1, __reserved2; uint32_t esp2; - uint16_t ss2; - uint16_t __reserved3; + uint16_t ss2, __reserved3; uint32_t cr3; uint32_t eip; uint32_t eflags; - uint32_t eax; - uint32_t ecx; - uint32_t edx; - uint32_t ebx; - uint32_t esp; - uint32_t ebp; - uint32_t esi; - uint32_t edi; - uint16_t es; - uint16_t __reserved4; - uint16_t cs; - uint16_t __reserved5; - uint16_t ss; - uint16_t __reserved6; - uint16_t ds; - uint16_t __reserved7; - uint16_t fs; - uint16_t __reserved8; - uint16_t gs; - uint16_t __reserved9; + uint32_t eax, ecx, edx, ebx, esp, ebp, esi, edi; + uint16_t es, __reserved4; + uint16_t cs, __reserved5; + uint16_t ss, __reserved6; + uint16_t ds, __reserved7; + uint16_t fs, __reserved8; + uint16_t gs, __reserved9; uint16_t ldtr; uint32_t __reserved10; uint16_t iopb; diff --git a/kernel/include/isr.h b/kernel/include/isr.h index aff983d..98f6b86 100644 --- a/kernel/include/isr.h +++ b/kernel/include/isr.h @@ -22,6 +22,6 @@ 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 +extern void isr128(void); // int 0x80 - syscall #endif diff --git a/kernel/include/sched.h b/kernel/include/sched.h new file mode 100644 index 0000000..a3733eb --- /dev/null +++ b/kernel/include/sched.h @@ -0,0 +1,9 @@ +#ifndef _SCHED_H +#define _SCHED_H + +// Amount of CPU time each process gets (in ms) +#define SCHED_QUANTUM 40 + +void schedule(void); + +#endif diff --git a/kernel/include/timer.h b/kernel/include/timer.h index 872c8a8..d21b0ee 100644 --- a/kernel/include/timer.h +++ b/kernel/include/timer.h @@ -9,6 +9,8 @@ #define PIT_CH2 0x42 #define PIT_CMD 0x43 +#define TIMER_FREQ 1000 + int timer_init(void); void timer_wait(size_t ticks); diff --git a/kernel/src/gdt.c b/kernel/src/gdt.c index 47a415b..073bdad 100644 --- a/kernel/src/gdt.c +++ b/kernel/src/gdt.c @@ -10,14 +10,14 @@ void gdt_reload(void) { __asm__ volatile ( "cli\n" "lgdt (%0)\n" - "ljmp $0x08, $1f\n" - "1:\n" "movw $0x10, %%ax\n" "movw %%ax, %%ds\n" "movw %%ax, %%es\n" "movw %%ax, %%fs\n" "movw %%ax, %%gs\n" "movw %%ax, %%ss\n" + "ljmp $0x08, $1f\n" + "1:" : : "m" (gdtp) : "memory", "rax" @@ -26,38 +26,28 @@ void gdt_reload(void) { void tss_reload(void) { __asm__ volatile ("ltr %0" : : "rm"(GDT_TSS)); - gdt[5].access |= 0x02; } void tss_esp0(uint32_t esp0) { tss.esp0 = esp0; } int gdt_init(void) { - // Null descriptor (0) - gdt[0] = GDT_DESC(0, 0, 0, 0); - - // Kernel code (8) - gdt[1] = GDT_DESC(0xfffff, 0, 0x9a, 0xc); - - // Kernel data (16) - gdt[2] = GDT_DESC(0xfffff, 0, 0x92, 0xc); - - // User code (24) - gdt[3] = GDT_DESC(0xfffff, 0, 0xfa, 0xc); - - // User data (32) - gdt[4] = GDT_DESC(0xfffff, 0, 0xf2, 0xc); - - // TSS (40) - memset(&tss, 0, sizeof(tss)); - gdt[5] = GDT_DESC(sizeof(tss) - 1, (uint32_t)&tss, 0x89, 0x40); - tss.iopb = sizeof(struct tss_pointer); - tss.esp0 = __kernel_stack_top; - tss.ss0 = GDT_KERNEL_DATA; - // GDT Pointer gdtp.limit = sizeof(gdt) - 1; gdtp.base = (uint32_t)&gdt; + // Setup TSS + memset(&tss, 0, sizeof(tss)); + tss.ss0 = GDT_KERNEL_DATA; + tss.iopb = sizeof(struct tss_pointer); + + // Load descriptors + gdt[0] = GDT_DESC(0, 0, 0, 0); // Null descriptor + gdt[1] = GDT_DESC(0xFFFFFFFF, 0, 0x9A, 0xC0); // Kernel code + gdt[2] = GDT_DESC(0xFFFFFFFF, 0, 0x92, 0xC0); // Kernel data + gdt[3] = GDT_DESC(0xFFFFFFFF, 0, 0xFA, 0xC0); // User code + gdt[4] = GDT_DESC(0xFFFFFFFF, 0, 0xF2, 0xC0); // User data + gdt[5] = GDT_DESC(sizeof(tss) - 1, (uint32_t)&tss, 0x89, 0); + gdt_reload(); tss_reload(); diff --git a/kernel/src/isr.c b/kernel/src/isr.c index b1593cb..b891668 100644 --- a/kernel/src/isr.c +++ b/kernel/src/isr.c @@ -89,12 +89,13 @@ void isr_send_eoi(uint8_t irq) { int isr_handle_interrupt(struct isr_int_frame iframe) { if (iframe.interrupt < 32) { // ISRs 0-31 - Exceptions + if (iframe.error) debugf("[kernel] Error code: 0x%08x\n", iframe.error); panic(exceptions[iframe.interrupt]); } else if (iframe.interrupt >= 32 && iframe.interrupt < 48) { // ISRs 32-47 - Hardware interrupts uint8_t irq = iframe.interrupt - 32; - if (irq_handlers[irq]) irq_handlers[irq](&iframe); isr_send_eoi(irq); + if (irq_handlers[irq]) irq_handlers[irq](&iframe); } else if (iframe.interrupt == 128) { // ISR 128 - System call return syscall_handler(&iframe); diff --git a/kernel/src/isr.s b/kernel/src/isr.s index f1141e1..76fbb75 100644 --- a/kernel/src/isr.s +++ b/kernel/src/isr.s @@ -30,14 +30,14 @@ isr_common: [extern isr_handle_interrupt] call isr_handle_interrupt [global isr_return] -isr_return: ; Also used by task switching +isr_return: ; Also used by a newly created task pop gs 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) + iret ; 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 4ffd116..6f15683 100644 --- a/kernel/src/kernel.c +++ b/kernel/src/kernel.c @@ -10,6 +10,8 @@ #include #include +void test(void); + __noreturn void kmain(void* ptr, uint32_t magic) { if (boot_init_mb1(ptr, magic) != 0) panic("Unsupported bootloader."); @@ -20,6 +22,8 @@ void kmain(void* ptr, uint32_t magic) { // System init if (gdt_init() != 0) panic("Failed to initalize GDT."); + debugf("[gdt] Segment selectors: NULL: 0x%04x, KCODE: 0x%04x, KDATA: 0x%04x, UCODE: 0x%04x, UDATA: 0x%04x, TSS: 0x%04x\n", + GDT_NULL, GDT_KERNEL_CODE, GDT_KERNEL_DATA, GDT_USER_CODE, GDT_USER_DATA, GDT_TSS); if (isr_init() != 0) panic("Failed to initalize ISRs."); if (pag_init() != 0) panic("Failed to initalize Paging."); @@ -34,14 +38,36 @@ void kmain(void* ptr, uint32_t magic) { if (timer_init() != 0) panic("Failed to initialize timer."); if (task_init() != 0) panic("Failed to initialize tasking."); - // TODO: Scheduler + // Create a temporary test task + task_create(task_create_id(), (uint32_t)test, 1, pag_get_pagedir(), 0, NULL); + + size_t delay = 0; + while (1) { + delay++; + + if (delay < 0x1FFFFFF) continue; + delay = 0; + + tty_puts("[kernel] Hello!\n"); + } + // TODO: Syscalls // TODO: Interprocess communication - // TODO: Tasking // TODO: Load & run /sbin/init - while (1); - panic("kmain() reached end."); } +// An imaginary second task +void test(void) { + size_t delay = 0; + while (1) { + delay++; + + if (delay < 0x2FFFFFF) continue; + delay = 0; + + tty_puts("[test] Hello!\n"); + } +} + diff --git a/kernel/src/sched.c b/kernel/src/sched.c new file mode 100644 index 0000000..4ef5ab0 --- /dev/null +++ b/kernel/src/sched.c @@ -0,0 +1,26 @@ +#include +#include +#include + +#include + +void schedule(void) { + if (task_first == NULL) return; + + struct task* next = task_current->next; + if (next == NULL) next = task_first; + + while (next->state != TASK_STATE_READY) { + next = next->next; + if (next == NULL) next = task_first; + } + + struct task* old = task_current; + task_current = next; + + tss_esp0(next->kstack_bottom); + + // NOTE: Might not return + task_switch(old, next); +} + diff --git a/kernel/src/task.c b/kernel/src/task.c index 139e313..8fc2a89 100644 --- a/kernel/src/task.c +++ b/kernel/src/task.c @@ -31,7 +31,7 @@ int task_init(void) { } 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 + __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; @@ -55,7 +55,7 @@ void task_create(size_t id, uint32_t eip, int kernel_task, uint32_t* pagedir, in trap->eflags = 0x200; // Enable interrupts trap->eip = eip; - kesp -= sizeof(struct isr_int_frame); + kesp -= sizeof(struct task_switch_ctx); struct task_switch_ctx* ctx = (struct task_switch_ctx*)kesp; ctx->edi = 0; ctx->esi = 0; @@ -82,6 +82,9 @@ void task_create(size_t id, uint32_t eip, int kernel_task, uint32_t* pagedir, in new->heap_start = USER_HEAP; new->heap_end = USER_HEAP; + debugf("[task] NEW! Task #%d - EIP = 0x%08x, ESP = 0x%08x%s\n", + id, eip, kesp, kernel_task ? " (kernel)" : ""); + // We don't need to setup a stack frame if (kernel_task) { __asm__ volatile ("sti"); @@ -91,7 +94,7 @@ void task_create(size_t id, uint32_t eip, int kernel_task, uint32_t* pagedir, in void* old_pagedir = pag_get_pagedir(); pag_set_pagedir(new->pagedir); - // Setup argc, argv & environ + // Setup argc, argv 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); @@ -186,7 +189,7 @@ void task_set_user_heap(struct task* task, size_t heap_end) { 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++) { + for (int 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); @@ -199,7 +202,7 @@ void task_set_user_heap(struct task* task, size_t heap_end) { } else { // Shrink int num = old_page_top - new_page_top; - for (size_t i = 0; i < num; i++) { + for (int 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); diff --git a/kernel/src/task.s b/kernel/src/task.s index 850ff29..6f10a44 100644 --- a/kernel/src/task.s +++ b/kernel/src/task.s @@ -7,8 +7,8 @@ bits 32 global task_switch task_switch: mov eax, [esp + 4] ; eax = struct task* old - mov edx, [esp + 8] ; ebx = struct task* nex - + mov edx, [esp + 8] ; ebx = struct task* new + ;; Push registers that weren't pushed by the CDECL call push ebp push ebx diff --git a/kernel/src/timer.c b/kernel/src/timer.c index afa29d8..5d8b989 100644 --- a/kernel/src/timer.c +++ b/kernel/src/timer.c @@ -1,5 +1,6 @@ #include #include +#include #include #include @@ -19,7 +20,7 @@ void timer_wait(size_t t) { int pit_init(void) { isr_register(0, pit_handler); - pit_phase(0, 2, 1000); + pit_phase(0, 2, TIMER_FREQ); return 0; } @@ -39,8 +40,10 @@ void pit_phase(uint8_t ch, uint8_t mode, uint32_t hz) { } void pit_handler(struct isr_int_frame* iframe) { + (void)iframe; // We must take it as an argument even if we don't use it timer_ticks++; - if (timer_ticks % 1000 == 0) tty_puts("."); + // TODO: This assumes the timer is at 1000 Hz + if (timer_ticks % SCHED_QUANTUM == 0) schedule(); }