如何创建并行堆栈并在其上运行协程?

2023-12-29

我决定我应该尝试实现协程(我认为我应该这样称呼它们)以获得乐趣和利润。我希望必须使用汇编程序,如果我想让它对任何事情都有用的话,可能还需要一些 C 语言。

请记住,这是出于教育目的。使用已经构建的协程库太容易了(而且真的没什么乐趣)。

你们知道setjmp and longjmp?它们允许您将堆栈展开到预定义的位置,并从那里恢复执行。但是,它无法回退到堆栈上的“稍后”。只能早点回来。

jmpbuf_t checkpoint;
int retval = setjmp(&checkpoint); // returns 0 the first time
/* lots of stuff, lots of calls, ... We're not even in the same frame anymore! */
longjmp(checkpoint, 0xcafebabe); // execution resumes where setjmp is, and now it returns 0xcafebabe instead of 0

我想要的是一种无需线程即可在不同堆栈上运行两个函数的方法。 (显然,一次只能运行一个。我说过,没有线程。)这两个函数必须能够恢复另一个函数的执行(并停止自己的执行)。有点像如果他们是longjmp向对方。一旦它返回到另一个函数,它必须从离开的地方恢复(即,在将控制权交给另一个函数的调用期间或之后),有点像longjmp返回到setjmp.

我是这样想的:

  1. 功能A创建并行堆栈并将其清零(分配内存等)。
  2. 功能A将其所有寄存器推送到当前堆栈。
  3. 功能A将堆栈指针和基指针设置到该新位置,并压入神秘的数据结构指示跳回何处以及将指令指针设置回何处。
  4. 功能A将大部分寄存器清零并将指令指针设置为函数的开头B.

那是为了初始化。现在,以下情况将无限循环:

  1. 功能B在该堆栈上工作,做它需要做的任何工作。
  2. 功能B到了需要打断并给予的地步A再次控制。
  3. 功能B将其所有寄存器压入堆栈,获取神秘的数据结构 A一开始就给了它,并将堆栈指针和指令指针设置到哪里A告诉它。在此过程中,它会回手A一个新的、修改过的数据结构告诉我们从哪里恢复B.
  4. 功能A唤醒,弹出它推入堆栈的所有寄存器,并继续工作,直到需要中断并给出B再次控制。

这一切对我来说听起来不错。然而,有很多事情我不太放心。

  • 显然,在好的 x86 上,有这个pusha将所有寄存器发送到堆栈的指令。然而,处理器架构不断发展,现在使用 x86_64,我们有了更多通用寄存器,可能还有几个 SSE 寄存器。我找不到任何证据表明pusha确实推动了他们。现代 x86 CPU 中有大约 40 个公共寄存器。我必须做所有的事情吗push是我自己吗?而且,没有push对于 SSE 寄存器(尽管肯定有一个等效的 - 我对整个“x86 汇编器”事物很陌生)。
  • 改变指令指针有那么简单吗?我可以这样做吗,比如,mov rip, rax(英特尔语法)?此外,从中获取价值必须有些特殊,因为它不断变化。如果我喜欢的话mov rax, rip(再次英特尔语法),将rip位于mov指令,到它后面的指令,还是介于两者之间? 这只是jmp foo. Dummy.
  • 我已经提到过一个神秘的数据结构几次。到目前为止,我假设它至少需要包含三件事:基指针、堆栈指针和指令指针。还有别的事吗?
  • 我是不是忘记了什么?
  • 虽然我真的很想理解事情是如何运作的,我很确定有一些库可以做到这一点。你知道任何?是否有任何 POSIX 或 BSD 定义的标准方法可以做到这一点,例如pthread对于线程?

感谢您阅读我的问题文本墙。


你是对的PUSHA不能在 x64 上工作,它会引发异常#UD, as PUSHA only压入 16 位或 32 位通用寄存器。请参阅英特尔手册 http://www.intel.com/products/processor/manuals/获取您想了解的所有信息。

Setting RIP很简单,jmp rax将设置RIP to RAX。要检索 RIP,如果您已经知道所有协程退出来源,则可以在编译时获取它,或者可以在运行时获取它,您可以在该调用之后调用下一个地址。像这样:

