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)). Теоретически эти проблемы могут быть решены, но эти решения также не являются портируемыми.

Примечания править

  1. The Open Group Base Specifications Issue 6 IEEE Std 1003.1, 2004 Edition. Дата обращения: 30 июля 2010. Архивировано 9 декабря 2010 года.

Ссылки править