setjmp и longjump для реализации потоков

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

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

#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, то как можно реализовать такие независимые процедуры (которые могут выполняться, когда другие умерли и не зависят друг от друга) в C ++?

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

Заранее спасибо!

1

Решение

Быстрый и грязный взлом на 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 (), настраивая структуру потоков. Он делает это, рассматривая ваш основной поток (единственный поток на данный момент) как просто поток среди других. Итак, сохраните контекст для основного потока и поместите его в список потоков. Затем мы планируем () запустить один поток. Это делается в initThreading (). Поскольку единственный работающий поток — это основной поток, мы продолжим выполнять функцию main ().

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

В главном потоке мы выполняем 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 на его сохраненном буфере и так далее, и так далее …

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

Есть яснее?

0

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]