Add support for dynamic per-cpu variables
authorBarret Rhoden <brho@cs.berkeley.edu>
Fri, 4 May 2018 21:51:01 +0000 (17:51 -0400)
committerBarret Rhoden <brho@cs.berkeley.edu>
Tue, 22 May 2018 21:51:44 +0000 (17:51 -0400)
They are a little hokey.  You get a pointer back, which you have to
dereference each time you use it, even when you want to get a pointer to
a specific core's pointer.  That's different than Linux.  e.g.

u64 *foo = percpu_alloc(u64, MEM_WAIT);
u64 *core_3_ptr = _PERCPU_VARPTR(*foo, 3);

This is because __PERCPU_OFFSET computes &(var), which is how the static
stuff works.  We need to compute the offset from whatever our allocator got
back, which foo, so we need to balance the & with the *.  Not ideal.

Also, there is a limited amount of space - 1024 for now.  We could do
better with a virtual memory region:
- alloc vaddrs for the right size (2x previous)
- map that to the paddr of the previous, existing space
- map the new space (1x previous) above the existing space
- WRITE_ONCE percpu_base.
- maybe free old vaddr after a grace period, which would imply all per-cpu
  accessors are RCU read-side critical sections.

Not worth doing, for now.  It might be a pain to shrink it too.

The right approach might be to port more of Linux's percpu mechanisms.

Signed-off-by: Barret Rhoden <brho@cs.berkeley.edu>
kern/include/percpu.h
kern/src/ktest/Kconfig.postboot
kern/src/ktest/pb_ktests.c
kern/src/percpu.c

index 2397a0f..32d4785 100644 (file)
@@ -2,6 +2,8 @@
  * Davide Libenzi <dlibenzi@google.com>
  * See LICENSE for details.
  *
+ * Static percpu variables:
+ *
  * The per CPU utility macros allow file local declaration of per CPU variables.
  * When a struct my_struct needs to have a per CPU instance, one would declare
  * something like:
  *           // Initialize ptr data
  *       }
  *   }
+ *
+ *
+ * Dynamic percpu variables:
+ *
+ * You can also declare per-cpu variables dynamically, though it's not quite the
+ * same as the static variables.  Careful - We return *pointers*, and our users
+ * need to dereference them when using any of the PERCPU_ helpers.
+ *
+ * Example (per core u64s)  Note each *use* dereferences 'foos':
+ *
+ * uint64_t *foos = percpu_zalloc(uint64_t, MEM_WAIT);
+ *
+ * // Each core increments
+ * PERCPU_VAR(*foos)++;
+ *
+ * // One core can print them all out
+ * for_each_core(i)
+ *             printk("Addr %p, value %lu\n", _PERCPU_VARPTR(*foos, i),
+ *                    _PERCPU_VAR(*foos, i));
+ *
+ * // Free, but don't deref here.  'foos' is your handle.
+ * percpu_free(foos);
  */
 
 #pragma once
 #define PERCPU_SECTION __percpu
 #define PERCPU_SECTION_STR STRINGIFY(PERCPU_SECTION)
 
-#define PERCPU_VARNAME(var) PASTE(__percpu_, var)
-
 #define PERCPU_START_VAR PASTE(__start_, PERCPU_SECTION)
 #define PERCPU_STOP_VAR PASTE(__stop_, PERCPU_SECTION)
 
-#define PERCPU_SIZE (PERCPU_STOP_VAR - PERCPU_START_VAR)
+#define PERCPU_DYN_SIZE 1024
+#define PERCPU_STATIC_SIZE (PERCPU_STOP_VAR - PERCPU_START_VAR)
+#define PERCPU_SIZE (PERCPU_STATIC_SIZE + PERCPU_DYN_SIZE)
 #define PERCPU_OFFSET(var) ((char *) &(var) - PERCPU_START_VAR)
 
 #define __PERCPU_VARPTR(var, cpu)                                                                              \
                        __cv = &var;                                                                                            \
                __cv;                                                                                                                   \
        })
-#define _PERCPU_VARPTR(var, cpu) __PERCPU_VARPTR(PERCPU_VARNAME(var), cpu)
-#define PERCPU_VARPTR(var) __PERCPU_VARPTR(PERCPU_VARNAME(var), core_id())
+#define _PERCPU_VARPTR(var, cpu) __PERCPU_VARPTR(var, cpu)
+#define PERCPU_VARPTR(var) __PERCPU_VARPTR(var, core_id())
 
-#define _PERCPU_VAR(var, cpu) (*__PERCPU_VARPTR(PERCPU_VARNAME(var), cpu))
-#define PERCPU_VAR(var) (*__PERCPU_VARPTR(PERCPU_VARNAME(var), core_id()))
+#define _PERCPU_VAR(var, cpu) (*__PERCPU_VARPTR(var, cpu))
+#define PERCPU_VAR(var) (*__PERCPU_VARPTR(var, core_id()))
 
 #define DEFINE_PERCPU(type, var)                                               \
