minix/servers/vm/mem_file.c
Ben Gras 49b9165251 vm: mmap support
. test74 for mmap functionality
	. vm: add a mem_file memory type that specifies an mmap()ped
	  memory range, backed by a file
	. add fdref, an object that keeps track of FD references within
	  VM per process and so knows how to de-duplicate the use of FD's
	  by various mmap()ped ranges; there can be many more than there can
	  be FD's
	. turned off for now, enable with 'filemap=1' as boot option

Change-Id: I640b1126cdaa522a0560301cf6732b7661555672
2013-05-31 15:42:01 +00:00

252 lines
6.7 KiB
C

/* This file implements the methods of memory-mapped files. */
#include <assert.h>
#include "proto.h"
#include "vm.h"
#include "region.h"
#include "glo.h"
#include "cache.h"
/* These functions are static so as to not pollute the
* global namespace, and are accessed through their function
* pointers.
*/
static void mappedfile_split(struct vmproc *vmp, struct vir_region *vr,
struct vir_region *r1, struct vir_region *r2);
static int mappedfile_unreference(struct phys_region *pr);
static int mappedfile_pagefault(struct vmproc *vmp, struct vir_region *region,
struct phys_region *ph, int write, vfs_callback_t callback, void *, int);
static int mappedfile_sanitycheck(struct phys_region *pr, char *file, int line);
static int mappedfile_writable(struct phys_region *pr);
static int mappedfile_copy(struct vir_region *vr, struct vir_region *newvr);
static int mappedfile_lowshrink(struct vir_region *vr, vir_bytes len);
static void mappedfile_delete(struct vir_region *region);
struct mem_type mem_type_mappedfile = {
.name = "file-mapped memory",
.ev_unreference = mappedfile_unreference,
.ev_pagefault = mappedfile_pagefault,
.ev_sanitycheck = mappedfile_sanitycheck,
.ev_copy = mappedfile_copy,
.writable = mappedfile_writable,
.ev_split = mappedfile_split,
.ev_lowshrink = mappedfile_lowshrink,
.ev_delete = mappedfile_delete,
};
static int mappedfile_unreference(struct phys_region *pr)
{
assert(pr->ph->refcount == 0);
if(pr->ph->phys != MAP_NONE)
free_mem(ABS2CLICK(pr->ph->phys), 1);
return OK;
}
static int cow_block(struct vmproc *vmp, struct vir_region *region,
struct phys_region *ph, u16_t clearend)
{
int r;
if((r=mem_cow(region, ph, MAP_NONE, MAP_NONE)) != OK) {
printf("mappedfile_pagefault: COW failed\n");
return r;
}
/* After COW we are a normal piece of anonymous memory. */
ph->memtype = &mem_type_anon;
if(clearend) {
phys_bytes phaddr = ph->ph->phys, po = VM_PAGE_SIZE-clearend;
assert(clearend < VM_PAGE_SIZE);
phaddr += po;
if(sys_memset(NONE, 0, phaddr, clearend) != OK) {
panic("cow_block: clearend failed\n");
}
}
return OK;
}
static int mappedfile_pagefault(struct vmproc *vmp, struct vir_region *region,
struct phys_region *ph, int write, vfs_callback_t cb,
void *state, int statelen)
{
u32_t allocflags;
int procfd = region->param.file.fdref->fd;
allocflags = vrallocflags(region->flags);
assert(ph->ph->refcount > 0);
assert(region->param.file.inited);
assert(region->param.file.fdref);
assert(region->param.file.fdref->dev != NO_DEV);
/* Totally new block? Create it. */
if(ph->ph->phys == MAP_NONE) {
struct cached_page *cp;
u64_t referenced_offset =
region->param.file.offset + ph->offset;
if(region->param.file.fdref->ino == VMC_NO_INODE) {
cp = find_cached_page_bydev(region->param.file.fdref->dev,
referenced_offset, VMC_NO_INODE, 0, 1);
} else {
cp = find_cached_page_byino(region->param.file.fdref->dev,
region->param.file.fdref->ino, referenced_offset, 1);
}
if(cp) {
int result = OK;
pb_unreferenced(region, ph, 0);
pb_link(ph, cp->page, ph->offset, region);
if(roundup(ph->offset+region->param.file.clearend,
VM_PAGE_SIZE) >= region->length) {
result = cow_block(vmp, region, ph,
region->param.file.clearend);
} else if(result == OK && write) {
result = cow_block(vmp, region, ph, 0);
}
return result;
}
if(!cb) {
printf("VM: mem_file: no callback, returning EFAULT\n");
sys_sysctl_stacktrace(vmp->vm_endpoint);
return EFAULT;
}
if(vfs_request(VMVFSREQ_FDIO, procfd, vmp, referenced_offset,
VM_PAGE_SIZE, cb, NULL, state, statelen) != OK) {
printf("VM: mappedfile_pagefault: vfs_request failed\n");
return ENOMEM;
}
return SUSPEND;
}
if(!write) {
printf("mappedfile_pagefault: nonwrite fault?\n");
return EFAULT;
}
return cow_block(vmp, region, ph, 0);
}
static int mappedfile_sanitycheck(struct phys_region *pr, char *file, int line)
{
MYASSERT(usedpages_add(pr->ph->phys, VM_PAGE_SIZE) == OK);
return OK;
}
static int mappedfile_writable(struct phys_region *pr)
{
/* We are never writable. */
return 0;
}
int mappedfile_copy(struct vir_region *vr, struct vir_region *newvr)
{
assert(vr->param.file.inited);
mappedfile_setfile(newvr->parent, newvr, vr->param.file.fdref->fd,
vr->param.file.offset,
vr->param.file.fdref->dev, vr->param.file.fdref->ino,
vr->param.file.clearend, 0, 0);
assert(newvr->param.file.inited);
return OK;
}
int mappedfile_setfile(struct vmproc *owner,
struct vir_region *region, int fd, u64_t offset,
dev_t dev, ino_t ino, u16_t clearend, int prefill, int mayclosefd)
{
vir_bytes vaddr;
struct fdref *newref;
newref = fdref_dedup_or_new(owner, ino, dev, fd, mayclosefd);
assert(newref);
assert(!region->param.file.inited);
assert(dev != NO_DEV);
fdref_ref(newref, region);
region->param.file.offset = offset;
region->param.file.clearend = clearend;
region->param.file.inited = 1;
if(!prefill) return OK;
for(vaddr = 0; vaddr < region->length; vaddr+=VM_PAGE_SIZE) {
struct cached_page *cp = NULL;
struct phys_region *pr;
u64_t referenced_offset = offset + vaddr;
if(roundup(vaddr+region->param.file.clearend,
VM_PAGE_SIZE) >= region->length) {
break;
}
if(ino == VMC_NO_INODE) {
cp = find_cached_page_bydev(dev, referenced_offset,
VMC_NO_INODE, 0, 1);
} else {
cp = find_cached_page_byino(dev, ino,
referenced_offset, 1);
}
if(!cp) continue;
if(!(pr = pb_reference(cp->page, vaddr, region,
&mem_type_mappedfile))) {
printf("mappedfile_setfile: pb_reference failed\n");
break;
}
if(map_ph_writept(region->parent, region, pr) != OK) {
printf("mappedfile_setfile: map_ph_writept failed\n");
break;
}
}
return OK;
}
static void mappedfile_split(struct vmproc *vmp, struct vir_region *vr,
struct vir_region *r1, struct vir_region *r2)
{
assert(!r1->param.file.inited);
assert(!r2->param.file.inited);
assert(vr->param.file.inited);
assert(r1->length + r2->length == vr->length);
assert(vr->def_memtype == &mem_type_mappedfile);
assert(r1->def_memtype == &mem_type_mappedfile);
assert(r2->def_memtype == &mem_type_mappedfile);
r1->param.file = vr->param.file;
r2->param.file = vr->param.file;
fdref_ref(vr->param.file.fdref, r1);
fdref_ref(vr->param.file.fdref, r2);
r1->param.file.clearend = 0;
r2->param.file.offset += r1->length;
assert(r1->param.file.inited);
assert(r2->param.file.inited);
}
static int mappedfile_lowshrink(struct vir_region *vr, vir_bytes len)
{
assert(vr->param.file.inited);
vr->param.file.offset += len;
return OK;
}
static void mappedfile_delete(struct vir_region *region)
{
assert(region->def_memtype == &mem_type_mappedfile);
assert(region->param.file.inited);
assert(region->param.file.fdref);
fdref_deref(region);
region->param.file.inited = 0;
}