setcontext (setcontext)
setcontext — одна из библиотечных функций стандарта POSIX (в число других входят getcontext, makecontext и swapcontext), используемая для управления контекстом. Семейство setcontext
позволяет реализовать на языке Си такие паттерны проектирования управления потоком, как итераторы, нити (fibers) и сопрограммы. Семейство можно рассматривать как расширенную версию setjmp/longjmp
; в то время как последние позволяют только один нелокальный прыжок из стека, setcontext
позволяет создание нескольких взаимодействующих потоков управления с собственными стеками.
Спецификация
[править | править код]setcontext
определён в POSIX.1-2001 и во второй версии Single UNIX Specification, однако доступен не во всех UNIX-подобных операционных системах. Функции и связанные с ними типы определены в заголовочном файле ucontext.h
. В их число входит тип ucontext_t
, с которым взаимодействуют все четыре функции:
typedef struct ucontext {
struct ucontext *uc_link;
sigset_t uc_sigmask;
stack_t uc_stack;
mcontext_t uc_mcontext;
...
} ucontext_t;
uc_link
указывает на контекст, который будет восстановлен при выходе из текущего контекста, если контекст создан с помощью makecontext
(вторичный контекст). uc_sigmask
используется для хранения сигналов, заблокированных в контексте, а uc_stack
является стеком, используемым контекстом. uc_mcontext
используется для хранения состояния исполнения, включая все регистры центрального процессора, счётчик команд и указатель стека; mcontext_t
является непрозрачным (opaque) указателем.
Также определены следующие функции:
int setcontext(const ucontext_t *ucp)
- Эта функция переносит управление в контекст в
ucp
. Исполнение продолжается с точки, на которой контекст был сохранён вucp
. В случае успешного выполнения возврата изsetcontext
не производится.
int getcontext(ucontext_t *ucp)
- Сохраняет текущий контекст в
ucp
. Возврат из этой функции происходит в двух случаях: после первичного вызова или при переключении потока на контекст вucp
с помощьюsetcontext
илиswapcontext
. Функцияgetcontext
не предоставляет возвращаемого значения для разделения этих случаев (оно служит лишь для сообщения об ошибке), поэтому разработчик должен явным образом использовать переменную-флаг, объявленную без модификатора register и с модификатором volatile во избежание свёртывания константных выражений и других оптимизаций компилятора.
void makecontext(ucontext_t *ucp, void *func(), int argc, ...)
- Функция
makecontext
устанавливает альтернативный поток управления вucp
, предварительно инициализированный с помощьюgetcontext
. Полеucp.uc_stack
должно указывать на место для стека необходимого размера; обычно используется константаSIGSTKSZ
. При совершении прыжка вucp
с помощьюsetcontext
илиswapcontext
исполнение начинается с точки входа в функциюfunc
с числом аргументовargc
. При завершенииfunc
управление передаётсяucp.uc_link
.
int swapcontext(ucontext_t *oucp, ucontext_t *ucp)
- Передаёт управление
ucp
и сохраняет текущее состояние выполнения вoucp
.
Пример
[править | править код]Пример ниже демонстрирует итератор, реализованный с помощью setcontext
. Подобный код можно встретить достаточно редко; вместо использования setcontext
для реализации кооперативной многозадачности часто используется различные библиотеки-обёртки, например, GNU Portable Threads.
#include <stdio.h>
#include <stdlib.h>
#include <ucontext.h>
/* Функция-итератор. Вход в неё осуществляется при первом вызове
* swapcontext, затем проходит в цикле от 0 до 9. Каждое значение сохраняется
* i_from_iterator, после чего производится возврат в основной цикл с помощью swapcontext.
* В основном цикле производится вывод значения и вызов swapcontext для возврата
* назад в функцию. При достижении конца цикла исполнение переключается на контекст main_context1*/
void loop(
ucontext_t *loop_context,
ucontext_t *other_context,
int *i_from_iterator)
{
int i;
for (i=0; i < 10; ++i) {
/* Запись счётчика цикла в место возврата итератора. */
*i_from_iterator = i;
/* Сохранение контекста цикла в ''loop_context'' и переключение на другой контекст. */
swapcontext(loop_context, other_context);
}
}
int main(void)
{
/* Три контекста:
* (1) main_context1 : указывает на main для возврата из цикла.
* (2) main_context2 : указывает на место переключения контекста в main
* (3) loop_context : указывает на место в цикле, в которое будет
* переходить управление из main. */
ucontext_t main_context1, main_context2, loop_context;
/* Стек для функции итератора. */
char iterator_stack[SIGSTKSZ];
/* Флаг, сообщающий о завершении итератора. */
volatile int iterator_finished;
/* Возвращаемое значение итератора. */
volatile int i_from_iterator;
/* Инициализация контекста итератора. uc_link указывает на main_context1,
* точку возврата при завершении итератора. */
loop_context.uc_link = &main_context1;
loop_context.uc_stack.ss_sp = iterator_stack;
loop_context.uc_stack.ss_size = sizeof(iterator_stack);
getcontext(&loop_context);
/* Заполнение loop_context, что позволяет swapcontext начать цикл.
* Преобразование в (void (*)(void)) необходимо для избежания предупреждения
* компилятора и не влияет на поведение функции. */
makecontext(&loop_context, (void (*)(void)) loop,
3, &loop_context, &main_context2, &i_from_iterator);
/* Очистка флага завершения. */
iterator_finished = 0;
/* Сохранения текущего контекста в main_context1. При завершении цикла
* управление будет возвращено в эту точку. */
getcontext(&main_context1);
if (!iterator_finished) {
/* Установка флага iterator_finished для отключения перезапуска итератора. */
iterator_finished = 1;
while (1) {
/* Сохранение этой точки в main_context2 и переключение на итератор.
* Первый вызов зачинает цикл, последующие осуществляют переключение
* через swapcontext в цикл. */
swapcontext(&main_context2, &loop_context);
printf("%d\n", i_from_iterator);
}
}
return 0;
}
Примечание: данный пример не соответствует справочной странице спецификации [1]. Функция makecontext
требует, чтобы дополнительные параметры были типа int
, а в примере передаются указатели. Это может привести к ошибке на 64-битных платформах (в частности, на архитектурах LP64, где sizeof(void*) > sizeof(int)
). Теоретически эти проблемы могут быть решены, но эти решения также не являются портируемыми.
Примечания
[править | править код]- ↑ The Open Group Base Specifications Issue 6 IEEE Std 1003.1, 2004 Edition . Дата обращения: 30 июля 2010. Архивировано 9 декабря 2010 года.
Ссылки
[править | править код]- System V Contexts — руководство GNU C Library
setcontext(3)
— страница справки man по библиотечным функциям GNU/Linux (англ.)- setcontext — man-страница FreeBSD