class: center, middle # Thread Context ## Alan Cox and Scott Rixner --- layout: true --- # User-Level Thread Context - Register state - Stack - Signal mask - "Next" thread --- # The `ucontext` Type ``` typedef struct ucontext { struct ucontext *uc_link; // Pointer to "next" context sigset_t uc_sigmask; // Signal mask stack_t uc_stack; // Stack mcontext_t uc_mcontext; // Machine-dependent thread context } ucontext_t; ``` - Contexts may be "paused" and "resumed" - `uc_link` is used to link contexts together - When this context completes - If `uc_link` is not `NULL`, that context is resumed - If `uc_link` is `NULL`, the process terminates --- # `getcontext(3)` ``` int getcontext(ucontext_t *ucp); ``` - Initializes structure pointed to by `ucp` with the current context - May be used to resume execution after the call to `getcontext(3)` - Returns -1 and sets `errno` on error - Returns 0 on success - Returns 0 when resumed - May also be used as a starting point to create a new context - Called once, but may return more than once! --- # `setcontext(3)` ``` int setcontext(const ucontext_t *ucp); ``` - Resumes the user context pointed to by `ucp` - Does not return unless there is an error - Returns -1 and sets `errno` on error - Otherwise, the new context is running! - `ucp` must have been initialized by `getcontext(3)`, `makecontext(3)`, or `swapcontext(3)` --- class: line-numbers # `setcontext(3)` Example ``` #include
#include
int main(void) { ucontext_t uctx; volatile int x = 0; getcontext(&uctx); // check for errors volatile int y = 0; printf("x=%d, y=%d\n", x++, y++); setcontext(&uctx); // check for errors // This line is never reached } ``` - Call to `setcontext(3)` (line 15) resumes the program right after the call to `getcontext(3)` (line 10) - Infinitely increments x and resets y --- # `makecontext(3)` ``` void makecontext(ucontext_t *ucp, void (*func)(), int argc, ...); ``` - Modifies the context pointed to by `ucp` to execute `func` - Context must have been initialized by `getcontext(3)` - Must first allocate a stack for the new context - Must set the `uc_link` field to `NULL` or the "next" context - Does not run the context - Use `setcontext(3)` or `swapcontext(3)` to do that - Function `func` is called with `argc` arguments at that point --- # `makecontext(3)` Limitations - Limited function arguments - May only pass zero or more integer arguments - May not return anything - More modern strategy: `void *func(void *arg)` - Use a wrapper function for more complex functions - Pass an index into a lookup table as an argument - Use the index to look up the data in the wrapper - Wrapper could also store return value in the table --- # `makecontext(3)` Example .twocolumn[ .col[ ``` #define STACK_SZ 16384 void thread_func(int arg1, int arg2) { printf("thread_func(%d, %d) called\n", arg1, arg2); } int main(void) { ucontext_t uctx; char stack[STACK_SZ]; getcontext(&uctx); // check for errors uctx.uc_stack.ss_sp = stack; uctx.uc_stack.ss_size = STACK_SZ; uctx.uc_link = NULL; makecontext(&uctx, (void (*)())thread_func, 2, 10, 20); setcontext(&uctx); // check for errors // This line is never reached } ``` ] .col[ - Call to `setcontext(3)` starts the new context made by `makecontext(3)` - When `thread_func` returns, the program ends ] ] --- # `swapcontext(3)` ``` int swapcontext(ucontext_t *oucp, const ucontext_t *ucp); ``` - Saves the current context in the context pointed to by `oucp` - Resumes the context pointed to by `ucp` - Does not return unless there is an error - Returns -1 and sets `errno` on error - On success, the new context is running! - Returns 0 when context pointed to by `oucp` is resumed --- # `swapcontext(3)` Example .twocolumn[ .col[ ``` void thread_func(void) { printf("thread_func: running\n"); } int main(int argc, char *argv[]) { ucontext_t uctx_main, uctx_func; char stack[STACK_SZ]; getcontext(&uctx_func); // check for errors uctx_func.uc_stack.ss_sp = stack; uctx_func.uc_stack.ss_size = STACK_SZ; uctx_func.uc_link = &uctx_main; makecontext(&uctx_func, thread_func, 0); swapcontext(&uctx_main, &uctx_func); // check for errors printf("main: exiting\n"); } ``` ] .col[ - Call to `swapcontext(3)` starts the new context made by `makecontext(3)` - Saves current context to `uctx_main` - When `thread_func` completes, the main function resumes after call to `swapcontext(3)` ] ] --- # Unconventional Functions - `setcontext(3)` and `swapcontext(3)` do not return on success - The newly resumed context is running instead - `getcontext(3)` and `swapcontext(3)` may return more than once - If the context is later resumed - If context is not modified, it can be resumed repeatedly --- # Exercise: Generators .mb-0[ In Python: ] ```python def gen(): for i in range(10): yield 10 - i for val in gen(): print(val) ``` - First call to `gen` calls the function - `gen` pauses and "yields" each value to the caller - Each subsequent call to `gen` resumes the generator --- # Simple Generators in C .mb-0[ Generator interface: ] ``` typedef struct generator Generator; Generator *gen_create(void (*func)(Generator *, void *), void *); void gen_destroy(Generator *gen); bool gen_next(Generator *gen, void **value); void gen_yield(Generator *gen, void *value); ``` -- .mb-0[ generator struct: ] ``` struct generator { int id; ucontext_t calling_context; ucontext_t generator_context; char stack[STACK_SIZE]; void (*func)(Generator *, void *); void *arg; void *yield_value; bool done; struct generator *prev; struct generator *next; }; ``` --- # Using the Generator Abstraction .mb-0[ Equivalent to previous Python example: ] ``` void gen(Generator *g, void *arg) { for (unsigned long i = 0; i < 10; i++) { gen_yield(g, (void *)(10 - i)); } } int main() { void *val; Generator *g = gen_create(gen, NULL); while (gen_next(g, &val)) { printf("%ld\n", (unsigned long)val); } gen_destroy(g); return (0); } ``` --- class: center, middle # Get Started!