1

プロセスでの共有メモリの使用についていくつか質問があります。以前の投稿をいくつか見ましたが、答えを十分に正確に収集できませんでした。よろしくお願いします。

  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 (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() を呼び出す正しい方法ですが、それ以上のロジックは検証していません。すべてのプロセスはマップを解除する必要があり、1 つだけ unlink を呼び出す必要があります。

于 2012-08-17T05:37:51.703 に答える
1

2b) ある種の妥協点として、次のように呼び出すことができます。

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

親プロセスで、argp または envp を介して fork()/execve() によって作成された子プロセスに fd を渡します。このタイプの開いているファイル記述子は 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 の man ページは不明です。「POSIX共有メモリ領域はマップされていません」と書かれていますが、マッピングが保持されないという以前の一般的なコメントとは冗長であり、shm_open()されたfdが閉じられるとは言いません。もちろん、前述したように、少なくとも 1 つのケースではコードが機能しているように見えるという事実があります。

このアプローチを使用する理由は、共有メモリ セグメント/ファイル名がリークする可能性が減るように思われるためであり、メモリ セグメントの永続化が必要ないことが明らかになります。

于 2014-09-12T22:09:20.557 に答える