Orion
Barry Importing existing Orion kernel d41a53c (3 years, 2 months ago)
/*
* This file handles the Virtual Memory system for processes. It splits each
* process into several memory regions, and points each of those reasons to a
* memory object. Each object can be modified on demand, and can be made up of
* several pages, and backed by various stores. This allows objects such as
* files to be easily mapped into an address space, or for large regions to be
* shared between processes.
*/
#include <stdint.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <errno.h>
#include "heap.h"
#include "paging.h"
#include "vm.h"
#include "../vfs/vfs.h"
#include "../vfs/inode.h"
#include "../vfs/cache.h"
#include "../vfs/tmpfs/fs.h"
#include "../task/task.h"
#include "../proc/proc.h"
#include "../screen.h"
/* Unmap a range of pages from page directory */
static void
unmap_page_range(uintptr_t start, uintptr_t end)
{
uintptr_t addr;
for (addr = start; addr < end; addr += 0x1000) {
*get_page((void *) addr) = 0x00000000;
flush_tlb(addr);
}
}
/* Remove a range of pages from a region's page cache */
static void
remove_cache_range(VMRegion *region, uintptr_t start, uintptr_t end)
{
Page *page;
Inode *inode;
uintptr_t p;
for (p = 0; p < end - start; p += 0x1000) {
page = NULL;
if (!page && region->front) {
inode = region->front->inode;
page = page_find(inode, region->offset + p);
}
if (!page && region->back) {
inode = region->back->inode;
page = page_find(inode, region->offset + p);
}
if (page)
page_remove(inode, page);
}
}
/* Create a new VM Region */
VMRegion *
vm_create_region(void *addr, size_t len, int prot, int flags, off_t offset,
File *back)
{
/* Create new region */
VMRegion *head, *next, *insert, *region = kmalloc(sizeof(VMRegion));
region->end = (uintptr_t) addr + len;
if (region->end % 0x1000)
region->end += 0x1000 - (region->end % 0x1000);
region->start = (uintptr_t) addr & ~0xFFF;
region->prot = prot;
region->flags = flags;
region->offset = offset;
region->front = NULL;
region->back = NULL;
if (back)
region->back = file_get(back);
/* Create new list */
if (!current->vm->regions) {
current->vm->regions = region;
return region;
}
/* Fix overlaps */
uintptr_t p;
for (head = current->vm->regions; head; head = next) {
next = head->next; /* head may be destroyed during iteration */
if (head->start >= region->end || head->end <= region->start)
continue;
/* Middle eclipsed */
if (head->start < region->start && head->end > region->end) {
/* Create region after current */
insert = kmalloc(sizeof(VMRegion));
insert->end = head->end;
insert->start = head->end = region->start;
insert->prot = head->prot;
insert->flags = head->flags;
insert->offset = head->offset;
insert->offset += (insert->start - head->start);
if (head->front)
insert->front = file_get(head->front);
if (head->back)
insert->back = file_get(head->back);
/* Insert into list */
insert->next = head->next;
head->next = insert;
insert->prev = head;
insert->next->prev = insert;
/* Inserted region will be dealt with on next pass */
}
/* Start eclipsed */
if (head->start >= region->start && head->end > region->end) {
unmap_page_range(head->start, region->end);
remove_cache_range(head, head->start, region->end);
head->start = region->end;
head->offset += (region->end - head->start);
}
/* End eclipsed */
if (head->start < region->start && head->end <= region->end) {
unmap_page_range(region->start, head->end);
remove_cache_range(head, region->start, head->end);
head->end = region->start;
}
/* Total eclipse */
if (head->start >= region->start && head->end <= region->end)
vm_destroy_region(head);
}
/* Add to ordered list */
for (head = current->vm->regions; head->next; head = head->next)
if (head->end <= region->start
&& head->next->start >= region->end)
break;
region->next = head->next;
region->prev = head;
region->prev->next = region;
if (region->next)
region->next->prev = region;
return region;
}
/* Remove a VM Region */
void
vm_remove_region(VMRegion *region)
{
/* Remove from list */
if (current->vm->regions == region)
current->vm->regions = region->next;
if (region->prev)
region->prev->next = region->next;
if (region->next)
region->next->prev = region->prev;
// region->prev = region->next = NULL;
}
/* Destroy a VM Region */
void
vm_destroy_region(VMRegion *region)
{
/* Unlink files */
if (region->front)
file_put(region->front);
if (region->back)
file_put(region->back);
/* Clean page directory */
unmap_page_range(region->start, region->end);
vm_remove_region(region);
kfree(region);
}
/* Clone a set of VM Regions */
VMRegion *
vm_clone_regions(VMRegion *head)
{
if (!head)
return NULL;
VMRegion *newhead = NULL, *newcurr, *newprev = NULL;
VMRegion *curr = head;
off_t i;
Page *page;
File *file;
while (curr) {
newcurr = kmalloc(sizeof(VMRegion));
if (!newhead)
newhead = newcurr;
newcurr->prev = newprev;
newcurr->next = NULL;
if (newprev)
newprev->next = newcurr;
newcurr->start = curr->start;
newcurr->end = curr->end;
newcurr->prot = curr->prot;
newcurr->flags = curr->flags;
newcurr->offset = curr->offset;
/* Front (anonymous regions) */
if (curr->front && (curr->flags & MAP_PRIVATE)) {
/* Copy the file */
file = kmalloc(sizeof(File));
file->inode = inode_get(kmalloc(sizeof(Inode)));
file->ops = &tmpfsFileOps;
newcurr->front = file_get(file);
for (i = 0; i < curr->end - curr->start; i += 0x1000) {
page = page_find(curr->front->inode,
i + curr->offset);
if (page)
page_add(file->inode, page);
}
} else if (curr->front) {
newcurr->front = file_get(curr->front);
}
/* Back (always a file) */
if (curr->back)
newcurr->back = file_get(curr->back);
curr = curr->next;
newprev = newcurr;
};
return newhead;
}
/* Map an object into memory */
void *
mmap(void *addr, size_t len, int prot, int flags, int fildes, off_t off)
{
VMRegion *region;
/* Find gap big enough */
if (!addr) {
for (region = current->vm->regions;
region->next; region = region->next) {
if (region->next->start - region->end >= len)
break;
}
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 = current->files->fd[fildes];
if (!file)
return (void *) -EBADF;
region = vm_create_region(addr, len, prot, flags, off, file);
end:
if (!region)
return (void *) -ENOMEM;
return (void *) region->start;
}