Linux / x86_64上跨进程的共享内存

时间:2012-08-17 05:19:30

标签: linux shared-memory mmap

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

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

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

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

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

  5. 感谢, / 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;
    }
    

2 个答案:

答案 0 :(得分:3)

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

  2. 是的,您可以在父进程fork中创建匿名共享映射,并让子进程继承映射,与父进程和任何其他子进程共享内存。尽管如此,obviusly并不存在于exec()中。

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

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

答案 1 :(得分:1)

2b)作为一种中间立场,可以打电话:

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

在父进程中,然后通过argp或envp将fd传递给fork()/ execve()创建的子进程。由于这种类型的打开文件描述符将在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;
  }

如果删除FD_CLOEXEC并且/或者假设在执行此操作之后fd确实能够在exec中存活,那么对我来说并不是100%清楚。 exec的手册页不清楚;它说:“POSIX共享内存区域是未映射的”,但对我而言,前面的一般注释是多余的,不保留映射,并且不会说shm_open()'d fd将被关闭。当然,事实是,正如我所提到的,代码确实在至少一个案例中起作用。

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