【问题标题】:Seeking articles on shared memory locking issues求共享内存锁定问题的文章
【发布时间】:2010-10-20 05:21:17
【问题描述】:

我正在审查一些代码,并对所使用的技术感到怀疑。

在linux环境下,有两个进程附加多个 共享内存段。第一个进程周期性地加载一个新的集合 要共享的文件,并将共享内存 id (shmid) 写入 “主”共享内存段中的位置。第二道工序 不断读取此“主”位置并使用 shmid 附加 其他共享段。

在多 CPU 主机上,在我看来它可能取决于实现 至于如果一个进程试图读取内存时会发生什么 被对方写。但也许硬件级总线锁定可以防止 电线上的碎屑?阅读过程是否得到并不重要 一个很快就会改变的值,只有当读取被破坏时才重要 对于既不是旧值也不是新值的东西。这是一个边缘情况:只有 32 位被写入和读取。

谷歌搜索 shmat 的东西并没有让我找到任何确定的东西 区域。

我强烈怀疑这既不安全也不理智,而我真正想要的是 like是一些详细描述问题的文章的指针。

【问题讨论】:

  • 你对问题的想法已经改变(如对文本的编辑所示);您可以更新问题标题以反映这一点吗?

标签: shared-memory


【解决方案1】:

这是合法的——因为在操作系统中不会阻止你这样做。

但它聪明吗?不,你应该有某种类型的同步。

不会有“电线上的损坏位”。它们会以 1 或 0 的形式出现。但是没有什么可说的,在另一个进程尝试读取它们之前,你的所有位都会被写出。而且无法保证它们的写入速度与读取速度。

您应该始终假设 2 个进程(或线程)的操作之间绝对没有关系。

除非您做对了,否则不会发生硬件级总线锁定。使您的编译器/库/操作系统/cpu正确执行可能比预期的要难。编写同步原语以确保其正确发生。

锁定将使其安全,而且并不难做到。所以就去做吧。


@unknown - 自从我的答案发布以来,这个问题发生了一些变化。但是,您描述的行为完全取决于平台(硬件、操作系统、库和编译器)。

如果不给编译器特定的指令,实际上并不能保证一次性写出 32 位。想象一下 32 位字未在字边界上对齐的情况。这种不对齐的访问在 x86 上是可以接受的,而在 x68 的情况下,访问被 cpu 变成了一系列对齐的访问。

在这些操作之间可能会发生中断。如果在中间发生上下文切换,则某些位会被写入,而另一些则不会。砰,你死定了。

另外,让我们考虑一下 16 位 CPU 或 64 位 CPU。两者仍然很受欢迎,但不一定按您的想法工作。

因此,实际上您可能会遇到“其他一些 cpu 核心拾取 1/2 写入的字大小值”的情况。你编写代码,就好像如果你不使用同步,这种事情会发生。

现在,有一些方法可以预先编写您的文章,以确保您写出一个完整的单词。这些方法属于同步的范畴,创建同步原语是最好留给库、编译器、操作系统和硬件设计人员的事情类型。特别是如果您对可移植性感兴趣(即使您从未移植过代码,也应该如此)

【讨论】:

  • 其实这是。你说“......你写的所有位......没有garuntee的......”。这是不正确的。正如海报所说,有一个 garuntee,“写 .. shmid”(shmid 是一个机器字大小的值)。他后来继续说,“只有 32 位被写入和读取。”。他保证将读取和写入同步到那些 32 位数量,CPU 将像 5 位或其他东西一样读取/写入,并让其他一些 CPU-CORE 拾取一个字大小值 1/2 也写了。想想看,这意味着 CORE 是按位读/写的,事实并非如此。
  • @RandomNickName42。正如詹姆斯指出的那样。在 x86 硬件上,您不需要正确对齐数据。如果不这样做,则值的存储不必是原子的,并且可能导致“半写”值。我认为“砰!,你死了”总结得很好:o)
  • 我明白了,是的,我不知道他是否在之后更清楚地补充了这一点,但我明白你现在在说什么;)
【解决方案2】:

这个问题实际上比一些人讨论的还要严重。 Zifre 是对的,在当前的 x86 CPU 上,内存写入是原子的,但这种情况很快就不再是这样了 - 内存写入仅对单个核心是原子的 - 其他核心可能不会以相同的顺序看到写入。

换句话说,如果你这样做了

a = 1;
b = 2;

在 CPU 2 上,您可能会看到位置 b 在位置“a”之前已修改。此外,如果您写入的值大于本机字大小(x32 处理器上的 32 位),则写入不是原子的 - 因此 64 位写入的高 32 位将在与低位不同的时间到达总线32位写入。这会使事情变得非常复杂。

使用内存屏障,你会没事的。

