/* Filename: contextSwitch_windows_src.c * Created by: drose (15Apr10) * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * PANDA 3D SOFTWARE * Copyright (c) Carnegie Mellon University. All rights reserved. * * All use of this software is subject to the terms of the revised BSD * license. You should have received a copy of this license along * with this source code in a file named "LICENSE." * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* This is the implementation of user-space context switching using native Windows threading constructs to manage the different execution contexts. This isn't strictly user-space, since we use OS threading constructs, but we use a global lock to ensure that only one thread at a time is active. Thus, we still don't have to defend the code against critical sections globally, so we still get the low-overhead benefit of SIMPLE_THREADS; this is just a simple, reliable way to manage context switches. */ #include #include /* The Windows implementation doesn't use the stack pointer. */ const int needs_stack_prealloc = 0; const int is_os_threads = 1; static struct ThreadContext *current_context = NULL; struct ThreadContext { /* Each context is really its own thread. */ HANDLE _thread; /* This event is in the signaled state when the thread is ready to roll. */ HANDLE _ready; /* This is set FALSE while the thread is alive, and TRUE if the thread is to be terminated when it next wakes up. */ int _terminated; /* These are preloaded with the startup parameters, then cleared to NULL for subsequent runs. */ ThreadFunction *_thread_func; void *_data; /* We use setjmp()/longjmp() to manage the detail of returning from save_thread_context() when we call switch_to_thread_context(). */ jmp_buf _jmp_context; }; static DWORD WINAPI thread_main(LPVOID data) { struct ThreadContext *context = (struct ThreadContext *)data; /* Wait for the thread to be awoken. */ WaitForSingleObject(context->_ready, INFINITE); if (context->_terminated) { /* We've been rudely terminated. Exit gracefully. */ ExitThread(1); } /* Now we can begin. */ (*context->_thread_func)(context->_data); return 0; } void init_thread_context(struct ThreadContext *context, unsigned char *stack, size_t stack_size, ThreadFunction *thread_func, void *data) { context->_thread_func = thread_func; context->_data = data; context->_thread = CreateThread(NULL, stack_size, thread_main, context, 0, NULL); } void save_thread_context(struct ThreadContext *context, ContextFunction *next_context, void *data) { /* Save the current context so we can return here when the thread is awoken. */ if (setjmp(context->_jmp_context) != 0) { /* We have just returned from longjmp. In this case, return from the function. */ return; } current_context = context; (*next_context)(context, data); /* Should not get here. */ assert(FALSE); abort(); } void switch_to_thread_context(struct ThreadContext *from_context, struct ThreadContext *to_context) { /* Pause the current thread, and switch to the indicated context. This function should not return. */ assert(from_context == current_context); /* Wake up the target thread. */ SetEvent(to_context->_ready); /* And now put the from thread to sleep until it is again awoken. */ WaitForSingleObject(from_context->_ready, INFINITE); if (from_context->_terminated) { /* We've been rudely terminated. Exit gracefully. */ ExitThread(1); } /* Now we have been signaled again, and we're ready to resume the thread. */ longjmp(from_context->_jmp_context, 1); /* Should not get here. */ assert(FALSE); abort(); } struct ThreadContext * alloc_thread_context() { struct ThreadContext *context = (struct ThreadContext *)malloc(sizeof(struct ThreadContext)); memset(context, 0, sizeof(struct ThreadContext)); context->_ready = CreateEvent(NULL, FALSE, FALSE, NULL); return context; } void free_thread_context(struct ThreadContext *context) { /* Make sure the thread wakes and exits gracefully. */ context->_terminated = TRUE; SetEvent(context->_ready); WaitForSingleObject(context->_thread, INFINITE); CloseHandle(context->_ready); if (context->_thread != NULL) { CloseHandle(context->_thread); } free(context); }