-       __typeof__(type) PERCPU_VARNAME(var) __attribute__ ((section (PERCPU_SECTION_STR)))
+       __typeof__(type) var __attribute__ ((section (PERCPU_SECTION_STR)))
 #define DECLARE_PERCPU(type, var)                                                              \
-       extern __typeof__(type) PERCPU_VARNAME(var)                                     \
+       extern __typeof__(type) var                                                                     \
                __attribute__ ((section (PERCPU_SECTION_STR)))
 
 #define PERCPU_INIT_SECTION __percpu_init
 #define PERCPU_INIT_START_VAR PASTE(__start_, PERCPU_INIT_SECTION)
 #define PERCPU_INIT_STOP_VAR PASTE(__stop_, PERCPU_INIT_SECTION)
 
+#define PERCPU_INIT_NAME(func) PASTE(__percpu_, func)
 #define DEFINE_PERCPU_INIT(func)                                                                               \
        static void func(void);                                                                                         \
-       void (* const PERCPU_VARNAME(func))(void)                                                       \
+       void (* const PERCPU_INIT_NAME(func))(void)                                                     \
                __attribute__ ((section (PERCPU_INIT_SECTION_STR))) = (func)
 
 extern char __attribute__((weak)) PERCPU_START_VAR[];
@@ -85,3 +110,12 @@ extern char __attribute__((weak)) PERCPU_STOP_VAR[];
 extern char *percpu_base;
 
 void percpu_init(void);
+
+#define percpu_alloc(x, flags) __percpu_alloc(sizeof(x), __alignof__(x), flags)
+#define percpu_zalloc(x, flags) __percpu_zalloc(sizeof(x), __alignof__(x), \
+                                                flags)
+#define percpu_free(x) __percpu_free(x, sizeof(*x))
+
+void *__percpu_alloc(size_t size, size_t align, int flags);
+void *__percpu_zalloc(size_t size, size_t align, int flags);
+void __percpu_free(void *base, size_t size);
index b92b426..a7ae670 100644 (file)
@@ -260,3 +260,13 @@ config TEST_cmdline_parse
     depends on PB_KTESTS
     bool "Tests command line parsing functions"
     default y
+
+config TEST_percpu_zalloc
+    depends on PB_KTESTS
+    bool "percpu dynamic zalloc"
+    default y
+
+config TEST_percpu_increment
+    depends on PB_KTESTS
+    bool "percpu dynamic alloc: increment"
+    default y
index 34dccb8..397a759 100644 (file)
@@ -1936,6 +1936,84 @@ bool test_cmdline_parse(void)
        return TRUE;
 }
 