【讨论】:

    【解决方案3】:

    您需要在某处锁定。如果不在代码级别,则在硬件内存缓存和总线。

    您在 PentiumPro 后的 Intel CPU 上可能没问题。从我刚刚读到的内容来看,英特尔让他们后来的 CPU 基本上忽略了机器代码上的 LOCK 前缀。相反,缓存一致性协议确保数据在所有 CPU 之间保持一致。因此,如果代码写入的数据不跨越缓存行边界,它将起作用。无法保证跨缓存线的内存写入顺序,因此多字写入是有风险的。

    如果您使用的不是 x86 或 x86_64,那么您不行。许多非英特尔 CPU(可能还有英特尔安腾)通过使用显式缓存一致性机器命令来获得性能,如果您不使用它们(通过自定义 ASM 代码、编译器内部函数或库),则无法保证通过缓存写入内存永远对另一个 CPU 可见或以任何特定顺序发生。

    因此,仅仅因为某些东西在您的 Core2 系统上运行并不意味着您的代码是正确的。如果您想检查可移植性,请在其他 SMP 架构上尝试您的代码,例如 PPC(较旧的 MacPro 或 Cell 刀片)或 Itanium 或 IBM Power 或 ARM。 Alpha 是一个出色的 CPU,可用于揭示不良 SMP 代码,但我怀疑你能找到它。

    【讨论】:

    • 确实需要 LOCK 前缀来处理原子增量之类的东西。 OTOH,我认为不能保证订购; lfence/sfence/mfence 指令的存在是有原因的,Linux 有 rmb()/wmb()/mb() 无处不在。内存屏障很便宜。互斥锁也很便宜。只需使用它们。
    【解决方案4】:

    通过内存共享数据时,两个进程、两个线程、两个cpu、两个内核都需要特别注意。

    这篇 IBM 文章很好地概述了您的选择。

    Linux 同步方法剖析 内核原子、自旋锁和互斥锁 作者:M. Tim Jones (mtj@mtjones.com),Emulex 顾问工程师

    http://www.ibm.com/developerworks/linux/library/l-linux-synchronization.html

    【讨论】:

      【解决方案5】:

      我实际上认为这应该是完全安全的(但取决于具体的实现)。假设“主”段基本上是一个数组,只要 shmid 可以原子地写入(如果它是 32 位那么可能没问题),并且第二个进程只是读取,你应该没问题。仅当两个进程都在写入时才需要锁定,或者正在写入的值不能以原子方式写入。你永远不会得到一个损坏的(半写的值)。当然,可能有一些奇怪的架构无法处理这个问题,但在 x86/x64 上应该没问题(可能还有 ARM、PowerPC 和其他常见架构)。

      【讨论】:

      • -1 你仍然需要一个内存屏障,即使写入是原子的。
      【解决方案6】:

      阅读Memory Ordering in Modern Microprocessors, Part IPart II

      他们给出了为什么这在理论上是不安全的背景。

      这是一场潜在的比赛:

      • 进程 A(在 CPU 内核 A 上)写入新的共享内存区域
      • 进程 A 将该共享内存 ID 放入共享的 32 位变量中(即 32 位对齐 - 如果您允许,任何编译器都会尝试像这样对齐)。
      • 进程 B(在 CPU 内核 B 上)读取变量。假设 32 位大小和 32 位对齐,它不应该在实践中得到垃圾。
      • 进程 B 尝试从共享内存区域读取。现在,不能保证它会看到 A 写入的数据,因为您错过了内存屏障。 (实际上,在映射共享内存段的库代码中,CPU B 上可能碰巧存在内存屏障;问题是进程 A 没有使用内存屏障。

      此外,尚不清楚如何使用这种设计安全地释放共享内存区域。

      使用最新的内核和 libc,您可以将 pthreads 互斥体放入共享内存区域。 (这确实需要带有 NPTL 的最新版本——我使用的是 Debian 5.0 “lenny”,它工作正常)。对共享变量的简单锁定意味着您不必担心神秘的内存屏障问题。

      【讨论】:

        【解决方案7】:

        我不敢相信你会问这个。 NO不一定安全。至少,这将取决于编译器是否生成代码,当您设置 shmid 时,该代码将自动设置共享内存位置。

        现在,我不了解 Linux,但我怀疑 shmid 是 16 到 64 位。这意味着至少有可能所有平台都有一些指令可以原子地写入这个值。但是你不能在没有被询问的情况下依赖编译器来做这件事。

        内存实现的细节是最特定于平台的东西之一!

        顺便说一句,在您的情况下可能无关紧要,但一般来说,您必须担心锁定,即使在单 CPU 系统上也是如此。一般来说,某些设备可以写入共享内存。

        【讨论】:

          【解决方案8】:

          我同意它可能会起作用 - 所以它可能是安全的,但不是理智的。 主要问题是是否真的需要这种低级共享 - 我不是 Linux 专家,但我会考虑为主共享内存段使用例如 FIFO 队列,以便操作系统为您完成锁定工作.无论如何,消费者/生产者通常需要队列进行同步。

          【讨论】:

            【解决方案9】:

            合法吗?我想。取决于您的“管辖权”。安全和理智?几乎肯定不会。

            编辑:我会更新更多信息。

            您可能想看看this 维基百科页面;特别是关于“协调对资源的访问”的部分。特别是,维基百科的讨论基本上描述了一个信心失败;对共享资源的非锁定访问,即使对于原子资源,也会导致误报/误传操作已完成的信心。本质上,在检查它是否可以修改资源之间的时间段内,资源被外部修改,因此,条件检查中固有的信心被破坏了。

            【讨论】:

              【解决方案10】:

              我认为这里没有人讨论过锁定争用会对总线产生多大影响,尤其是在总线带宽受限的系统上。

              Here 是一篇关于这个问题的深度文章,他们讨论了一些替代调度算法,这些算法减少了对通过总线的独占访问的总体需求。在某些情况下,这比单纯的调度程序增加了 60% 以上的总吞吐量(当考虑显式锁定前缀指令或隐式 xchg cmpx 的成本时)。这篇论文不是最新的工作,也没有多少真正的代码(当学术的),但它值得阅读和考虑这个问题。

              最近的 CPU ABI 提供了比简单的 lock 任何其他操作。

              Jeffr,来自 FreeBSD(许多内部内核组件的作者),讨论了监视器和 mwait,为 SSE3 添加了 2 条指令,在一个简单的测试用例中确定了 20% 的改进。他后来假设;

              所以这是现在的第一阶段 自适应算法,我们旋转了一会儿, 然后在高功率状态下休眠,并且 然后在低功耗状态下休眠 取决于负载。

              ...

              在大多数情况下,我们仍然处于闲置状态 hlt 也是如此,所以应该没有 对权力的负面影响。事实上,它 浪费大量时间和精力 进入和退出空闲状态,所以它 可能会提高负载下的功率 减少所需的总 CPU 时间。

              我想知道使用 pause 代替 hlt 会有什么效果。

              来自Intel's TBB;

                      ALIGN 8
                      PUBLIC __TBB_machine_pause
              __TBB_machine_pause:
              L1:
                      dw 090f3H; pause
                      add ecx,-1
                      jne L1
                      ret
              end
              

              Art of Assembly 也使用同步而不使用锁定前缀或 xchg。我有一段时间没有读过这本书,也不会直接谈论它在用户级保护模式 SMP 上下文中的适用性,但值得一看。

              祝你好运!

              【讨论】:

                【解决方案11】:

                如果 shmid 的类型不是volatile sig_atomic_t,那么即使在同一个 CPU 上,单独的线程也会遇到麻烦。如果类型是volatile sig_atomic_t,那么您就不能完全确定,但是您仍然可能会很幸运,因为多线程可以比信号可以做更多的交错。

                如果 shmid 跨越缓存行(部分在一个缓存行中,部分在另一个缓存行中),那么当写入 cpu 正在写入时,您肯定会发现一个正在读取的 cpu 读取新值的一部分和旧值的一部分。

                这正是发明“比较和交换”等指令的原因。

                【讨论】:

                • 这是 Linux,不是 Windows,因此这是 IPC,而不是多线程。
                • 这是 Linux,因此存在 pthread。但即使 pthread 不存在,我向您保证两个进程不会在同一个线程中执行。
                【解决方案12】:

                听起来你需要一个读写器锁:http://en.wikipedia.org/wiki/Readers-writer_lock

                【讨论】:

                  【解决方案13】:

                  答案是——同时读写是绝对安全的。

                  很明显shm机制 为 用户。必须采取所有访问控制 由程序员照顾。 锁定和 同步是善意的 由内核提供,这意味着 用户对种族的担忧更少 条件。请注意,此模型 只提供了一种对称的方式 在进程之间共享数据。如果一个 进程希望通知另一个 处理新数据已经 插入共享内存,它会 必须使用信号、消息队列、 管道、套接字或其他类型的 IPC。

                  来自Shared Memory in Linux 文章。

                  最新的 Linux shm 实现只使用了copy_to_usercopy_from_user 调用,它们在内部与内存总线同步。

                  【讨论】:

                  • “最新的 Linux shm 实现只使用了 copy_to_user 和 copy_from_user 调用”。这是错误的。 shm 的优点之一是您无需将数据复制到内核空间并再次退出。
                  • 是的,你不需要。内核可以。 IT 使用的是 copy_to_user,而不是你。
                  • -1。那篇文章肯定听错了。如果需要复制,共享内存毫无意义。
                  • @tc:你熟悉内核内部吗?特别是使用 copy_to_user 和 copy_from_user 内核函数?我建议在投票之前先阅读。
                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2014-06-17
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多