2012-08-17 97 views
1

我在使用进程共享内存方面有几个问题。我看了几个以前的帖子,无法精确地收集答案。在此先感谢您的帮助。Linux/x86_64上的进程之间的共享内存

  1. 我正在使用像下面这样的shm_open + mmap。这段代码按照父母和孩子交替增加g_shared-> count的方式工作(同步不是可移植的;它只适用于某些内存模型,但对于我现在的情况来说足够好)。但是,当我将MAP_SHARED更改为MAP_ANONYMOUS |时MAP_SHARED,内存不共享和程序挂起,因为'标志'不翻转。删除标志可以确认每个从0到10的过程发生了什么(意味着每个过程都有自己的结构副本,因此也包含'count'字段)。这是预期的行为?我不希望内存被文件支持;我真的很想模仿可能发生的情况,如果它们是线程而不是流程(它们需要是其他原因的流程)。

  2. 我真的需要shm_open吗?由于进程属于同一个层次结构,我可以单独使用mmap吗?我知道如果没有'exec',这将是相当直接的,但是如果在'fork'之后有'exec',我怎么才能使它工作?

  3. 我使用的是x86_64的内核版本3.2.0-23(英特尔i7-2600)。对于这种实现,mmap是否与共享同一个全局对象的pthread共享内存一样提供相同的行为(正确性和性能)?例如,MMU是否将该段映射为“可缓存”的MTRR/TLB属性?

  4. cleanup_shared()代码是否正确?它泄漏了任何内存吗?我怎么检查?例如,是否有相当于System V的'ipcs?'?

感谢, /Doobs

shmem.h:

#ifndef __SHMEM_H__ 
#define __SHMEM_H__ 

//includes 

#define LEN 1000 
#define ITERS 10 

#define SHM_FNAME "/myshm" 

typedef struct shmem_obj { 
    int count; 
    char buff[LEN]; 
    volatile int flag; 
} shmem_t; 

extern shmem_t* g_shared; 
extern char proc_name[100]; 
extern int fd; 

void cleanup_shared() { 
    munmap(g_shared, sizeof(shmem_t)); 
    close(fd); 
    shm_unlink(SHM_FNAME); 
} 

static inline 
void init_shared() { 
    int oflag; 

    if (!strcmp(proc_name, "parent")) { 
     oflag = O_CREAT | O_RDWR; 
    } else { 
     oflag = O_RDWR; 
    } 

    fd = shm_open(SHM_FNAME, oflag, (S_IREAD | S_IWRITE)); 
    if (fd == -1) { 
     perror("shm_open"); 
     exit(EXIT_FAILURE); 
    } 

    if (ftruncate(fd, sizeof(shmem_t)) == -1) { 
     perror("ftruncate"); 
     shm_unlink(SHM_FNAME); 
     exit(EXIT_FAILURE); 
    } 

    g_shared = mmap(NULL, sizeof(shmem_t), 
        (PROT_WRITE | PROT_READ), 
        MAP_SHARED, fd, 0); 
    if (g_shared == MAP_FAILED) { 
     perror("mmap"); 
     cleanup_shared(); 
     exit(EXIT_FAILURE); 
    } 
} 

static inline 
void proc_write(const char* s) { 
    fprintf(stderr, "[%s] %s\n", proc_name, s); 
} 

#endif // __SHMEM_H__ 

shmem1.c(父进程):

#include "shmem.h" 

int fd; 
shmem_t* g_shared; 
char proc_name[100]; 

void work() { 
    int i; 
    for (i = 0; i &lt ITERS; ++i) { 
     while (g_shared->flag); 
     ++g_shared->count; 
     sprintf(g_shared->buff, "%s: %d", proc_name, g_shared->count); 
     proc_write(g_shared->buff); 
     g_shared->flag = !g_shared->flag; 
    } 
} 

int main(int argc, char* argv[], char* envp[]) { 
    int status, child; 
    strcpy(proc_name, "parent"); 
    init_shared(argv); 
    fprintf(stderr, "Map address is: %p\n", g_shared); 

    if (child = fork()) { 
     work(); 
     waitpid(child, &status, 0); 
     cleanup_shared(); 
     fprintf(stderr, "Parent finished!\n"); 
    } else { /* child executes shmem2 */ 
     execvpe("./shmem2", argv + 2, envp); 
    } 
} 

shmem2.c(子进程):

#include "shmem.h" 

int fd; 
shmem_t* g_shared; 
char proc_name[100]; 

void work() { 
    int i; 
    for (i = 0; i &lt ITERS; ++i) { 
     while (!g_shared->flag); 
     ++g_shared->count; 
     sprintf(g_shared->buff, "%s: %d", proc_name, g_shared->count); 
     proc_write(g_shared->buff); 
     g_shared->flag = !g_shared->flag; 
    } 
} 

