Сегапрограммы swapcontext при переключении на uc_link ucontext_t

Я работаю над написанием небольшой библиотеки концептуальных волокон на языке C, используя обычные процедуры makecontext/swapcontext, однако это доставляет мне некоторые проблемы (моя платформа - OSX 10.9 Mavericks, использующая clang-503.0.40).

Вот структуры данных, с которыми я имею дело:

typedef enum {
    /// Fiber is waiting to start execution
    FIBER_PENDING,

    /// Fiber is in the midst of executing
    FIBER_EXECUTING,

    /// Fiber has finished executing
    FIBER_FINISHED,

    /// Fiber is in the process of yielding
    FIBER_YIELDING
} fiber_state;

typedef struct {
    char *stack;
    fiber_state state;
    ucontext_t context;
} fiber;

Вот мини-библиотека (три функции, fiber_init, fiber_run, а также fiber_yield:

#include <string.h>
#include <stdlib.h>
#include <signal.h>
#include <assert.h>
#include <stdio.h>

// Generic log w/ file name and line number
#define LOG(x) fprintf(stderr, "%s:%d |-> %s\n", __FILE__, __LINE__, x)

// the current executing fiber in this thread (or NULL of none are executing)
// (TODO: make this TLS)
static fiber *current_fiber = NULL;

/// prepare fiber 'f' to be run
void fiber_init(fiber *f, void(* fiber_func)()) {
    // zero out the fiber
    memset(f, 0, sizeof(fiber));

    f->state = FIBER_PENDING;
    f->stack = (char*) malloc(SIGSTKSZ);

    // init stack config of the fiber context
    ucontext_t *f_context = &(f->context);

    getcontext(f_context);
    f_context->uc_stack.ss_sp    = f->stack;
    f_context->uc_stack.ss_size  = SIGSTKSZ;
    f_context->uc_stack.ss_flags = 0;

    // initialize the context
    makecontext(f_context, fiber_func, 0);
}

/// Deallocate resources associated with 'f'
void fiber_destroy(fiber *f) {
    free(f->stack);
}

/// Start or resume fiber 'f'
void fiber_run(fiber *f) {
    // context to switch back to when yielding, or when the fiber returns
    ucontext_t return_context;
    f->context.uc_link = &return_context;

    // save the fiber being swapped away from (or NULL)
    fiber *old_fiber = current_fiber;
    current_fiber = f;

    LOG("Swapping into fiber context");

    getcontext(&return_context);
    int status = swapcontext(
        &return_context,
        &(f->context));
    assert(status == 0 && "Failed to swap to fiber context");

    LOG("Back to parent context from swap");

    if(f->state == FIBER_YIELDING) {
        f->state = FIBER_EXECUTING;
        LOG("Fiber yielded");
    }
    else {
        LOG("Fiber done executing; marking as finished");
        current_fiber->state = FIBER_FINISHED;
    }

    // restore old fiber
    current_fiber = old_fiber;
}

/// from witin a fiber, yield control to the caller's context
void fiber_yield() {
    assert(current_fiber && "No fiber is currently running!");

    current_fiber->state = FIBER_YIELDING;

    LOG("Yielding back to caller context");
    int status = swapcontext(
        &(current_fiber->context),
        current_fiber->context.uc_link);
    assert(status == 0 && "Failed to swap to parent context");
    LOG("Swapped back into fiber context (post-yield)");
}

/// query fiber state
int fiber_is_pending(const fiber *const f) {
    return f->state == FIBER_PENDING;
}
int fiber_is_finished(const fiber *const f) {
    return f->state == FIBER_FINISHED;
}
int fiber_is_executing(const fiber *const f) {
    return f->state == FIBER_EXECUTING;
}

Тем не менее, кажется, что вызов fiber_yield() внутри волокна неправильно заменяет контекст контекстом вызывающего абонента (ссылка на который хранится в uc_link контекста волокна, см. current_fiber->context.uc_link в fiber_yield)

След запуска этой программы:

void my_func() {
    LOG(" ------- I'm the fiber function! yielding");
    fiber_yield();
    LOG(" ------- End of my_func");
}

int main() {
    fiber f;
    fiber_init(&f, my_func);

    while(!fiber_is_finished(&f)) {
        fiber_run(&f);
        LOG("Back in main run loop");
    }

    fiber_destroy(&f);
    return 0;
}

дает выход:

fibers.c:70 |-> Swapping into fiber context
test_harness.c:5 |->  ------- I'm the fiber function! yielding
fibers.c:99 |-> Yielding back to caller context
Segmentation fault: 11

Я читал, что OSX имеет ограничения на выравнивание стека (до 16 байтов), но я использую malloc выделить стеки, которые возвращают блок, который выровнен по 16-байтовой границе (или так я прочитал). Тем не менее, кажется, что перестановка порядка объявлений может привести к тому, что segfault иногда не произойдет, но это очень поддельное и трудно воспроизвести.

инспектирование fiber_yield прямо перед вызовом swapcontext показывает, что current_fiber->context имеет очень большой размер стека; намного больше, чем должно быть. Возможно, это признак коррупции

(lldb) p current_fiber->context
(ucontext_t) $3 = {
  uc_onstack = 0
  uc_sigmask = 0
  uc_stack = (ss_sp = 0x00007fff5fbff720, ss_size = 140734799804176, ss_flags = 0)
  uc_link = 0x00007fff5fbff780
  uc_mcsize = 0
  uc_mcontext = 0x00007fff5fbff828
}
(lldb) p *(current_fiber->context.uc_link)
(__darwin_ucontext) $4 = {
  uc_onstack = -541067328
  uc_sigmask = 0
  uc_stack = (ss_sp = 0x00007fff5fbff700, ss_size = 8388608, ss_flags = 0)
  uc_link = 0x0000000000000000
  uc_mcsize = 140734799804400
  uc_mcontext = 0x00007fff5fbff7b8
}

Любая подсказка, что может происходить? Спасибо!

2 ответа

Решение

Я смог воспроизвести ту же проблему, используя ваш код, скомпилированный с Apple gcc-4.2.1, на OS X 10.6.8.

Я заметил, что вы не включаете ucontext.h. Компилирование с -Wall заставляет компилятор предупреждать о неявных объявлениях функций ucontext.

Добавление #include <ucontext.h> вызвал ошибку:

In file included from foo.c:6:
/usr/include/ucontext.h:42:2: error: #error ucontext routines are deprecated, and require _XOPEN_SOURCE to be defined

Добавление #define _XOPEN_SOURCE прежде чем все исправлено, а также поведение программы. Очевидно, что этот макрос изменяет расположение соответствующих структур, чтобы соответствовать ожиданиям и потребностям этих функций.

Я не уверен, что сказать вам о том, что эти функции устарели. Я не знаю ни одной поддерживаемой замены.

Вот Это Да! Последние несколько дней я ломал голову, пытаясь понять, почему указатель в uc_link выглядит испорченным. Тем более, что подобная программа работала безупречно раньше.

На самом деле это было потому, что я переделал заголовок! #define _XOPEN_SOURCE на вершине всех моих файлов сделал свое дело.

Я предполагаю, что включение этого определения изменяет структуру критических структур по всей базе кода.

Другие вопросы по тегам