BarryServer : Git

All the code for all my projects
// BarryServer : Git / Nucleus / blob / b0cc93c299def7155031fe255095ce906485cd4d / memory / fault.c

// Related

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);
}