2015-10-29 2 views
1

У меня возник вопрос об использовании setjmp и longjump для создания стеков функций, которые могут работать независимо друг от друга. Со ссылкой на this questionsetjmp и longjump для реализации потоков

Здесь стек функций для B(), по-видимому, находится над вершиной для A, поэтому, когда A выходит за пределы области действия, и я пытаюсь переходить на B() код segfaults. Модифицированный код выглядит так

#include <stdio.h> 
#include <setjmp.h> 
#include <iostream> 
using namespace std; 

jmp_buf bufferA, bufferB; 

void routineB(); // forward declaration 

void routineA() 
{ 
    int r ; 

    printf("(A1)\n"); 

    r = setjmp(bufferA); 
    if (r == 0) routineB(); 

    printf("(A2) r=%d\n",r); 

    r = setjmp(bufferA); 
    if (r == 0) longjmp(bufferB, 20001); 

    printf("(A3) r=%d\n",r); 

    r = setjmp(bufferA); 
    if (r == 0) longjmp(bufferB, 20002); 

    printf("(A4) r=%d\n",r); 
} 

void routineB() 
{ 
    int r; 

    printf("(B1)\n"); 

    r = setjmp(bufferB); 
    if (r == 0) longjmp(bufferA, 10001); 

    printf("(B2) r=%d\n", r); 

    r = setjmp(bufferB); 
    if (r == 0) longjmp(bufferA, 10002); 

    printf("(B3) r=%d\n", r); 

    r = setjmp(bufferB); 
    if (r == 0) longjmp(bufferA, 10003); 

    cout << "WHAT" << endl; 
} 


int main(int argc, char **argv) 
{ 
    routineA(); 
    longjmp(bufferB, 123123); 
    return 0; 
} 

Я думал, что мы можем иметь одну главную функцию каждого сопрограмму (в данном случае B и A) отскакивает назад, к которому затем, в свою очередь переходит к одной из сопрограммам при условии, что они в живых. Будет ли это работать или будет это также segfault, потому что некоторые из стеков для сопрограмм, которые, возможно, мертвы, находятся поверх тех, которые хотят работать?

Если это похоже на segfault, то как можно реализовать такие независимые курутки (которые могут работать, когда другие умерли и не зависят друг от друга) в C++?

ПРИМЕЧАНИЕ. Я не хочу использовать swapcontext, makecontext, setcontext и getcontext, поскольку они устарели. Цель этой публикации - помочь мне найти альтернативы этим функциям

Заранее благодарен!

+2

вы не можете 'longjmp()' в функцию глубже в стеке вызовов, чем выполняемая в данный момент функция. –

+0

По крайней мере, для C++ (и я тоже считаю C) ответ, на который ссылается, просто неверен. Вы вызываете неопределенное поведение. – MikeMB

+0

Можно реализовать резьбу в C с помощью setjmp/longjmp, я помню, как это делалось в университетском классе. Если вам это действительно нужно, я буду копаться в своем мозгу, чтобы узнать, как это сделать. Моя первая мысль состоит в том, что вам нужно выделить пространство стека и настроить jmp_buf, а также сделать его приятным, вам нужен сигнал тревоги или что-то (если только вы просто не захотите сменить текущий поток), который вызовет longjmp для следующего запущенного потока , – mattiash

ответ

0

Быстрый и грязный взломать Windows. Извините за беспорядочный именование и грязный код.

// mythreads.cpp : Defines the entry point for the console application. 
// 

#include <setjmp.h> 
#include <stdlib.h> 
#include <stdio.h> 
#include <stdint.h> 
#include "List.h" 

#include <Windows.h> 

struct thread_t 
{ 
    util::Node node; 
    jmp_buf buf; 
}; 

util::List thread_list; 
thread_t* runningThread = NULL; 

void schedule(void); 

void thread_yield() 
{ 
    if (setjmp(runningThread->buf) == 0) 
    { 
    thread_list.push_back(&runningThread->node); 
    schedule(); 
    } 
    else 
    { 
    /* EMPTY */ 
    } 
} 

void myThread1(void *args) 
{ 
    printf("myThread1\n"); 
} 

