Commit b4f0767d authored by David Gibson's avatar David Gibson

coroutine: Stack allocation

At present, coroutine stacks must be allocated explicitly by the user,
then initialized with coroutine_stack_init().  This adds a new
coroutine_stack_alloc() function which allocates a stack, making life
easier for users.  coroutine_stack_release() will automatically determine
if the given stack was set up with _init() or alloc() and act
accordingly.

The stacks are allocate with mmap() rather than a plain malloc(), and a
guard page is added, so an overflow of the stack should result in a
relatively debuggable SEGV instead of random data corruption.
Signed-off-by: default avatarDavid Gibson <david@gibson.dropbear.id.au>
parent fe3995b4
...@@ -5,6 +5,9 @@ ...@@ -5,6 +5,9 @@
#include <inttypes.h> #include <inttypes.h>
#include <stdlib.h> #include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
#include <ccan/ptrint/ptrint.h> #include <ccan/ptrint/ptrint.h>
#include <ccan/compiler/compiler.h> #include <ccan/compiler/compiler.h>
#include <ccan/build_assert/build_assert.h> #include <ccan/build_assert/build_assert.h>
...@@ -70,23 +73,90 @@ struct coroutine_stack *coroutine_stack_init(void *buf, size_t bufsize, ...@@ -70,23 +73,90 @@ struct coroutine_stack *coroutine_stack_init(void *buf, size_t bufsize,
((char *)buf + bufsize - metasize) - 1; ((char *)buf + bufsize - metasize) - 1;
#endif #endif
stack->magic = COROUTINE_STACK_MAGIC; stack->magic = COROUTINE_STACK_MAGIC_BUF;
stack->size = size; stack->size = size;
vg_register_stack(stack); vg_register_stack(stack);
return stack; return stack;
} }
struct coroutine_stack *coroutine_stack_alloc(size_t totalsize, size_t metasize)
{
struct coroutine_stack *stack;
size_t pgsz = getpagesize();
size_t mapsize;
char *map, *guard;
int rc;
mapsize = ((totalsize + (pgsz - 1)) & ~(pgsz - 1)) + pgsz;
map = mmap(NULL, mapsize, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (map == MAP_FAILED)
return NULL;
#if HAVE_STACK_GROWS_UPWARDS
guard = map + mapsize - pgsz;
stack = (struct coroutine_stack *)(guard - totalsize + metasize);
#else
guard = map;
stack = (struct coroutine_stack *)(map + pgsz + totalsize - metasize)
- 1;
#endif
rc = mprotect(guard, pgsz, PROT_NONE);
if (rc != 0) {
munmap(map, mapsize);
return NULL;
}
stack->magic = COROUTINE_STACK_MAGIC_ALLOC;
stack->size = totalsize - sizeof(*stack) - metasize;
vg_register_stack(stack);
return stack;
}
static void coroutine_stack_free(struct coroutine_stack *stack, size_t metasize)
{
void *map;
size_t pgsz = getpagesize();
size_t totalsize = stack->size + sizeof(*stack) + metasize;
size_t mapsize = ((totalsize + (pgsz - 1)) & ~(pgsz - 1)) + pgsz;
#if HAVE_STACK_GROWS_UPWARDS
map = (char *)(stack + 1) + stack->size + pgsz - mapsize;
#else
map = (char *)stack - stack->size - pgsz;
#endif
munmap(map, mapsize);
}
void coroutine_stack_release(struct coroutine_stack *stack, size_t metasize) void coroutine_stack_release(struct coroutine_stack *stack, size_t metasize)
{ {
vg_deregister_stack(stack); vg_deregister_stack(stack);
memset(stack, 0, sizeof(*stack));
switch (stack->magic) {
case COROUTINE_STACK_MAGIC_BUF:
memset(stack, 0, sizeof(*stack));
break;
case COROUTINE_STACK_MAGIC_ALLOC:
coroutine_stack_free(stack, metasize);
break;
default:
abort();
}
} }
struct coroutine_stack *coroutine_stack_check(struct coroutine_stack *stack, struct coroutine_stack *coroutine_stack_check(struct coroutine_stack *stack,
const char *abortstr) const char *abortstr)
{ {
if (stack && vg_addressable(stack, sizeof(*stack)) if (stack && vg_addressable(stack, sizeof(*stack))
&& (stack->magic == COROUTINE_STACK_MAGIC) && ((stack->magic == COROUTINE_STACK_MAGIC_BUF)
|| (stack->magic == COROUTINE_STACK_MAGIC_ALLOC))
&& (stack->size >= COROUTINE_MIN_STKSZ)) && (stack->size >= COROUTINE_MIN_STKSZ))
return stack; return stack;
......
...@@ -53,10 +53,16 @@ struct coroutine_state; ...@@ -53,10 +53,16 @@ struct coroutine_state;
#define COROUTINE_MIN_STKSZ 2048 #define COROUTINE_MIN_STKSZ 2048
/** /**
* COROUTINE_STACK_MAGIC - Magic number for coroutine stacks * COROUTINE_STACK_MAGIC_BUF - Magic number for coroutine stacks in a user
* supplied buffer
*/ */
#define COROUTINE_STACK_MAGIC 0xc040c040574c574c #define COROUTINE_STACK_MAGIC_BUF 0xc040c040574cb00f
/**
* COROUTINE_STACK_MAGIC_ALLOC - Magic number for coroutine stacks
* allocated by this module
*/
#define COROUTINE_STACK_MAGIC_ALLOC 0xc040c040574ca110
/** /**
* coroutine_stack_init - Prepare a coroutine stack in an existing buffer * coroutine_stack_init - Prepare a coroutine stack in an existing buffer
...@@ -75,6 +81,23 @@ struct coroutine_state; ...@@ -75,6 +81,23 @@ struct coroutine_state;
struct coroutine_stack *coroutine_stack_init(void *buf, size_t bufsize, struct coroutine_stack *coroutine_stack_init(void *buf, size_t bufsize,
size_t metasize); size_t metasize);
/**
* coroutine_stack_alloc - Allocate a coroutine stack
* @totalsize: total size to allocate
* @metasize: size of metadata to add to the stack (not including
* coroutine internal overhead)
*
* Allocates a coroutine stack of size @totalsize, including both
* internal overhead (COROUTINE_STK_OVERHEAD) and metadata of size
* @metasize. Where available this will also create a guard page, so
* that overruning the stack will result in an immediate crash, rather
* than data corruption.
*
* This will fail if the totalsize < (COROUTINE_MIN_STKSZ +
* COROUTINE_STK_OVERHEAD + metasize).
*/
struct coroutine_stack *coroutine_stack_alloc(size_t bufsize, size_t metasize);
/** /**
* coroutine_stack_release - Stop using a coroutine stack * coroutine_stack_release - Stop using a coroutine stack
* @stack: coroutine stack to release * @stack: coroutine stack to release
......
...@@ -3,6 +3,8 @@ ...@@ -3,6 +3,8 @@
#include <ccan/coroutine/coroutine.h> #include <ccan/coroutine/coroutine.h>
#include <ccan/tap/tap.h> #include <ccan/tap/tap.h>
#define STKSZ (COROUTINE_MIN_STKSZ + COROUTINE_STK_OVERHEAD)
static int global = 0; static int global = 0;
static void trivial_fn(void *p) static void trivial_fn(void *p)
...@@ -18,6 +20,10 @@ static void test_trivial(struct coroutine_stack *stack) ...@@ -18,6 +20,10 @@ static void test_trivial(struct coroutine_stack *stack)
{ {
struct coroutine_state t, master; struct coroutine_state t, master;
ok1(stack != NULL);
ok1(coroutine_stack_check(stack, NULL) == stack);
ok1(coroutine_stack_size(stack) == COROUTINE_MIN_STKSZ);
if (!COROUTINE_AVAILABLE) { if (!COROUTINE_AVAILABLE) {
skip(1, "Coroutines not available"); skip(1, "Coroutines not available");
return; return;
...@@ -30,22 +36,22 @@ static void test_trivial(struct coroutine_stack *stack) ...@@ -30,22 +36,22 @@ static void test_trivial(struct coroutine_stack *stack)
} }
static char buf[COROUTINE_MIN_STKSZ + COROUTINE_STK_OVERHEAD]; static char buf[STKSZ];
int main(void) int main(void)
{ {
struct coroutine_stack *stack; struct coroutine_stack *stack;
/* This is how many tests you plan to run */ /* This is how many tests you plan to run */
plan_tests(4); plan_tests(2 * 4 + 1);
stack = coroutine_stack_init(buf, sizeof(buf), 0); stack = coroutine_stack_init(buf, sizeof(buf), 0);
ok1(stack != NULL);
ok1(coroutine_stack_check(stack, NULL) == stack);
ok1(coroutine_stack_size(stack) == COROUTINE_MIN_STKSZ);
test_trivial(stack); test_trivial(stack);
coroutine_stack_release(stack, 0);
ok1(!coroutine_stack_check(stack, NULL));
stack = coroutine_stack_alloc(STKSZ, 0);
test_trivial(stack);
coroutine_stack_release(stack, 0); coroutine_stack_release(stack, 0);
/* This exits depending on whether all tests passed */ /* This exits depending on whether all tests passed */
......
...@@ -59,20 +59,13 @@ static void f2(void *p) ...@@ -59,20 +59,13 @@ static void f2(void *p)
static void test1(size_t bufsz) static void test1(size_t bufsz)
{ {
void *buf1, *buf2;
struct coroutine_stack *stack1, *stack2; struct coroutine_stack *stack1, *stack2;
buf1 = malloc(bufsz); stack1 = coroutine_stack_alloc(bufsz, 0);
ok1(buf1 != NULL);
stack1 = coroutine_stack_init(buf1, bufsz, 0);
diag("buf1=%p stack1=%p bufsz=0x%zx overhead=0x%zx\n",
buf1, stack1, bufsz, COROUTINE_STK_OVERHEAD);
ok1(coroutine_stack_check(stack1, NULL) == stack1); ok1(coroutine_stack_check(stack1, NULL) == stack1);
ok1(coroutine_stack_size(stack1) == bufsz - COROUTINE_STK_OVERHEAD); ok1(coroutine_stack_size(stack1) == bufsz - COROUTINE_STK_OVERHEAD);
buf2 = malloc(bufsz); stack2 = coroutine_stack_alloc(bufsz, 0);
ok1(buf2 != NULL);
stack2 = coroutine_stack_init(buf2, bufsz, 0);
ok1(coroutine_stack_check(stack2, NULL) == stack2); ok1(coroutine_stack_check(stack2, NULL) == stack2);
ok1(coroutine_stack_size(stack2) == bufsz - COROUTINE_STK_OVERHEAD); ok1(coroutine_stack_size(stack2) == bufsz - COROUTINE_STK_OVERHEAD);
...@@ -90,18 +83,14 @@ static void test1(size_t bufsz) ...@@ -90,18 +83,14 @@ static void test1(size_t bufsz)
ok(1, "Completed test1"); ok(1, "Completed test1");
coroutine_stack_release(stack1, 0); coroutine_stack_release(stack1, 0);
ok1(coroutine_stack_check(stack1, NULL) == NULL);
free(buf1);
coroutine_stack_release(stack2, 0); coroutine_stack_release(stack2, 0);
ok1(coroutine_stack_check(stack2, NULL) == NULL);
free(buf2);
} }
int main(void) int main(void)
{ {
/* This is how many tests you plan to run */ /* This is how many tests you plan to run */
plan_tests(14); plan_tests(10);
test1(8192); test1(8192);
......
...@@ -38,6 +38,11 @@ static void test_metadata(struct coroutine_stack *stack) ...@@ -38,6 +38,11 @@ static void test_metadata(struct coroutine_stack *stack)
{ {
struct metadata *meta; struct metadata *meta;
ok1(stack != NULL);
ok1(coroutine_stack_check(stack, NULL) == stack);
ok1(coroutine_stack_size(stack)
== BUFSIZE - COROUTINE_STK_OVERHEAD - sizeof(struct metadata));
meta = coroutine_stack_to_metadata(stack, sizeof(*meta)); meta = coroutine_stack_to_metadata(stack, sizeof(*meta));
ok1(coroutine_stack_from_metadata(meta, sizeof(*meta)) == stack); ok1(coroutine_stack_from_metadata(meta, sizeof(*meta)) == stack);
...@@ -68,22 +73,19 @@ int main(void) ...@@ -68,22 +73,19 @@ int main(void)
struct coroutine_stack *stack; struct coroutine_stack *stack;
/* This is how many tests you plan to run */ /* This is how many tests you plan to run */
plan_tests(10); plan_tests(1 + 2 * 9);
/* Fix seed so we get consistent, though pseudo-random results */ /* Fix seed so we get consistent, though pseudo-random results */
srandom(0); srandom(0);
stack = coroutine_stack_alloc(BUFSIZE, sizeof(struct metadata));
test_metadata(stack);
coroutine_stack_release(stack, sizeof(struct metadata));
buf = malloc(BUFSIZE); buf = malloc(BUFSIZE);
ok1(buf != NULL); ok1(buf != NULL);
stack = coroutine_stack_init(buf, BUFSIZE, sizeof(struct metadata)); stack = coroutine_stack_init(buf, BUFSIZE, sizeof(struct metadata));
ok1(stack != NULL);
ok1(coroutine_stack_check(stack, NULL) == stack);
ok1(coroutine_stack_size(stack)
== BUFSIZE - COROUTINE_STK_OVERHEAD - sizeof(struct metadata));
test_metadata(stack); test_metadata(stack);
coroutine_stack_release(stack, sizeof(struct metadata)); coroutine_stack_release(stack, sizeof(struct metadata));
free(buf); free(buf);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment