1

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

  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”等价物?

谢谢,/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;
}
4

2 回答 2

3
  1. 传递 MAP_ANONYMOUS 会导致内核忽略您的文件描述符参数并给您一个私有映射。那不是你想要的。

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

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

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

于 2012-08-17T05:37:51.703 回答
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 共享内存区域未映射”,但对我来说,这与前面的一般评论是多余的,即不保留映射,也没有说 shm_open() 的 fd 将被关闭。当然,正如我所提到的,代码似乎在至少一种情况下确实有效。

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

于 2014-09-12T22:09:20.557 回答