首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >setjmp和远程跳转来实现线程

setjmp和远程跳转来实现线程
EN

Stack Overflow用户
提问于 2015-10-29 15:14:14
回答 1查看 1.7K关注 0票数 1

我有一个关于使用setjmp和长跳跃来创建可以彼此独立运行的函数堆栈的问题。关于this question

在这里,B()的函数栈似乎位于A的函数栈之上,所以当A超出作用域时,我尝试长跳转到B(),代码就会出现分段错误。修改后的代码如下所示

代码语言:javascript
复制
#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,因为它们已被弃用。这篇文章的目的是帮助我找到这些函数的替代方法

提前感谢!

EN

回答 1

Stack Overflow用户

发布于 2015-10-29 18:02:40

在Windows上快速和肮脏的黑客攻击。对于混乱的命名和混乱的代码,很抱歉。

代码语言:javascript
复制
    // 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子句中运行。

代码语言:javascript
复制
  else
    {
        /* This is where the new thread will start to execute */
        func(args);
      }

它会一直运行,直到我们做一个thread_yield,然后我们会做同样的事情,但是取而代之的是主线程,在它保存的缓冲区上做一个longjmp _ we,依此类推……

如果一个人想要花哨并且想要时间片,可以实现一些警报来保存当前线程上下文,然后调用schedule()。

有没有更清楚的?

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/33408007

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档