【问题标题】:Fast stack switching under x86-64x86-64下的快速堆栈切换
【发布时间】:2018-09-07 14:51:53
【问题描述】:

在 x86-64 下切换堆栈的最少代码是多少?我正在尝试在 Windows 和 Linux 下实现纤程,没有 getcontext 或 setjmp + 内联汇编。真的像交换 $rsp 和 $rbp 一样简单吗?因为我可以很容易地做到这一点。我只是不知道该怎么做。我对我的 x86-64 知识生疏了。

【问题讨论】:

  • 我不确定最快的方法是什么,但您可能想看看libcoro,它有很多实现。另外,(作者)告诉我this 非常快。在 Windows 上,有一个 fibers API,这对于您的代码来说非常少……
  • 你需要一些 asm,因为没有它你不能切换rsp。此外,根据您放置(例如)coroutine_switchto() 调用的位置,您需要保存/恢复一些被调用者保存的 regs 和 [可能] 标志寄存器
  • 这个问题有点令人困惑。您想知道切换堆栈的最简单方式还是最快方式或smallest-code-size方式?标题说得很快,但问题的主体说代码最少。

标签: c linux windows assembly x86-64


【解决方案1】:

必须将 RSP 更改为指向不同的堆栈,作为上下文切换的一部分,该上下文切换保存旧线程/光纤中的所有寄存器并从新寄存器加载保存的架构状态。不仅是 RBP,还有所有 RAX-RDI 和 R8-R15 以及 RIP(通过 jmpret)。我认为所有其他调用保留的架构状态,包括 Windows x86-64 上的 xmm6-15。如果您的代码曾经更改 MXCSR 或 x87 控制寄存器,您也需要保存/恢复这些。

但是如果您将上下文切换放在noinline 函数中,编译器将为常规函数调用生成代码(实际上返回的时间要晚得多),并且函数调用已经崩溃所有呼叫破坏的寄存器。您不必保存调用者的 zmm0-31 或 MPX bnd 寄存器或 RFLAGS。所以可能不值得使用xsaveopt / xrstor 来保存FPU / SIMD状态。

如果你不修改其他 regs,你将有一个糟糕的时间,因为切换到新堆栈和使用旧寄存器的新代码基本上与从编译器下破坏调用保留寄存器相同,即违反 ABI .

您不需要保存RFLAGS,因为在用户空间中唯一可以更改的是条件代码,并且这些代码是调用破坏的。 ABI / 调用约定已经要求在函数调用/返回时明确 DF。

【讨论】:

    【解决方案2】:

    上下文切换由 ABI 确定(必须保留被调用者保存的寄存器)。 boost.context 已经为多种架构提供了实现。 boost.fiber 是一种基于 boost.context 的具有 std::thread 类 API 的纤程抽象。

    【讨论】:

      【解决方案3】:

      Microsoft 确实提供了setjmpsetjmp3longjmp。他们还提供Using setjmp/longjmp

      下面的代码示例可以在Explain setjmp() and longjmp() Functions in Standard C Library with Examples找到

      /* setjmp_longjmp.c -- program handles error through 'setjmp()' */
      /* and longjmp() */
      #include <stdio.h>
      #include <stdlib.h>
      #include <setjmp.h>
      
      /* declare variable of type jmp_buf */
      jmp_buf resume_here;
      
      void hello(void);
      
      int main(void)
      {
          int ret_val;
      
          /* Initialize 'resume_here' by calling setjmp() */
          if (setjmp(resume_here)) {
      
      
          printf("After \'longjump()\', back in \'main()\'\n");
              printf("\'jump buffer variable \'resume_here\'\' becomes "
                        "INVALID!\n");
          }
          else {
              printf("\'setjmp()\' returns first time\n");
              hello();
          }
      
          return 0;
      }
      
      void hello(void)
      {
          printf("Hey, I'm in \'hello()\'\n");
          longjmp(resume_here, 1);
      
          /* other code */
          printf("can't be reached here because I did longjmp!\n");
      }
      

      【讨论】:

      • 在 ISO C 中,longjmp 仅在 jmp_buf 由父函数而非兄弟函数保存时才有效。即您可以返回相同的调用堆栈,而不是切换同级堆栈。 MS 的setjmp3 是否做了一些不同的事情来允许堆栈切换?
      • 我不相信它,它只是允许上下文的附加参数。
      • 没有必要在" 中转义',反之亦然
      • 而不是printf("Hey, I'm in \'hello()\'\n");,只需使用puts("Hey, I'm in 'hello()'");
      猜你喜欢
      • 2018-11-07
      • 2016-04-02
      • 2021-12-05
      • 1970-01-01
      • 1970-01-01
      • 2011-08-16
      • 2013-12-31
      • 1970-01-01
      • 2015-09-03
      相关资源
      最近更新 更多