c++ - 实现上下文切换 - 第二个功能不再运行

标签 c++ multithreading x86-64 inline-assembly

我正在尝试在 x86-64 上使用 C++ 和内联汇编 (AT&T) 实现上下文切换。
如果我为同一功能保存并重新加载上下文,它似乎可以正常工作。
但是,当我尝试 yield 函数时,它在尝试加载第二个函数上下文后使用 GDB 给了我段错误/损坏的堆栈。

例如,它打印
打印1
打印2
打印1
//损坏的堆栈和程序停止运行

但是,如果我在重新加载之前保存线程 1(第一个函数)的上下文,就不会有问题。

例如,它打印
打印1
打印1
打印1

我正在为上下文保存和堆栈创建内存空间。保存上下文时,堆栈指针和基指针将被保存到一个结构中。之后,堆栈指针将指向上下文内存以压入寄存器值。

我想知道导致堆栈损坏的原因以及为什么我无法加载第二个函数的上下文。如果可能,请帮助我指出代码中的任何错误。谢谢!

#include <stdio.h>
#include <iostream>
#include <vector>
#include <map>
#include <memory>
#include <algorithm>
namespace CORO
{
using ThreadID = unsigned;
static int thread_count;

enum STATE
{
  READY,
  ACTIVE,
  WAITING,
  ENDED
};

struct thd_data
{
  int parent_ID = 0;
  int id = 0;
  STATE state = READY;

  int * stack_mem;
  void * stackptr;
  void * stackbp;
  void*(*funcptr)(void*);
  void * param = nullptr;
  int * context_mem;
  int * context_sp;

  thd_data()
  :stack_mem{new int[1024]}, context_mem{new int[1024]} 
  {

  }

  thd_data(const thd_data & rhs)
  :stack_mem{new int[1024]}, context_mem{new int[1024]} 
  {

  }

  thd_data & operator=(const thd_data & rhs)
  {

  }
};

static thd_data* curr_thd;

std::map<int, std::shared_ptr<thd_data>> threadmap;
std::vector<int>activeListID;

// Returns a pointer to next thread
thd_data * FindNextThread()
{
  int new_id;
  for(const auto & elem : activeListID)
  {
    if(elem != curr_thd->id)
    {
      new_id = elem;
      break;
    }
  }
  auto threadmap_elem = threadmap.find(new_id);
  if(threadmap_elem != threadmap.end())
  {
    return &(*threadmap_elem->second);
  }
  else
  {
    return nullptr;
  }
}

void thd_init()
{
  threadmap[0] = std::make_shared<thd_data>();
  auto main_thd = threadmap.find(0)->second;

  main_thd->state = ACTIVE;
  main_thd->id = 0;
  main_thd->param = nullptr;
  main_thd->funcptr = nullptr;
  activeListID.push_back(main_thd->id);

  curr_thd = &(*main_thd);
}

ThreadID new_thd( void*(*func)(void*), void *param)
{
  thread_count += 1; // increment counter

  threadmap[thread_count] = std::make_shared<thd_data>();
  auto thd = threadmap.find(thread_count)->second;

  thd->state = READY;
  thd->id = thread_count;
  activeListID.push_back(thd->id);

  thd->stackptr = thd->stack_mem+1024;
  thd->stackbp = thd->stack_mem;
  thd->funcptr = func;
  thd->param = param;
  return thd->id;
}

void thd_yield()
{
  // Find the next ready thread
  thd_data* thd = FindNextThread();

  if(thd == nullptr)
    return;

  // Move ID to the end of vector
  activeListID.erase(std::remove(activeListID.begin(), activeListID.end(), curr_thd->id), activeListID.end());
  activeListID.push_back(curr_thd->id);

  // Save context
  {
    asm volatile
    (
      "movq %%rsp, %0\n\t" // save stack pointer
      "movq %%rbp, %1\n\t" // save rbp
      "movq %3, %%rsp\n\t" // point to context mem then push register values into it
      "pushq %%rax\n\t"
      "pushq %%rbx\n\t"
      "pushq %%rcx\n\t"
      "pushq %%rdx\n\t"
      "pushq %%rsi\n\t"
      "pushq %%rdi\n\t"
      "pushq %%r8\n\t"
      "pushq %%r9\n\t"
      "pushq %%r10\n\t"
      "pushq %%r11\n\t"
      "pushq %%r12\n\t"
      "pushq %%r13\n\t"
      "pushq %%r14\n\t"
      "pushq %%r15\n\t"
      "pushfq\n\t"
      "movq %%rsp, %2\n\t" // save rsp into context sp (end of context mem)
      "movq %4, %%rsp\n\t" // restore stackptr into rsp
      :"+m"(curr_thd->stackptr)   
      ,"+m"(curr_thd->stackbp)    
      ,"+m"(curr_thd->context_sp)
      :"m"(curr_thd->context_mem) 
      ,"m"(curr_thd->stackptr)    
      :"rsp"
    );
  }

  curr_thd->state = WAITING;
  curr_thd = thd;

  // Calls function if thread is not running
  if(thd->state == READY)
  {
    thd->state = ACTIVE;
    thd->funcptr(thd->param);
  }
  else
  {
    // Restore context
    {
      asm volatile
      (
        "movq %0, %%rbp\n\t" // restore stackbp into rbp
        "movq %1, %%rsp\n\t" // point to context memory to pop
        "popfq\n\t"
        "popq %%r15\n\t"
        "popq %%r14\n\t"
        "popq %%r13\n\t"
        "popq %%r12\n\t"
        "popq %%r11\n\t"
        "popq %%r10\n\t"
        "popq %%r9\n\t"
        "popq %%r8\n\t"
        "popq %%rdi\n\t"
        "popq %%rsi\n\t"
        "popq %%rdx\n\t"
        "popq %%rcx\n\t"
        "popq %%rbx\n\t"
        "popq %%rax\n\t"
        "movq %2, %%rsp\n\t" // point to TCB stack pointer
        :
        :"m"(thd->stackbp)
        ,"m"(thd->context_sp)
        ,"m"(thd->stackptr)
        :"rsp"
      );
    }
  }
}
} // end namespace
void* print1(void *a)
{
  int i;
  for(i=0; i< 20; i++)
  {
    std::cout<<"Print1 i: "<<i<<std::endl;
    if((i+1)%4==0)
        CORO::thd_yield();
  }
  return NULL;
}

