pm: Add pm_writeback_pages()
[akaros.git] / kern / src / pagemap.c
index c22b41e..7052afd 100644 (file)
@@ -11,6 +11,8 @@
 #include <kref.h>
 #include <assert.h>
 #include <stdio.h>
+#include <pagemap.h>
+#include <rcu.h>
 
 void pm_add_vmr(struct page_map *pm, struct vm_region *vmr)
 {
@@ -98,17 +100,17 @@ static void *pm_slot_set_page(void *slot_val, struct page *pg)
                                       ~((1UL << PM_FLAGS_SHIFT) - 1)));
 }
 
-/* Initializes a PM.  Host should be an *inode or a *bdev (doesn't matter).  The
- * reference this stores is uncounted. */
+/* Initializes a PM.  Host should be an fs_file.  The reference this stores is
+ * uncounted. */
 void pm_init(struct page_map *pm, struct page_map_operations *op, void *host)
 {
-       pm->pm_bdev = host;                                             /* note the uncounted ref */
+       pm->pm_file = host;
        radix_tree_init(&pm->pm_tree);
-       pm->pm_num_pages = 0;                                   /* no pages in a new pm */
+       pm->pm_num_pages = 0;
        pm->pm_op = op;
+       qlock_init(&pm->pm_qlock);
        spinlock_init(&pm->pm_lock);
        TAILQ_INIT(&pm->pm_vmrs);
-       atomic_set(&pm->pm_removal, 0);
 }
 
 /* Looks up the index'th page in the page map, returning a refcnt'd reference
@@ -118,8 +120,10 @@ static struct page *pm_find_page(struct page_map *pm, unsigned long index)
        void **tree_slot;
        void *old_slot_val, *slot_val;
        struct page *page = 0;
-       /* Read walking the PM tree TODO: (RCU) */
-       spin_lock(&pm->pm_lock);
+
+       /* We use rcu to protect our radix walk, specifically the tree_slot pointer.
+        * We get our own 'pm refcnt' on the slot itself, which doesn't need RCU. */
+       rcu_read_lock();
        /* We're syncing with removal.  The deal is that if we grab the page (and
         * we'd only do that if the page != 0), we up the slot ref and clear
         * removal.  A remover will only remove it if removal is still set.  If we
@@ -141,7 +145,7 @@ static struct page *pm_find_page(struct page_map *pm, unsigned long index)
        } while (!atomic_cas_ptr(tree_slot, old_slot_val, slot_val));
        assert(page->pg_tree_slot == tree_slot);
 out:
-       spin_unlock(&pm->pm_lock);
+       rcu_read_unlock();
        return page;
 }
 
@@ -160,8 +164,7 @@ static int pm_insert_page(struct page_map *pm, unsigned long index,
        int ret;
        void **tree_slot;
        void *slot_val = 0;
-       /* write locking the PM */
-       spin_lock(&pm->pm_lock);
+
        page->pg_mapping = pm;  /* debugging */
        page->pg_index = index;
        /* no one should be looking at the tree slot til we stop write locking.  the
@@ -170,16 +173,15 @@ static int pm_insert_page(struct page_map *pm, unsigned long index,
        slot_val = pm_slot_inc_refcnt(slot_val);
        /* passing the page ref from the caller to the slot */
        slot_val = pm_slot_set_page(slot_val, page);
-       /* shouldn't need a CAS or anything for the slot write, since we hold the
-        * write lock.  o/w, we'd need to get the slot and CAS instead of insert. */
+       qlock(&pm->pm_qlock);
        ret = radix_insert(&pm->pm_tree, index, slot_val, &tree_slot);
        if (ret) {
-               spin_unlock(&pm->pm_lock);
+               qunlock(&pm->pm_qlock);
                return ret;
        }
        page->pg_tree_slot = tree_slot;
        pm->pm_num_pages++;
-       spin_unlock(&pm->pm_lock);
+       qunlock(&pm->pm_qlock);
        return 0;
 }
 
@@ -288,26 +290,38 @@ static bool vmr_has_page_idx(struct vm_region *vmr, unsigned long pg_idx)
        return ((start_pg <= pg_idx) && (pg_idx < start_pg + nr_pgs));
 }
 
-static void *vmr_idx_to_va(struct vm_region *vmr, unsigned long pg_idx)
-{
-       uintptr_t va = vmr->vm_base + ((pg_idx << PGSHIFT) - vmr->vm_foff);
-       assert(va < vmr->vm_end);
-       return (void*)va;
-}
-
 static unsigned long vmr_get_end_idx(struct vm_region *vmr)
 {
        return ((vmr->vm_end - vmr->vm_base) + vmr->vm_foff) >> PGSHIFT;
 }
 
