繁体   English   中英

在子进程中从共享内存中的指针读取到共享内存时出现分段错误

[英]Segmentation fault when reading from a pointer to shared memory from shared memory in a child process

概述

我有一个程序,需要在多个进程之间共享状态(大概是80,我正在使用80核的服务器上处理一个令人尴尬的并行问题)。 理想情况下,我将能够以可以扩展这些进程之间的共享状态数量的方式分配此共享内存。

我的怀疑是它失败了,因为指针没有指向实际的内存,因此,如果一个进程中mmap的返回值为0xDEADBEEF,这并不意味着0xDEADBEEF将指向另一个进程中的同一部分内存。 但是,我几乎不了解C编程,因此怀疑很容易是错误的。

谁能告诉我我的怀疑是否正确? 如果是这样,我应该怎么做才能共享状态? 在不使用所有内核的情况下,服务器每个数据集将至少花费18天,而且我们有很多数据集,因此放弃并行计算并不是真正的选择。 但是,我愿意从进程切换到线程或类似的方式(如果有帮助的话)(我不知道如何在C中做到这一点)。 在此先感谢您的帮助。

以下是一些有效和无效的示例代码,以及来自gdb的结果。

borked.h

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/mman.h>

// Expandable array, doubles memory allocation when it runs out of space.
struct earr {
    int *vals;
    int capacity;
    int length;
};

void *shared_calloc(size_t nmemb, size_t size) {
    void *mem = mmap(NULL, nmemb * size, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
    return memset(mem, 0, nmemb * size);
}

void shared_free_size(void *mem, size_t nmemb, size_t size) {
    if(mem) munmap(mem, nmemb * size);
}

struct earr *create_earr() {
    struct earr *a = shared_calloc(1, sizeof(struct earr));
    a->length = 0;
    a->capacity = 16;
    a->vals = shared_calloc(a->capacity, sizeof(int));
    return a;
}

void earr_expand(struct earr *a) {
    int *new_vals = shared_calloc(a->capacity * 2, sizeof(int));
    memcpy(new_vals, a->vals, a->capacity * sizeof(int));
    a->vals = new_vals;
    a->capacity *= 2;
}

void earr_insert(struct earr *a, int val) {
    if(a->length >= a->capacity) earr_expand(a);
    a->vals[a->length] = val;
    a->length++;
}

int earr_lookup(struct earr *a, int index) {
    return a->vals[index];
}

working.c

#include "borked.h"

int main(void) {
    struct earr *a = create_earr();
    int i;
    pid_t pid;
    int size = 0x10000;
    for(i = 0; i < size; i++) {
        earr_insert(a, i);
    }
    for(i = 0; i < size; i++) {
        earr_lookup(a, i);
    }
    return EXIT_SUCCESS;
}

broken.c

#include "borked.h"

int main(void) {
    struct earr *a = create_earr();
    int i;
    pid_t pid;
    int size = 0x10000;
    if(0 == (pid = fork())) {
        for(i = 0; i < size; i++) {
            earr_insert(a, i);
        }
    } else {
        int status;
        waitpid(pid, &status, 0);
        for(i = 0; i < size; i++) {
            earr_lookup(a, i);
        }
    }
    return EXIT_SUCCESS;
}

GDB调试

$ gdb broken
...
(gdb) run
Starting program /path/to/broken

Program received signal SIGSEGV, Segmentation Fault
0x08048663 in earr_lookup (a=0xb7fda000, index=0) at /path/to/borked.h:46
46      return a->vals[index];
(gdb) x/3x 0xb7fda000
0xb7fda000: 0xb7da6000  0x00010000  0x00010000
(gdb) x/x 0xb7da6000
0xb7da6000: Cannot access memory at address 0xb7da6000

您的方法确实被破坏了。 当子进程之一最终调用earr_expand以增加数组的大小并最终调用mmap ,结果新映射仅存在于该子进程中-不会传播到父进程或其他子进程。

您将需要使用其他技术,例如在fork()之前预先分配全部内存,或使用POSIX共享内存。

是的,共享内存的地址因进程而异。

通常的解决方案是使用索引而不是指针。 在OP的示例代码中,可以使用带有C99灵活数组成员的结构来描述共享内存映射,例如

struct shared_mmap {
    unsigned int  size;
    unsigned int  used;
    int           data[];
};

当然, data元素类型本身可以是结构。

但是,使用mremap()匿名内存映射将为您提供进程本地的新页面。 新页面将不会在流程之间共享。 您需要使用文件支持(而不是匿名共享内存),或者更好的是使用POSIX共享内存

(如果有足够的可用RAM,文件备份不一定会变慢,因为文件页面通常位于页面缓存中。文件备份具有有趣的特性,允许停止计算并以相同或什至不同的数目继续进行计算进程,显然取决于共享内存映射的结构。如果使用MAP_SHARED | MAP_NORESERVE ,则可以使用比可用RAM + swap大得多的内存映射-但是,如果磁盘空间或配额用完,进程将收到(并死于SIGBUS信号。)

尽管在x86和x86-64上int加载和存储是原子的,但您很可能最终会需要(pthread)互斥锁和/或原子的内置组件 (由GCC-4.7和更高版本提供;较老的GCC和ICC等)。提供内置的旧版同步

最后,如果您有一组无序的int ,我会考虑使用固定大小(在Linux中为536,870,912字节,即2 32位)的内存映射,以及原子按位运算符:

#include <limits.h>

#define UINT_BITS (CHAR_BIT * sizeof (unsigned int))
#define ULONG_BITS (CHAR_BIT * sizeof (unsigned long))

/* Pointer to shared memory of (1<<UINT_BITS)/CHAR_BIT chars */
static unsigned long *shared;

static inline unsigned int shared_get(unsigned int i)
{
    return !!__atomic_load_n(shared + (i / ULONG_BITS), 1UL << (i % ULONG_BITS), __ATOMIC_SEQ_CST);
}

static inline void shared_set(unsigned int i)
{
    __atomic_fetch_or(shared + (i / ULONG_BITS), 1UL << (i % ULONG_BITS), __ATOMIC_SEQ_CST);
}

static inline void shared_clear(unsigned int i)
{
    __atomic_fetch_nand(shared + (i / ULONG_BITS), 1UL << (i % ULONG_BITS), __ATOMIC_SEQ_CST);
}

无论如何,内存映射长度必须是sysconf(_SC_PAGESIZE)的倍数,您应该在运行时对其进行评估。

(在所有已知体系结构上都是2的幂,因此,在目前所有受支持的Linux体系结构中,较大的2的幂(当前为2 21或更大)是页面大小的倍数。)

有问题吗?

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM