【问题标题】:Shared memory across processes on Linux/x86_64Linux/x86_64 上的跨进程共享内存
【发布时间】:2012-08-13 13:41:05
【问题描述】:

我有几个关于将共享内存用于进程的问题。我查看了之前的几篇帖子,但无法准确地收集到答案。提前感谢您的帮助。

  1. 我正在使用 shm_open + mmap,如下所示。此代码按预期工作,父子交替递增 g_shared->count(同步不可移植;它仅适用于某些内存模型,但对于我现在的情况来说已经足够了)。但是,当我将 MAP_SHARED 更改为 MAP_ANONYMOUS | MAP_SHARED,内存不共享,程序挂起,因为“标志”没有被翻转。删除标志可以确认每个进程从 0 到 10 的计数发生了什么(意味着每个进程都有自己的结构副本,因此有“计数”字段)。这是预期的行为吗?我不希望内存由文件支持;我真的很想模拟如果这些是线程而不是进程会发生什么(出于其他原因,它们需要是进程)。

  2. 我真的需要 shm_open 吗?由于进程属于同一层次结构,我可以单独使用 mmap 吗?我知道如果没有“执行者”,这将相当简单,但是当“分叉”后面有“执行者”时,我该如何让它工作?

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

  4. cleanup_shared() 代码是否正确?它是否泄漏了任何内存?我怎么查?例如,是否有 System V 的“ipcs”等价物?

谢谢, /笨蛋

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;
}

【问题讨论】:

  • Linux 用来实现共享内存的tmpfs 是一个伪文件系统。那里的文件内容完全驻​​留在内存中,mmap-ing 它们直接映射tmpfs 用于存储共享内存“文件”内容的相同物理页面。 shm_open 所做的是在/dev/shm(或安装tmpfs 的任何位置)中创建/打开一个文件并返回文件描述符。您可以使用open(..., O_CREAT) 自行创建文件,但它不可移植。打开的文件描述符将在execve 中保留,但映射不会。

标签: linux shared-memory mmap


【解决方案1】:

2b) 作为一种中间立场,可以调用:

int const shm_fd = shm_open(fn,...);
shm_unlink(fn);

在父进程中,然后通过 argp 或 envp 将 fd 传递给由 fork()/execve() 创建的子进程。由于这种类型的打开文件描述符将在 fork()/execve() 中继续存在,因此您可以在父进程和任何衍生进程中映射 fd。这是从我在 Ubuntu 12.04 / linux kernel 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 真的会在 exec 中存活下来。 exec 的手册页不清楚;它说:“POSIX 共享内存区域未映射”,但对我来说,这对于之前的一般 cmets 来说是多余的,即不保留映射,也没有说 shm_open() 的 fd 将被关闭。当然,正如我所提到的,代码似乎在至少一种情况下确实有效。

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

【讨论】:

    【解决方案2】:
    1. 传递 MAP_ANONYMOUS 会导致内核忽略您的文件描述符参数,而是为您提供私有映射。这不是你想要的。

    2. 是的,您可以在父进程 fork 中创建匿名共享映射,并让子进程继承该映射,与父进程和任何其他子进程共享内存。不过,这显然无法在 exec() 中存活。

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

    4. 这是调用 munmap() 的正确方法,但我没有验证除此之外的逻辑。所有进程都需要取消映射,只有一个应该调用 unlink。

    【讨论】:

    • 谢谢安迪。进一步澄清: 2. 尽管这是我的问题,但这显然无法在 exec() 中存活。我可以破解它吗?我可以派生 MAP_ANONYMOUS 最终映射到的地址并将其显式传递给子进程吗? 3.我不明白这个问题 我特意要求示例代码中显示的共享内存。 '*g_shared' 是否会被缓存。相反,当使用 pthreads 时,对象将被分配在线程共享的全局段中并因此被缓存。我认为您的回答表明即使我像上面那样使用 MAP_SHARED 也会如此?
    • @user1605883, mmap(NULL, length, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_SHARED, -1, 0) 创建一个非写时复制共享匿名映射,该映射将在fork 中存在,但在execve 中不存在。 execve(支持所有 exec*() 库调用的系统调用)安装一个新的页表,从而破坏所有现有的映射。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-05-08
    • 2011-10-29
    • 2015-11-30
    • 1970-01-01
    • 2021-03-26
    • 2011-02-18
    • 1970-01-01
    相关资源
    最近更新 更多