Быстрый и грязный взломать 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 на сохраненном буфере и так далее и так далее ...
Если вы хотите быть фантазией и хотите временные фрагменты, можно реализовать некоторый сигнал тревоги, чтобы сохранить текущий контекст потока, а затем вызвать расписание().
Какое время лучше?
вы не можете 'longjmp()' в функцию глубже в стеке вызовов, чем выполняемая в данный момент функция. –
По крайней мере, для C++ (и я тоже считаю C) ответ, на который ссылается, просто неверен. Вы вызываете неопределенное поведение. – MikeMB
Можно реализовать резьбу в C с помощью setjmp/longjmp, я помню, как это делалось в университетском классе. Если вам это действительно нужно, я буду копаться в своем мозгу, чтобы узнать, как это сделать. Моя первая мысль состоит в том, что вам нужно выделить пространство стека и настроить jmp_buf, а также сделать его приятным, вам нужен сигнал тревоги или что-то (если только вы просто не захотите сменить текущий поток), который вызовет longjmp для следующего запущенного потока , – mattiash