+static bool __pcpu_ptr_is_dyn(void *ptr)
+{
+       char *p_c = ptr;
+
+       return (PERCPU_STOP_VAR <= p_c) &&
+              (p_c < PERCPU_STOP_VAR + PERCPU_DYN_SIZE);
+}
+
+static bool test_percpu_zalloc(void)
+{
+       uint8_t *u8 = percpu_zalloc(*u8, MEM_WAIT);
+       uint64_t *u64 = percpu_zalloc(uint64_t, MEM_WAIT);
+       uint32_t *u32 = percpu_zalloc(uint32_t, MEM_WAIT);
+       uint64_t *old_u64;
+
+       KT_ASSERT(__pcpu_ptr_is_dyn(u8));
+       KT_ASSERT(__pcpu_ptr_is_dyn(u64));
+       KT_ASSERT(__pcpu_ptr_is_dyn(u32));
+
+       /* The order here is a bit hokey too - the first alloc is usually 16 byte
+        * aligned, so if we did a packed alloc, the u64 wouldn't be aligned. */
+       KT_ASSERT(ALIGNED(u8, __alignof__(*u8)));
+       KT_ASSERT(ALIGNED(u64, __alignof__(*u64)));
+       KT_ASSERT(ALIGNED(u32, __alignof__(*u32)));
+
+       /* Testing zalloc.  Though the first alloc ever is likely to be zero. */
+       for_each_core(i)
+               KT_ASSERT(_PERCPU_VAR(*u64, i) == 0);
+       for_each_core(i)
+               _PERCPU_VAR(*u64, i) = i;
+       for_each_core(i)
+               KT_ASSERT(_PERCPU_VAR(*u64, i) == i);
+       /* If we free and realloc, we're likely to get the same one.  This is due
+        * to the ARENA_BESTFIT policy with xalloc. */
+       old_u64 = u64;
+       percpu_free(u64);
+       u64 = percpu_zalloc(uint64_t, MEM_WAIT);
+       /* If this trips, then we didn't test this as well as we'd like. */
+       warn_on(u64 != old_u64);
+       for_each_core(i)
+               KT_ASSERT(_PERCPU_VAR(*u64, i) == 0);
+
+       /* Yes, if an assert failed, we leak memory. */
+       percpu_free(u8);
+       percpu_free(u64);
+       percpu_free(u32);
+       return true;
+}
+
+static void __inc_foo(uint32_t srcid, long a0, long a1, long a2)
+{
+       uint64_t *foos = (uint64_t*)a0;
+       atomic_t *check_in_p = (atomic_t*)a1;
+
+       for (int i = 0; i < core_id() + 1; i++)
+               PERCPU_VAR(*foos)++;
+       cmb();
+       atomic_dec(check_in_p);
+}
+
+static bool test_percpu_increment(void)
+{
+       uint64_t *foos = percpu_zalloc(uint64_t, MEM_WAIT);
+       atomic_t check_in;
+
+       atomic_set(&check_in, num_cores);
+       for_each_core(i)
+               send_kernel_message(i, __inc_foo, (long)foos, (long)&check_in, 0,
+                                   KMSG_IMMEDIATE);
+       while (atomic_read(&check_in))
+               cpu_relax();
+       for_each_core(i)
+               KT_ASSERT(_PERCPU_VAR(*foos, i) == i + 1);
+       /* Yes, if an assert failed, we leak memory. */
+       percpu_free(foos);
+       return true;
+}
+
 static struct ktest ktests[] = {
 #ifdef CONFIG_X86
        KTEST_REG(ipi_sending,        CONFIG_TEST_ipi_sending),
@@ -1970,6 +2048,8 @@ static struct ktest ktests[] = {
        KTEST_REG(uaccess,            CONFIG_TEST_uaccess),
        KTEST_REG(sort,               CONFIG_TEST_sort),
        KTEST_REG(cmdline_parse,      CONFIG_TEST_cmdline_parse),
+       KTEST_REG(percpu_zalloc,      CONFIG_TEST_percpu_zalloc),
+       KTEST_REG(percpu_increment,   CONFIG_TEST_percpu_increment),
 };
 static int num_ktests = sizeof(ktests) / sizeof(struct ktest);
 linker_func_1(register_pb_ktests)
index 65806ac..12af818 100644 (file)
 #include <assert.h>
 #include <string.h>
 #include <percpu.h>
+#include <arena.h>
+#include <page_alloc.h>
+#include <smp.h>
 
 char *percpu_base;
+static struct arena *pcpu_dyn_arena;
 
 static void run_init_functions(void)
 {
@@ -30,13 +34,48 @@ static void run_init_functions(void)
 void percpu_init(void)
 {
        assert(num_cores > 0);
-       percpu_base = kmalloc(num_cores * PERCPU_SIZE, 0);
-       assert(percpu_base);
 
+       percpu_base = kpages_alloc(num_cores * PERCPU_SIZE, MEM_WAIT);
        if (PERCPU_START_VAR) {
                for (int i = 0; i < num_cores; i++)
                        memcpy(percpu_base + i * PERCPU_SIZE, PERCPU_START_VAR,
-                                  PERCPU_SIZE);
+                                  PERCPU_STATIC_SIZE);
        }
+       /* We hand out addresses starting right above the static section, which ends
+        * at PERCPU_STOP_VAR. */
+       pcpu_dyn_arena = arena_create("pcpu_dyn", PERCPU_STOP_VAR, PERCPU_DYN_SIZE,
+                                     1, NULL, NULL, NULL, 0, MEM_WAIT);
+       assert(pcpu_dyn_arena);
        run_init_functions();
 }
+
+/* We return pointers, but our users need to dereference them when using any of
+ * the PERCPU_ helpers so that they are treated like the static vars. */
+void *__percpu_alloc(size_t size, size_t align, int flags)
+{
+       assert(pcpu_dyn_arena);
+       /* our alignment is limited to the alignment of percpu_base */
+       warn_on(align > PGSIZE);
+       return arena_xalloc(pcpu_dyn_arena, size, align, 0, 0, NULL, NULL,
+                           flags | ARENA_BESTFIT);
+}
+
+void *__percpu_zalloc(size_t size, size_t align, int flags)
+{
+       /* Yikes! */
+       struct {
+               uint8_t data[size];
+       } *ret;
+
+       ret = __percpu_alloc(size, align, flags);
+       if (!ret)
+               return NULL;
+       for_each_core(i)
+               memset(_PERCPU_VARPTR(*ret, i), 0, size);
+       return ret;
+}
+
+void __percpu_free(void *base, size_t size)
+{
+       arena_free(pcpu_dyn_arena, base, size);
+}