Skip to content

Commit

Permalink
vfs: support flushing cached pages to backing device
Browse files Browse the repository at this point in the history
  • Loading branch information
moodyhunter committed Aug 1, 2024
1 parent c39bc7e commit 740f662
Show file tree
Hide file tree
Showing 31 changed files with 980 additions and 147 deletions.
15 changes: 10 additions & 5 deletions kernel/filesystem/dentry.c
Original file line number Diff line number Diff line change
Expand Up @@ -303,21 +303,26 @@ static dentry_t *dentry_resolve_lastseg(dentry_t *parent, char *leaf, lastseg_re

void dentry_attach(dentry_t *d, inode_t *inode)
{
MOS_ASSERT(d->inode == NULL);
MOS_ASSERT_X(d->inode == NULL, "reattaching an inode to a dentry");
MOS_ASSERT(inode != NULL);
// MOS_ASSERT_X(d->refcount == 1, "dentry %p refcount %zu is not 1", (void *) d, d->refcount);

inode_ref(inode);
for (atomic_t i = 0; i < d->refcount; i++)
inode_ref(inode); // refcount the inode for each reference to the dentry

inode_ref(inode); // refcount the inode for each reference to the dentry
d->inode = inode;
}

void dentry_detach(dentry_t *d)
{
MOS_ASSERT(d->inode != NULL);
if (d->inode == NULL)
return;

// the caller should have the only reference to the dentry
MOS_ASSERT(d->refcount == 1);
// MOS_ASSERT(d->refcount == 1); // !! TODO: this assertion fails in vfs_unlinkat

inode_unref(d->inode);
(void) inode_unref(d->inode); // we don't care if the inode is freed or not
d->inode = NULL;
}

Expand Down
42 changes: 33 additions & 9 deletions kernel/filesystem/dentry_utils.c
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@ dentry_t *dentry_ref(dentry_t *dentry)
MOS_ASSERT(dentry);
MOS_ASSERT(dentry->inode); // one cannot refcount a dentry without an inode
dentry->refcount++;
if (dentry->inode)
inode_ref(dentry->inode);
inode_ref(dentry->inode);
pr_dinfo2(dcache_ref, "dentry %p '%s' increased refcount to %zu", (void *) dentry, dentry_name(dentry), dentry->refcount);
return dentry;
}
Expand Down Expand Up @@ -61,7 +60,13 @@ __nodiscard bool dentry_unref_one_norelease(dentry_t *dentry)
dentry->refcount--;

if (dentry->inode)
inode_unref(dentry->inode);
{
if (inode_unref(dentry->inode))
{
pr_dinfo2(vfs, "inode %p has no more references, releasing", (void *) dentry->inode);
dentry->inode = NULL;
}
}

pr_dinfo2(dcache_ref, "dentry %p '%s' decreased refcount to %zu", (void *) dentry, dentry_name(dentry), dentry->refcount);

Expand Down Expand Up @@ -187,12 +192,31 @@ ssize_t dentry_path(dentry_t *dentry, dentry_t *root, char *buf, size_t size)
if (current->name == NULL)
current = dentry_root_get_mountpoint(current);

char *newpath = kmalloc(strlen(current->name) + 1 + strlen(path) + 1);
strcpy(newpath, current->name);
strcat(newpath, "/");
strcat(newpath, path);
kfree(path);
path = newpath;
if (current == NULL)
{
// root for other fs trees
char *newpath = kmalloc(strlen(path) + 3);
strcpy(newpath, ":/");
strcat(newpath, path);
kfree(path);
path = newpath;

if (strlen(path) + 1 > size)
return -1;

const size_t real_size = snprintf(buf, size, "%s", path);
kfree(path);
return real_size;
}
else
{
char *newpath = kmalloc(strlen(current->name) + 1 + strlen(path) + 1);
strcpy(newpath, current->name);
strcat(newpath, "/");
strcat(newpath, path);
kfree(path);
path = newpath;
}
}

if (strlen(path) + 1 > size)
Expand Down
37 changes: 21 additions & 16 deletions kernel/filesystem/inode.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

#include "mos/filesystem/inode.h"

#include "mos/filesystem/page_cache.h"
#include "mos/filesystem/vfs_types.h"
#include "mos/lib/sync/spinlock.h"
#include "mos/mm/slab_autoinit.h"
Expand All @@ -13,17 +14,6 @@
slab_t *inode_cache;
SLAB_AUTOINIT("inode", inode_cache, inode_t);

static bool do_flush_and_drop_cache_page(const uintn key, void *value, void *data)
{
MOS_UNUSED(key);
MOS_UNUSED(value);
MOS_UNUSED(data);
return true;
// phyframe_t *page = value;
// mmstat_dec1(MEM_PAGECACHE);
// pmm_free(page);
}

static bool vfs_generic_inode_drop(inode_t *inode)
{
MOS_UNUSED(inode);
Expand All @@ -35,9 +25,12 @@ static bool inode_try_drop(inode_t *inode)
{
if (inode->refcount == 0 && inode->nlinks == 0)
{
pr_dinfo2(vfs, "inode %p has 0 refcount and 0 nlinks, dropping", (void *) inode);

// drop the inode
inode_cache_t *icache = &inode->cache;
hashmap_foreach(&icache->pages, do_flush_and_drop_cache_page, NULL);
spinlock_acquire(&inode->cache.lock);
pagecache_flush_or_drop_all(&inode->cache, true);
spinlock_release(&inode->cache.lock);

bool dropped = false;
if (inode->superblock->ops && inode->superblock->ops->drop_inode)
Expand All @@ -46,7 +39,7 @@ static bool inode_try_drop(inode_t *inode)
dropped = vfs_generic_inode_drop(inode);

if (!dropped)
pr_warn("inode %p has 0 refcount and 0 nlinks, but could not be dropped", (void *) inode);
pr_warn("inode %p has 0 refcount and 0 nlinks, but failed to be dropped", (void *) inode);

return dropped;
}
Expand Down Expand Up @@ -96,8 +89,20 @@ bool inode_unlink(inode_t *dir, dentry_t *dentry)
inode_t *inode = dentry->inode;
MOS_ASSERT(dir && inode);
MOS_ASSERT(inode->nlinks > 0);

inode->nlinks--;
bool ok = true;
if (dir->ops->unlink)
dir->ops->unlink(dir, dentry);
return inode_try_drop(dentry->inode);
ok = dir->ops->unlink(dir, dentry);

if (!ok)
{
inode->nlinks++;
return false;
}

const bool dropped = inode_try_drop(dentry->inode);
MOS_ASSERT_X(!dropped, "inode %p was dropped accidentally, where dentry %p should be holding a reference", (void *) inode, (void *) dentry);

return true;
}
86 changes: 80 additions & 6 deletions kernel/filesystem/page_cache.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,102 @@

#include "mos/filesystem/page_cache.h"

#include "mos/filesystem/vfs_types.h"
#include "mos/filesystem/vfs_utils.h"
#include "mos/lib/sync/spinlock.h"
#include "mos/mm/mm.h"
#include "mos/mm/mmstat.h"
#include "mos/mm/physical/pmm.h"
#include "mos/syslog/printk.h"

#include <mos/mos_global.h>
#include <mos_stdlib.h>
#include <mos_string.h>

phyframe_t *pagecache_get_page_for_read(inode_cache_t *cache, off_t pgoff)
struct _flush_and_drop_data
{
inode_cache_t *icache;
bool should_drop_page;
long ret;
};

static bool do_flush_and_drop_cached_page(const uintn key, void *value, void *data)
{
MOS_ASSERT(spinlock_is_locked(&cache->lock));
// key = page number, value = phyframe_t *, data = _flush_and_drop_data *
struct _flush_and_drop_data *const fdd = data;
inode_cache_t *const icache = fdd->icache;
phyframe_t *const page = value;
off_t const pgoff = key;
const bool drop_page = fdd->should_drop_page;

// !! TODO
// !! currently we have no reliable way to track if a page is dirty
// !! so we always flush it
// !! this causes performance issues, but it's simple and works for now
// if (!page->pagecache.dirty)

spinlock_assert_locked(&icache->lock);

long ret = 0;
if (icache->ops->flush_page)
ret = icache->ops->flush_page(icache, pgoff, page);
else
ret = simple_flush_page_discard_data(icache, pgoff, page);

if (!IS_ERR_VALUE(ret) && drop_page)
{
// only when the page was successfully flushed
hashmap_remove(&icache->pages, pgoff);
mmstat_dec1(MEM_PAGECACHE);
pmm_unref_one(page);
}

fdd->ret = ret;
return ret == 0;
}

long pagecache_flush_or_drop(inode_cache_t *icache, off_t pgoff, size_t npages, bool drop_page)
{
struct _flush_and_drop_data data = { .icache = icache, .should_drop_page = drop_page };
spinlock_assert_locked(&icache->lock);
long ret = 0;
for (size_t i = 0; i < npages; i++)
{
phyframe_t *page = hashmap_get(&icache->pages, pgoff + i);
if (!page)
continue;

do_flush_and_drop_cached_page(pgoff + i, page, &data);

void *p = hashmap_get(&cache->pages, pgoff);
if (p)
return p;
if (data.ret != 0)
{
ret = data.ret;
break;
}
}
return ret;
}

long pagecache_flush_or_drop_all(inode_cache_t *icache, bool drop_page)
{
spinlock_assert_locked(&icache->lock);
struct _flush_and_drop_data data = { .icache = icache, .should_drop_page = drop_page };
hashmap_foreach(&icache->pages, do_flush_and_drop_cached_page, &data);
return data.ret;
}

phyframe_t *pagecache_get_page_for_read(inode_cache_t *cache, off_t pgoff)
{
spinlock_assert_locked(&cache->lock);
phyframe_t *page = hashmap_get(&cache->pages, pgoff);
if (page)
return page; // fast path

if (!cache->ops)
return ERR_PTR(-EIO);

MOS_ASSERT_X(cache->ops && cache->ops->fill_cache, "no page cache ops for inode %p", (void *) cache->owner);
phyframe_t *page = cache->ops->fill_cache(cache, pgoff);
page = cache->ops->fill_cache(cache, pgoff);
if (IS_ERR(page))
return page;
mmstat_inc1(MEM_PAGECACHE);
Expand Down
Loading

0 comments on commit 740f662

Please sign in to comment.