a:
call b
b:
pop rax

RAX现在将是b。这有效是因为CALL压入下一条指令的地址。这项技术也适用于 IA32(尽管我认为在 x64 上有更好的方法,因为它支持 RIP 相对寻址,但我不知道有哪一种)。当然如果你做了一个函数coroutine_yield,它只能拦截调用者地址:)

由于您无法在一条指令中将所有寄存器推送到堆栈,因此我不建议将协程状态存储在堆栈上,因为无论如何这都会使事情变得复杂。我认为最好的做法是为每个协程实例分配一个数据结构。

为什么你要把函数归零A?那可能没有必要。

以下是我处理整个事情的方法,试图使其尽可能简单:

创建一个结构coroutine_state包含以下内容:

  • initarg
  • arg
  • registers(还包含标志)
  • caller_registers

创建一个函数:

coroutine_state* coroutine_init(void (*coro_func)(coroutine_state*), void* initarg);

where coro_func是指向协程函数体的指针。

该函数执行以下操作:

  1. 分配一个coroutine_state结构cs
  2. assign initarg to cs.initarg,这些将是协程的初始参数
  3. assign coro_func to cs.registers.rip
  4. 将当前标志复制到cs.registers(不是寄存器,只有标志,因为我们需要一些理智的标志来防止世界末日)
  5. 为协程的堆栈分配一些合适大小的区域并将其分配给cs.registers.rsp
  6. 返回指向分配的指针coroutine_state结构

现在我们有另一个函数:

void* coroutine_next(coroutine_state cs, void* arg)

where cs是从返回的结构coroutine_init它代表一个协程实例,并且arg当协程恢复执行时将被输入到协程中。

该函数由协程调用者调用,将一些新参数传递给协程并恢复它,该函数的返回值是协程返回(产生)的任意数据结构。

  1. 将所有当前标志/寄存器存储在cs.caller_registers除了RSP,参见步骤 3。
  2. 存储arg in cs.arg
  3. 修复调用者堆栈指针(cs.caller_registers.rsp),添加2*sizeof(void*)如果你幸运的话会修复它,你必须查找这个来确认它,你可能希望这个函数是stdcall,这样在调用它之前不会篡改寄存器
  4. mov rax, [rsp], 分配RAX to cs.caller_registers.rip;解释:除非你的编译器是破解的,[RSP]将保存指向调用该函数的调用指令后面的指令的指令指针(即:返回地址)
  5. 加载标志和寄存器cs.registers
  6. jmp cs.registers.rip,有效地恢复协程的执行

请注意,我们永远不会从此函数返回,我们跳转到的协程会为我们“返回”(请参阅coroutine_yield)。另请注意,在该函数内部,您可能会遇到许多复杂情况,例如 C 编译器生成的函数序言和结尾,也许还包括寄存器参数,您必须处理所有这些问题。就像我说的,stdcall 会拯救你lots麻烦的是,我认为 gcc -fomit-frame 指针会删除尾声的内容。

最后一个函数声明为:

void coroutine_yield(void* ret);

该函数在协程内部调用,以“暂停”协程的执行并返回到调用者coroutine_next.

  1. 存储标志/寄存器in cs.registers
  2. 修复协程堆栈指针(cs.registers.rsp),再次添加2*sizeof(void*)到它,并且你希望这个函数也成为 stdcall
  3. mov rax, arg(让我们假装编译器中的所有函数都将其参数返回到RAX)
  4. 加载标志/寄存器cs.caller_registers
  5. jmp cs.caller_registers.rip这本质上是从coroutine_next调用协程调用者的堆栈帧,并且由于返回值被传入RAX, 我们回来了arg。我们就说如果arg is NULL,则协程已终止,否则它是任意数据结构。

回顾一下,您可以使用以下命令初始化协程coroutine_init,然后您可以重复调用实例化的协程coroutine_next.

协程的函数本身声明为:void my_coro(coroutine_state cs)

