我有一个程序,需要在多个进程之间拥有共享状态(可能是80,我正在处理80个内核的服务器上一个令人尴尬的并行问题)。理想情况下,我能够以这样的方式分配这个共享内存,以便扩展这些进程之间的共享状态量。
我怀疑它失败了因为指针没有指向实际内存,所以如果一个进程中mmap
的返回值是0xDEADBEEF,那并不意味着0xDEADBEEF将指向另一个进程中的相同内存部分。但是,我对C编程几乎一无所知,因此怀疑可能很容易出错。
有人能告诉我我的怀疑是否正确吗?如果是这样,我应该为共享状态做些什么呢?服务器每个数据集至少需要18天而不使用所有核心,并且我们有相当多的数据集,所以放弃并行计算并不是一个真正的选择。但是,我愿意从进程切换到线程或类似的东西,如果这会有所帮助(我不知道如何在C中做到这一点)。在此先感谢您的帮助。
下面是一些工作和非工作的示例代码,以及gdb的结果。
#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];
}
#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;
}
#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 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
答案 0 :(得分:3)
你的方法确实被打破了。当其中一个子进程最终调用earr_expand
以增加数组的大小并最终调用mmap
时,生成的新映射仅存在于该子进程中 - 它不会传播到父进程或其他子进程。
您需要使用其他技术,例如在fork()
之前预先分配全部内存,或者使用POSIX共享内存。
答案 1 :(得分:0)
是的,共享内存的地址因处理过程而异。
解决方案通常是使用索引而不是指针。在OP的示例代码中,可以使用具有C99灵活数组成员的结构来描述共享内存映射,类似于
struct shared_mmap {
unsigned int size;
unsigned int used;
int data[];
};
当然,data
元素类型本身可以是一种结构。
但是,使用mremap()
增加匿名内存映射将为您提供进程本地新页面;新页面不会在进程之间共享。您需要使用文件备份(而不是匿名共享内存),或者更好,POSIX shared memory。
(如果你有足够的RAM可用,文件支持不一定要慢,因为文件页面通常位于页面缓存中。文件支持具有允许计算停止并继续使用相同或更高的有趣属性。甚至不同数量的进程,显然取决于共享内存映射结构。如果使用MAP_SHARED | MAP_NORESERVE
,则可以使用比可用RAM + swap大得多的内存映射 - 但是如果磁盘空间或配额用完,您的过程将收到(并死于)SIGBUS
信号。)
虽然在x86和x86-64上int
加载和存储是原子的,但您最有可能最终需要(pthread) mutexes和/或atomic built-ins(由GCC-4.7及更高版本提供) ;较旧的GCC和ICC等人提供遗留sync built-ins。
最后,如果你有一组无序的int
s,我会考虑在内存映射中使用固定大小(536,870,912字节,或2 32 位,在Linux中)和原子位运算符:
#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的幂,所以当前2个 21 和更大的合理大功率是所有当前支持的Linux架构上页面大小的倍数。)< / p>
有问题吗?