处理器没有看到 POSIX 共享内存的更改?

处理器没有看到 POSIX 共享内存的更改?

语境:我在用POSIX 共享内存为一组进程提供共享内存空间。我已经使用这个方案一段时间了,以便共享数据,并且工作正常。然而,我最近在某类程序中遇到了一个奇怪的问题。

问题:我编写了一个程序,其中每个进程都必须为共享内存空间中的共享总和贡献一个值。当共享对象早些时候映射到内存时,总和被初始化为零。然而,当每个进程尝试将其部分添加到共享总和中时,它可以看到最新的值,但是加法的结果总是像它自己的值加上零。见下文:

[21017] Adding 6 to 0!
[21020] Adding 33 to 0!
[21016] Adding 15 to 0!
[21018] Adding 24 to 0!
[21017] Got access! (0x7fe953fcb000 = 0)
[21017] Done (0x7fe953fcb000 = 6)
[21016] Got access! (0x7fe953fcb000 = 6)
[21016] Done (0x7fe953fcb000 = 15)
[21018] Got access! (0x7fe953fcb000 = 15)
[21018] Done (0x7fe953fcb000 = 24)
[21020] Got access! (0x7fe953fcb000 = 24)
[21020] Done (0x7fe953fcb000 = 33)
Sum = 33

每个进程“看到”写入的最新值,但不知何故,在添加其自己的组件后,似乎忽略了现有值。您可以看到每次访问都是按顺序排序的,因为有一个访问控制系统管理谁可以写入共享内存空间。使用的测试程序如下所示(尽管我不希望读者运行它):

int main (void) {
    int local_sum = 0, gid = -1;
    volatile int *sum;

    // Fork for four processes.
    for (int i = 1; i < 4; i++) {
        if (fork() == 0) break;
    }

    // Initialize the DSM. Set GID.
    sum = (int *)dsm_init(&cfg);
    gid = dsm_get_gid();

    // Compute range.
    for (int i = 0; i < 3; i++) {
        local_sum += array[(gid * 3) + i];
    }

    // Add to local sum.
    printf("[%d] Adding %d to %d!\n", getpid(), local_sum, *sum);
    *sum = *sum + local_sum;

    // Barrier.
    dsm_barrier();

    // Print sum if process zero.
    if (gid == 0) printf("Sum = %d\n", *sum);

    // Exit.
    dsm_exit();
}

为什么每个进程都可以“看到”0x7fe953fcb000共享空间中地址处的正确值,但在添加之后,表现得好像添加期间该地址处的值仍然为零?


以下是这个问题困扰我的地方:

  • 如果是缓存问题,为什么我可以在算术运算之前打印正确的值,但它仍然不正确?
  • 我正在添加进程堆上的共享值。编译器无法假设该值为零,并优化了任何内容。

对于为什么会发生这种情况有什么解释吗?我尝试在我的程序中使用 GDB 来看看发生了什么。但据我所知,它只是将内存地址处的值移动到寄存器中。我还没有看到任何优化问题。

答案1

据我所见,四个进程快速连续生成,每个进程都尝试执行 * sum += some_value; 操作。完全有可能他们都认为 * sum 在相加之前为零。

让我们使用抽象汇编语法。 C 语句

*sum = *sum + local_sum

被编译成

LOAD *sum into R0
LOAD local_sum into R1
ADD R1 to R0
STORE R0 to *sum

四个进程竞相执行该序列。完全有可能在它们中的任何一个有机会将 R0 存储到 *sum 之前,它们都将 LOAD *sum 存储到 R0 中;事实上,正如您所说,考虑到有一个由 STORE R0 触发的系统调用(因此是一个重新规划点)到 *sum,这是一个很好的机会。您需要同步对共享变量的访问,例如使用信号量。

相关内容