【问题标题】:Change stack space更改堆栈空间
【发布时间】:2013-06-20 18:48:51
【问题描述】:

目前,我尝试在集群系统中实现共享堆栈。该系统有两个地址空间,一个是私有的,一个在所有处理器之间共享。

如何在 c 编程中更改堆栈的开始和结束?

也就是说,我想把堆栈放在一个共享空间中,并改变程序的流程来使用它。

【问题讨论】:

  • 你需要这个做什么?希望所有线程使用相同的堆栈?没有办法永远有效。想要将指向一个堆栈上的东西的指针传递给另一个线程?坏主意,同步影响会杀了你。想要有一个共享堆栈,任何线程都可以推送/弹出东西,现在这是另一回事了。所以请详细说明你真正想要实现的目标。
  • 我的想法是使用共享堆栈通过传递指针在线程之间进行通信。这个想法是,通过消息将共享变量的指针发送到其他处理器。然后处理器可以访问这个共享地址而不需要将数据复制到它的空间。
  • 另外,我不想使用malloc或其他形式来分配数据并传递它的指针。我需要做与OpenMP模型相同的过程。
  • 你的想法是共享内存;为什么它必须是共享堆栈?如果它只是共享内存而不是共享堆栈,则您不需要线程来更改堆栈,您的问题就会消失。
  • ... 我想你会发现,如果你在所有进程之间都有未区分的共享内存,那么你很可能很难确保所有线程都可以使用低延迟的内存,并且良好的一致性(其他人通过谈论同步来暗示这一点)。

标签: c linux gcc compiler-construction


【解决方案1】:

我的建议是:不惜一切代价避免将指针传递给基于堆栈的对象。如果您这样做了,您将要求发送线程永远不要从创建共享对象的函数返回,然后才能证明没有其他线程将再以任何方式访问该对象。这 a) 几乎是不可能的,并且 b) 需要性能扼杀锁定。未能正确执行此操作的后果将是完全不确定的错误!不要这样做!

我建议 malloc 所有 对象离开一个线程的上下文并对其使用线程安全引用计数。您还可以看看线程安全的共享数据结构,如队列、双链表等。

【讨论】:

  • 感谢您的反馈,OpenMP 的多个实现已移植到集群或 DSM 系统上。我的平台类似于 DSM。我试图实现一个支持集群上共享内存应用程序的新编程模型。因此,我需要像本文第 4.6 节中的想法那样实现共享堆栈:link.springer.com/chapter/10.1007%2F978-3-540-68555-5_7#page-1
【解决方案2】:

我怀疑你能否在 C 中很好地实现你想要的(除非你想改变编译器)。

您说您想要一种 OpenMP 风格的编程。这相当于一个顶级线程,可以分叉子级以在父级提供的共享空间上进行操作。 Fancy OpenMP 以递归方式执行此操作。

要做到这一点很好,你的系统/工具/编译器需要能够识别

*   threads of computations  
*   what variables are declared by each thread
*   what variables (or parts thereof) are offered by a thread to its thread children

如果您可以做到这些(并且通过显式语言支持更容易,启用 OpenMP 的编译器在此过程中由 OpenMP 编译指示辅助),那么您可以将线程的数据分成 3 个部分:

1.  storage accessed only by the thread, and not by its children
2.  storage declared by the thread, but accessed only by individual children (e.g., slices of arrays)
3.  storage read and written by the parent thread and its children

此时,您可以考虑为线程布局“局部变量”,从而获得堆栈空间。 线程本地存储分配给父线程的堆栈空间。父声明但子处理的存储成为分配给子本地空间/堆栈的空间。所有人都可以读写的存储可以放置在可以访问的任何地方(在父线程的本地空间中,在他的堆栈中,在堆存储中),并且需要访问保护以防止数据竞争。 [你不能强迫传统的 C 编译器为你做这件事。]

这种将数据划分到不同线程本地/堆栈空间的做法使您使用 C 并将“所有线程堆栈”设置为 一个 的明显方案在共享内存中难以驾驭。如果所有线程都具有相同的堆栈区域,那么哪个存储是线程本地的?特别是,如果两个线程都想写入自己的局部变量 I,并且 I 在共享空间中,那么它就不是真正的局部变量。如果您将共享空间划分为不相交的线程堆栈,那么您实际上并没有共享存储空间,至少没有按名称共享;充其量您可以使用指向其他线程堆栈的指针进行共享。所有这些都很难编程,因此容易出错,我不想调试为这样的系统编写的程序。它还对您宝贵的共享空间提出了额外的要求;你有线程局部变量吃掉它,但不需要共享。

如果您有一个静态数量的线程,并继续坚持使用一些可用的 C 编译器,您最好手动分配共享数据(在运行时动态分配,或者在编码/编译时通过分区共享内存)。但是您的线程现在可以使用其本地内存中的“标准”堆栈运行,并且不需要堆栈切换。

[编辑:在我对仙人掌堆发表评论后,OP 想了解更多。我包括 此处的评论,以及一些指向有关它们的详细信息的指针]

一个栈可以被多个线程共享。 仙人掌堆栈的概念是这样一个概念,其中一个父线程拥有一个现有的(仙人掌)堆栈,与所有并行子线程共享该堆栈,每个子线程都有自己的堆栈空间,但可以看到/共享父堆栈。

