BarryServer : Git

All the code for all my projects
// BarryServer : Git / Orion / commit / d41a53cbc7d055b1c00cf0a339dbed6925f4f02c / mem / pagefault.c

// Related

Orion

Barry Importing existing Orion kernel d41a53c (2 years, 4 months ago)
diff --git a/mem/pagefault.c b/mem/pagefault.c
new file mode 100644
index 0000000..14219b7
--- /dev/null
+++ b/mem/pagefault.c
@@ -0,0 +1,264 @@
+/*
+ * This is the page fault handler.  It handles/dispatches all handlers for page
+ * faults.  This includes various tasking functions.
+ */
+
+#include <stdint.h>
+#include <signal.h>
+#include "paging.h"
+#include "../vfs/cache.h"
+#include "../vfs/inode.h"
+#include "../vfs/tmpfs/fs.h"
+#include "../mem/heap.h"
+#include "../mem/mem.h"
+#include "../task/task.h"
+#include "../proc/proc.h"
+#include "../screen.h"
+
+extern size_t numFrames, usedFrames;
+
+void copy_page_frame(void *src, void *dest);
+
+/* Copy-On-Write */
+static void
+copy_on_write(VMRegion *region, uintptr_t addr)
+{
+	Page *page = NULL;
+	File *front = region->front,
+	     *back  = region->back;
+	off_t offset = ((addr & ~0xFFF) - region->start) + region->offset;
+	page_t *pg = get_page((void *) addr);
+
+	/* Create front if it doesn't exist and is needed */
+	uint8_t private = region->flags & MAP_PRIVATE;
+	uint8_t sharedanon = (region->flags & MAP_SHARED) &&
+	                     (region->flags & MAP_ANONYMOUS);
+	uint8_t created = 0;
+	if (!front && (private || sharedanon)) {
+		/*
+		 * A private mapping will always write to the front.  A shared
+		 * mapping will write to the back.  If a shared mapping is
+		 * anonymous, then the back is the front.  The front must be
+		 * created if it is required - which means if the mapping is
+		 * private, or if the mapping is shared & anonymous.
+		 */
+		front = kmalloc(sizeof(File));
+		front->inode = inode_get(kmalloc(sizeof(Inode)));
+		front->ops = &tmpfsFileOps;
+		region->front = file_get(front);
+		created++;
+	}
+
+	/* Find original page frame */
+	Inode *inode;
+	if (!page && front) {
+		inode = front->inode;
+		ASSERT(inode);
+		page = page_find(inode, offset);
+	}
+	if (!page && back) {
+		inode = back->inode;
+		ASSERT(inode);
+		page = page_find(inode, offset);
+	}
+	ASSERT(page);
+
+	/* Copy already happened, just link */
+	if (page->usage == 1 && page->frame != zeroFrame) {
+		*pg |= PTE_WRITE;
+		return;
+	}
+	/* Put that page, and create a new one */
+	*pg = 0;
+	alloc_page(pg, PTE_PRESENT | PTE_USER | PTE_WRITE, -1);
+	copy_page_frame((void *) PG_ADDR(page->frame),
+	                (void *) PG_ADDR(*pg));
+	page_remove(inode, page);
+	page = page_create(front->inode, PG_ADDR(*pg), offset);
+}
+
+/* Handle a not-present read page fault */
+static void
+not_present_read(VMRegion *region, uintptr_t addr)
+{
+	Page *page;
+	File *front = region->front,
+	     *back  = region->back;
+	off_t offset = ((addr & ~0xFFF) - region->start) + region->offset;
+	page_t *pg = get_page((void *) addr);
+
+	/* Handle uninitialised anonymous regions */
+	if (!front && (region->flags & MAP_ANONYMOUS)) {
+		front = kmalloc(sizeof(File));
+		front->inode = inode_get(kmalloc(sizeof(Inode)));
+		front->ops = &tmpfsFileOps;
+		region->front = file_get(front);
+	}
+
+	/* Attempt to use front */
+	if (front) {
+		page = page_find(front->inode, offset);
+		if (page) {
+			page_get(page);
+			alloc_page(pg, PTE_PRESENT | PTE_USER, page->frame);
+			return;
+		}
+		if (region->flags & MAP_ANONYMOUS) {
+			/* Must be anonymous, zero-fill */
+			alloc_page(pg, PTE_PRESENT | PTE_USER, zeroFrame);
+			page_create(front->inode, zeroFrame, offset);
+			return;
+		}
+	}
+
+	/* Use back */
+	ASSERT(back);
+	page = page_find(back->inode, offset);
+	if (page) {
+		page_get(page);
+		alloc_page(pg, PTE_PRESENT | PTE_USER, page->frame);
+		return;
+	}
+	/* Create new block cache entry */
+	alloc_page(pg, PTE_PRESENT | PTE_USER, -1);
+	file_mmap(back, (void *) PG_ADDR(addr), 0x1000, offset);
+	page_create(back->inode, PG_ADDR(*pg), offset);
+}
+
+/* Handle a not-present write page fault */
+static void
+not_present_write(VMRegion *region, uintptr_t addr)
+{
+	Page *page = NULL;
+	File *front = region->front,
+	     *back  = region->back;
+	off_t offset = ((addr & ~0xFFF) - region->start) + region->offset;
+	page_t *pg = get_page((void *) addr);
+
+	/* Handle uninitialised anonymous regions */
+	if (!front && ((region->flags & MAP_PRIVATE)
+	 || (region->flags & MAP_ANONYMOUS))) {
+		/*
+		 * This applies to all private regions, anonymous or not.
+		 * Unless the region is shared, the process should write to the
+		 * front, which will be the private copy.  If the region is
+		 * shared, and also anonymous, then the write will occur to the
+		 * front too.
+		 */
+		front = kmalloc(sizeof(File));
+		front->inode = inode_get(kmalloc(sizeof(Inode)));
+		front->ops = &tmpfsFileOps;
+		region->front = file_get(front);
+	}
+
+	/* Shared region, write-through to back */
+	if (region->flags & MAP_SHARED) {
+		if (region->flags & MAP_ANONYMOUS)
+			back = front;
+		ASSERT(back);
+		page = page_find(back->inode, offset);
+		if (page) {
+			page_get(page);
+			alloc_page(pg, PTE_PRESENT | PTE_USER | PTE_WRITE,
+			           page->frame);
+			return;
+		}
+		*pg = 0;
+		alloc_page(pg, PTE_PRESENT | PTE_USER | PTE_WRITE, -1);
+		memset((void *) PG_ADDR(addr), 0, 0x1000);
+		page_create(back->inode, PG_ADDR(*pg), offset);
+		return;
+	}
+
+	/* Private region, copy to front */
+	alloc_page(pg, PTE_PRESENT | PTE_USER | PTE_WRITE, -1);
+	if (front)
+		page = page_find(front->inode, offset);
+	if (page) {
+		copy_page_frame((void *) PG_ADDR(page->frame),
+		                (void *) PG_ADDR(*pg));
+		page_remove(front->inode, page);
+		page_create(front->inode, PG_ADDR(*pg), offset);
+		return;
+	}
+
+	/* Anonymous region, zero-fill */
+	if (region->flags & MAP_ANONYMOUS) {
+		memset((void *) PG_ADDR(addr), 0, 0x1000);
+		page_create(front->inode, PG_ADDR(*pg), offset);
+		return;
+	}
+
+	/* Use back */
+	ASSERT(back);
+	page = page_find(back->inode, offset);
+	if (page) {
+		copy_page_frame((void *) PG_ADDR(page->frame),
+		                (void *) PG_ADDR(*pg));
+		page_remove(back->inode, page);
+	} else {
+		file_mmap(back, (void *) PG_ADDR(addr), 0x1000, offset);
+	}
+	page_create(front->inode, PG_ADDR(*pg), offset);
+}
+
+/* Page fault handler */
+void
+page_fault_handler(InterruptFrame *frame)
+{
+	uintptr_t addr;
+	asm volatile("mov %%cr2, %0" : "=r" (addr));
+	uint8_t present = frame->errCode & (1 << 0);
+	uint8_t write   = frame->errCode & (1 << 1);
+	uint8_t user    = frame->errCode & (1 << 2);
+
+	/* Iterate VM Regions */
+	VMRegion *region;
+	for (region = current->vm->regions; region; region = region->next) {
+		if (region->start <= addr && region->end > addr)
+			break;
+	}
+	if (!region && current->stack) {
+		region = current->stack;
+		if (region->start > addr || region->end <= addr)
+			region = NULL;
+	}
+	if (!region && current->tls) {
+		region = current->tls;
+		if (region->start > addr || region->end <= addr)
+			region = NULL;
+	}
+	/* Not in a region */
+	if (!region) {
+		page_t *pg = get_page((void *) 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))
+		return (void) kill(current->tgid, SIGSEGV);
+
+	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(InterruptFrame *frame)
+{
+	uintptr_t addr;
+	asm volatile("mov %%cr2, %0" : "=r" (addr));
+	if (!PG_ADDR(addr))
+		panic("Null dereference @ %#.8x", frame->eip);
+	alloc_page(get_page((void *) addr),
+	           PTE_PRESENT | PTE_WRITE | PTE_GLOBAL, -1);
+}