#include "kernel.hh" #include "k-apic.hh" #include "k-vmiter.hh" #include "util.h" #include <atomic> // kernel.c // // This is the kernel. // INITIAL PHYSICAL MEMORY LAYOUT // // +-------------- Base Memory --------------+ // v v // +-----+--------------------+----------------+--------------------+---------/ // | | Kernel Kernel | : I/O | App 1 App 1 | App 2 // | | Code + Data Stack | ... : Memory | Code + Data Stack | Code ... // +-----+--------------------+----------------+--------------------+---------/ // 0 0x40000 0x80000 0xA0000 0x100000 0x140000 // ^ // | \___ PROC_SIZE ___/ // PROC_START_ADDR #define PROC_SIZE 0x40000 // initial state only proc ptable[NPROC]; // array of process descriptors // Note that `ptable[0]` is never used. proc* current; // pointer to currently executing proc #define HZ 100 // timer interrupt frequency (interrupts/sec) static std::atomic<unsigned long> ticks; // # timer interrupts so far // Memory state // Information about physical page with address `pa` is stored in // `pages[pa / PAGESIZE]`. In the handout code, each `pages` entry // holds an `refcount` member, which is 0 for free pages. // You can change this as you see fit. pageinfo pages[NPAGES]; [[noreturn]] void schedule(); [[noreturn]] void run(proc* p); void exception(regstate* regs); uintptr_t syscall(regstate* regs); void memshow(); extern void memdump_virtual(x86_64_pagetable* pagetable, const char* name); extern void memdump_virtual_all(void); extern void memdump_physical(void); // kernel_start(command) // Initialize the hardware and processes and start running. The `command` // string is an optional string passed from the boot loader. static void process_setup(pid_t pid, const char* program_name); void kernel_start(const char* command) { // initialize hardware init_hardware(); log_printf("Starting WeensyOS\n"); ticks = 1; init_timer(HZ); // clear screen console_clear(); // (re-)initialize kernel page table map_to_nobody(kernel_pagetable, (uintptr_t)NULL); memory_foreach(kernel_pagetable, PROC_START_ADDR, map_to_kernel_space); map_to_user_space(kernel_pagetable, CONSOLE_ADDR); // set up process descriptors for (pid_t i = 0; i < NPROC; i++) { ptable[i].pid = i; ptable[i].state = P_FREE; } if (command && !program_image(command).empty()) { process_setup(1, command); } else { process_setup(1, "allocator"); process_setup(2, "allocator2"); process_setup(3, "allocator3"); process_setup(4, "allocator4"); } // Switch to the first process using run() run(&ptable[1]); } // kalloc(sz) // Kernel memory allocator. Allocates `sz` contiguous bytes and // returns a pointer to the allocated memory, or `nullptr` on failure. // // The returned memory is initialized to 0xCC, which corresponds to // the x86 instruction `int3` (this may help you debug). You'll // probably want to reset it to something more useful. // // On WeensyOS, `kalloc` is a page-based allocator: if `sz > PAGESIZE` // the allocation fails; if `sz < PAGESIZE` it allocates a whole page // anyway. // // The handout code returns the next allocatable free page it can find. // It checks all pages. (You could maybe make this faster!) void* kalloc(size_t sz) { if (sz > PAGESIZE) { return nullptr; } for (uintptr_t pa = 0; pa != MEMSIZE_PHYSICAL; pa += PAGESIZE) { if (allocatable_physical_address(pa) && !pages[pa / PAGESIZE].used()) { ++pages[pa / PAGESIZE].refcount; memset((void*) pa, 0xCC, PAGESIZE); return (void*) pa; } } return nullptr; } // kfree(kptr) // Free `kptr`, which must have been previously returned by `kalloc`. // If `kptr == nullptr` does nothing. void kfree(void* kptr) { (void) kptr; assert(false); } // process_setup(pid, program_name) // Load application program `program_name` as process number `pid`. // This loads the application's code and data into memory, sets its // %rip and %rsp, gives it a stack page, and marks it as runnable. void process_setup(pid_t pid, const char* program_name) { init_process(&ptable[pid], 0); // initialize process page table x86_64_pagetable *process_pagetable = kalloc_pagetable(); ptable[pid].pagetable = process_pagetable; map_to_nobody(process_pagetable, (uintptr_t)NULL); memory_foreach(process_pagetable, PROC_START_ADDR, map_to_kernel_space); map_to_user_space(process_pagetable, CONSOLE_ADDR); // obtain reference to the program image program_image pgm(program_name); // allocate and map global memory required by loadable segments for (auto seg = pgm.begin(); seg != pgm.end(); ++seg) { for (uintptr_t a = round_down(seg.va(), PAGESIZE); a < seg.va() + seg.size(); a += PAGESIZE) { uintptr_t pa = (uintptr_t) kalloc(PAGESIZE); memory_map(process_pagetable, a, pa, PTE_PWU); } } // initialize data in loadable segments for (auto seg = pgm.begin(); seg != pgm.end(); ++seg) { void *pa = memory_virtual_to_physical(process_pagetable, seg.va()); memset(pa, 0, seg.size()); memcpy(pa, seg.data(), seg.data_size()); } // mark entry point ptable[pid].regs.reg_rip = pgm.entry(); // allocate and map stack segment uintptr_t stack_addr = PROC_START_ADDR + PROC_SIZE * pid - PAGESIZE; uintptr_t pa = (uintptr_t) kalloc(PAGESIZE); memory_map(process_pagetable, stack_addr, pa, PTE_PWU); ptable[pid].regs.reg_rsp = stack_addr + PAGESIZE; // mark process as runnable ptable[pid].state = P_RUNNABLE; } // exception(regs) // Exception handler (for interrupts, traps, and faults). // // The register values from exception time are stored in `regs`. // The processor responds to an exception by saving application state on // the kernel's stack, then jumping to kernel assembly code (in // k-exception.S). That code saves more registers on the kernel's stack, // then calls exception(). // // Note that hardware interrupts are disabled when the kernel is running. void exception(regstate* regs) { // Copy the saved registers into the `current` process descriptor. current->regs = *regs; regs = ¤t->regs; // It can be useful to log events using `log_printf`. // Events logged this way are stored in the host's `log.txt` file. /* log_printf("proc %d: exception %d at rip %p\n", current->pid, regs->reg_intno, regs->reg_rip); */ // Show the current cursor location and memory state // (unless this is a kernel fault). console_show_cursor(cursorpos); if (regs->reg_intno != INT_PF || (regs->reg_errcode & PFERR_USER)) { memshow(); if (TICK_LIMIT != 0 && ticks >= TICK_LIMIT) { memdump_physical(); memdump_virtual_all(); poweroff(); } } // If Control-C was typed, exit the virtual machine. check_keyboard(); // Actually handle the exception. switch (regs->reg_intno) { case INT_IRQ + IRQ_TIMER: ++ticks; lapicstate::get().ack(); schedule(); break; /* will not be reached */ case INT_PF: { // Analyze faulting address and access type. uintptr_t addr = rdcr2(); const char* operation = regs->reg_errcode & PFERR_WRITE ? "write" : "read"; const char* problem = regs->reg_errcode & PFERR_PRESENT ? "protection problem" : "missing page"; if (!(regs->reg_errcode & PFERR_USER)) { panic("Kernel page fault on %p (%s %s)!\n", addr, operation, problem); } console_printf(CPOS(24, 0), 0x0C00, "Process %d page fault on %p (%s %s, rip=%p)!\n", current->pid, addr, operation, problem, regs->reg_rip); current->state = P_BROKEN; break; } default: panic("Unexpected exception %d!\n", regs->reg_intno); } // Return to the current process (or run something else). if (current->state == P_RUNNABLE) { run(current); } else { schedule(); } } // syscall(regs) // System call handler. // // The register values from system call time are stored in `regs`. // The return value, if any, is returned to the user process in `%rax`. // // Note that hardware interrupts are disabled when the kernel is running. int syscall_page_alloc(uintptr_t addr); uintptr_t syscall(regstate* regs) { // Copy the saved registers into the `current` process descriptor. current->regs = *regs; regs = ¤t->regs; // It can be useful to log events using `log_printf`. // Events logged this way are stored in the host's `log.txt` file. /* log_printf("proc %d: syscall %d at rip %p\n", current->pid, regs->reg_rax, regs->reg_rip); */ // Show the current cursor location and memory state. console_show_cursor(cursorpos); memshow(); // If Control-C was typed, exit the virtual machine. check_keyboard(); // Actually handle the exception. switch (regs->reg_rax) { case SYSCALL_PANIC: panic(nullptr); // does not return case SYSCALL_GETPID: return current->pid; case SYSCALL_YIELD: current->regs.reg_rax = 0; schedule(); // does not return case SYSCALL_PAGE_ALLOC: return syscall_page_alloc(current->regs.reg_rdi); default: panic("Unexpected system call %ld!\n", regs->reg_rax); } panic("Should not get here!\n"); } // syscall_page_alloc(addr) // Handles the SYSCALL_PAGE_ALLOC system call. This function // should implement the specification for `sys_page_alloc` // in `u-lib.hh` (but in the handout code, it does not). int syscall_page_alloc(uintptr_t addr) { uintptr_t pa = (uintptr_t) kalloc(PAGESIZE); memory_map(current->pagetable, addr, pa, PTE_PWU); memset(memory_virtual_to_physical(current->pagetable, addr), 0, PAGESIZE); return 0; } // schedule // Pick the next process to run and then run it. // If there are no runnable processes, spins forever. void schedule() { pid_t pid = current->pid; for (unsigned spins = 1; true; ++spins) { pid = (pid + 1) % NPROC; if (ptable[pid].state == P_RUNNABLE) { run(&ptable[pid]); } // If Control-C was typed, exit the virtual machine. check_keyboard(); // If spinning forever, show the memviewer. if (spins % (1 << 12) == 0) { memshow(); log_printf("%u\n", spins); } } } // run(p) // Run process `p`. This involves setting `current = p` and calling // `exception_return` to restore its page table and registers. void run(proc* p) { assert(p->state == P_RUNNABLE); current = p; // Check the process's current pagetable. check_pagetable(p->pagetable); // This function is defined in k-exception.S. It restores the process's // registers then jumps back to user mode. exception_return(p); // should never get here while (true) { } } // memshow() // Draw a picture of memory (physical and virtual) on the CGA console. // Switches to a new process's virtual memory map every 0.25 sec. // Uses `console_memviewer()`, a function defined in `k-memviewer.cc`. void memshow() { static unsigned last_ticks = 0; static int showing = 0; // switch to a new process every 0.25 sec if (last_ticks == 0 || ticks - last_ticks >= HZ / 2) { last_ticks = ticks; showing = (showing + 1) % NPROC; } proc* p = nullptr; for (int search = 0; !p && search < NPROC; ++search) { if (ptable[showing].state != P_FREE && ptable[showing].pagetable) { p = &ptable[showing]; } else { showing = (showing + 1) % NPROC; } } extern void console_memviewer(proc* vmp); console_memviewer(p); if (!p) { console_printf(CPOS(10, 29), 0x0F00, "VIRTUAL ADDRESS SPACE\n" " [All processes have exited]\n" "\n\n\n\n\n\n\n\n\n\n\n"); } }