/* Pthread-friendly coroutines with pth * Copyright (C) 2001 Andy Wingo * * cothread-stack.c: various methods of allocating cothread stacks * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ /* chunks can contain 1 or more blocks, each block contains one cothread stack */ enum cothread_attr_method { COTHREAD_ATTR_METHOD_MALLOC, /* cothread stacks on the heap, one block per chunk */ COTHREAD_ATTR_METHOD_GTHREAD_STACK, /* cothread stacks within the current gthread's stack */ COTHREAD_ATTR_METHOD_LINUXTHREADS, /* a hack that allows for linuxthreads compatibility */ } struct cothread_attr { enum cothread_attr_method method; int chunk_size; int blocks_per_chunk; } #ifdef HAVE_LINUXTHREADS static struct cothread_attr cothread_attr_default = { COTHREAD_ATTR_METHOD_LINUXTHREADS, /* use the linuxthreads hack */ 0x20000, /* 2 MB */ 8 /* for a stack size of 256 KB */ } #else static struct cothread_attr cothread_attr_default = { COTHREAD_ATTR_METHOD_GTHREAD_STACK, /* this is what the old cothreads code does */ 0x10000, /* only 1 MB due the the FreeBSD defaults */ 8 /* for a stack size of 128 KB */ } #endif static struct cothread_attr *_attr = NULL; /* set in cothread_init() */ enum cothread_block_state { COTHREAD_BLOCK_STATE_UNUSED=0, COTHREAD_BLOCK_STATE_IN_USE } struct cothread_chunk { struct cothread_chunk *next; enum cothread_block_state *block_states; char *chunk; int size; int reserved_bottom; gboolean needs_free; } /* size must be a power of two. */ struct cothread_chunk* cothread_chunk_new (unsigned long size, gboolean allocate) { struct cothread_chunk *ret; char *sp = CURRENT_STACK_FRAME; ret = g_new0 (struct cothread_chunk, 1); ret->block_states = g_new0 (enum cothread_block_state, _attr->blocks_per_chunk); if (allocate) { if (!posix_memalign(&ret->chunk, size, size)) g_error ("memalign operation failed"); } else { /* if we don't allocate the chunk, we must already be in it. */ ret->chunk = (unsigned long) sp &~ (size - 1); #if PTH_STACK_GROWTH > 0 ret->reserved_bottom = sp - ret->chunk; #else ret->reserved_bottom = sp + size - ret->chunk; #endif } ret->size = size; ret->needs_free = allocate; return ret; } gboolean cothread_stack_alloc_on_heap (char **low, char **high) { *low = g_malloc (_attr->chunk_size / _attr->blocks_per_chunk); *high = *low + sizeof (*low); return TRUE; } /** * cothread_stack_alloc_chunked: * @chunk: the chunk for the * Make a new cothread stack out of a chunk. Chunks are assumed to be aligned on * boundaries of _attr->chunk_size. * * Returns: the new cothread context */ /* we assume that the stack is aligned on _attr->chunk_size boundaries */ static gboolean cothread_stack_alloc_chunked (struct cothread_chunk *chunk, char **low, char **high, (struct cothread_chunk*)(*chunk_new)(struct cothread_chunk*)) { int block; struct cothread_chunk *walk, *last; for (walk=chunk; walk; last=walk, walk=walk->next) { if (chunk->block_states[0] == COTHREAD_BLOCK_STATE_UNUSED) { chunk->block_states[0] = COTHREAD_BLOCK_STATE_IN_USE; #if PTH_STACK_GROWTH > 0 *low = chunk->chunk + chunk->reserved_bottom; *high = chunk->chunk + chunk->chunk_size / _attr->blocks_per_chunk; #else *low = chunk->chunk + chunk->size * (chunk->nblocks - 1) / chunk->nblocks; *high = chunk->chunk + chunk->size - chunk->reserved_bottom; #endif return TRUE; } for (block = 1; block < _attr->blocks_per_chunk; block++) { if (chunk->block_states[block] == COTHREAD_BLOCK_STATE_UNUSED) { #if PTH_STACK_GROWTH > 0 *low = chunk->chunk + chunk->size * (chunk->nblocks - block - 1) / chunk->nblocks; #else *low = chunk->chunk + chunk->size * (block - 1) / chunk->nblocks; #endif *high = *low + chunk->size / chunk->nblocks; return TRUE; } } } if (!chunk_new) return FALSE; else return cothread_stack_alloc_chunked (chunk_new (last), low, high, NULL); } gboolean cothread_stack_alloc_on_gthread_stack (char **low, char **high) { struct cothread_chunk *chunk = NULL; static GStaticPrivate chunk_key = G_STATIC_PRIVATE_INIT; if (!(chunk = g_static_private_get(&chunk_key))) { chunk = cothread_chunk_new (_attr->size, FALSE); g_static_private_set (&chunk_key, chunk, cothread_chunk_free); } return cothread_stack_alloc_chunked (chunk, low, high, NULL); } gboolean cothread_stack_alloc_linuxthreads (char **low, char **high) { struct cothread_chunk *chunk = NULL; static GStaticPrivate chunk_key = G_STATIC_PRIVATE_INIT; if (!(chunk = g_static_private_get(&chunk_key))) { chunk = cothread_chunk_new (_attr->size, FALSE); g_static_private_set (&chunk_key, chunk, cothread_chunk_free); } return cothread_stack_alloc_chunked (chunk, low, high, cothread_chunk_new_linuxthreads); } struct cothread_chunk* cothread_chunk_new_linuxthreads (struct cothread_chunk* old) { struct cothread_chunk *new; void *pthread_descr; new = cothread_chunk_new (_attr->chunk_size, TRUE); pthread_descr = __linuxthreads_self(); #if PTH_STACK_GROWTH > 0 /* we don't really know pthread_descr's size in this case, but we can be * conservative. it's normally 1K in the down-growing case, so we allocate 2K. */ new->reserved_bottom = 2048; memcpy(new->chunk, pthread_descr, 2048); #else new->reserved_bottom = ((unsigned long) pthread_descr | (_attr->chunk_size - 1)) - (unsigned long) pthread_descr; memcpy(new->chunk + new->size - new->reserved_bottom - 1, pthread_descr, new->reserved_bottom); #endif old->next = new; return new; }