+/* Runs CB on every PTE in the VMR that corresponds to the file's pg_idx, for up
+ * to max_nr_pgs. */
 static void vmr_for_each(struct vm_region *vmr, unsigned long pg_idx,
                          unsigned long max_nr_pgs, mem_walk_callback_t callback)
 {
-       void *start_va = vmr_idx_to_va(vmr, pg_idx);
-       size_t len = vmr->vm_end - (uintptr_t)start_va;
-       len = MIN(len, max_nr_pgs << PGSHIFT);
-       /* TODO: start using pml_for_each, across all arches */
-       env_user_mem_walk(vmr->vm_proc, start_va, len, callback, 0);
+       uintptr_t start_va;
+       off64_t file_off = pg_idx << PGSHIFT;
+       size_t len = max_nr_pgs << PGSHIFT;
+
+       if (file_off < vmr->vm_foff) {
+               len -= vmr->vm_foff - file_off;
+               file_off = vmr->vm_foff;
+       }
+
+       start_va = vmr->vm_base + (file_off - vmr->vm_foff);
+       if (start_va < vmr->vm_base) {
+               warn("wraparound! %p %p %p %p", start_va, vmr->vm_base, vmr->vm_foff,
+                    pg_idx);
+               return;
+       }
+       if (start_va >= vmr->vm_end)
+               return;
+
+       len = MIN(len, vmr->vm_end - start_va);
+       if (!len)
+               return;
+       env_user_mem_walk(vmr->vm_proc, (void*)start_va, len, callback, vmr);
 }
 
 /* These next two helpers are called on a VMR's range of VAs corresponding to a
@@ -392,21 +406,14 @@ int pm_remove_contig(struct page_map *pm, unsigned long index,
        void *ptr_store[PTR_ARR_LEN];
        int ptr_free_idx = 0;
        struct page *page;
+
        /* could also call a simpler remove if nr_pgs == 1 */
        if (!nr_pgs)
                return 0;
