Add fullmem test
authorJack Miller <jack@codezen.org>
Wed, 13 Apr 2016 04:23:48 +0000 (23:23 -0500)
committerJack Miller <jack@codezen.org>
Wed, 13 Apr 2016 06:00:17 +0000 (01:00 -0500)
A decent stress test for the page allocator, vsalloc, and objcache. It's
rather intensive so it's default off, but it runs the page allocator
until it's dry, recording every page into an objcache, and then reverses
course.

include/page_alloc.h
kernel/main.c
mm/page_alloc.c

index cc62460..eb1deba 100644 (file)
@@ -26,8 +26,12 @@ void *page_vrealloc(void *virtual, u32 cur_pages, u32 want_pages);
 
 #ifdef TEST_PAGE_ALLOC
 void test_page_alloc(u64 max_pages);
-void test_fullmem(void);
 #else
 #define test_page_alloc(...)
+#endif
+
+#ifdef TEST_FULLMEM
+void test_fullmem(void);
+#else
 #define test_fullmem(...)
 #endif
index 9efb168..b2193c3 100644 (file)
@@ -34,8 +34,8 @@ void main(void *info_phys, unsigned long end_structures)
     /* ACPI init probes ACPI tables and generates smp_wake calls */
     acpi_init();
 
-    test_fullmem();
     test_objcache();
+    test_fullmem();
 
  asm("sti": :);
 
index c425865..59c9bb6 100644 (file)
@@ -976,13 +976,130 @@ void test_page_alloc(u64 max_pages)
         used_pages[i] = NULL;
     }
 }
+#endif
+
+#ifdef TEST_FULLMEM
 
-/* This set of tests is run after vsalloc is up, so we need to test the overall
- * infrastructure in this file, instead of just the physical page allocator
+/* The fullmem test is designed to stress the page allocator, vsalloc (domain
+ * allocator), and the objcache all to the point of memory exhaustion. This
+ * test is quite intensive and very slow so it shouldn't be enabled by default.
+ *
+ * I'd really like to be able to assert that the pre free/used page count is
+ * identical to post free/used page count, but we can't. The domain allocator
+ * will never free memory. It will only use a handful of pages, and it will
+ * reuse those pages, but it won't ever return them to the allocator.
+ *
+ * Ironically, in this test, most of those allocations are done via vfree()
+ * because where vsalloc() is able to combine a ton of page allocations into a
+ * single struct, when we free, that range is split up between the pages we're
+ * freeing and the pages that were handed out to the objcache that we're not
+ * freeing until the objcache_destroy call at the end.
+ *
+ * To ensure that this is a flat cost, instead of a memory leak, this test can
+ * be run twice. The second time, vsalloc() should be expanded as far as it
+ * needs right off the bat, and so pre and post numbers should be identical.
+ * Indeed, the second run outputs something like this:
+ *
+ * free_pages : 0x3fb5a -> 0x0 -> 0x3fb5a
+ *
+ * used_pages : 0x12 -> 0x3fb6c -> 0x12
+ *
+ * Which looks just right. We could solve this by making the domain allocator
+ * free unused memory, but that would possibly happen on alloc or free and be
+ * intensive enough (since we'd be searching multiple times and potentially
+ * shifting memory around) that I don't think that's worth it to save a page or
+ * two in the actual usecase where you're *not* allocating the entire world in
+ * a single context and the memory will be freed when the context is destroyed
+ * anyway.
  */
 
+#include <objcache.h>
+
+DEFINE_OBJCACHE(fullmem_oc, u64);
+
+static u64 __num_pages(struct page_block **head)
+{
+    struct page_block *cur;
+    u64 ret = 0;
+    int i;
+
+    for(i = 0; i <= MAX_ORDER; i++) {
+        cur = head[i];
+        while (cur) {
+            ret += (1 << i);
+            cur = cur->next;
+        }
+    }
+
+    return ret;
+}
+
+#define free_pages() __num_pages(free_pages)
+#define used_pages() __num_pages(used_pages)
+
 void test_fullmem(void)
 {
+    void *virt;
+    u64 start_long = OVERHEAD_LONGS(&fullmem_oc);
+    u64 objs_per_page = (OVERHEAD_BITS(&fullmem_oc) - OVERHEAD_OBJECTS(&fullmem_oc));
+    u64 *got, *curpage;
+
+    u64 pre_free, mid_free, post_free;
+    u64 pre_used, mid_used, post_used;
+
+    int i = 0, j = 0;
+
+    printk("Starting full memory test\n");
+
+    pre_free = free_pages();
+    pre_used = used_pages();
+
+    while(1) {
+        virt = page_alloc(0);
+
+        if (!virt)
+            break;
+
+        got = objcache_get(&fullmem_oc);
+
+        if (!got) {
+            page_alloc_free(virt);
+            break;
+        }
+
+        *got = (u64) virt;
+
+        if (i % 256 == 0) /* 256 pages per MB */
+            printk("%d MB\n", i / 256);
+
+        i++;
+    }
+
+    printk("Alloc'd all mem in %d page_alloc calls\n", i);
+
+    mid_free = free_pages();
+    mid_used = used_pages();
+
+    curpage = (u64 *) fullmem_oc.start_page;
+
+    while(j < i) {
+        virt = (void *) curpage[start_long + j % objs_per_page];
+        page_alloc_free(virt);
+
+        j++;
+
+        if (j % objs_per_page == 0)
+            curpage = (u64 *) curpage[start_long - 1];
+    }
+
+    objcache_destroy(&fullmem_oc);
+
+    post_free = free_pages();
+    post_used = used_pages();
+
+    printk("free_pages : 0x%lx -> 0x%lx -> 0x%lx\n", pre_free, mid_free, post_free);
+    printk("used_pages : 0x%lx -> 0x%lx -> 0x%lx\n", pre_used, mid_used, post_used);
 
+    printk("Freed everything\n");
 }
 #endif