BarryServer : Git

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

// Related

Nucleus

Barry Kernel threads + threads share address space 6217f0d (3 years, 1 month 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 <nucleus/cpu.h>
#include <nucleus/kernel.h>
#include <nucleus/lib.h>
#include <nucleus/memory.h>
#include <nucleus/task.h>
#include "namespace.h"

extern page_t zeroFrame;

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

	/* Determine what to do */
	if (usage(page) > 1 || page->frame == zeroFrame) {
		/* Copy page */
		newPage = create_page(front->inode->pages,
		                      alloc_frame(), offset);
		copy_page_frame(PAGE_ADDR(page->frame),
		                PAGE_ADDR(newPage->frame));
		remove(inode->pages, page);
		page = newPage;
	} else if (back && inode == back->inode && private) {
		/* Page in wrong inode for write */
		add(front->inode->pages, page);
		remove(inode->pages, page);
	}
	return install_page(addr, page, region->prot);
}

/* 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, region->prot);
		/* 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, region->prot);
		page = create_page(inode->pages, alloc_frame(), offset);
		install_page(addr, page, region->prot);
		/* Zero-fill if anonymous, otherwise read */
		if (region->flags & MAP_ANONYMOUS)
			memset((void *) PAGE_ADDR(addr), 0, PAGE_SIZE);
		else
			file_mmap(back, (void *) PAGE_ADDR(addr),
		                  PAGE_SIZE, offset);
		return;
	}

	/* Private region, copy to front */
	ASSERT(front);
	inode = front->inode;
	page = find_page(inode->pages, offset);
	if (page)
		return install_page(addr, page, region->prot);
	newPage = create_page(inode->pages, alloc_frame(), offset);
	install_page(addr, newPage, region->prot);

	/* 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)
{
	uintptr_t addr;
	asm volatile("mov %%cr2, %0" : "=r" (addr));
	uint8_t present = frame->err & (1 << 0);
	uint8_t write   = frame->err & (1 << 1);
	uint8_t user    = frame->err & (1 << 2);
	page_t pg = get_page(addr);

	ASSERT(current && current->vm);

	/* Handle lazy invalidation */
	if (!present && (pg & PTE_PRESENT))
		return flush_tlb(addr);
	if (write && (pg & PTE_WRITE))
		return flush_tlb(addr);

	/* Iterate VM Regions */
	VMRegion *region = find_region(addr);
	if (__builtin_expect(!region, 0)) {
		/* Not in a region */
		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");
	}

	/* Protection violation, kill process */
	if (user && write && !(region->prot & PROT_WRITE))
		panic("Segmentation violation : %#.8x[%#.8x] (%#.8x -> %#.8x)",
		      region, region->prot, frame->eip, addr);

	/* Update paging structures correctly */
	if (present && write)
		return copy_on_write(region, addr);
	if (!present && write)
		return not_present_write(region, addr);
	if (!present && !write)
		return not_present_read(region, addr);
}