我有一个关于使用setjmp和长跳跃来创建可以彼此独立运行的函数堆栈的问题。关于this question
在这里,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)跳回一个主函数,然后再跳到其中一个协程,假设它们是活动的。这是否会起作用,或者是否也会分段错误,因为一些可能已经死掉的协程堆栈位于想要运行的堆栈之上?
如果它看起来确实应该分段错误,那么如何在C++中实现这样的独立couroutines (它可以在其他人已经死亡时运行,并且彼此不依赖)?
注意:我不想使用swapcontext、makecontext、setcontext和getcontext,因为它们已被弃用。这篇文章的目的是帮助我找到这些函数的替代方法
提前感谢!
发布于 2015-10-29 18:02:40
在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 (以及要发送给func的arg NULL )。startThread()函数的作用是为新线程的堆栈分配内存(每个线程都需要自己的堆栈)。分配后,我们将正在运行的线程的上下文保存到新的线程上下文缓冲区(jmp_buf)中,并更改堆栈指针(以及所有其他应该更改的寄存器),使其指向新创建的堆栈。然后,我们将这个新线程添加到等待线程列表中。然后我们从函数中返回。我们仍然在主线中。
在主线程中,我们执行thread_yield(),它会说“好吧,我不想再运行了,让别人来运行吧!”thread_yield函数存储当前上下文,并将主线程放在thread_list的后面。然后我们安排。
Schedule()从thread_list获取下一个线程,并对保存在线程buf (jmp_buf)中的上下文执行longjmp _ in。在这种情况下,线程将继续在我们存储上下文的setjmp的else子句中运行。
else
{
/* This is where the new thread will start to execute */
func(args);
}它会一直运行,直到我们做一个thread_yield,然后我们会做同样的事情,但是取而代之的是主线程,在它保存的缓冲区上做一个longjmp _ we,依此类推……
如果一个人想要花哨并且想要时间片,可以实现一些警报来保存当前线程上下文,然后调用schedule()。
有没有更清楚的?
https://stackoverflow.com/questions/33408007
复制相似问题