Introduction
In this work, we implement the Unix ucontext_t
operations on Windows platforms based on the Win32 API GetThreadContext
and SetThreadContext
functions. It is useful for Unix programmers that need to migrate their user-level threading code directly on Windows instead of using a Unix-to-NT porting environment ([1,2]).
Background
Most modern Unix environments provide to the programmer two options that allow user-level context switching between multiple threads of control within a process: either with the type jmpbuf
defined in <setjmp.h>
and the setjmp
/longjmp
pair of functions, or with the type ucontext_t
defined in <ucontext.h>
and the four functions getcontext
, setcontext
, makecontext
, and swapcontext
. For more information on the usage of these functions, you can look at [3]. On Windows platforms, however, the Microsoft C Runtime Library provides only the first set of functions [4].
Using the Code
The following program (testcontext.c - included in the demo project) is a typical example that makes use of the ucontex_t
operations. It runs successfully on both Unix and Windows platforms. On Windows, the programmer has only to include the two files (ucontext.[c,h]) in his application code. In the demo project, these two files are stored in the unix2nt directory, in order to make straightforward the compilation and execution of the test program on Unix platforms.
#include <stdio.h>
#include <stdlib.h>
#include <ucontext.h>
ucontext_t auc,buc,mainuc;
void a(DUMMYARGS int p)
{
int i;
for (i = 0; i < 10; i++)
{
printf("a%d ", p);
swapcontext(&auc, &buc);
}
printf("\nswitching to main\n");
swapcontext(&auc, &mainuc);
}
void b(DUMMYARGS int p)
{
int i;
for (i = 0; i < 10; i++)
{
printf("b%d ", p);
swapcontext(&buc, &auc);
}
}
int main(void)
{
int aparam = 1, bparam = 2;
printf("start\n");
getcontext(&auc);
auc.uc_stack.ss_size = 16 * 1024;
if ((auc.uc_stack.ss_sp = malloc(auc.uc_stack.ss_size)) == NULL)
perror("malloc"), exit(1);
auc.uc_stack.ss_flags = 0;
makecontext(&auc, a, 1, aparam);
getcontext(&buc);
buc.uc_stack.ss_size = 16 * 1024;
if ((buc.uc_stack.ss_sp = malloc(buc.uc_stack.ss_size)) == NULL)
perror("malloc"), exit(1);
buc.uc_stack.ss_flags = 0;
makecontext(&buc, b, 1, bparam);
getcontext(&mainuc);
swapcontext(&mainuc, &auc);
printf("\ndone\n");
return 0;
}
If you compile and run the above program, you get the following output:
C:\>testcontext.exe
start
a1 b2 a1 b2 a1 b2 a1 b2 a1 b2 a1 b2 a1 b2 a1 b2 a1 b2 a1 b2
switching to main
done
C:\>
Implementation
We implemented the above functionality based on the CONTEXT
data structure and the GetThreadContext
and SetThreadContext
functions [4]. In ucontext.h, we defined the appropriate data structures and function prototypes. The implementation of getcontext
and setcontext
is straightforward, using the corresponding GetThreadContext
and SetThreadContext
functions. In swapcontext
, the current thread saves its context (getcontext
) and then restores a new context (setcontext
). The trickiest point is in the implementation of makecontext
, where we reserve stack space for the function arguments; then we set appropriately the instruction (Eip
) and the stack pointer (Esp
) fields, and finally, we copy the function arguments in the reserved space. Although the latest specification of makecontext
[3] restricts the arguments of the function to be of type int
, according to our implementation, the maximum allowed size of an argument is 64 bits, enabling thus the passing of variables of type double
. For more implementation details, see the ucontext.c and ucontext.h files.
Win64 Support
For Win32 compilation, the code works exactly as before. To support Win64 (x64) compilation, the following additions were introduced:
- The instruction and stack pointers are appropriately set using the R
ip
and Rsp
fields of the CONTEXT
data structure. - The
DUMMYARGS
macro has been added in ucontext.h. This macro must be used at the beginning of the argument list of the function specified at makecontext
. For Win32, the macro is empty and can be omitted, while for Win64 it defines 4 long integer variables. These hidden and unused variables are passed to the function through registers, according to the __fastcall x64 calling convention [5], allowing for the actual user parameters to be passed through the stack, similarly to the Win32 case. - A macro in ucontext.h
replaces
malloc(x)
with _aligned_malloc(x,64)
.
History
- May 22, 2003 - Initial release
- May 25, 2004 - Revised source code
- Mar 6, 2007 - Distributed under the LGPL license
- Mar 15, 2014 - Updated for x86_64 support
References