Nucleus
Barry Virtual Memory page fault handling b0cc93c (3 years, 2 months ago)
diff --git a/include/nucleus/vfs.h b/include/nucleus/vfs.h
index 40239c7..fa193fb 100644
--- a/include/nucleus/vfs.h
+++ b/include/nucleus/vfs.h
@@ -85,6 +85,7 @@ struct FileOps {
size_t (*read)(File *, char *, size_t, off_t);
size_t (*write)(File *, char *, size_t, off_t);
int (*open)(File *);
+ void (*mmap)(File *, void *, size_t, off_t);
};
extern ObjectType fstypeType;
@@ -96,6 +97,7 @@ extern ObjectType dirEntryType;
extern ObjectType fileType;
void init_vfs(void);
+File *create_anonymous_file(void);
void register_fstype(const char *name, mount_callback_t mount);
int mount(const char *src, const char *target, const char *type,
unsigned long flags, void *data);
@@ -115,6 +117,7 @@ DirEntry *find_direntry(ObjectList *list, const char *name);
int file_open(File *file);
size_t file_read(File *file, char *buf, size_t count);
size_t file_write(File *file, char *buf, size_t count);
+void file_mmap(File *file, void *addr, size_t len, off_t offset);
/* Files namespace functions */
File *get_file_by_fd(int fd);
int allocate_fd(void);
diff --git a/kernel/acpi/apic.c b/kernel/acpi/apic.c
index 8bdfbf8..73da356 100644
--- a/kernel/acpi/apic.c
+++ b/kernel/acpi/apic.c
@@ -228,4 +228,5 @@ ap_startup(void)
/* Start running tasks */
schedule();
+ __builtin_unreachable();
}
diff --git a/memory/fault.c b/memory/fault.c
index 08a4252..3c5ecfe 100644
--- a/memory/fault.c
+++ b/memory/fault.c
@@ -1,14 +1,204 @@
/*
- * This file contains the page fault handler.
+ * 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
diff --git a/vfs/vfs.c b/vfs/vfs.c
index bbd61db..303f9c7 100644
--- a/vfs/vfs.c
+++ b/vfs/vfs.c
@@ -9,6 +9,8 @@
#include <nucleus/object.h>
#include <nucleus/vfs.h>
+extern FileOps tmpfsFileOps;
+
Inode *tmpfs_mount(FSType *type, int flags, const char *dev, void *data);
Inode *devfs_mount(FSType *type, int flags, const char *dev, void *data);
@@ -23,3 +25,13 @@ init_vfs(void)
mkdir("dev", 0);
mount("devfs", "/dev", "devfs", 0, NULL);
}
+
+/* Create an anonymous file */
+File *
+create_anonymous_file(void)
+{
+ File *file = new(&fileType);
+ file->inode = new(&inodeType);
+ file->ops = &tmpfsFileOps;
+ return file;
+}