vego
vego

Reputation: 1049

How to create shared memory after fork?

My code is

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

And the output is

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

As you can see, the shared memory(shmem) created before fork works, but the shared memory(shmem_child) created inside child does not work.

Am I doing something wrong? How can I create shared memory inside child so that parent and even brothers(other children of same parent) can access?

Upvotes: 0

Views: 1870

Answers (1)

Example
Example

Reputation: 181

Anonymous shared memory stays shared across a fork().

So, both the parent and the child(ren) should access the same memory area, shmem.

You cannot create anonymous shared memory in a child process, and have it magically appear in the parent process. Anonymous shared memory must be created in the parent; then it will be accessible to all children.

You can create non-anonymous shared memory, via e.g. shm_open(). The creator ftruncate()s it to appropriate length, and all processes memory-map the descriptor. You do need to remember to remove the shared memory when no longer needed, via shm_unlink().


Here is a simple (tested and verified) example of anonymous shared memory between a parent and a child:

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

Save it as e.g. example.c, then compile and run it via e.g.

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

It will output

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

showing that this indeed works.


To create shared memory after fork(), or between unrelated processes, all processes have to agree on the name. For POSIX shared memory objects (that you obtain a descriptor to using shm_open(), the name must start with a slash.

Note that I used mode 0600, which corresponds to (decimal 384) -rw-------, i.e. only accessible to processes running as the same user.

Consider the following example:

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

Save it as e.g. example2.c, and compile it using e.g.

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

Open up multiple windows. In one, run

./ex2 /myshared +

to create the shared memory; and in others, run

./ex2 /myshared newmessage

When you are done, remember to remove the shared memory object using

./ex2 /myshared -

Upvotes: 3

Related Questions