Nucleus
Barry Virtual Memory page fault handling b0cc93c (3 years, 3 months ago)
/*
* This file contains the page fault handler. The main handler determines what
* went wrong and calls the relevant routine to handle it. That includes:
* non-present reads, non-present writes, copy-on-write.
* There is an early handler which just gives out memory when requested. This
* is used by the kernel before it has initialised multi-tasking and the virtual
* file system.
*/
#include <stdint.h>
#include <string.h>
#include <nucleus/cpu.h>
#include <nucleus/memory.h>
#include <nucleus/task.h>
#include <nucleus/panic.h>
#include "paging.h"
#include "namespace.h"
VMRegion *find_region(uintptr_t addr);
void copy_page_frame(page_t src, page_t dest);
/* Copy on write */
static void
copy_on_write(VMRegion *region, uintptr_t addr)
{
Inode *inode;
Page *page = NULL, *newPage = NULL;
File *front = region->front,
*back = region->back;
off_t offset = (PAGE_ADDR(addr) - region->start) + region->offset;
/* Handle uninitialised anonymous region */
int private = region->flags & MAP_PRIVATE;
int anonymous = (region->flags & MAP_ANONYMOUS);
if (!front && (private || anonymous))
region->front = front = create_anonymous_file();
/* Find original page frame */
if (!page && front) {
inode = front->inode;
page = find_page(inode->pages, offset);
}
if (!page && back) {
inode = back->inode;
page = find_page(inode->pages, offset);
}
ASSERT(page);
/* Copy already happened, or region is shared file */
if (usage(page) == 1 && page->frame != zeroFrame
&& ((front && inode == front->inode) || !private))
return install_page(addr, page, PROT_WRITE);
/* Page is still in use, or in wrong inode for writing */
if (usage(inode) == 1) {
add(front->inode->pages, page);
install_page(addr, page, PROT_WRITE);
remove(inode->pages, page);
} else {
newPage = create_page(front->inode->pages,
alloc_frame(), offset);
install_page(addr, newPage, PROT_WRITE);
copy_page_frame(PAGE_ADDR(page->frame),
PAGE_ADDR(newPage->frame));
}
}
/* Handle a non-present read page fault */
static void
not_present_read(VMRegion *region, uintptr_t addr)
{
Inode *inode;
Page *page = NULL;
File *front = region->front,
*back = region->back;
off_t offset = (PAGE_ADDR(addr) - region->start) + region->offset;
/* Handle uninitialised anonymous regions */
if (!front && (region->flags & MAP_ANONYMOUS))
region->front = front = create_anonymous_file();
/* Attempt to use front */
if (front) {
inode = front->inode;
page = find_page(inode->pages, offset);
if (page)
return install_page(addr, page, PROT_READ);
/* Zero-fill if anonymous */
if (region->flags & MAP_ANONYMOUS) {
page = create_page(inode->pages, zeroFrame, offset);
return install_page(addr, page, PROT_READ);
}
}
/* Use back */
ASSERT(back);
inode = back->inode;
page = find_page(inode->pages, offset);
if (page)
return install_page(addr, page, PROT_READ);
/* Create new block cache entry */
page = create_page(inode->pages, alloc_frame(), offset);
install_page(addr, page, PROT_READ);
file_mmap(back, (void *) PAGE_ADDR(addr), PAGE_SIZE, offset);
}
/* Handle a non-present write page fault */
static void
not_present_write(VMRegion *region, uintptr_t addr)
{
Inode *inode;
Page *page = NULL, *newPage = NULL;
File *front = region->front,
*back = region->back;
off_t offset = (PAGE_ADDR(addr) - region->start) + region->offset;
/* Handle uninitialised anonymous regions */
if (!front && ((region->flags & MAP_PRIVATE)
|| (region->flags & MAP_ANONYMOUS)))
region->front = front = create_anonymous_file();
/* Shared region, write through to back */
if (region->flags & MAP_SHARED) {
if (region->flags & MAP_ANONYMOUS)
back = front;
ASSERT(back);
inode = back->inode;
page = find_page(inode->pages, offset);
if (page)
return install_page(addr, page, PROT_WRITE);
page = create_page(inode->pages, alloc_frame(), offset);
install_page(addr, page, PROT_WRITE);
memset((void *) PAGE_ADDR(addr), 0, PAGE_SIZE);
return;
}
/* Private region, copy to front */
ASSERT(front);
inode = front->inode;
page = find_page(inode->pages, offset);
newPage = create_page(inode->pages, alloc_frame(), offset);
install_page(addr, newPage, PROT_WRITE);
if (page) {
copy_page_frame(PAGE_ADDR(page->frame),
PAGE_ADDR(newPage->frame));
remove(inode->pages, page);
return;
}
/* Anonymous region, zero-fill */
if (region->flags & MAP_ANONYMOUS) {
memset((void *) PAGE_ADDR(addr), 0, PAGE_SIZE);
return;
}
/* Use back */
ASSERT(back);
inode = back->inode;
page = find_page(inode->pages, offset);
if (page) {
copy_page_frame(PAGE_ADDR(page->frame),
PAGE_ADDR(newPage->frame));
remove(inode->pages, page);
} else {
file_mmap(back, (void *) PAGE_ADDR(addr), PAGE_SIZE, offset);
}
}
/* Page fault handler */
void
page_fault_handler(struct InterruptFrame *frame, uint32_t err)
{
uintptr_t addr;
asm volatile("mov %%cr2, %0" : "=r" (addr));
uint8_t present = err & (1 << 0);
uint8_t write = err & (1 << 1);
uint8_t user = err & (1 << 2);
/* Iterate VM Regions */
VMRegion *region = find_region(addr);
/* Not in a region */
if (__builtin_expect(!region, 0)) {
page_t pg = get_page(addr);
panic("Page Fault [%d:%d] (%#.8x -> %#.8x [tbl:%d, pg:%d][%#.8x], %s, %s, %s)",
current->tgid, current->tid, frame->eip,
addr, (addr >> 12) / 1024, (addr >> 12) % 1024, pg,
present ? "present" : "not present",
write ? "write" : "read",
user ? "user" : "kernel");
}
if (user && write && !(region->prot & PROT_WRITE))
panic("Segmentation violation");
if (present && write)
return copy_on_write(region, addr);
if (write)
return not_present_write(region, addr);
else
return not_present_read(region, addr);
}
/* Early (pre-VFS/tasking) page fault handler */
void
early_page_fault_handler(struct InterruptFrame *frame, uint32_t err)
{
uintptr_t addr;
asm volatile("mov %%cr2, %0" : "=r" (addr));
uint8_t present = err & (1 << 0);
uint8_t write = err & (1 << 1);
uint8_t user = err & (1 << 2);
if (!PAGE_ADDR(addr))
panic("Null dereference @ %#.8x (CPU#%d)", frame->eip, CPUID);
ASSERT(!present);
kprintf("Allocating frame for %#.8x [%#.8x] CPU#%d",
addr, frame->eip,CPUID);
/* Allocate a page */
set_page(addr, alloc_frame() | PTE_PRESENT | PTE_WRITE | PTE_GLOBAL);
}