我们的 PARLANSE 并行编程语言直接实现了这个概念,我们在大约 2-4 百万行代码的应用程序中使用它。每个函数调用堆分配 它的激活记录(使用线程本地分配器来提高速度),并且可以通过词法级向上寻址访问所有父堆栈段,实现为在函数调用上由父传递给其子的指针框。下面的英特尔博客准确地描述了我们这样做的原因。

参考资料:

【讨论】:

  • 在OpenMP模型原版中,主线程的栈是所有线程共享的。因此,gcc 编译器收集这些局部/全局变量的所有指针并将它们放入元数据(结构)中并作为参数传递给其他线程。然后,编译器将变量的所有引用交换为指针(例如,int a ---> int *a)。最后,所有线程都可以通过指针访问它们。
  • 在我的实现中,我需要通过将主处理器的堆栈分配到已知的地址空间中,然后将每个从处理器的地址空间重新映射为相同的虚拟地址,从而使所有其他处理器可见主核心。最后,每个处理器都可以使用 gcc 编译器的原始机制访问这些变量。因此,它不需要改变 OpenMP 方法的原始实现。
  • 关于 GCC 的 OpenMP 方案:“主线程的堆栈是共享的”这句话很容易被误解。您所描述的内容是有道理的,但是在所有线程将其堆栈指针设置为主堆栈位置或其中任何位置的事件的意义上,主线程堆栈并不是共享的;相反,正如您所描述的,每个线程都有自己的堆栈,并且将一个指向主堆栈中有趣位置的指针框传递给子级。所以 1)它不像你的问题是要求做的,并且 2)它需要 GCC 编译器的帮助才能做到这一点。你能修改你的编译器吗?
  • 如果你可以修改你的编译器,我建议一个主线程,其空间不在你的共享空间中。相反,理想的解决方案是将所有共享变量从主堆栈中取出,并将它们分配到共享内存区域。将指针框传递给 master 和所有子节点,或者将它们全部传递给共享虚拟地址页面。
  • ...你提议使用GCC不变?嗯。好吧,我可能相信这会奏效。是的,您需要设置每个线程的堆栈指针,但您需要将它们设置到不同的位置。它很容易。对于主线程,实际上是在 main 中放入汇编程序,将现有的 ESP 保存到某个主程序 StackSave 中,使用共享内存的位置加载 ESP(并在退出 main 时反转此操作)。我不确定您如何处理 OpenMP 子线程,因为它们是由 GCC 编译器生成的一些自定义代码启动的,您需要拦截和修改这些代码。
【解决方案3】:

为了清楚起见,让我稍微编辑一下:

你只是想改变堆栈的含义和定义。按照今天的定义,堆栈只能由一个线程用于真正的堆栈操作。堆栈可以由进程或线程共享,以便像普通内存一样使用。在这种情况下,您真的应该将那块内存称为“共享内存”而不是“共享堆栈”。您在集群环境中真正想要做什么或实现什么?

这是一个为什么不能共享 STACK 的示例...

Thread-1 函数调用顺序:

main()
    thr1_f1()
        thr1_f2()
            thr1_f3()
    thr1_f4()

Thread-2 函数调用顺序:

main()
    thr2_f1()
        thr2_f2()
            thr2_f3()
    thr2_f4()

假设这些线程共享堆栈。这是一个可能的函数调用序列...

  1. Thread-1 位于 thr1_f1() 的中间,并在处理器 core-1 中进入睡眠状态。
  2. 线程 2 在 thr2_f2() 的中间启动并在处理器核心 2 中进入睡眠状态。
  3. Thread-1 出现并调用 thr1_f2()。这将覆盖 Thread-2 STACK 区域。 thr2_f2() 的局部变量和参数现在乱七八糟了。
  4. Thread-2 将无法运行和/或在 thr2_f1() 中获取它的返回地址。根据时间的不同,它可能会获取一些地址并转储内核。

【讨论】:

  • 该堆栈定义的任何参考资料?
  • 一个栈可以被多个线程共享。仙人掌堆栈的概念,其中具有现有(仙人掌)堆栈的父线程与其所有并行子线程共享该堆栈,每个子线程都获得自己的堆栈空间,但可以查看/共享父堆栈。我们的 PARLANSE 并行编程语言直接实现了这个概念,我们使用它的应用程序大约有 2-4 百万行代码。见semanticdesigns.com/Products/PARLANSE
  • @Ira Baxter:你能给我们更多关于仙人掌堆栈模式的细节吗?另外,可以在Linux中实现它吗?提前致谢。
  • 如果你对编译器有足够的控制权,你可以在任何你想要的地方实现它。这是有代价的:你可以控制编译器,这通常是一个非常大的项目。
  • @Ira Baxter:正如你所说,每个线程都有自己的堆栈,每个线程只会使用自己的堆栈进行推送和弹出操作。这才是我真正的意思。一个线程只能将一块内存视为堆栈,并将其用于函数调用和返回。一个子线程看到父线程的堆栈并不意味着它可以将其用作堆栈(父线程的堆栈上没有推送和弹出)。它只能将其用作任何其他内存位置并以这种方式使用它......
猜你喜欢
  • 2021-12-31
  • 1970-01-01
  • 2011-10-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-06-15
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多