mirror of
https://github.com/boostorg/context.git
synced 2026-01-27 06:42:20 +00:00
334 lines
12 KiB
Plaintext
334 lines
12 KiB
Plaintext
[/
|
|
Copyright Oliver Kowalke 2009.
|
|
Distributed under the Boost Software License, Version 1.0.
|
|
(See accompanying file LICENSE_1_0.txt or copy at
|
|
http://www.boost.org/LICENSE_1_0.txt
|
|
]
|
|
|
|
[section:context Context]
|
|
|
|
Each instance of __fcontext__ represents a context (CPU registers and stack
|
|
space). Together with its related functions __start_fcontext__,
|
|
__jump_fcontext__ and __make_fcontext__ it provides a execution control transfer
|
|
mechanism similar interface like
|
|
[@http://www.kernel.org/doc/man-pages/online/pages/man2/getcontext.2.html ucontext_t].
|
|
__fcontext__ and its functions are located in __context_ns__ and the functions
|
|
are declared as extern "C".
|
|
|
|
[warning If __fcontext__ is used in a multithreaded application, it can migrated
|
|
between threads, but must not reference __tls__.]
|
|
|
|
[note If __fls__ is used on Windows, the user is responsible for calling
|
|
__fls_alloc__, __fls_free__.]
|
|
|
|
[important The low level API is the part to port to new platforms.]
|
|
|
|
|
|
[heading Executing a context]
|
|
|
|
A new context supposed to execute a __context_fn__ (returning void and accepting
|
|
intptr_t as argument) must be initialized by function __make_fcontext__.
|
|
|
|
// context-function
|
|
void f( intptr);
|
|
|
|
// creates and manages a protected stack (with guard page)
|
|
boost::ctx::protected_stack stack( boost::ctx::default_stacksize() );
|
|
|
|
// let fcontext_t fc use stack
|
|
fc.fc_stack.base = stack.address();
|
|
fc.fc_stack.limit =
|
|
static_cast< char * >( fc.fc_stack.base) - stack.size();
|
|
|
|
// context fc used f() as context function
|
|
// 3 is the argument with which f() will be called
|
|
make_fcontext( & fc, f, 3);
|
|
|
|
__fcontext__ requires a pointer to the top of the stack (__fc_base__) as well
|
|
as a pointer to the lower bound of the stack (__fc_limit__).
|
|
|
|
Calling __start_fcontext__ invokes the __context_fn__ in a newly created context
|
|
complete with registers, flags, stack and instruction pointers. When control
|
|
should be returned to the original calling context, call __jump_fcontext__.
|
|
The current context information (registers, flags, and stack and instruction
|
|
pointers) is saved and the original context information is restored. Calling
|
|
__jump_fcontext__ again resumes execution in the second context after saving the
|
|
new state of the original context.
|
|
Note that __start_fcontext__ must be called first and only once.
|
|
|
|
namespace ctx = boost::ctx;
|
|
|
|
ctx::fcontext_t fcm, fc1, fc2;
|
|
|
|
void f1( intptr_t)
|
|
{
|
|
std::cout << "f1: entered" << std::endl;
|
|
std::cout << "f1: call jump_fcontext( & fc1, & fc2)" << std::endl;
|
|
ctx::jump_fcontext( & fc1, & fc2, 0);
|
|
std::cout << "f1: return" << std::endl;
|
|
ctx::jump_fcontext( & fc1, & fcm, 0);
|
|
}
|
|
|
|
void f2( intptr_t)
|
|
{
|
|
std::cout << "f2: entered" << std::endl;
|
|
std::cout << "f2: call jump_fcontext( & fc2, & fc1)" << std::endl;
|
|
ctx::jump_fcontext( & fc2, & fc1, 0);
|
|
BOOST_ASSERT( false && ! "f2: never returns");
|
|
}
|
|
|
|
int main( int argc, char * argv[])
|
|
{
|
|
ctx::stack_allocator alloc1, alloc2;
|
|
|
|
fc1.fc_stack.base = alloc1.allocate(ctx::minimum_stacksize());
|
|
fc1.fc_stack.limit =
|
|
static_cast< char * >( fc1.fc_stack.base) - ctx::minimum_stacksize();
|
|
ctx::make_fcontext( & fc1, f1, 0);
|
|
|
|
fc2.fc_stack.base = alloc2.allocate(ctx::minimum_stacksize());
|
|
fc2.fc_stack.limit =
|
|
static_cast< char * >( fc2.fc_stack.base) - ctx::minimum_stacksize();
|
|
ctx::make_fcontext( & fc2, f2, 0);
|
|
|
|
std::cout << "main: call start_fcontext( & fcm, & fc1)" << std::endl;
|
|
ctx::start_fcontext( & fcm, & fc1);
|
|
|
|
std::cout << "main: done" << std::endl;
|
|
|
|
return EXIT_SUCCESS;
|
|
}
|
|
|
|
output:
|
|
main: call start_fcontext( & fcm, & fc1)
|
|
f1: entered
|
|
f1: call jump_fcontext( & fc1, & fc2)
|
|
f2: entered
|
|
f2: call jump_fcontext( & fc2, & fc1)
|
|
f1: return
|
|
main: done
|
|
|
|
Function `start_fcontext()` enters the __context_fn__ `f1()` by starting
|
|
context fc1 (context fcm saves the registers of `main()`). For jumping between
|
|
context's fc1 and fc2 `jump_fcontext()` is called.
|
|
Because context fcm is chained to fc1, `main()` is entered (returning from
|
|
`start_fcontext()`) after context fc1 becomes complete (return from
|
|
`f1()`).
|
|
|
|
[warning Calling __jump_fcontext__ to the same context from inside the same
|
|
context results in undefined behaviour.]
|
|
|
|
[note In contrast to threads, which are preemtive, __fcontext__ switches are
|
|
cooperative (programmer controls when switch will happen). The kernel is not
|
|
involved in the context switches.]
|
|
|
|
|
|
[heading Transfer of data]
|
|
|
|
The argument passed to __jump_fcontext__, in one context, is returned by
|
|
__jump_fcontext__ in the other context. The intptr_t passed to
|
|
__jump_fcontext__, in one context, is returned by __jump_fcontext__ (or
|
|
__start_fcontext__, depending upon which function was called previously) in the
|
|
other context.
|
|
__start_fcontext__ has no data argument because it enters the start of the function,
|
|
and there is no previous call to __jump_fcontext__ to return a value.
|
|
|
|
namespace ctx = boost::ctx;
|
|
|
|
ctx::fcontext_t fc1, fcm;
|
|
|
|
typedef std::pair< int, int > pair_t;
|
|
|
|
void f1( intptr_t param)
|
|
{
|
|
pair_t * p = ( pair_t *) param;
|
|
|
|
p = ( pair_t *) ctx::jump_fcontext( & fc1, & fcm, ( intptr_t) ( p->first + p->second) );
|
|
|
|
ctx::jump_fcontext( & fc1, & fcm, ( intptr_t) ( p->first + p->second) );
|
|
}
|
|
|
|
int main( int argc, char * argv[])
|
|
{
|
|
ctx::stack_allocator alloc;
|
|
|
|
fc1.fc_stack.base = alloc.allocate(ctx::minimum_stacksize());
|
|
fc1.fc_stack.limit =
|
|
static_cast< char * >( fc1.fc_stack.base) - ctx::minimum_stacksize();
|
|
fc1.fc_link = & fcm;
|
|
pair_t p( std::make_pair( 2, 7) );
|
|
ctx::make_fcontext( & fc1, f1, ( intptr_t) & p);
|
|
|
|
int res = ( int) ctx::start_fcontext( & fcm, & fc1);
|
|
std::cout << p.first << " + " << p.second << " == " << res << std::endl;
|
|
|
|
p = std::make_pair( 5, 6);
|
|
res = ( int) ctx::jump_fcontext( & fcm, & fc1, ( intptr_t) & p);
|
|
std::cout << p.first << " + " << p.second << " == " << res << std::endl;
|
|
|
|
std::cout << "main: done" << std::endl;
|
|
|
|
return EXIT_SUCCESS;
|
|
}
|
|
|
|
output:
|
|
2 + 7 == 9
|
|
5 + 6 == 11
|
|
main: done
|
|
|
|
|
|
[heading Exceptions in __context_fn__]
|
|
|
|
If the __context_fn__ emits an exception, the application will terminate.
|
|
|
|
|
|
[heading Chaining contexts]
|
|
|
|
__boost_context__ provides the ability to chain context instances by passing
|
|
a pointer to another context to __fc_link__.
|
|
In this way, it is possible to create a chain of contexts.
|
|
|
|
namespace ctx = boost::ctx;
|
|
|
|
ctx::fcontext_t fc1, fc2;
|
|
|
|
void f1( intptr_t)
|
|
{
|
|
std::cout << "f1: entered" << std::endl;
|
|
std::cout << "f1: call jump_fcontext( & fc1, & fc2)" << std::endl;
|
|
ctx::jump_fcontext( & fc1, & fc2, 0);
|
|
std::cout << "f1: return" << std::endl;
|
|
// implizit jump back to fcm -> main()
|
|
}
|
|
|
|
void f2( intptr_t)
|
|
{
|
|
std::cout << "f2: entered" << std::endl;
|
|
std::cout << "f2: call jump_fcontext( & fc2, & fc1)" << std::endl;
|
|
ctx::jump_fcontext( & fc2, & fc1, 0);
|
|
BOOST_ASSERT( false && ! "f2: never returns");
|
|
}
|
|
|
|
int main( int argc, char * argv[])
|
|
{
|
|
ctx::fcontext_t fcm;
|
|
ctx::stack_allocator alloc;
|
|
|
|
fc1.fc_stack.base = alloc.allocate(ctx::minimum_stacksize());
|
|
fc1.fc_stack.limit =
|
|
static_cast< char * >( fc1.fc_stack.base) - ctx::minimum_stacksize();
|
|
// chain fc1 and fcm
|
|
fc1.fc_link = & fcm;
|
|
ctx::make_fcontext( & fc1, f1, 0);
|
|
|
|
fc2.fc_stack.base = alloc.allocate(ctx::minimum_stacksize());
|
|
fc2.fc_stack.limit =
|
|
static_cast< char * >( fc2.fc_stack.base) - ctx::minimum_stacksize();
|
|
ctx::make_fcontext( & fc2, f2, 0);
|
|
|
|
std::cout << "main: call start_fcontext( & fcm, & fc1)" << std::endl;
|
|
ctx::start_fcontext( & fcm, & fc1);
|
|
|
|
std::cout << "main: done" << std::endl;
|
|
|
|
return EXIT_SUCCESS;
|
|
}
|
|
|
|
output:
|
|
main: call start_fcontext( & fcm, & fc1)
|
|
f1: entered
|
|
f1: call jump_fcontext( & fc1, & fc2)
|
|
f2: entered
|
|
f2: call jump_fcontext( & fc2, & fc1)
|
|
f1: return
|
|
main: done
|
|
|
|
Because context fcm is chained to fc1, `main()` is entered (returning from
|
|
`start_fcontext()`) after context fc1 becomes complete (return from
|
|
`f1()`).
|
|
|
|
|
|
[heading Stack unwinding]
|
|
|
|
Sometimes it is necessary to unwind the stack of an unfinished context to
|
|
destroy local stack variables so they can release allocated resources (RAII
|
|
pattern). The user is responsible for this task.
|
|
|
|
|
|
[section:boost_fcontext Struct `fcontext_t` and related functions]
|
|
|
|
typedef struct boost_fcontext_stack boost_fcontext_stack_t;
|
|
struct boost_fcontext_stack
|
|
{
|
|
void * base;
|
|
void * limit;
|
|
};
|
|
|
|
typedef struct boost_fcontext fcontext_t;
|
|
struct boost_fcontext
|
|
{
|
|
< platform specific >
|
|
|
|
boost_fcontext_stack_t fc_stack;
|
|
fcontext_t * fc_link;
|
|
};
|
|
|
|
intptr_t start_fcontext( fcontext_t * ofc, fcontext_t const* nfc);
|
|
intptr_t jump_fcontext( fcontext_t * ofc, fcontext_t const* nfc, intptr_t vp);
|
|
void make_fcontext( fcontext_t * fc, void(* fn)(void*), intptr_t p);
|
|
|
|
[heading `base`]
|
|
[variablelist
|
|
[[Member:] [Pointer to the top of the stack.]]
|
|
]
|
|
|
|
[heading `limit`]
|
|
[variablelist
|
|
[[Member:] [Pointer to the bottom of the stack.]]
|
|
]
|
|
|
|
[heading `fc_stack`]
|
|
[variablelist
|
|
[[Member:] [Tracks the memory for the context's stack.]]
|
|
]
|
|
|
|
[heading `fc_link`]
|
|
[variablelist
|
|
[[Member:] [The address of the next context link in a chain, if any.]]
|
|
]
|
|
|
|
[heading `intptr_t start_fcontext( fcontext_t * ofc,
|
|
fcontext_t * nfc)`]
|
|
[variablelist
|
|
[[Effects:] [Stores the current context data (stack pointer, instruction
|
|
pointer, and CPU registers) to `*ofc` and restores the context data from `*nfc`,
|
|
which implies jumping to `*nfc`'s execution context. This function must be
|
|
called when first entering `*nfc`'s execution context.]]
|
|
[[Returns:] [The result of calling `jump_fcontext()`.]]
|
|
]
|
|
|
|
[heading `intptr_t jump_fcontext( fcontext_t * ofc, fcontext_t * nfc, intptr_t p)`]
|
|
[variablelist
|
|
[[Effects:] [Stores the current context data (stack pointer, instruction
|
|
pointer, and CPU registers) to `*ofc` and restores the context data from `*nfc`,
|
|
which implies jumping to `*nfc`'s execution context. The intptr_t argument, `p`,
|
|
is passed to the current context to be returned by the most recent call to
|
|
`start_fcontext()` or `jump_fcontext()` in the same thread.]]
|
|
[[Returns:] [The third pointer argument passed to the most recent call to
|
|
`jump_fcontext()`, if any.]]
|
|
]
|
|
|
|
[heading `void make_fcontext( fcontext_t * fc, void(*fn)(intptr_t), intptr_t p)`]
|
|
[variablelist
|
|
[[Precondition:] [A stack is applied to `*fc` before
|
|
`make_fcontext()` is called. If a successor context should be executed
|
|
after `*fc` finishes the address of the successor context must be stored ir
|
|
`fc->fc_link`. The application exits otherwise.]]
|
|
[[Effects:] [Modifies `*fc` in order to execute `fn` with argument `p` when the
|
|
context is activated next.]]
|
|
]
|
|
|
|
[endsect]
|
|
|
|
[endsect]
|