Nucleus
Barry Improved page fault handling and mmap system call 665af0a (3 years, 2 months ago)
diff --git a/include/nucleus/memory.h b/include/nucleus/memory.h
index 5395278..059ba9f 100644
--- a/include/nucleus/memory.h
+++ b/include/nucleus/memory.h
@@ -71,4 +71,6 @@ void *map_page(Page *page);
int verify_access(const void *addr, size_t len, int prot);
+VMRegion *vm_create_stack(void);
+
#endif
diff --git a/memory/fault.c b/memory/fault.c
index 3c5ecfe..02c95ca 100644
--- a/memory/fault.c
+++ b/memory/fault.c
@@ -44,25 +44,26 @@ copy_on_write(VMRegion *region, uintptr_t addr)
inode = back->inode;
page = find_page(inode->pages, offset);
}
+ if (!page) {
+ panic("no page! %#.8x = %#.8x", addr, get_page(addr));
+ }
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 {
+ /* Determine what to do */
+ if (usage(page) > 1 || page->frame == zeroFrame) {
+ /* Copy page */
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));
+ 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 */
@@ -84,7 +85,7 @@ not_present_read(VMRegion *region, uintptr_t addr)
inode = front->inode;
page = find_page(inode->pages, offset);
if (page)
- return install_page(addr, page, PROT_READ);
+ return install_page(addr, page, region->prot);
/* Zero-fill if anonymous */
if (region->flags & MAP_ANONYMOUS) {
page = create_page(inode->pages, zeroFrame, offset);
@@ -127,10 +128,15 @@ not_present_write(VMRegion *region, uintptr_t addr)
inode = back->inode;
page = find_page(inode->pages, offset);
if (page)
- return install_page(addr, page, PROT_WRITE);
+ return install_page(addr, page, region->prot);
page = create_page(inode->pages, alloc_frame(), offset);
- install_page(addr, page, PROT_WRITE);
- memset((void *) PAGE_ADDR(addr), 0, PAGE_SIZE);
+ 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;
}
@@ -139,7 +145,7 @@ not_present_write(VMRegion *region, uintptr_t addr)
inode = front->inode;
page = find_page(inode->pages, offset);
newPage = create_page(inode->pages, alloc_frame(), offset);
- install_page(addr, newPage, PROT_WRITE);
+ install_page(addr, newPage, region->prot);
if (page) {
copy_page_frame(PAGE_ADDR(page->frame),
PAGE_ADDR(newPage->frame));
@@ -194,9 +200,9 @@ page_fault_handler(struct InterruptFrame *frame, uint32_t err)
if (present && write)
return copy_on_write(region, addr);
- if (write)
+ if (!present && write)
return not_present_write(region, addr);
- else
+ if (!present && !write)
return not_present_read(region, addr);
}
diff --git a/memory/frame.c b/memory/frame.c
index c477121..2e063d5 100644
--- a/memory/frame.c
+++ b/memory/frame.c
@@ -108,8 +108,10 @@ alloc_frame(void)
if (idx != -1)
break;
}
- if (idx == -1)
+ if (idx == -1) {
+ panic("Could not allocate frame!");
return 0x00000000;
+ }
set_frame(region, idx);
return region->base + (idx << 12);
diff --git a/memory/mmap.c b/memory/mmap.c
new file mode 100644
index 0000000..b7a99eb
--- /dev/null
+++ b/memory/mmap.c
@@ -0,0 +1,53 @@
+/*
+ * This file implements the mmap() routine. It takes the parameters and sets up
+ * the relevant memory regions. If no address is passed it will find the first
+ * available one and use it.
+ */
+
+#include <sys/mman.h>
+#include <errno.h>
+#include <nucleus/object.h>
+#include <nucleus/memory.h>
+#include <nucleus/task.h>
+#include <nucleus/vfs.h>
+#include "namespace.h"
+
+/* Map an object into memory */
+void *
+mmap(void *addr, size_t len, int prot, int flags, int fildes, off_t off)
+{
+ VMRegion *region = NULL;
+
+ /* Find gap big enough */
+ if (!addr) {
+ VMRegion *head;
+ foreach (current->vm->regions, head) {
+ if (region && (head->start - region->end >= len))
+ break;
+ region = head;
+ }
+ if (!head && region->end + len >= 0xDFC00000)
+ return (void *) -ENOMEM;
+ addr = (void *) region->end;
+ }
+
+ /* Map anonymous memory */
+ if (flags & MAP_ANONYMOUS) {
+ region = vm_create_region(addr, len, prot, flags, 0, NULL);
+ goto end;
+ }
+
+ /* Map a file */
+ if (fildes < 0 || fildes >= NFILES)
+ return (void *) -EBADF;
+ File *file = get_file_by_fd(fildes);
+ if (!file)
+ return (void *) -EBADF;
+ region = vm_create_region(addr, len, prot, flags, off, file);
+end:
+ if (!region)
+ return (void *) -ENOMEM;
+ void *start = (void *) region->start;
+ put(region);
+ return start;
+}
diff --git a/memory/namespace.h b/memory/namespace.h
index 20a8e20..bd5b0a1 100644
--- a/memory/namespace.h
+++ b/memory/namespace.h
@@ -27,4 +27,7 @@ struct VMRegion {
File *front, *back;
};
+VMRegion *vm_create_region(void *addr, size_t len, int prot, int flags,
+ off_t offset, File *back);
+
#endif
diff --git a/memory/page.c b/memory/page.c
index 3aa4923..95b1548 100644
--- a/memory/page.c
+++ b/memory/page.c
@@ -6,6 +6,7 @@
#include <nucleus/object.h>
#include <nucleus/memory.h>
+#include "paging.h"
#include "namespace.h"
static void page_delete(Object *);
@@ -22,7 +23,8 @@ static void
page_delete(Object *obj)
{
Page *page = (void *) obj;
- free_frame(PAGE_ADDR(page->frame));
+ if (page->frame != zeroFrame)
+ free_frame(PAGE_ADDR(page->frame));
}
/* Create a new Page entry */
diff --git a/memory/paging.c b/memory/paging.c
index 75d762a..fe08a96 100644
--- a/memory/paging.c
+++ b/memory/paging.c
@@ -46,6 +46,8 @@ set_page(uintptr_t vaddr, page_t page)
/* Create table if not present */
if (!(tables[tbl] & PDE_PRESENT)) {
tables[tbl] = alloc_frame() | PDE_PRESENT | PDE_WRITE;
+ if (page & PTE_USER)
+ tables[tbl] |= PDE_USER;
memset(mappings + (tbl * 1024), 0, PAGE_SIZE);
}
mappings[address] = page;
diff --git a/memory/region.c b/memory/region.c
index bea28ca..ef43efc 100644
--- a/memory/region.c
+++ b/memory/region.c
@@ -13,9 +13,10 @@
static void vm_new(Object *);
static void vm_delete(Object *);
+static void vm_copy(Object *, Object *);
static void region_new(Object *);
static void region_delete(Object *);
-static void region_copy(Object *a, Object *b);
+static void region_copy(Object *, Object *);
/* Virtual Memory Namespace object type */
ObjectType virtualMemoryType = {
@@ -23,6 +24,7 @@ ObjectType virtualMemoryType = {
.size = sizeof(VirtualMemory),
.new = vm_new,
.delete = vm_delete,
+ .copy = vm_copy,
};
/* VMRegion object type */
@@ -135,9 +137,14 @@ find_region(uintptr_t addr)
VMRegion *region;
foreach (current->vm->regions, region) {
if (region->start <= addr && region->end > addr)
- break;
+ return region;
}
- return region;
+
+ region = current->stack;
+ if (region->start <= addr && region->end > addr)
+ return region;
+
+ return NULL;
}
/* Create a new Virtual Memory Region */
@@ -148,7 +155,7 @@ vm_create_region(void *addr, size_t len, int prot, int flags, off_t offset,
/* Create new region */
VMRegion *region = new(&vmRegionType);
region->end = (uintptr_t) addr + len;
- if (region-> end % PAGE_SIZE)
+ if (region->end % PAGE_SIZE)
region->end += PAGE_SIZE - (region->end % PAGE_SIZE);
region->start = PAGE_ADDR((uintptr_t) addr);
region->prot = prot;
@@ -190,9 +197,20 @@ vm_create_region(void *addr, size_t len, int prot, int flags, off_t offset,
}
/* Total eclipse */
if (head->start >= region->start && head->end <= region->end)
- remove(current->vm->regions, region);
+ remove(current->vm->regions, head);
}
add(current->vm->regions, region);
return region;
}
+
+/* Create a Virtual Memory Region for the stack */
+VMRegion *
+vm_create_stack(void)
+{
+ VMRegion *stack = vm_create_region((void *) 0xDFC00000, 0x400000,
+ PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, 0, NULL);
+ remove(current->vm->regions, stack);
+ return stack;
+}
diff --git a/memory/user.c b/memory/user.c
index cdf4bcd..ec19d1f 100644
--- a/memory/user.c
+++ b/memory/user.c
@@ -6,19 +6,27 @@
*/
#include <nucleus/memory.h>
+#include <nucleus/task.h>
+#include "namespace.h"
+
+VMRegion *find_region(uintptr_t addr);
/* Check if user has access to a region of memory */
int
verify_access(const void *addr, size_t len, int prot)
{
- /*
- * This should iterate all memory regions to check if the address range
- * fits inside one, if not then access should be denied. If a matching
- * region is found, the prot parameter should be checked against the
- * protection value for that region. If this function is called from
- * the kernel directly, or the user did not pass an address or a length
- * (NULL and 0, respectively), then it should just grant access
- * immediately. This can be implemented when userspace is implemented.
- */
- return 1;
+ if (!len || !(current && current->inSyscall))
+ return 1;
+
+ VMRegion *region;
+ int minprot = ~0;
+ uintptr_t end = (uintptr_t) addr;
+ do {
+ region = find_region(end);
+ if (!region)
+ return 0;
+ minprot &= region->prot;
+ end = region->end;
+ } while (end < (uintptr_t) addr + len);
+ return (minprot & prot);
}