-       /* only one remover at a time (since we walk the PM multiple times as our
-        * 'working list', and need the REMOVAL flag to tell us which pages we're
-        * working on.  with more than one remover, we'd be confused and would need
-        * another list.) */
-       if (atomic_swap(&pm->pm_removal, 1)) {
-               /* We got a 1 back, so someone else is already removing */
-               return 0;
-       }
-       /* TODO: RCU: we're read walking the PM tree and write walking the VMR list.
-        * the reason for the write lock is since we need to prevent new VMRs or the
-        * changing of a VMR to being pinned. o/w, we could fail to unmap and check
-        * for dirtiness. */
+
+       /* This is a mess.  Qlock due to the radix_delete later.  spinlock for the
+        * VMR lists. */
+       qlock(&pm->pm_qlock);
        spin_lock(&pm->pm_lock);
        assert(index + nr_pgs > index); /* til we figure out who validates */
        /* check for any pinned VMRs.  if we have none, then we can skip some loops
@@ -608,13 +615,184 @@ handle_dirty:
        }
        pm->pm_num_pages -= nr_removed;
        spin_unlock(&pm->pm_lock);
-       atomic_set(&pm->pm_removal, 0);
+       qunlock(&pm->pm_qlock);
        return nr_removed;
 }
 
+static bool pm_has_vmr_with_page(struct page_map *pm, unsigned long pg_idx)
+{
+       struct vm_region *vmr_i;
+
+       spin_lock(&pm->pm_lock);
+       TAILQ_FOREACH(vmr_i, &pm->pm_vmrs, vm_pm_link) {
+               if (vmr_has_page_idx(vmr_i, pg_idx)) {
+                       spin_unlock(&pm->pm_lock);
+                       return true;
+               }
+       }
+       spin_unlock(&pm->pm_lock);
+       return false;
+}
+
+static bool __remove_or_zero_cb(void **slot, unsigned long tree_idx, void *arg)
+{
+       struct page_map *pm = arg;
+       struct page *page;
+       void *old_slot_val, *slot_val;
+
+       old_slot_val = ACCESS_ONCE(*slot);
+       slot_val = old_slot_val;
+       page = pm_slot_get_page(slot_val);
+       /* We shouldn't have an item in the tree without a page, unless there's
+        * another removal.  Currently, this CB is called with a qlock. */
+       assert(page);
+       /* Don't even bother with VMRs that might have faulted in the page */
+       if (pm_has_vmr_with_page(pm, tree_idx)) {
+               memset(page2kva(page), 0, PGSIZE);
+               return false;
+       }
+       /* syncing with lookups, writebacks, etc - anyone who gets a ref on a PM
+        * leaf/page (e.g. pm_load_page / pm_find_page. */
+       slot_val = pm_slot_set_page(slot_val, NULL);
+       if (pm_slot_check_refcnt(slot_val) ||
+               !atomic_cas_ptr(slot, old_slot_val, slot_val)) {
+               memset(page2kva(page), 0, PGSIZE);
+               return false;
+       }
+       /* We yanked the page out.  The radix tree still has an item until we return
+        * true, but this is fine.  Future lock-free lookups will now fail (since
+        * the page is 0), and insertions will block on the write lock. */
+       atomic_set(&page->pg_flags, 0); /* cause/catch bugs */
+       page_decref(page);
+       return true;
+}
+
+void pm_remove_or_zero_pages(struct page_map *pm, unsigned long start_idx,
+                             unsigned long nr_pgs)
+{
+       unsigned long end_idx = start_idx + nr_pgs;
+
+       assert(end_idx > start_idx);
+       qlock(&pm->pm_qlock);
+       radix_for_each_slot_in_range(&pm->pm_tree, start_idx, end_idx,
+                                    __remove_or_zero_cb, pm);
+       qunlock(&pm->pm_qlock);
+}
+
+static int __pm_mark_and_clear_dirty(struct proc *p, pte_t pte, void *va,
+                                     void *arg)
+{
+       struct page *page = pa2page(pte_get_paddr(pte));
+       struct vm_region *vmr = arg;
+
+       if (!pte_is_present(pte) || !pte_is_dirty(pte))
+               return 0;
+       if (!(atomic_read(&page->pg_flags) & PG_DIRTY))
+               atomic_or(&page->pg_flags, PG_DIRTY);
+       pte_clear_dirty(pte);
+       vmr->vm_shootdown_needed = true;
+       return 0;
+}
+
+/* Dirty PTE bits will get marked to the struct page itself, and the PTEs will
+ * have the dirty bit cleared.  VMRs that need a shootdown are marked.  Note
+ * this only marks PTEs and VMRs if they were the one to do some of the
+ * dirtying. */
+static void mark_and_clear_dirty_ptes(struct page_map *pm)
+{
+       struct vm_region *vmr_i;
+       pte_t pte;
+
+       spin_lock(&pm->pm_lock);
+       TAILQ_FOREACH(vmr_i, &pm->pm_vmrs, vm_pm_link) {
+               if (!(vmr_i->vm_prot & PROT_WRITE))
+                       continue;
+               /* Only care about shared mappings, not private.  Private mappings have
+                * a reference to the file, but the pages are not in the page cache -
+                * they hang directly off the PTEs (for now). */
+               if (!(vmr_i->vm_flags & MAP_SHARED))
+                       continue;
+               spin_lock(&vmr_i->vm_proc->pte_lock);
+               vmr_for_each(vmr_i, 0, ULONG_MAX, __pm_mark_and_clear_dirty);
+               spin_unlock(&vmr_i->vm_proc->pte_lock);
+       }
+       spin_unlock(&pm->pm_lock);
+}
+
+static void shootdown_vmrs(struct page_map *pm)
+{
+       struct vm_region *vmr_i;
+
+       /* The VMR flag shootdown_needed is owned by the PM.  Each VMR is hooked to
+        * at most one file, so there's no issue there.  We might have a proc that
+        * has multiple non-private VMRs in the same file, but it shouldn't be a big
+        * enough issue to worry about. */
+       spin_lock(&pm->pm_lock);
+       TAILQ_FOREACH(vmr_i, &pm->pm_vmrs, vm_pm_link) {
+               if (vmr_i->vm_shootdown_needed) {
+                       vmr_i->vm_shootdown_needed = false;
+                       proc_tlbshootdown(vmr_i->vm_proc, 0, 0);
+               }
+       }
+       spin_unlock(&pm->pm_lock);
+}
+
+/* Send any queued WBs that haven't been sent yet. */
+static void flush_queued_writebacks(struct page_map *pm)
+{
+       /* TODO (WB) */
+}
+
+/* Batches up pages to be written back, preferably as one big op.  If we have a
+ * bunch outstanding, we'll send them. */
+static void queue_writeback(struct page_map *pm, struct page *page)
+{
+       /* TODO (WB): add a bulk op (instead of only writepage()), collect extents,
+        * and send them to the device.  Probably do something similar for reads. */
+       pm->pm_op->writepage(pm, page);
+}
+
+static bool __writeback_cb(void **slot, unsigned long tree_idx, void *arg)
+{
+       struct page_map *pm = arg;
+       struct page *page = pm_slot_get_page(*slot);
+
+       /* We're qlocked, so all items should have pages. */
+       assert(page);
+       if (atomic_read(&page->pg_flags) & PG_DIRTY) {
+               atomic_and(&page->pg_flags, ~PG_DIRTY);
+               queue_writeback(pm, page);
+       }
+       return false;
+}
+
+/* Every dirty page gets written back, regardless of whether it's in a VMR or
+ * not.  All the dirty bits get cleared too, before writing back. */
+void pm_writeback_pages(struct page_map *pm)
+{
+       qlock(&pm->pm_qlock);
+       mark_and_clear_dirty_ptes(pm);
+       shootdown_vmrs(pm);
+       radix_for_each_slot(&pm->pm_tree, __writeback_cb, pm);
+       flush_queued_writebacks(pm);
+       qunlock(&pm->pm_qlock);
+}
+
+static bool __destroy_cb(void **slot, unsigned long tree_idx, void *arg)
+{
+       struct page *page = pm_slot_get_page(*slot);
+
+       /* Should be no users or need to sync */
+       assert(pm_slot_check_refcnt(*slot) == 0);
+       atomic_set(&page->pg_flags, 0); /* catch bugs */
+       page_decref(page);
+       return true;
+}
+
 void pm_destroy(struct page_map *pm)
 {
-       /* TODO: implement me! */
+       radix_for_each_slot(&pm->pm_tree, __destroy_cb, pm);
+       radix_tree_destroy(&pm->pm_tree);
 }
 
 void print_page_map_info(struct page_map *pm)