cs.initarg保存初始函数参数(想想构造函数)。每一次my_coro叫做,cs.arg有一个不同的参数,由coroutine_next。这就是协程调用者与协程通信的方式。最后,每次协程想要暂停自己时,它都会调用coroutine_yield,并向其传递一个参数,该参数是协程调用程序的返回值。

好吧,你现在可能会想“这很简单!”,但我忽略了以正确的顺序加载寄存器和标志的所有复杂性,同时仍然保持未损坏的堆栈帧并以某种方式保留协程数据结构的地址(你只需以线程安全的方式覆盖所有寄存器)。对于这一部分,您需要了解编译器内部是如何工作的...祝您好运:)

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何创建并行堆栈并在其上运行协程? 的相关文章

  • 为什么 F# 的默认集合是排序的,而 C# 的不是?

    当从 C 世界迁移到 F 最惯用的可能 思维方式时 我发现了这个有趣的差异 在 C 的 OOP mutable 世界中 默认的集合集合似乎是HashSet https learn microsoft com en us dotnet api
  • 在路由mvc 4中添加公司名称

    我一直在尝试为 Facebook 等用户提供在 URL 中添加公司名称的选项 http localhost 50753 MyCompany Login 我尝试过不同的网址 但没有成功 routes MapRoute name Default
  • 如何在另一个应用程序中挂钩 api 调用

    我正在尝试挂钩另一个应用程序的 ExtTextOut 和 DrawTextExt GDI 方法调用 我知道我需要使用 GetProcAddress 来查找 gdi32 dll 中那些方法的地址 并用我的函数的地址覆盖我想要挂钩的进程中的地址
  • 寻找有效的移位/加法/LEA 指令序列来乘以给定常量(避免 MUL/IMUL)

    我正在尝试编写一个 C 程序 mult c 它有一个接收 1 个 int 参数的 main 函数 用atoi argv 1 这是一些常数k我们想要乘以 该程序将生成一个汇编文件mult s实现 int mult int x return x
  • 在 Java 中创建 T 的新实例

    在C 中 我们可以定义一个泛型class A
  • 删除是如何工作的? [复制]

    这个问题在这里已经有答案了 可能的重复 C 编程 free 如何知道要释放多少 https stackoverflow com questions 1518711 c programming how does free know how m
  • 检测wlan是否关闭

    任何人都可以给我一个提示 如何在 Windows Phone 上以编程方式检测 C 8 1 应用程序 不是 8 0 是否启用 禁用 WLAN 我不想更改这些设置 只是需要知道 该解决方案是一个 Windows 8 1 通用应用程序 Wind
  • CSharpRepl emacs 集成?

    我碰巧知道莫诺CSharpRepl http www mono project com CsharpRepl 是否有 emacs csharp 模式使用它在一个窗口中运行 REPL 并像 python 模式一样在另一个窗口中编译 运行 C
  • 气体:内存引用太多

    编译时指令如下 movl 4 ebp 8 ebp I got 内存引用过多 它出什么问题了 括号之前的数字是字节偏移量 这会导致发生内存引用 并且不能有两个movl 您需要先将值暂时移至寄存器 movl 4 ebp ecx movl ecx
  • 从模板切换传递的类型

    在 C 中是否可以检查传递给模板函数的类型 例如 template
  • C# 开源 NMEA 解析器 [已关闭]

    Closed 这个问题正在寻求书籍 工具 软件库等的推荐 不满足堆栈溢出指南 help closed questions 目前不接受答案 我正在寻找 C 开源 NMEA 解析器 嗯 我自己也不熟悉 但是一些快速搜索显示了一个代码项目 htt
  • 访问 ascx 文件中的母版页控件

    我有一个母版页文件 其中包含 2 个面板控件中的 2 个菜单 我还使用控件来检查用户是否登录并获取用户类型 根据我想要显示 隐藏面板的类型 控件本身不在母版页中引用 而是通过 CMS 系统动态引用 我想在用户控件中使用findcontrol
  • MFC:如何设置CEdit框的焦点?

    我正在开发我的第一个简单的 MFC 项目 但我正在努力解决一个问题 想要设置所有的焦点CEdit其中一个对话框中的框 我的想法是 当打开对话框时 焦点位于第一个编辑框上 然后使用 选项卡 在它们之间交换 我看到了方法SetFocus 但我无
  • UI 函数在快速事件完成之前触发

    我有一个停靠在 Silverlight 应用程序中的 Web 浏览器框架 有时会在其上弹出全窗口 XAML Silverlight UI 元素 我已经或多或少修复了一个老问题 即 Web 框架的内容似乎与 Silverlight 内容不能很
  • 使用 C# 和 wpf 创建类似 Dock 的应用程序

    我需要创建一个与我们购买笔记本电脑时获得的应用程序类似的应用程序 仅当鼠标指针到达窗口顶部时它才可见 那么我怎样才能使用 C 4 0 来做到这一点呢 http www notebookcheck net uploads pics win2
  • 如何对 NServiceBus.Configure.WithWeb() 进行单元测试?

    我正在构建一个 WCF 服务 该服务接收外部 IP 上的请求并将其转换为通过 NServiceBus 发送的消息 我的单元测试之一调用Global Application Start 它执行应用程序的配置 然后尝试将 Web 服务解析为 验
  • 析构函数中的异步操作

    尝试在类析构函数中运行异步操作失败 这是代码 public class Executor public static void Main var c1 new Class1 c1 DoSomething public class Class
  • 如何使用 NPOI 按地址(A1、A2)获取 Excel 单元格值

    我有一个 Excel 单元格地址 例如 A1 A2 如何使用 C 中的 NPOI 框架以编程方式访问此单元格 我找到的一些 Java POI 示例代码 CellReference cr new CellReference A1 row my
  • 如何得知客户端从服务器的下载速度?

    根据客户的下载速度 我想以低质量或高质量显示视频 任何 Javascript 或 C 解决方案都是可以接受的 Thanks 没有任何办法可以确定 您只能测量向客户端发送数据的速度 如果没有来自客户端的任何类型的输入来表明其获取信息的速度 您
  • 使用未分配的局部变量

    我遇到了一个错误 尽管声明了变量 failturetext 和 userName 错误仍然出现 谁能帮帮我吗 Use of Unassigned local variable FailureText Use of Unassigned lo