void* print2(void *a)
{
  int i;
  for(i=0; i< 20; i++)
  {
    std::cout<<"Print2 i: "<<i<<std::endl;
    if((i+1)%4==0)
        CORO::thd_yield();
  }
  return NULL;
}


int main()
{
  CORO::ThreadID id;
  CORO::thd_init();
  id = CORO::new_thd(print2, NULL);
  print1(NULL);
}

最佳答案

首先,当您在写入非早期 clobber 输出后读取输入时,您的第一个 asm 语句使用未定义的值覆盖 rsp;对于这样的操作,它应该读取输出参数,curr_thd->stackptr 不应该是输入参数。

当开始一个新的线程时,你的代码并没有切换到一个新的堆栈,而是使用旧的线程堆栈。这解释了您的崩溃。

您的第二个 asm 语句恢复适合离开第一个 asm 语句的寄存器值,但退出时堆栈状态和指令指针适合离开第二个 asm 语句;这会导致未定义的行为。如果函数以某种方式被复制,它也会在上下文切换函数的错误拷贝中。

GCC 内联汇编程序不得更改不在输出或破坏列表中的寄存器的内容,也不得控制进入一个 asm 语句并离开另一个(在同一线程中);这样做会导致未定义的行为。因此,保存上下文和恢复它不能是单独的 asm 语句。

您应该为上下文切换使用单个程序集 block 。尤其是上下文切换,避免内联汇编是最简单的。

关于c++ - 实现上下文切换 - 第二个功能不再运行,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45049249/

相关文章:

java - 使用 H2 数据库了解 JdbcConnectionPool

go - Go,x64汇编和CMOVLMI : Where is this opcode described?

c - 这是一个多么好的内存分配器?

c++ - 使用线程局部变量可能导致的最大缓存未命中数

c++ - 如何创建定义变量的宏?

java - qt项目代码到xml文件转换

c++ - 找不到 Gurobi 库

c# - AutoResetEvent 与 bool 值停止线程

java - 防止 JList 刷新失去焦点

c++ - String Vector 程序在输入前退出