Replace the old page allocator with the base arena
authorBarret Rhoden <brho@cs.berkeley.edu>
Fri, 28 Oct 2016 20:53:46 +0000 (16:53 -0400)
committerBarret Rhoden <brho@cs.berkeley.edu>
Tue, 29 Nov 2016 16:27:40 +0000 (11:27 -0500)
The old allocator couldn't handle higher order allocations efficiently.
As memory was used, it'd take longer and longer to find contiguous
pages.

We bootstrap the base arena and add free segments to it based on the
free memory regions of multiboot.  The kpages_arena is used for the
main pages allocator.  Right now, it's just a pass-through arena that
imports from base.  In the future, it'll have its own qcaches built in,
which will make common allocations even faster.

Signed-off-by: Barret Rhoden <brho@cs.berkeley.edu>
kern/arch/riscv/page_alloc.c
kern/arch/x86/page_alloc.c
kern/include/page_alloc.h
kern/src/page_alloc.c
kern/src/pmap.c

index 71720c5..dac28d2 100644 (file)
@@ -1,7 +1,9 @@
 /* Copyright (c) 2009 The Regents of the University  of California.
+ * Copyright (c) 2016 Google Inc
  * See the COPYRIGHT files at the top of this source tree for full
  * license information.
  *
+ * Barret Rhoden <brho@cs.berkeley.edu>
  * Kevin Klues <klueska@cs.berkeley.edu>
  */
 
 #include <kmalloc.h>
 #include <multiboot.h>
 
-/*
- * Initialize the memory free lists.
- * After this point, ONLY use the functions below
- * to allocate and deallocate physical memory via the
- * page_free_lists.
- */
-void page_alloc_init(struct multiboot_info *mbi)
+void base_arena_init(struct multiboot_info *mbi)
 {
-       init_once_racy(return);
+       void *base_pg;
+       uintptr_t first_free_page, first_invalid_page;
+
+       /* Need to do the boot-allocs before our last look at the top of
+        * boot_freemem. */
+       base_pg = boot_alloc(PGSIZE, PGSHIFT);
 
-       uintptr_t first_free_page = ROUNDUP(boot_freemem, PGSIZE);
-       uintptr_t first_invalid_page = LA2PPN(boot_freelimit);
-       assert(first_invalid_page == max_nr_pages);
+       first_free_page = ROUNDUP(boot_freemem, PGSIZE);
+       first_invalid_page = ROUNDUP(boot_freelimit, PGSIZE);
+       assert(first_invalid_page == max_nr_pages * PGSIZE);
 
-       // append other pages to the free lists
-       for (uintptr_t page = first_free_page; page < first_invalid_page; page++)
-       {
-               BSD_LIST_INSERT_HEAD(&page_free_list, &pages[page], pg_link);
-               &pages[page]->pg_is_free = TRUE;
-       }
-       nr_free_pages = first_invalid_page - first_free_page;
+       base_arena = arena_builder(base_pg, "base", PGSIZE, NULL, NULL, NULL,
+                                  0);
+       arena_add(base_arena, KADDR(first_free_page),
+                 first_invalid_page - first_free_page, MEM_WAIT);
 }
index 6e2c939..e0161cb 100644 (file)
-/* Copyright (c) 2009 The Regents of the University  of California.
- * See the COPYRIGHT files at the top of this source tree for full
- * license information.
- *
+/* Copyright (c) 2013 The Regents of the University of California.
+ * Copyright (c) 2016 Google Inc
  * Barret Rhoden <brho@cs.berkeley.edu>
- * Kevin Klues <klueska@cs.berkeley.edu> */
+ * See LICENSE for details. */
 
-#include <sys/queue.h>
 #include <page_alloc.h>
 #include <pmap.h>
 #include <kmalloc.h>
 #include <multiboot.h>
+#include <arena.h>
 
