fork后如何创建共享内存?

Posted

技术标签:

【中文标题】fork后如何创建共享内存?【英文标题】:How to create shared memory after fork? 【发布时间】:2020-06-11 09:03:05 【问题描述】:

我的代码是

#include <string.h>
#include <sys/mman.h>                                                                               
#include <unistd.h>
#include <stdio.h>

int main() 
  char parent[] = "parent";
  char child[]  = "child";

  char *shmem = (char*)mmap(NULL, 1000, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);

  char *shmem_child = "NOT CHANGE";

  memcpy(shmem, parent, sizeof(parent));

  int pid = fork();

  if (pid == 0) 
    char child_new[] = "new child";

    printf("Child read: %s\n", shmem);
    memcpy(shmem, child, sizeof(child));
    printf("Child wrote: %s\n", shmem);

    shmem_child = (char*)mmap(NULL, 1000, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);

    memcpy(shmem_child, child_new, sizeof(child_new));
    printf("Child create: %s\n", shmem_child);
   else 
    printf("Parent read: %s\n", shmem);
    sleep(1);
    printf("After 1s, parent read: %s\n", shmem);

    printf("After 1s, parent read shmem_child: %s\n", shmem_child);
  

输出是

Parent read: parent
Child read: parent
Child wrote: child
Child create: new child
After 1s, parent read: child
After 1s, parent read shmem_child: NOT CHANGE

可以看到,fork之前创建的共享内存(shmem)可以工作,但是child内部创建的共享内存(shmem_child)却不行。

我做错了吗?如何在孩子内部创建共享内存,以便父母甚至兄弟(同一父母的其他孩子)可以访问?

【问题讨论】:

【参考方案1】:

匿名共享内存在fork() 之间保持共享。

所以,父母和孩子都应该访问相同的内存区域,shmem

您不能在子进程中创建匿名共享内存,并让它神奇地出现在父进程中。必须在父级中创建匿名共享内存;那么所有孩子都可以访问它。

您可以创建非匿名共享内存,例如通过shm_open()。创建者ftruncate()s 将其设置为适当的长度,并且所有进程都对描述符进行内存映射。您确实需要记住在不再需要时通过 shm_unlink() 删除共享内存。


这是一个简单的(经过测试和验证的)父子之间匿名共享内存的示例:

#define _POSIX_C_SOURCE 200809L
#define _GNU_SOURCE
/* SPDX-License-Identifier: CC0-1.0 */

#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/mman.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>

typedef struct 
    char  message[256];
 shared_mem;

static size_t page_aligned(const size_t size)

    const size_t  page = sysconf(_SC_PAGESIZE);
    if (size <= page)
        return page;
    else
        return page * (size_t)(size / page + !!(size % page));
    /* !!(size % page) is 0 if size is a multiple of page, 1 otherwise. */


int child_process(shared_mem *shared)

    printf("Child: shared memory contains \"%s\".\n", shared->message);
    fflush(stdout);

    snprintf(shared->message, sizeof shared->message, "Child");

    printf("Child: changed shared memory to \"%s\".\n", shared->message);
    fflush(stdout);

    return EXIT_SUCCESS;



int main(void)

    const size_t  size = page_aligned(sizeof (shared_mem));
    shared_mem   *shared;
    pid_t         child, p;

    shared = mmap(NULL, size, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_SHARED, -1, (off_t)0);
    if (shared == MAP_FAILED) 
        fprintf(stderr, "Cannot map %zu bytes of shared memory: %s.\n", size, strerror(errno));
        return EXIT_FAILURE;
    

    snprintf(shared->message, sizeof shared->message, "Parent");

    printf("Parent: set shared memory to \"%s\".\n", shared->message);
    fflush(stdout);

    /* Create the child process. */
    child = fork();
    if (!child) 
        /* This is the child process. */
        return child_process(shared);
     else
    if (child == -1) 
        fprintf(stderr, "Cannot create a child process: %s.\n", strerror(errno));
        return EXIT_FAILURE;
    

    /* This is the parent process. */

    /* Wait for the child to exit. */
    do 
        p = waitpid(child, NULL, 0);
     while (p == -1 && errno == EINTR);
    if (p == -1) 
        fprintf(stderr, "Cannot reap child process: %s.\n", strerror(errno));
        return EXIT_FAILURE;
    

    /* Describe the shared memory, */
    printf("Parent: shared memory contains \"%s\".\n", shared->message);
    fflush(stdout);

    /* and tear it down. Done. */
    munmap(shared, size);
    return EXIT_SUCCESS;

另存为,例如example.c,然后通过例如编译运行它

gcc -Wall -Wextra -O2 example1.c -o ex1
./ex1

会输出

Parent: set shared memory to "Parent".
Child: shared memory contains "Parent".
Child: changed shared memory to "Child".
Parent: shared memory contains "Child".

表明这确实有效。


