Multithreading 不使用偏移量就可以在共享内存中存储指针吗?

Multithreading 不使用偏移量就可以在共享内存中存储指针吗?,multithreading,atomic,shared-memory,race-condition,mmap,Multithreading,Atomic,Shared Memory,Race Condition,Mmap,当使用共享内存时,每个进程可以将共享区域映射到其各自地址空间的不同区域。这意味着,在共享区域内存储指针时,需要指定共享区域的起始位置。不幸的是,这会使原子指令的使用变得复杂(例如,如果您正试图编写原子指令)。例如,假设您在共享内存中有一组引用计数节点,这些节点是由单个编写器创建的。写入程序定期自动更新指针“p”,以指向引用计数为正的有效节点。读者希望以原子方式写入“p”,因为它指向第一个元素为引用计数的节点(结构)的开头。由于p始终指向有效节点,因此增加ref计数是安全的,并且可以安全地取消引用

当使用共享内存时,每个进程可以将共享区域映射到其各自地址空间的不同区域。这意味着,在共享区域内存储指针时,需要指定共享区域的起始位置。不幸的是,这会使原子指令的使用变得复杂(例如,如果您正试图编写原子指令)。例如,假设您在共享内存中有一组引用计数节点,这些节点是由单个编写器创建的。写入程序定期自动更新指针“p”,以指向引用计数为正的有效节点。读者希望以原子方式写入“p”,因为它指向第一个元素为引用计数的节点(结构)的开头。由于p始终指向有效节点,因此增加ref计数是安全的,并且可以安全地取消引用“p”并访问其他成员。但是,只有当所有内容都位于同一地址空间时,这一切才起作用。如果节点和“p”指针存储在共享内存中,则客户端会遇到竞争情况:

  • x=读取p
  • y=x+偏移量
  • y处的增量引用计数
  • 在步骤2中,p可能会改变,x可能不再指向有效节点。我能想到的唯一解决办法是以某种方式强制所有进程在何处映射共享内存上达成一致,以便在mmap'd区域中存储真正的指针而不是偏移量。有办法吗?我在mmap文档中看到MAP_已修复,但我不知道如何选择一个安全的地址


    编辑:在x86上使用内联汇编和“lock”前缀,也许可以构建一个“偏移量为Y的增量ptr X乘以值Z”?其他体系结构上的等效选项?没有编写过很多程序集,不知道是否存在所需的说明。

    我们有与您的问题描述类似的代码。我们使用内存映射文件、偏移和文件锁定。我们还没有找到替代方案。

    在低级别上,x86原子构造可以一次完成所有这些树步骤:

  • x=读取p
  • y=x+偏移增量
  • 在y点重新计数

  • 这在UNIX系统上是微不足道的;只需使用共享内存功能:

    shgmet、shmat、shmctl、shmdt

    void*shmat(内部shmid,const void*shmaddr,内部shmflg)

    shmat()附加共享内存 由shmid识别的段到 调用进程的地址空间。 附加地址由指定 shmaddr具有以下特性之一: 标准:

    如果shmaddr为空,则系统选择 一个合适的(未使用的)地址 以附加段

    只需在此处指定您自己的地址;e、 g.0x20000000

    如果在每个进程中使用相同的键和大小,则会得到相同的共享内存段。如果将shmat()置于同一地址,则所有进程中的虚拟地址都将相同。内核不关心您使用的地址范围,只要它不与通常分配内容的位置冲突。(如果省略地址,您可以看到它喜欢放置东西的一般区域;另外,检查堆栈上的地址并从malloc()/new[]返回。)

    在Linux上,确保root将/proc/sys/kernel/SHMMAX中的SHMMAX设置为足够大的数量,以容纳共享内存段(默认值为32MB)

    至于原子操作,您可以从Linux内核源代码获得它们,例如

    包括/asm-x86/atomic_64.h

    64位版本:

    typedef struct {
            long counter;
    } atomic64_t;
    
    /**
     * atomic64_add - add integer to atomic64 variable
     * @i: integer value to add
     * @v: pointer to type atomic64_t
     *
     * Atomically adds @i to @v.
     */
    static inline void atomic64_add(long i, atomic64_t *v)
    {
            asm volatile(LOCK_PREFIX "addq %1,%0"
                         : "=m" (v->counter)
                         : "er" (i), "m" (v->counter));
    }
    

    您不应该害怕随机创建地址,因为内核只会拒绝它不喜欢的地址(冲突的地址)。使用0x20000000

    使用mmap:

    void*mmap(void*addr,size\u t length,int prot,int flags,int fd,off\u t offset)

    如果addr不为NULL,那么内核 把它当作是去哪里的提示 放置地图;在Linux上 映射将在下一步创建 更高的页面边界。地址 新映射将作为 通话结果

    flags参数决定是否 映射的更新对用户可见 映射相同的其他进程 区域,以及是否更新 结转至下一级 文件这种行为是由 包括以下其中一项: 标志中的值:

    地图\u共享共享此地图。 映射的更新对用户可见 映射到此的其他进程 文件,并传递到 基础文件。该文件可能不存在 实际更新到msync(2)或 调用munmap()

    错误

    EINVAL我们不喜欢addr、length或 偏移量(例如,它们太大,或 未在页面边界上对齐)


    向指针添加偏移量不会产生竞争的可能性,它已经存在。由于ARM和x86至少都不能自动读取指针,然后访问它所指的内存,因此无论是否添加偏移量,都需要使用锁来保护指针访问。

    如果cmpxchg已经执行原子读取和原子写入,是否需要“锁”?或者,这是否确保edi+edx以原子方式完成?我只真正使用过MIPS汇编。锁保证了对内存总线的原子访问,所以锁指令是必要的。您可能还可以使用API InterlocatedCompareeExchange(查看MSDN以获取解释)。首先将内存32位指针作为OldValue加载,然后将其递增以获得NewValue,然后尝试执行InterlockedCompareeExchange。InterlocatedCompareExchange(Destination+Offset,NewValue,OldValue)将返回比较值,如果与OldValue不同,则其他线程将对其进行交换,因此没有进行交换,您必须重复该过程。有趣的是,我假设您只能在编写设备驱动程序或其他较低级别的黑客时使用它。这是一个
    /*
     * Make sure gcc doesn't try to be clever and move things around
     * on us. We need to use _exactly_ the address the user gave us,
     * not some alias that contains the same information.
     */
    typedef struct {
            int counter;
    } atomic_t;
    
    /**
     * atomic_read - read atomic variable
     * @v: pointer of type atomic_t
     *
     * Atomically reads the value of @v.
     */
    #define atomic_read(v)          ((v)->counter)
    
    /**
     * atomic_set - set atomic variable
     * @v: pointer of type atomic_t
     * @i: required value
     *
     * Atomically sets the value of @v to @i.
     */
    #define atomic_set(v, i)                (((v)->counter) = (i))
    
    
    /**
     * atomic_add - add integer to atomic variable
     * @i: integer value to add
     * @v: pointer of type atomic_t
     *
     * Atomically adds @i to @v.
     */
    static inline void atomic_add(int i, atomic_t *v)
    {
            asm volatile(LOCK_PREFIX "addl %1,%0"
                         : "=m" (v->counter)
                         : "ir" (i), "m" (v->counter));
    }
    
    typedef struct {
            long counter;
    } atomic64_t;
    
    /**
     * atomic64_add - add integer to atomic64 variable
     * @i: integer value to add
     * @v: pointer to type atomic64_t
     *
     * Atomically adds @i to @v.
     */
    static inline void atomic64_add(long i, atomic64_t *v)
    {
            asm volatile(LOCK_PREFIX "addq %1,%0"
                         : "=m" (v->counter)
                         : "er" (i), "m" (v->counter));
    }