;
vm
INFO
+ SETCACHEPAGE
+ CLEARCACHE
;
uid 0;
};
ipc
SYSTEM pm vfs rs vm
;
+ vm
+ SETCACHEPAGE
+ CLEARCACHE
+ ;
};
service vbfs
ipc
SYSTEM pm vfs rs ds vm vbox
;
+ vm
+ SETCACHEPAGE
+ CLEARCACHE
+ ;
};
service printer
service devman
{
uid 0;
+ vm
+ SETCACHEPAGE
+ CLEARCACHE
+ ;
};
service mmc
IRQCTL # 19
PADCONF # 57
;
+ vm
+ SETCACHEPAGE
+ CLEARCACHE
+ ;
irq
29 # GPIO module 1 (dm37xx)
30 # GPIO module 2 (dm37xx)
int vm_procctl_handlemem(endpoint_t ep, vir_bytes m1, vir_bytes m2, int wr);
int vm_set_cacheblock(void *block, dev_t dev, off_t dev_offset,
- ino_t ino, off_t ino_offset, u32_t *flags, int blocksize);
+ ino_t ino, off_t ino_offset, u32_t *flags, int blocksize,
+ int setflags);
void *vm_map_cacheblock(dev_t dev, off_t dev_offset,
ino_t ino, off_t ino_offset, u32_t *flags, int blocksize);
/* special inode number for vm cache functions */
#define VMC_NO_INODE 0 /* to reference a disk block, no associated file */
+/* setflags for vm_set_cacheblock, also used internally in VM */
+#define VMSF_ONCE 0x01 /* discard block after one-time use */
+
#endif /* _MINIX_VM_H */
#include "fsdriver.h"
#include <minix/ds.h>
+#include <sys/mman.h>
/*
* Process a READSUPER request from VFS.
if (r == OK) {
/* This one we can set on the file system's behalf. */
- if (fdp->fdr_peek != NULL && fdp->fdr_bpeek != NULL)
+ if ((fdp->fdr_peek != NULL && fdp->fdr_bpeek != NULL) ||
+ major(dev) == NONE_MAJOR)
res_flags |= RES_HASPEEK;
m_out->m_fs_vfs_readsuper.inode = root_node.fn_ino_nr;
if (fdp->fdr_unmount != NULL)
fdp->fdr_unmount();
+ /* If we used mmap emulation, clear any cached blocks from VM. */
+ if (fdp->fdr_peek == NULL && major(fsdriver_device) == NONE_MAJOR)
+ vm_clear_cache(fsdriver_device);
+
/* Update library-local state. */
fsdriver_mounted = FALSE;
return read_write(fdp, m_in, m_out, FSC_WRITE);
}
+/*
+ * A read-based peek implementation. This allows file systems that do not have
+ * a buffer cache and do not implement peek, to support a limited form of mmap.
+ * We map in a block, fill it by calling the file system's read function, tell
+ * VM about the page, and then unmap the block again. We tell VM not to cache
+ * the block beyond its immediate use for the mmap request, so as to prevent
+ * potentially stale data from being cached--at the cost of performance.
+ */
+static ssize_t
+builtin_peek(const struct fsdriver * __restrict fdp, ino_t ino_nr,
+ size_t nbytes, off_t pos)
+{
+ static u32_t flags = 0; /* storage for the VMMC_ flags of all blocks */
+ static off_t dev_off = 0; /* fake device offset, see below */
+ struct fsdriver_data data;
+ char *buf;
+ ssize_t r;
+
+ if ((buf = mmap(NULL, nbytes, PROT_READ | PROT_WRITE,
+ MAP_ANON | MAP_PRIVATE, -1, 0)) == MAP_FAILED)
+ return ENOMEM;
+
+ data.endpt = SELF;
+ data.grant = (cp_grant_id_t)buf;
+ data.size = nbytes;
+
+ r = fdp->fdr_read(ino_nr, &data, nbytes, pos, FSC_READ);
+
+ if (r >= 0) {
+ if ((size_t)r < nbytes)
+ memset(&buf[r], 0, nbytes - r);
+
+ /*
+ * VM uses serialized communication to VFS. Since the page is
+ * to be used only once, VM will use and then discard it before
+ * sending a new peek request. Thus, it should be safe to
+ * reuse the same device offset all the time. However, relying
+ * on assumptions in protocols elsewhere a bit dangerous, so we
+ * use an ever-increasing device offset just to be safe.
+ */
+ r = vm_set_cacheblock(buf, fsdriver_device, dev_off, ino_nr,
+ pos, &flags, nbytes, VMSF_ONCE);
+
+ if (r == OK) {
+ dev_off += nbytes;
+
+ r = nbytes;
+ }
+ }
+
+ munmap(buf, nbytes);
+
+ return r;
+}
+
/*
* Process a PEEK request from VFS.
*/
pos = m_in->m_vfs_fs_readwrite.seek_pos;
nbytes = m_in->m_vfs_fs_readwrite.nbytes;
- if (fdp->fdr_peek == NULL)
- return ENOSYS;
-
if (pos < 0 || nbytes > SSIZE_MAX)
return EINVAL;
- r = fdp->fdr_peek(ino_nr, NULL /*data*/, nbytes, pos, FSC_PEEK);
+ if (fdp->fdr_peek == NULL) {
+ if (major(fsdriver_device) != NONE_MAJOR)
+ return ENOSYS;
+
+ /*
+ * For file systems that have no backing device, emulate peek
+ * support by reading into temporary buffers and passing these
+ * to VM.
+ */
+ r = builtin_peek(fdp, ino_nr, nbytes, pos);
+ } else
+ r = fdp->fdr_peek(ino_nr, NULL /*data*/, nbytes, pos,
+ FSC_PEEK);
/* Do not return a new position. */
if (r >= 0) {
if(vmcache && bp->lmfs_needsetcache && dev != NO_DEV) {
if((r=vm_set_cacheblock(bp->data, dev, dev_off,
bp->lmfs_inode, bp->lmfs_inode_offset,
- &bp->lmfs_flags, fs_block_size)) != OK) {
+ &bp->lmfs_flags, fs_block_size, 0)) != OK) {
if(r == ENOSYS) {
printf("libminixfs: ENOSYS, disabling VM calls\n");
vmcache = 0;
static int vm_cachecall(message *m, int call, void *addr, dev_t dev,
off_t dev_offset, ino_t ino, off_t ino_offset, u32_t *flags,
- int blocksize)
+ int blocksize, int setflags)
{
if(blocksize % PAGE_SIZE)
panic("blocksize %d should be a multiple of pagesize %d\n",
m->m_vmmcp.flags_ptr = flags;
m->m_vmmcp.dev = dev;
m->m_vmmcp.pages = blocksize / PAGE_SIZE;
- m->m_vmmcp.flags = 0;
+ m->m_vmmcp.flags = setflags;
return _taskcall(VM_PROC_NR, call, m);
}
message m;
if(vm_cachecall(&m, VM_MAPCACHEPAGE, NULL, dev, dev_offset,
- ino, ino_offset, flags, blocksize) != OK)
+ ino, ino_offset, flags, blocksize, 0) != OK)
return MAP_FAILED;
return m.m_vmmcp_reply.addr;
}
int vm_set_cacheblock(void *block, dev_t dev, off_t dev_offset,
- ino_t ino, off_t ino_offset, u32_t *flags, int blocksize)
+ ino_t ino, off_t ino_offset, u32_t *flags, int blocksize, int setflags)
{
message m;
return vm_cachecall(&m, VM_SETCACHEPAGE, block, dev, dev_offset,
- ino, ino_offset, flags, blocksize);
+ ino, ino_offset, flags, blocksize, setflags);
}
int
return NULL;
}
-int addcache(dev_t dev, u64_t dev_off, ino_t ino, u64_t ino_off, struct phys_block *pb)
+int addcache(dev_t dev, u64_t dev_off, ino_t ino, u64_t ino_off, int flags,
+ struct phys_block *pb)
{
int hv_dev;
struct cached_page *hb;
hb->dev_offset = dev_off;
hb->ino = ino;
hb->ino_offset = ino_off;
+ hb->flags = flags & VMSF_ONCE;
hb->page = pb;
hb->page->refcount++; /* block also referenced by cache now */
hb->page->flags |= PBF_INCACHE;
ino_t ino; /* which ino is it about */
u64_t ino_offset; /* offset within ino */
+ int flags; /* currently only VMSF_ONCE or 0 */
struct phys_block *page; /* page ptr */
struct cached_page *older; /* older in lru chain */
struct cached_page *newer; /* newer in lru chain */
dev_t dev = msg->m_vmmcp.dev;
off_t dev_off = msg->m_vmmcp.dev_offset;
off_t ino_off = msg->m_vmmcp.ino_offset;
+ int flags = msg->m_vmmcp.flags;
int n;
struct vmproc *caller;
phys_bytes offset;
if((hb=find_cached_page_bydev(dev, dev_off + offset,
msg->m_vmmcp.ino, ino_off + offset, 1))) {
/* block inode info updated */
- if(hb->page != phys_region->ph) {
+ if(hb->page != phys_region->ph ||
+ (hb->flags & VMSF_ONCE)) {
/* previous cache entry has become
* obsolete; make a new one. rmcache
* removes it from the cache and frees
phys_region->memtype = &mem_type_cache;
- if((r=addcache(dev, dev_off + offset,
- msg->m_vmmcp.ino, ino_off + offset, phys_region->ph)) != OK) {
+ if((r=addcache(dev, dev_off + offset, msg->m_vmmcp.ino,
+ ino_off + offset, flags, phys_region->ph)) != OK) {
printf("VM: addcache failed\n");
return r;
}
cp = find_cached_page_byino(region->param.file.fdref->dev,
region->param.file.fdref->ino, referenced_offset, 1);
}
- if(cp) {
+ /*
+ * Normally, a cache hit saves a round-trip to the file system
+ * to load the page. However, if the page in the VM cache is
+ * marked for one-time use, then force a round-trip through the
+ * file system anyway, so that the FS can update the page by
+ * by readding it to the cache. Thus, for one-time use pages,
+ * no caching is performed. This approach is correct even in
+ * the light of concurrent requests and disappearing processes
+ * but relies on VM requests to VFS being fully serialized.
+ */
+ if(cp && (!cb || !(cp->flags & VMSF_ONCE))) {
int result = OK;
pb_unreferenced(region, ph, 0);
pb_link(ph, cp->page, ph->offset, region);
result = cow_block(vmp, region, ph, 0);
}
+ /* Discard one-use pages after mapping them in. */
+ if (result == OK && (cp->flags & VMSF_ONCE))
+ rmcache(cp);
+
return result;
}
cp = find_cached_page_byino(dev, ino,
referenced_offset, 1);
}
- if(!cp) continue;
+ /*
+ * If we get a hit for a page that is to be used only once,
+ * then either we found a stale page (due to a process dying
+ * before a requested once-page could be mapped in) or this is
+ * a rare case of concurrent requests for the same page. In
+ * both cases, force the page to be obtained from its FS later.
+ */
+ if(!cp || (cp->flags & VMSF_ONCE)) continue;
if(!(pr = pb_reference(cp->page, vaddr, region,
&mem_type_mappedfile))) {
printf("mappedfile_setfile: pb_reference failed\n");
struct cached_page *find_cached_page_bydev(dev_t dev, u64_t dev_off,
ino_t ino, u64_t ino_off, int touchlru);
struct cached_page *find_cached_page_byino(dev_t dev, ino_t ino, u64_t ino_off, int touchlru);
-int addcache(dev_t dev, u64_t def_off, ino_t ino, u64_t ino_off, struct phys_block *pb);
+int addcache(dev_t dev, u64_t def_off, ino_t ino, u64_t ino_off, int flags,
+ struct phys_block *pb);
void cache_sanitycheck_internal(void);
int cache_freepages(int pages);
void get_stats_info(struct vm_stats_info *vsi);
}
int vm_set_cacheblock(void *block, dev_t dev, off_t dev_offset,
- ino_t ino, off_t ino_offset, u32_t *flags, int blocksize)
+ ino_t ino, off_t ino_offset, u32_t *flags, int blocksize, int setflags)
{
return ENOSYS;
}
#include <sys/ioctl.h>
#include <sys/ioc_memory.h>
#include <sys/param.h>
+#include <minix/paths.h>
#include <stdio.h>
#include <assert.h>
#include <string.h>
}
+/*
+ * Test mmap on none-dev file systems - file systems that do not have a buffer
+ * cache and therefore have to fake mmap support. We use procfs as target.
+ * The idea is that while we succeed in mapping in /proc/uptime, we also get
+ * a new uptime value every time we map in the page -- VM must not cache it.
+ */
+static void
+nonedev_regression(void)
+{
+ int fd;
+ char *buf;
+ unsigned long uptime1, uptime2, uptime3;
+
+ subtest++;
+
+ if ((fd = open(_PATH_PROC "uptime", O_RDONLY)) < 0) e(1);
+
+ buf = mmap(NULL, 4096, PROT_READ, MAP_PRIVATE | MAP_FILE, fd, 0);
+ if (buf == MAP_FAILED) e(2);
+
+ if (buf[4095] != 0) e(3);
+
+ if ((uptime1 = atoi(buf)) == 0) e(4);
+
+ if (munmap(buf, 4096) != 0) e(5);
+
+ sleep(2);
+
+ buf = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_FILE,
+ fd, 0);
+ if (buf == MAP_FAILED) e(6);
+
+ if (buf[4095] != 0) e(7);
+
+ if ((uptime2 = atoi(buf)) == 0) e(8);
+
+ if (uptime1 == uptime2) e(9);
+
+ if (munmap(buf, 4096) != 0) e(10);
+
+ sleep(2);
+
+ buf = mmap(NULL, 4096, PROT_READ, MAP_SHARED | MAP_FILE, fd, 0);
+ if (buf == MAP_FAILED) e(11);
+
+ if (buf[4095] != 0) e(12);
+
+ if ((uptime3 = atoi(buf)) == 0) e(13);
+
+ if (uptime1 == uptime3) e(14);
+ if (uptime2 == uptime3) e(15);
+
+ if (munmap(buf, 4096) != 0) e(16);
+
+ close(fd);
+}
+
int
main(int argc, char *argv[])
{
basic_regression();
+ nonedev_regression();
+
test_memory_types_vs_operations();
makefiles(MAXFILES);
memcpy(bdata, block, blocksize);
if(mustset && (r=vm_set_cacheblock(bdata, MYDEV, dev_off,
- VMC_NO_INODE, 0, NULL, blocksize)) != OK) {
+ VMC_NO_INODE, 0, NULL, blocksize, 0)) != OK) {
printf("dowriteblock: vm_set_cacheblock failed %d\n", r);
exit(1);
}