要在 fork() 之后或在不相关的进程之间创建共享内存,所有进程必须就名称达成一致。对于 POSIX 共享内存对象(您获得使用 shm_open() 的描述符,名称必须以斜杠开头。

请注意,我使用的是模式 0600,它对应于(十进制 384)-rw-------,即只能由以同一用户身份运行的进程访问。

考虑以下示例:

#define _POSIX_C_SOURCE  200809L
#define _GNU_SOURCE
/* SPDX-License-Identifier: CC0-1.0 */

#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <time.h>
#include <errno.h>
#include <string.h>
#include <stdio.h>

typedef struct 
    pid_t   changer;
    time_t  when;
    char    message[256];
 shared_mem;

static size_t page_aligned(const size_t size)

    const size_t  page = sysconf(_SC_PAGESIZE);
    if (size <= page)
        return page;
    else
        return page * (size_t)(size / page + !!(size % page));
    /* !!(size % page) is 0 if size is a multiple of page, 1 otherwise. */


enum 
    ACTION_NONE   = 0,
    ACTION_CREATE = (1<<0),
    ACTION_REMOVE = (1<<1),
    ACTION_MODIFY = (1<<2),
;

int main(int argc, char *argv[])

    const size_t  size = page_aligned(sizeof (shared_mem));
    shared_mem   *shared;
    const char   *name;
    time_t        now;
    const char   *message = NULL;
    int           action = ACTION_NONE;
    int           arg, shm_fd;

    if (argc < 2 || argc > 4 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) 
        const char *argv0 = (argc > 0 && argv && argv[0]) ? argv[0] : "(this)";
        fprintf(stderr, "\n");
        fprintf(stderr, "Usage: %s [ -h | --help ]\n", argv0);
        fprintf(stderr, "       %s /NAME [ +CREATE ] [ MESSAGE ] [ -REMOVE ]\n", argv0);
        fprintf(stderr, "\n");
        return EXIT_FAILURE;
    

    /* Grab and check name */
    name = argv[1];
    if (name[0] != '/' || name[1] == '\0') 
        fprintf(stderr, "%s: Shared memory name must begin with a slash.\n", argv[1]);
        return EXIT_FAILURE;
    

    /* Check other command-line parameters. */
    for (arg = 2; arg < argc; arg++) 
        if (argv[arg][0] == '+') 
            action |= ACTION_CREATE;
            if (argv[arg][1] != '\0') 
                message = argv[arg] + 1;
                action |= ACTION_MODIFY;
            

         else
        if (argv[arg][0] == '-') 
            action |= ACTION_REMOVE;
            if (argv[arg][1] != '\0') 
                message = argv[arg] + 1;
                action |= ACTION_MODIFY;
            

         else
        if (argv[arg][0] != '\0') 
            if (message) 
                fprintf(stderr, "%s: Can only set one message (already setting '%s').\n", argv[arg], message);
                return EXIT_FAILURE;
            
            message = argv[arg];
            action |= ACTION_MODIFY;
        
    

    if (action & ACTION_CREATE) 
        /* Create the shared memory object. */
        shm_fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, 0600);
        if (shm_fd == -1) 
            fprintf(stderr, "%s: Cannot create shared memory object: %s.\n", name, strerror(errno));
            return EXIT_FAILURE;
        
        /* Resize it to desired size. */
        if (ftruncate(shm_fd, (off_t)size) == -1) 
            fprintf(stderr, "%s: Cannot resize shared memory object to %zu bytes: %s.\n", name, size, strerror(errno));
            close(shm_fd);
            shm_unlink(name);
            return EXIT_FAILURE;
        

     else 
        /* Open an existing shared memory object. */
        shm_fd = shm_open(name, O_RDWR, 0600);
        if (shm_fd == -1) 
            fprintf(stderr, "%s: Cannot open shared memory object: %s.\n", name, strerror(errno));
            return EXIT_FAILURE;
        
    

    /* Map the shared memory object. */
    shared = mmap(NULL, size, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_NORESERVE, shm_fd, (off_t)0);
    if (shared == MAP_FAILED) 
        fprintf(stderr, "%s: Cannot map %zu bytes of shared memory object: %s.\n", name, size, strerror(errno));
        close(shm_fd);
        if (action & (ACTION_CREATE | ACTION_REMOVE))
            shm_unlink(name);
        return EXIT_FAILURE;
    

    /* The shared memory object descriptor is no longer needed. */
    if (close(shm_fd) == -1) 
        fprintf(stderr, "Warning: Error closing shared memory object: %s.\n", strerror(errno));
    

    /* Current time in UTC */
    now = time(NULL);

    /* If we created it, we need to initialize it too. */
    if (action & ACTION_CREATE) 
        shared->changer = getpid();
        shared->when = now;
        snprintf(shared->message, sizeof shared->message, "Initialized");
    

    /* Show contents. */
    printf("Shared memory was last changed %ld seconds ago by process %ld to '%s'.\n",
           (long)(now - shared->when), (long)(shared->changer), shared->message);
    fflush(stdout);

    /* Modify contents. */
    if (action & ACTION_MODIFY) 
        printf("Changing shared memory contents into '%s'.\n", message);
        fflush(stdout);

        shared->changer = getpid();
        shared->when = now;
        snprintf(shared->message, sizeof shared->message, "%s", message);
    

    /* Unmap shared memory object. */
    munmap(shared, size);

    /* Remove shared memory? */
    if (action & ACTION_REMOVE) 
        if (shm_unlink(name) == -1) 
            fprintf(stderr, "Warning: %s: Cannot remove shared memory object: %s.\n", name, strerror(errno));
            return EXIT_FAILURE;
         else 
            printf("%s: Shared memory object removed successfully.\n", name);
            fflush(stdout);
        
    

    /* All done. */
    return EXIT_SUCCESS;

另存为,例如example2.c,并使用例如编译它

gcc -Wall -Wextra -O2 example2.c -lrt -o ex2

打开多个窗口。合二为一,运行

./ex2 /myshared +

创建共享内存;在其他情况下,运行

./ex2 /myshared newmessage

完成后,记得删除共享内存对象使用

./ex2 /myshared -

【讨论】:

以上是关于fork后如何创建共享内存?的主要内容,如果未能解决你的问题,请参考以下文章

撸代码--linux进程通信(基于共享内存)

与struct和malloc共享内存fork

fork(),C 中的共享内存和指针

如何在 C 中与 Linux 一起使用共享内存

fork()函数的执行过程孤儿进程和僵尸进程

linux vfork的子程序与父进程共享内存,那为啥子进程执行exec就不会覆盖父进程呢?