随机推荐

  • 在android中旋转图像时调整图像大小

    我正在处理 android 项目 在该项目中我想旋转图像并触摸到某个固定的枢轴点 我已经完成了所有这些事情 但我面临一个问题 当我尝试旋转图像时 图像位图被调整大小 我不知道为什么会发生 如果有人有的话请给我一个想法来帮助解决这个问题 my
  • 如何从控制台运行twisted?

    我在 Windows 7 上使用 Python 3 和 Anaconda 我安装了 Twistedconda install twisted 现在我正在尝试运行twisted or twistd 从控制台 但我收到此错误 twisted 不
  • 如何使用 Javascript 从 CDN 加载外部 css 文件?

    简而言之 我想在 a 上加载 bootstrap css 文件web page https secure helpscout net members register 13 在互联网上 不是在我的网站上 在浏览器控制台中使用 Javascr
  • 在 Internet Explorer 中添加事件监听器

    Internet Explorer 9 中的元素对象相当于什么 if Element prototype addEventListener Element prototype addEventListener function 它在 Int
  • 什么情况下需要调用两次GC.Collect

    我们有一个 WPF 应用程序 基于 Unity 具有 MMVVVM 模式 在应用程序生命周期中可以有多个项目生命周期 在每个项目生命周期之后我们都会进行手动 Tear Down 并尝试释放 ViewModel 的所有引用 对于 Unity
  • 写入 PIC 上的 EEPROM

    这里有PIC单片机编程器吗 我正在学习一些使用 pickit2 和它附带的 16F690 芯片的 PIC 微控制器编程 我目前正在尝试各种设施 我可以成功read如果我在 MPLAB 中设置 EEPROM vaklue 则代码中来自 EEP
  • Pushwoosh 无法在 Unity 的发布版本中工作

    我试图将 Pushwoosh 与 Unity 集成 最新版本需要我实际使用 Pro Guard 和 gradle 因为存在 multidex 错误 之后 由于 proguard 出现了重复文件错误 我通过创建 proguard user t
  • 无法弄清楚为什么模型在回发时为空?

    我是 ASP NET MVC 的新手 我正在尝试创建一个非常简单的博客类型网站作为学习一切工作原理的方法 但是 当我从评论表单发布到空模型时遇到问题 我不知道为什么 在博客文章页面上 我有一个 添加评论 链接 它调用一些 JQuery 来呈
  • 在 Qt Creator 中运行纯 C 项目

    我使用 qt Creator 创建了一个普通的 c 项目文件 gt 新文件或项目 gt 非 Qt 项目 gt 普通 C 项目 main c include
  • Maps V2 InfoWindow 中的动态内容

    我想在 Maps V2 片段中的标记上显示信息窗口 问题是 我想显示从网络动态加载的位图通用图像下载器 https github com nostra13 Android Universal Image Loader 这是我的 InfoWi
  • PHPUnit 启用颜色输出

    我正在 Windows 7 上从命令行运行 PHPUnit 我尝试通过以下方式启用颜色输出phpunit color 但它仍然显示相同 没有颜色输出 如何在 Win7 上从命令行获取 PHPUnit 的颜色输出 你将不得不使用http so
  • Aeson:派生结构的某些(但不是全部)字段

    我有一个大型结构 我需要它是 FromJSON 的实例 以便我可以将 json 数据解析到其中 我想自动派生 但单个字段需要 特别小心 因为它是 json 中的一个对象 并且我希望它是我的结构中的值的数组 如何在不编写重复所有字段的巨大 F
  • 如何从角度 6 的数组中删除重复的对象

    我正在尝试删除数组中的重复值对象 但不起作用 我认为重复函数正在起作用 但没有反映在li列表 你能找出我需要改变的地方吗 我的服务文件 addComp Names c this item push name Names componenti
  • NTFS 性能和大量文件和目录

    采用 NTFS 的 Windows 如何处理大量文件和目录 在遇到性能问题或其他问题之前 是否有关于可以放置在单个目录中的文件或目录限制的任何指导 例如 拥有一个包含 100 000 个文件夹的文件夹可以吗 以下是来自某个环境中的一些建议
  • Outlook-根据vba规则将邮件导出为文本

    我很确定以前已经这样做过 有谁知道如何将电子邮件导出到我的 C 驱动器上的文本文件中 我知道如何将电子邮件移动到 Outlook 中但在我的 C 驱动器上的其他文件夹中 Thanks 查看MailItem SaveAs方法 您指定路径和文件
  • 清单合并失败。添加并链接react-native-device-info后

    清单合并失败 属性元数据 android support VERSION value value 26 0 1 来自 com android support cardview v7 25 3 1 AndroidManifest xml 24
  • SqlRoleProvider:调用 Roles.GetRolesForUser 时出现 NullReferenceException

    设想 使用 SqlRoleProvider 对 Sql Server 2012 数据库服务器进行身份验证的 WCF 服务 WCF 托管在 IIS7 Web 服务器上 请看这个错误 System NullReferenceException
  • 将请求从一个jsp转发到另一个jsp并包含所有请求参数?

    我有这种情况 用户在浏览器中的jsp表单上输入一些内容并提交 在 servlet 中 我处理请求并向刚刚继续的客户端显示 jsp page1 按钮 现在单击 继续 我想将此请求转发到另一个 jsp page2 其中所有请求参数都出现在 pa
  • Parse.com,将用户添加到创建的角色

    我有一个云代码 它在用户注册时创建两个帐户角色 下面是方法 Parse Cloud afterSave account function request var accountName request object get name cre
  • 如何创建并行堆栈并在其上运行协程?

    我决定我应该尝试实现协程 我认为我应该这样称呼它们 以获得乐趣和利润 我希望必须使用汇编程序 如果我想让它对任何事情都有用的话 可能还需要一些 C 语言 请记住 这是出于教育目的 使用已经构建的协程库太容易了 而且真的没什么乐趣 你们知道s