int main(int argc, char* argv[], char* envp[]) { 
    int status; 
    strcpy(proc_name, "child"); 
    init_shared(argv); 
    fprintf(stderr, "Map address is: %p\n", g_shared); 
    work(); 
    cleanup_shared(); 
    return 0; 
} 
+0

Linux用来实现共享内存的'tmpfs'是一个伪文件系统。这些文件的内容完全存在于内存中,并且它们直接映射“tmpfs”用于存储共享内存“文件”内容的相同物理页面。 'shm_open'的功能是在'/ dev/shm'(或者'tmpfs'挂载的任何地方)创建/打开一个文件,然后给你返回文件描述符。您可以使用'open(...,O_CREAT)'自己创建文件,但它不可移植。打开的文件描述符将在'execve'中存活,但映射不会。 – 2012-08-17 17:02:12

回答

3
  1. 传递MAP_ANONYMOUS将导致内核忽略您的文件描述符参数,并为您提供私有映射。这不是你想要的。

  2. 是的,您可以在父进程fork中创建匿名共享映射,并让子进程继承映射,与父级和其他子进程共享内存。这obviusius没有生存执行()虽然。

  3. 我不明白这个问题; pthreads不分配内存。缓存能力取决于您映射的文件描述符。如果它是磁盘文件或匿名映射,那么它是可缓存的内存。如果它是一个视频帧缓冲设备,它可能不是。

  4. 这是调用munmap()以正确的方式,但我没有验证超出逻辑。所有进程都需要取消映射,只有一个应该取消链接。

+0

谢谢安迪。进一步澄清: 2. 这显然不能执行exec()虽然 这是我的问题。我可以绕过吗?我可以导出MAP_ANONYMOUS最终映射到的地址并将其明确传递给子进程吗? 3.我不明白这个问题 我特别要求示例代码中显示的共享内存。将'* g_shared'缓存。相反,当pthreads时,对象将被分配在由线程共享的全局段中,并因此被缓存。我认为你的答案表明即使我使用如上所述的MAP_SHARED,它也会如此? – user1605883 2012-08-17 06:09:34

+0

@ user1605883,'mmap(NULL,length,PROT_READ | PROT_WRITE,MAP_ANONYMOUS | MAP_SHARED,-1,0)'创建一个非拷贝写共享匿名映射,它将在fork'而不是'execve'中存活。 'execve'(支持所有'exec *()'库调用的系统调用)安装一个新的页表,从而销毁所有现有的映射。 – 2012-08-17 17:12:42

0

2B)作为一种的中间地,有可能调用:

int const shm_fd = shm_open(fn,...); 
shm_unlink(fn); 
在父进程

,然后通过FD由叉创建子进程()/ execve()通过argp或envp。因为此类型的打开文件描述符将在fork()/ execve()中生存,所以您可以在父进程和任何受骗进程中对fd进行mmap映射。这里有一个更完整的代码示例复制并简化/从码消毒我的Ubuntu 12.04下成功地跑/ Linux内核3.13/glibc的2.15:

int create_shm_fd(void) { 
    int oflags = O_RDWR | O_CREAT | O_TRUNC; 
    string const fn = "/some_shm_fn_maybe_with_pid"; 
    int fd; 
    neg_one_fail(fd = shm_open(fn.c_str(), oflags, S_IRUSR | S_IWUSR), "shm_open"); 
    if(fd == -1) { rt_err(strprintf("shm_open() failed with errno=%s", str(errno).c_str())); } 
    // for now, we'll just pass the open fd to our child process, so 
    // we don't need the file/name/link anymore, and by unlinking it 
    // here we can try to minimize the chance/amount of OS-level shm 
    // leakage. 
    neg_one_fail(shm_unlink(fn.c_str()), "shm_unlink"); 
    // by default, the fd returned from shm_open() has FD_CLOEXEC 
    // set. it seems okay to remove it so that it will stay open 
    // across execve. 
    int fd_flags = 0; 
    neg_one_fail(fd_flags = fcntl(fd, F_GETFD), "fcntl"); 
    fd_flags &= ~FD_CLOEXEC; 
    neg_one_fail(fcntl(fd, F_SETFD, fd_flags), "fcntl"); 
    // resize the shm segment for later mapping via mmap() 
    neg_one_fail(ftruncate(fd, 1024*1024*4), "ftruncate"); 
    return fd; 
    } 

它不是100%我清楚,如果它的好规范明智删除FD_CLOEXEC和/或假设在这样做后,fd真的会在执行期间生存下来。执行官的手册页不清楚;它说:“POSIX共享内存区域未映射”,但对我来说,这与先前的一般注释是多余的,映射不会保留,并且不会说shm_open()'d fd将被关闭。任何当然有事实,正如我所提到的,代码似乎在至少一个案例中起作用。

我可能会使用这种方法的原因是它似乎减少了泄漏共享内存段/文件名的机会,并且它清楚地表明我不需要内存段的持久性。