-/* Can do whatever here.  For now, our page allocator just works with colors,
- * not NUMA zones or anything. */
-static void track_free_page(struct page *page)
-{
-       BSD_LIST_INSERT_HEAD(&page_free_list, page, pg_link);
-       nr_free_pages++;
-       page->pg_is_free = TRUE;
-}
-
-static struct page *pa64_to_page(uint64_t paddr)
-{
-       return &pages[paddr >> PGSHIFT];
-}
-
-static bool pa64_is_in_kernel(uint64_t paddr)
-{
-       extern char end[];
-       /* kernel is linked and loaded here (in kernel{32,64}.ld */
-       return (EXTPHYSMEM <= paddr) && (paddr < PADDR(end));
-}
-
-/* Helper.  For every page in the entry, this will determine whether or not the
- * page is free, and handle accordingly.  All pages are marked as busy by
- * default, and we're just determining which of them could be free. */
+/* Helper.  Adds free entries to the base arena.  Most entries are page aligned,
+ * though on some machines below EXTPHYSMEM we may have some that aren't. */
 static void parse_mboot_region(struct multiboot_mmap_entry *entry, void *data)
 {
        physaddr_t boot_freemem_paddr = (physaddr_t)data;
-       bool in_bootzone = (entry->addr <= boot_freemem_paddr) &&
-                          (boot_freemem_paddr < entry->addr + entry->len);
+       physaddr_t start = entry->addr;
+       size_t len = entry->len;
+       extern char end[];
 
        if (entry->type != MULTIBOOT_MEMORY_AVAILABLE)
                return;
-       /* TODO: we'll have some issues with jumbo allocation */
-       /* Most entries are page aligned, though on some machines below EXTPHYSMEM
-        * we may have some that aren't.  If two regions collide on the same page
-        * (one of them starts unaligned), we need to only handle the page once, and
-        * err on the side of being busy.
-        *
-        * Since these regions happen below EXTPHYSMEM, they are all marked busy (or
-        * else we'll panic).  I'll probably rewrite this for jumbos before I find a
-        * machine with unaligned mboot entries in higher memory. */
-       if (PGOFF(entry->addr))
-               assert(entry->addr < EXTPHYSMEM);
-       for (uint64_t i = ROUNDDOWN(entry->addr, PGSIZE);
-            i < entry->addr + entry->len;
-            i += PGSIZE) {
-               /* Skip pages we'll never map (above KERNBASE).  Once we hit one of
-                * them, we know the rest are too (for this entry). */
-               if (i >= max_paddr)
-                       return;
-               /* Mark low mem as busy (multiboot stuff is there, usually, too).  Since
-                * that memory may be freed later (like the smp_boot page), we'll treat
-                * it like it is busy/allocated. */
-               if (i < EXTPHYSMEM)
-                       continue;
-               /* Mark as busy pages already allocated in boot_alloc() */
-               if (in_bootzone && (i < boot_freemem_paddr))
-                       continue;
-               /* Need to double check for the kernel, in case it wasn't in the
-                * bootzone.  If it was in the bootzone, we already skipped it. */
-               if (pa64_is_in_kernel(i))
-                       continue;
-               track_free_page(pa64_to_page(i));
-       }
-}
-
-/* Expect == 1 -> busy, 0 -> free */
-static void check_range(uint64_t start, uint64_t end, int expect)
-{
-       int free;
-
-       if (PGOFF(start))
-               printk("Warning: check_range given unaligned addr 0x%016llx\n", start);
-       for (uint64_t i = start; i < end; i += PGSIZE)  {
-               free = pa64_to_page(i)->pg_is_free ? 0 : 1;
-               if (free != expect) {
-                       printk("Error: while checking range [0x%016llx, 0x%016llx), "
-                              "physaddr 0x%016llx free was %d, expected %d\n", start,
-                              end, i, free, expect);
-                       panic("");
-               }
-       }
-}
-
-/* Note this doesn't check all of memory.  There are some chunks of 'memory'
- * that aren't reported by MB at all, like the VRAM sections at 0xa0000. */
-static void check_mboot_region(struct multiboot_mmap_entry *entry, void *data)
-{
-       extern char end[];
-       physaddr_t boot_freemem_paddr = (physaddr_t)data;
-       bool in_bootzone = (entry->addr <= boot_freemem_paddr) &&
-                          (boot_freemem_paddr < entry->addr + entry->len);
-       /* Need to deal with 32b wrap-around */
-       uint64_t zone_end = MIN(entry->addr + entry->len, (uint64_t)max_paddr);
-
-       if (entry->type != MULTIBOOT_MEMORY_AVAILABLE) {
-               check_range(entry->addr, zone_end, 1);
+       /* Skip anything over max_paddr - might be bad entries(?) */
+       if (start >= max_paddr)
                return;
-       }
-       if (zone_end <= EXTPHYSMEM) {
-               check_range(entry->addr, zone_end, 1);
+       if (start + len > max_paddr)
+               len = max_paddr - start;
+       /* For paranoia, skip anything below EXTPHYSMEM.  If we ever change this,
+        * we'll need to deal with smp_boot's trampoline page. */
+       if ((start < EXTPHYSMEM) && (start + len < EXTPHYSMEM))
                return;
+       if ((start < EXTPHYSMEM) && (EXTPHYSMEM <= start + len)) {
+               len = start + len - EXTPHYSMEM;
+               start = EXTPHYSMEM;
        }
-       /* this may include the kernel */
-       if (in_bootzone) {
-               /* boot_freemem might not be page aligned.  If it's part-way through a
-                * page, that page should be busy */
-               check_range(entry->addr, ROUNDUP(PADDR(boot_freemem), PGSIZE), 1);
-               check_range(ROUNDUP(PADDR(boot_freemem), PGSIZE), zone_end, 0);
-               assert(zone_end == PADDR(boot_freelimit));
-               return;
+       /* Skip over any pages already allocated in boot_alloc().
+        * (boot_freemem_paddr is the next free addr.) */
+       if ((start < boot_freemem_paddr) && (boot_freemem_paddr <= start + len)) {
+               len = start + len - boot_freemem_paddr;
+               start = boot_freemem_paddr;
        }
-       /* kernel's range (hardcoded in the linker script).  If we're checking now,
-        * it means the kernel is not in the same entry as the bootzone. */
-       if (entry->addr == EXTPHYSMEM) {
-               check_range(EXTPHYSMEM, PADDR(end), 1);
-               check_range(ROUNDUP(PADDR(end), PGSIZE), zone_end, 0);
-               return;
+       /* Skip any part that intersects with the kernel, which is linked and loaded
+        * from EXTPHYSMEM to end in kernel64.ld */
+       if (regions_collide_unsafe(EXTPHYSMEM, PADDR(end), start, start + len)) {
+               len = start + len - PADDR(end);
+               start = PADDR(end);
        }
+       /* We need to give the arena PGSIZE-quantum segments. */
+       if (PGOFF(start)) {
+               len -= PGOFF(start);
+               start = ROUNDUP(start, PGSIZE);
+       }
+       len = ROUNDDOWN(len, PGSIZE);
+       if (!len)
+               return;
+       arena_add(base_arena, KADDR(start), len, MEM_WAIT);
 }
 
 /* Since we can't parse multiboot mmap entries, we need to just guess at what
@@ -156,10 +80,6 @@ static void check_mboot_region(struct multiboot_mmap_entry *entry, void *data)
  *
  * where TOP_OF_1 is the min of IOAPIC_PBASE and max_paddr.
  *
- * For the busy regions, I don't actually need to mark the pages as busy.  They
- * were marked busy when the pages array was created (same as when we parse
- * multiboot info).  I'll just assert that they are properly marked as busy.
- *
  * As with parsing mbi regions, this will ignore the hairy areas below
  * EXTPHYSMEM, and mark the entire kernel and anything we've boot alloc'd as
  * busy. */
@@ -170,10 +90,8 @@ static void account_for_pages(physaddr_t boot_freemem_paddr)
        physaddr_t start_of_free_2;
 
        printk("Warning: poor memory detection (qemu?).  May lose 1GB of RAM\n");
-       for (physaddr_t i = 0; i < top_of_busy; i += PGSIZE)
-               assert(!pa64_to_page(i)->pg_is_free);
-       for (physaddr_t i = top_of_busy; i < top_of_free_1; i += PGSIZE)
-               track_free_page(pa64_to_page(i));
+       arena_add(base_arena, KADDR(top_of_busy), top_of_free_1 - top_of_busy,
+                 MEM_WAIT);
        /* If max_paddr is less than the start of our potential second free mem
         * region, we can just leave.  We also don't want to poke around the pages
         * array either (and accidentally run off the end of the array).
@@ -182,14 +100,13 @@ static void account_for_pages(physaddr_t boot_freemem_paddr)
        start_of_free_2 = 0x0000000100000000;
        if (max_paddr < start_of_free_2)
                return;
-       for (physaddr_t i = top_of_free_1; i < start_of_free_2; i += PGSIZE)
-               assert(!pa64_to_page(i)->pg_is_free);
-       for (physaddr_t i = start_of_free_2; i < max_paddr; i += PGSIZE)
-               track_free_page(pa64_to_page(i));
+       arena_add(base_arena, KADDR(start_of_free_2), max_paddr - start_of_free_2,
+                 MEM_WAIT);
 }
 
-/* Initialize the memory free lists.  After this, do not use boot_alloc. */
-void page_alloc_init(struct multiboot_info *mbi)
+/* Initialize base arena based on available free memory.  After this, do not use
+ * boot_alloc. */
+void base_arena_init(struct multiboot_info *mbi)
 {
        /* First, all memory is busy / not free by default.
         *
@@ -203,21 +120,20 @@ void page_alloc_init(struct multiboot_info *mbi)
         * we'll never use them.  'pages' does not track them either.
         *
         * One special note: we actually use the memory at 0x1000 for smp_boot.
-        * It'll get set to 'used' like the others; just FYI.
-        *
-        * Finally, if we want to use actual jumbo page allocation (not just
-        * mapping), we need to round up _end, and make sure all of multiboot's
-        * sections are jumbo-aligned. */
-       physaddr_t boot_freemem_paddr = PADDR(ROUNDUP(boot_freemem, PGSIZE));
+        * It'll never get freed; just FYI. */
+       physaddr_t boot_freemem_paddr;
+       void *base_pg;
 
+       /* Need to do the boot-allocs before our last look at the top of
+        * boot_freemem. */
+       base_pg = boot_alloc(PGSIZE, PGSHIFT);
+       base_arena = arena_builder(base_pg, "base", PGSIZE, NULL, NULL, NULL,
+                                  0);
+       boot_freemem_paddr = PADDR(ROUNDUP(boot_freemem, PGSIZE));
        if (mboot_has_mmaps(mbi)) {
                mboot_foreach_mmap(mbi, parse_mboot_region, (void*)boot_freemem_paddr);
-               /* Test the page alloc - if this gets slow, we can CONFIG it */
-               mboot_foreach_mmap(mbi, check_mboot_region, (void*)boot_freemem_paddr);
        } else {
                /* No multiboot mmap regions (probably run from qemu with -kernel) */
                account_for_pages(boot_freemem_paddr);
        }
-       printk("Number of free pages: %lu\n", nr_free_pages);
-       printk("Page alloc init successful\n");
 }
index ecbddf3..3852d9d 100644 (file)
@@ -56,13 +56,19 @@ extern spinlock_t page_list_lock;
 extern page_list_t page_free_list;
 
 /*************** Functional Interface *******************/
-void page_alloc_init(struct multiboot_info *mbi);
+void base_arena_init(struct multiboot_info *mbi);
 
 error_t upage_alloc(struct proc *p, page_t **page, bool zero);
 error_t kpage_alloc(page_t **page);
 void *kpage_alloc_addr(void);
 void *kpage_zalloc_addr(void);
 
+/* Direct allocation from the kpages arena (instead of kmalloc).  These will
+ * give you PGSIZE quantum. */
+void *kpages_alloc(size_t size, int flags);
+void *kpages_zalloc(size_t size, int flags);
+void kpages_free(void *addr, size_t size);
+
 void *get_cont_pages(size_t order, int flags);
 void *get_cont_pages_node(int node, size_t order, int flags);
 void free_cont_pages(void *buf, size_t order);
index 2e7f31d..6747a44 100644 (file)
@@ -1,64 +1,24 @@
-/* Copyright (c) 2009, 2010 The Regents of the University  of California.
- * See the COPYRIGHT files at the top of this source tree for full
- * license information.
+/* Copyright (c) 2009, 2010 The Regents of the University of California.
+ * Copyright (c) 2016 Google Inc
+ * See LICENSE for details.
  *
- * Kevin Klues <klueska@cs.berkeley.edu>
- * Barret Rhoden <brho@cs.berkeley.edu> */
+ * Barret Rhoden <brho@cs.berkeley.edu>
+ * Kevin Klues <klueska@cs.berkeley.edu> */
 
-#include <ros/errno.h>
-#include <sys/queue.h>
-#include <bitmask.h>
 #include <page_alloc.h>
 #include <pmap.h>
-#include <err.h>
-#include <string.h>
 #include <kmalloc.h>
-#include <blockdev.h>
-
-spinlock_t page_list_lock = SPINLOCK_INITIALIZER_IRQSAVE;
-
-page_list_t page_free_list = BSD_LIST_HEAD_INITIALIZER(page_free_list);
-
-static void __page_decref(page_t *page);
-static error_t __page_alloc_specific(page_t **page, size_t ppn);
-
-/* Initializes a page.  We can optimize this a bit since 0 usually works to init
- * most structures, but we'll hold off on that til it is a problem. */
-static void __page_init(struct page *page)
-{
-       memset(page, 0, sizeof(page_t));
-       sem_init(&page->pg_sem, 0);
-       page->pg_is_free = FALSE;
-}
-
-static void __real_page_alloc(struct page *page)
-{
-       BSD_LIST_REMOVE(page, pg_link);
-       __page_init(page);
-}
-
-/* Internal version of page_alloc_specific.  Grab the lock first. */
-static error_t __page_alloc_specific(page_t** page, size_t ppn)
-{
-       page_t* sp_page = ppn2page(ppn);
-       if (!page_is_free(ppn))
-               return -ENOMEM;
-       *page = sp_page;
-       __real_page_alloc(sp_page);
-       return 0;
-}
+#include <arena.h>
 
 /* Helper, allocates a free page. */
 static struct page *get_a_free_page(void)
 {
-       struct page *ret;
+       void *addr;
 
-       spin_lock_irqsave(&page_list_lock);
-       ret = BSD_LIST_FIRST(&page_free_list);
-       if (ret)
-               __real_page_alloc(ret);
-       spin_unlock_irqsave(&page_list_lock);
-       return ret;
+       addr = kpages_alloc(PGSIZE, MEM_ATOMIC);
+       if (!addr)
+               return NULL;
+       return kva2page(addr);
 }
 
 /**
@@ -113,107 +73,47 @@ void *kpage_zalloc_addr(void)
        return retval;
 }
 
-/**
- * @brief Allocated 2^order contiguous physical pages.  Will increment the
- * reference count for the pages.
- *
- * @param[in] order order of the allocation
- * @param[in] flags memory allocation flags
- *
- * @return The KVA of the first page, NULL otherwise.
- */
-void *get_cont_pages(size_t order, int flags)
+/* Helper function for allocating from the kpages_arena.  This may be useful
+ * later since we might send the caller to a different NUMA domain. */
+void *kpages_alloc(size_t size, int flags)
+{
+       return arena_alloc(kpages_arena, size, flags);
+}
+
+void *kpages_zalloc(size_t size, int flags)
 {
-       size_t npages = 1 << order;
+       void *ret = arena_alloc(kpages_arena, size, flags);
 
-       size_t naddrpages = max_paddr / PGSIZE;
-       // Find 'npages' free consecutive pages
-       int first = -1;
-       spin_lock_irqsave(&page_list_lock);
-       for(int i=(naddrpages-1); i>=(npages-1); i--) {
-               int j;
-               for(j=i; j>=(i-(npages-1)); j--) {
-                       if( !page_is_free(j) ) {
-                               /* i will be j - 1 next time around the outer loop */
-                               i = j;
-                               break;
-                       }
-               }
-               /* careful: if we change the allocator and allow npages = 0, then this
-                * will trip when we set i = j.  then we'll be handing out in-use
-                * memory. */
-               if( j == (i-(npages-1)-1)) {
-                       first = j+1;
-                       break;
-               }
-       }
-       //If we couldn't find them, return NULL
-       if( first == -1 ) {
-               spin_unlock_irqsave(&page_list_lock);
-               if (flags & MEM_ERROR)
-                       error(ENOMEM, ERROR_FIXME);
+       if (!ret)
                return NULL;
-       }
-
-       for(int i=0; i<npages; i++) {
-               page_t* page;
-               __page_alloc_specific(&page, first+i);
-       }
-       spin_unlock_irqsave(&page_list_lock);
-       return ppn2kva(first);
+       memset(ret, 0, size);
+       return ret;
 }
 
-/**
- * @brief Allocated 2^order contiguous physical pages.  Will increment the
- * reference count for the pages. Get them from NUMA node node.
- *
- * @param[in] node which node to allocate from. Unimplemented.
- * @param[in] order order of the allocation
- * @param[in] flags memory allocation flags
- *
- * @return The KVA of the first page, NULL otherwise.
- */
-void *get_cont_pages_node(int node, size_t order, int flags)
+void kpages_free(void *addr, size_t size)
 {
-       return get_cont_pages(order, flags);
+       arena_free(kpages_arena, addr, size);
 }
 
-void free_cont_pages(void *buf, size_t order)
+void *get_cont_pages(size_t order, int flags)
 {
-       size_t npages = 1 << order;
-       spin_lock_irqsave(&page_list_lock);
-       for (size_t i = kva2ppn(buf); i < kva2ppn(buf) + npages; i++) {
-               page_t* page = ppn2page(i);
-               __page_decref(ppn2page(i));
-               assert(page_is_free(i));
-       }
-       spin_unlock_irqsave(&page_list_lock);
-       return;
+       return kpages_alloc(PGSIZE << order, flags);
 }
 
-/* Check if a page with the given physical page # is free. */
-int page_is_free(size_t ppn)
+void *get_cont_pages_node(int node, size_t order, int flags)
 {
-       return ppn2page(ppn)->pg_is_free;
+       return get_cont_pages(order, flags);
 }
 
-/* Frees the page */
-void page_decref(page_t *page)
+void free_cont_pages(void *buf, size_t order)
 {
-       spin_lock_irqsave(&page_list_lock);
-       __page_decref(page);
-       spin_unlock_irqsave(&page_list_lock);
+       kpages_free(buf, PGSIZE << order);
 }
 
-/* Frees the page.  Don't call this without holding the lock already. */
-static void __page_decref(page_t *page)
+/* Frees the page */
+void page_decref(page_t *page)
 {
-       if (atomic_read(&page->pg_flags) & PG_BUFFER)
-               free_bhs(page);
-       /* Give our page back to the free list.  The protections for this are that
-        * the list lock is grabbed by page_decref. */
-       BSD_LIST_INSERT_HEAD(&page_free_list, page, pg_link);
-       page->pg_is_free = TRUE;
+       kpages_free(page2kva(page), PGSIZE);
 }
 
 /* Attempts to get a lock on the page for IO operations.  If it is already
@@ -234,35 +134,3 @@ void unlock_page(struct page *page)
        atomic_and(&page->pg_flags, ~PG_LOCKED);
        sem_up(&page->pg_sem);
 }
-
-void print_pageinfo(struct page *page)
-{
-       int i;
-       if (!page) {
-               printk("Null page\n");
-               return;
-       }
-       printk("Page %d (%p), Flags: 0x%08x Is Free: %d\n", page2ppn(page),
-              page2kva(page), atomic_read(&page->pg_flags),
-              page->pg_is_free);
-       if (page->pg_mapping) {
-               printk("\tMapped into object %p at index %d\n",
-                      page->pg_mapping->pm_host, page->pg_index);
-       }
-       if (atomic_read(&page->pg_flags) & PG_BUFFER) {
-               struct buffer_head *bh = (struct buffer_head*)page->pg_private;
-               i = 0;
-               while (bh) {
-                       printk("\tBH %d: buffer: %p, sector: %d, nr_sector: %d\n", i,
-                              bh->bh_buffer, bh->bh_sector, bh->bh_nr_sector);
-                       i++;
-                       bh = bh->bh_next;
-               }
-               printk("\tPage is %sup to date\n",
-                      atomic_read(&page->pg_flags) & PG_UPTODATE ? "" : "not ");
-       }
-       printk("\tPage is %slocked\n",
-              atomic_read(&page->pg_flags) & PG_LOCKED ? "" : "un");
-       printk("\tPage is %s\n",
-              atomic_read(&page->pg_flags) & PG_DIRTY ? "dirty" : "clean");
-}
index 557738a..cbb6c86 100644 (file)
 #include <stdio.h>
 #include <mm.h>
 #include <multiboot.h>
+#include <arena.h>
 
 physaddr_t max_pmem = 0;       /* Total amount of physical memory (bytes) */
 physaddr_t max_paddr = 0;      /* Maximum addressable physical address */
 size_t max_nr_pages = 0;       /* Number of addressable physical memory pages */
-size_t nr_free_pages = 0;      /* TODO: actually track this, after init */
 struct page *pages = 0;
 struct multiboot_info *multiboot_kaddr = 0;
 uintptr_t boot_freemem = 0;
@@ -45,10 +45,18 @@ static void adjust_max_pmem(struct multiboot_mmap_entry *entry, void *data)
        max_pmem = MAX(max_pmem, (size_t)(entry->addr + entry->len));
 }
 
+static void kpages_arena_init(void)
+{
+       void *kpages_pg;
+
+       kpages_pg = arena_alloc(base_arena, PGSIZE, MEM_WAIT);
+       kpages_arena = arena_builder(kpages_pg, "kpages", PGSIZE, arena_alloc,
+                                    arena_free, base_arena, 8 * PGSIZE);
+}
+
 /**
  * @brief Initializes physical memory.  Determines the pmem layout, sets up the
- * array of physical pages and memory free list, and turns on virtual
- * memory/page tables.
+ * base and kpages arenas, and turns on virtual memory/page tables.
  *
  * Regarding max_pmem vs max_paddr and max_nr_pages: max_pmem is the largest
  * physical address that is in a FREE region.  It includes RESERVED regions that
@@ -72,9 +80,13 @@ void pmem_init(struct multiboot_info *mbi)
        printk("Max physical RAM (appx, bytes): %lu\n", max_pmem);
        printk("Max addressable physical RAM (appx): %lu\n", max_paddr);
        printk("Highest page number (including reserved): %lu\n", max_nr_pages);
+       /* We should init the page structs, but zeroing happens to work, since the
+        * sems are not irqsave. */
        pages = (struct page*)boot_zalloc(max_nr_pages * sizeof(struct page),
                                          PGSIZE);
-       page_alloc_init(mbi);
+       base_arena_init(mbi);
+       kpages_arena_init();
+       printk("Base arena total mem: %lu\n", arena_amt_total(base_arena));
        vm_init();
 
        static_assert(PROCINFO_NUM_PAGES*PGSIZE <= PTSIZE);
@@ -327,25 +339,3 @@ bool regions_collide_unsafe(uintptr_t start1, uintptr_t end1,
                return TRUE;
        }
 }
-
-void print_free_mem(void)
-{
-       static uint8_t *bm = 0;
-       /* racy, but this is debugging code */
-       if (!bm)
-               bm = kzmalloc((max_nr_pages + 1) / 8, 0);
-
-       long x = 0;
-       for (int i = 0; i < max_nr_pages; i++) {
-               if (page_is_free(i)) {
-                       x++;
-                       SET_BITMASK_BIT(bm, i);
-               } else {
-                       if (GET_BITMASK_BIT(bm, i)) {
-                               print_pageinfo(ppn2page(i));
-                               CLR_BITMASK_BIT(bm, i);
-                       }
-               }
-       }
-       printk("Nr Free pages: %lld\n", x);
-}