Технологические интерфейсы
Манипулирование пользовательскими контекстами
Согласно стандарту POSIX-2001, пользовательский контекст потока управления включает содержимое машинных регистров, маску сигналов и текущий стек выполнения . Эти данные сосредоточены в структуре типа ucontext_t, содержащей по крайней мере следующие поля.
ucontext_t *uc_link; /* Указатель на контекст, */ /* в котором будет возобновлено */ /* выполнение при выходе из */ /* данного контекста */ sigset_t uc_sigmask; /* Набор сигналов, блокированных */ /* в данном контексте */ stack_t uc_stack; /* Стек, используемый в данном */ /* контексте */ mcontext_t uc_mcontext; /* Машинно-зависимое представление */ /* сохраненного контекста */
Стандарт POSIX-2001 предоставляет функции для опроса ( getcontext() ), модификации ( makecontext() ) и смены ( setcontext() и swapcontext() ) пользовательских контекстов (см. листинг 9.33).
#include <ucontext.h> int getcontext (ucontext_t *ucp); void makecontext (ucontext_t *ucp, void (*func) (void), int argc, ...); int setcontext (const ucontext_t *ucp); int swapcontext (ucontext_t *restrict oucp, const ucontext_t *restrict ucp);Листинг 9.33. Описание функций, манипулирующих пользовательскими контекстами потоков управления.
Функция getcontext() – штатное средство получения исходного материала для манипулирования контекстами. Она запоминает текущий контекст вызывающего потока управления в структуре, на которую указывает аргумент ucp. Ее нормальный результат равен нулю.
Функция makecontext() модифицирует контекст, заданный аргументом ucp. Когда (после вызовов setcontext() или swapcontext() ) выполнение будет возобновлено в этом контексте, оно продолжится обращением к функции func() с передачей ей аргументов типа int в количестве argc, помещенных после argc при вызове makecontext(). Приложение должно позаботиться о том, чтобы модифицируемый контекст включал стек достаточного размера.
Элемент uc_link структуры типа ucontext_t определяет контекст, в котором будет возобновлено выполнение после выхода из модифицированного функцией makecontext() контекста. Приложение должно позаботиться об инициализации этого элемента до обращения к makecontext().
Функция setcontext() устанавливает пользовательский контекст вызывающего потока управления в соответствии с содержимым структуры, на которую указывает аргумент ucp. После успешного вызова setcontext() возврата не происходит – выполнение возобновляется с точки, специфицированной новым контекстом, а именно: если этот контекст был сформирован в результате обращения к getcontext(), выполнение возобновляется возвратом из getcontext() ; если контекст получен после makecontext(), вызывается функция func(), после возврата из которой поток управления продолжает работу во входном для makecontext() контексте.
Если значением элемента uc_link структуры, на которую указывает аргумент ucp, служит пустой указатель, то данный контекст соответствует функции main(), после выхода из которой выполнение потока управления завершается.
Функция swapcontext() производит перестановку пользовательских контекстов. Текущий контекст сохраняется в структуре, на которую указывает аргумент oucp, а новый контекст формируется по значению аргумента ucp.
Обратим внимание на следующую тонкость. Когда вызывается функция обработки сигнала, текущий пользовательский контекст запоминается, а для выполнения обработчика формируется новый контекст. Стандарт POSIX-2001 не гарантирует, что после нелокального перехода из функции обработки сигнала посредством longjmp() будет аккуратно восстановлен контекст соответствующего вызова setjmp(). В подобных ситуациях рекомендуется применять функции siglongjmp() или setcontext().
В качестве примера применения функций, манипулирующих пользовательскими контекстами, рассмотрим модифицированную программу из текста стандарта POSIX-2001 (см. листинг 9.34).
/* * * * * * * * * * * * * * * * * * * * * * * */ /* Программа демонстрирует применение функций, */ /* манипулирующих пользовательскими контекстами*/ /* * * * * * * * * * * * * * * * * * * * * * * */ #include <stdio.h> #include <ucontext.h> /* Размер стеков в формируемых пользовательских контекстах */ #define STACK_SIZE 4096 /* Пространство для стеков */ static char st1 [STACK_SIZE]; static char st2 [STACK_SIZE]; static ucontext_t ctx [3]; static void f1 (int arg) { printf ("Вызвана функция %s с аргументом %d\n", "f1", arg); if (swapcontext (&ctx [1], &ctx [2]) != 0) { perror ("SWAPCONTEXT-1"); } printf ("Выход из функции %s\n", "f1"); } static void f2 (int arg1, int arg2) { printf ("Вызвана функция %s с аргументами %d, %d\n", "f2", arg1, arg2); if (swapcontext (&ctx [2], &ctx [1]) != 0) { perror ("SWAPCONTEXT-2"); } printf ("Выход из функции %s\n", "f2"); } int main (void) { (void) getcontext (&ctx [1]); printf ("Параметры первоначального контекста:\n" "адрес стека %p, размер стека %d\n", ctx[1].uc_stack.ss_sp, ctx[1].uc_stack.ss_size); /* В соответствии с общими рекомендациями */ /* позаботимся о стеке для модифицируемых контекстов */ ctx[1].uc_stack.ss_sp = st1; ctx[1].uc_stack.ss_size = sizeof (st1); ctx[1].uc_link = &ctx [0]; makecontext (&ctx [1], (void (*) (void)) f1, 1, 2); (void) getcontext (&ctx [2]); ctx[2].uc_stack.ss_sp = st2; ctx[2].uc_stack.ss_size = sizeof (st2); ctx[2].uc_link = &ctx [1]; makecontext (&ctx [2], (void (*) (void)) f2, 2, 3, 4); if (swapcontext (&ctx [0], &ctx [2]) != 0) { perror ("SWAPCONTEXT-3"); return (1); } return 0; }Листинг 9.34. Пример применения функций, манипулирующих пользовательскими контекстами.
Обратим внимание на резервирование пространства под стек перед обращением к функции makecontext(), а также на связывание нескольких контекстов в список посредством поля uc_link.
Результаты выполнения приведенной программы показаны на листинге 9.35.
Параметры первоначального контекста: адрес стека (nil), размер стека 0 Вызвана функция f2 с аргументами 3, 4 Вызвана функция f1 с аргументом 2 Выход из функции f2 Выход из функции f1Листинг 9.35. Возможные результаты выполнения программы, применяющей функции манипулирования пользовательскими контекстами.