void startThread(void(*func)(void*), void *args) 
{ 
    thread_t* newThread = (thread_t*)malloc(sizeof(thread_t)); 

    // Create new stack here! This part is Windows specific. Find the current stack 
    // and copy the contents. One might do more stuff here, e.g. change the return address 
    // of this function to be a thread_exit function. 
    NT_TIB* tib = (NT_TIB*)__readfsdword(0x18); 
    uint8_t* stackBottom = (uint8_t*)tib->StackLimit; 
    uint8_t* stackTop = (uint8_t*)tib->StackBase; 

    size_t stack_size = stackTop - stackBottom; 
    uint8_t *new_stack = (uint8_t*)malloc(stackTop - stackBottom); 
    memcpy(new_stack, stackBottom, stack_size); 

    _JUMP_BUFFER *jp_buf = (_JUMP_BUFFER*)newThread->buf; 

    if (setjmp(newThread->buf) == 0) 
    { 
    // Modify necessary registers to point to new stack. I may have 
    // forgotten a bunch of registers here, you must do your own homework on 
    // which registers to copy. 
    size_t sp_offset = jp_buf->Esp - (unsigned long)stackBottom; 
    jp_buf->Esp = (size_t)new_stack + sp_offset; 
    size_t si_offset = jp_buf->Esi - (unsigned long)stackBottom; 
    jp_buf->Esi = (size_t)new_stack + si_offset; 
    size_t bp_offset = jp_buf->Ebp - (unsigned long)stackBottom; 
    jp_buf->Ebp = (size_t)new_stack + bp_offset; 
    thread_list.push_back(&newThread->node); 
    } 
    else 
    { 
    /* This is where the new thread will start to execute */ 
    func(args); 
    } 
} 

void schedule(void) 
{ 
    if (runningThread != NULL) 
    { 

    } 

    if (thread_list.size() > 0) 
    { 
    thread_t* t = (thread_t*)thread_list.pop_front(); 
    runningThread = t; 
    longjmp(t->buf, 1); 
    } 
} 

void initThreading() 
{ 
    thread_list.init(); 

    thread_t* mainThread = (thread_t*)malloc(sizeof(thread_t)); 

    if (setjmp(mainThread->buf) == 0) 
    { 
    thread_list.push_back(&mainThread->node); 
    schedule(); 
    } 
    else 
    { 
    /* This is where the main thread will start to execute again */ 
    printf("Main thread running!\n"); 
    } 
} 

int main() 
{ 
    initThreading(); 

    startThread(myThread1, NULL); 

    thread_yield(); 

    printf("Main thread exiting!\n"); 
} 

Кроме того, Microsoft и setjmp/longjmp могут не совпадать. Если бы у меня была рядом с Unix, я бы сделал это на этом.

И мне, вероятно, придется изучить немного больше о копии стека, чтобы убедиться, что она работает правильно.

РЕДАКТИРОВАТЬ: Чтобы проверить, какие регистры должны быть изменены после замены батареи, вы можете обратиться к руководству по компилятору.

Код начинается с main(), настраивая структуру потоков. Он делает это, рассматривая ваш основной поток (единственный поток в этой точке) как только поток среди других. Итак, сохраните контекст для основного потока и поместите его в thread_list. Затем мы планируем(), чтобы один поток выполнялся. Это делается в initThreading(). Поскольку основной поток является единственным потоком, мы продолжим работу в main().

Следующее, что делает главная функция, - это startThread с указателем функции как параметр (и аргумент arg NULL для отправки в func). Что делает функция startThread(), так это то, что она выделяет память для стека для нового потока (каждый поток нуждается в собственном стеке). После выделения мы сохраняем контекст работающего потока в новый буфер контекста потока (jmp_buf) и изменяем указатели стека (и allo другие регистры, которые следует изменить), чтобы он указывал на вновь созданный стек. Затем мы добавляем этот новый поток в список ожидающих потоков. И тогда мы возвращаемся от функции. Мы все еще в главной теме.

В основном потоке мы делаем thread_yield(), который гласит: «Хорошо, я больше не хочу запускать, пусть кто-то еще запускается!». Функция thread_yield сохраняет текущий контекст и помещает основной поток в конец списка thread_list. Затем мы планируем.

Schedule() принимает следующий поток из thread_list и делает longjmp контексту, сохраненному в потоке buf (jmp_buf). В этом случае поток будет продолжать выполняться в предложении else для setjmp, где мы сохраняем контекст.

else 
    { 
     /* This is where the new thread will start to execute */ 
     func(args); 
     } 

Он будет работать, пока мы не делаем thread_yield и тогда мы будем делать то же самое, но вместо того, чтобы взять на себя основной поток и сделать longjmp на сохраненном буфере и так далее и так далее ...

Если вы хотите быть фантазией и хотите временные фрагменты, можно реализовать некоторый сигнал тревоги, чтобы сохранить текущий контекст потока, а затем вызвать расписание().

Какое время лучше?

+0

Извините, мне очень трудно понять это. Не могли бы вы попытаться объяснить? Или вы могли бы связать меня со своим университетским классом, который реализовал это и получил объяснение? – Curious

+0

А, я взял этот класс 20 лет назад ... Я отредактирую свой ответ, чтобы попытаться объяснить. – mattiash

+0

Спасибо за объяснение! Единственной частью, которую я смутил в вашем коде, была часть, в которой вы изменяете стек в jmp_buf. Что там происходит? – Curious

Смежные вопросы