3

I have a linked list. I have many child processes working on this linked list, adding and removing elements. How can it be shared between all the children?

I' ve tried this code that uses memory mapping:

#include <stdio.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <err.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>

typedef struct Struttura
{
    int prova;
    struct Struttura* next;
} Struttura;

Struttura* glob;
int main()
{
    int fd = open("/dev/zero", O_RDWR, 0);
    glob = mmap(NULL, sizeof(Struttura), PROT_READ|PROT_WRITE, MAP_FILE|MAP_SHARED, fd, 0);

    int pid = fork();
    if(pid == 0)
    {
        printf("Glob value: %d\n", glob->prova);
        glob->next = mmap(NULL, sizeof(Struttura), PROT_READ|PROT_WRITE, MAP_FILE|MAP_SHARED, fd, 0);
        glob->next->prova = 3;
        printf("The next value: %d\n\n", glob->next->prova);
        return (0);
    }
    else
    {
        wait(NULL);
        printf("Glob value: %d\n", glob->prova);
        printf("The next value: %d\n\n", glob->next->prova); //Segmentation fault core dumped
    }
    return (0);
}

How to do this? Please don't mind synchronization and unchecked return values since this was just a try :)

Federico Ponzi
  • 2,682
  • 4
  • 34
  • 60
  • 1
    How about a `public static` variable? – mustangDC Jul 16 '15 at 09:20
  • 1
    In c? `public static`? – Federico Ponzi Jul 16 '15 at 09:21
  • lol :), my brain is stuffed with Java. But for your scenario a `static` variable would work. Which has only one copy of the variable and if declared globally, it should work fine – mustangDC Jul 16 '15 at 09:23
  • Yeah i know java too, but i'm noob at c so i was asking this :) – Federico Ponzi Jul 16 '15 at 09:24
  • Then u can use some concepts from Java too, as per your case the `static` variable concept. I guess this should solve it – mustangDC Jul 16 '15 at 09:25
  • C has a different meaning for `static`. See http://stackoverflow.com/questions/572547/what-does-static-mean-in-a-c-program – Federico Ponzi Jul 16 '15 at 09:27
  • 4
    `fork()` does a *copy* of the address-space for the child. Thus, changes to global variables will not be visible between processes. But you already have a tool to get shared memory between the processes: `mmap(...MAP_SHARED)`. Just put the shared variable in a shared memory segment. Remember though, you will need to synchronize the accesses, or the behavior will be undefined. – EOF Jul 16 '15 at 09:44
  • Ehi @EOF, thanks for the comment. Actually as you can see in my code i do `mmap(...MAP_SHARED)` in fact the problem it isn't the shared memory between father and child, but between child and father or other childs. Thanks – Federico Ponzi Jul 16 '15 at 09:47
  • 3
    If you want to share memory between processes *after* the `fork()`, the `shmop()`-family, or `shm_open()` might help you. – EOF Jul 16 '15 at 09:49
  • Why do you `mmap()` `/dev/zero`? That's not a regular file. You never check whether `mmap()` *actually worked*. – EOF Jul 16 '15 at 10:02
  • The output is fine untile the last printf so i assume that mmap works fine. Also I've tried with MAP_ANON and still not working. Thanks for the shm_open() tip, i'm looking for it. – Federico Ponzi Jul 16 '15 at 10:05
  • @EOF could you please add an answer with an example? I can't get this working. Thanks – Federico Ponzi Jul 16 '15 at 10:42

2 Answers2

4

With mmap(2), you can't easily do it without imposing some limit on the maximum size of your list. The reason is that it's easy to allocate a big chunk of memory to share with child processes, but growing that chunk after forking is infeasible.

You could get around that by using the XSI shared memory interfaces (see man 7 shm_overview): you'd use shm_open(3) to create and access a shared memory block, and you could set its size with ftruncate(2). This would theoretically give you the power of dynamically growing and shrinking, but at the cost of more complex code: XSI shared memory objects, unlike mmaped memory, are not deleted from the system until it is shut down or until all processes have unmapped the object and deleted it with shm_unlink(3). If any process crashes and terminates abnormally, you have a cleanup problem. Furthermore, you are faced with the problem of notifying every other process every time someone changes the size of the memory object. And what if a process is notified in the middle of insertion? What if a process is inserting a node into a memory location that doesn't exist anymore because someone in the meantime shrinked its size?

You don't really mention why you are trying to do this, but in all cases, you are better off with threads. Threads are a much more sane and reasonable choice here, because they serve exactly this purpose: easy resource sharing. You won't have to set up shared memory segments, and the size of your list is not limited to the size of the chunk you allocated.

Anyway, here's an example of a program that creates a shared memory segment with mmap(2) and has a bunch of child processes adding and removing elements from a linked list that is kept in that memory segment. The first few blocks of memory are used for some house-keeping stuff, namely, pointers to the list head and to the list of free nodes, and a mutex to synchronize accesses. We need to keep a list of free nodes so that the worker processes can find free nodes when adding new nodes to the real list. Each worker process (there are 10 of them) inserts 10 new nodes into the list storing the worker's process ID. Upon each insertion, the worker process prints the entire list. After inserting all of these nodes, the worker process removes them from the list and terminates.

Note that the maximum size of the list is 1024 because that's what we allocated for.

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

#define MAX_LIST_SZ 1024
#define WORKERS 10
#define WORK_UNIT 10

struct list_node {
    long value;
    struct list_node *next;
};

static struct list_node **list_head;
static struct list_node **free_head;
static pthread_mutex_t *mutex;

void print_list(void) {
    struct list_node *curr = *list_head;

    while (curr != NULL) {
        printf("%ld -> ", curr->value);
        curr = curr->next;
    }

    printf("NULL\n");
}

void do_work(void) {
    int i;
    for (i = 0; i < WORK_UNIT; i++) {
        pthread_mutex_lock(mutex);

        struct list_node *n = *free_head;

        if (n == NULL) {
            pthread_mutex_unlock(mutex);
            assert(0);
        }

        *free_head = (*free_head)->next;
        n->value = (long) getpid();
        n->next = *list_head;
        *list_head = n;

        print_list();

        pthread_mutex_unlock(mutex);
    }

    for (i = 0; i < WORK_UNIT; i++) {
        pthread_mutex_lock(mutex);
        struct list_node *n = *list_head;
        *list_head = (*list_head)->next;
        n->next = *free_head;
        *free_head = n;
        pthread_mutex_unlock(mutex);
    }

}

int main(void) {
    void *ptr;
    size_t region_sz = 0;

    /* Space for the nodes */
    region_sz += sizeof(**list_head)*MAX_LIST_SZ;

    /* Space for house-keeping pointers */
    region_sz += sizeof(list_head)+sizeof(free_head);

    /* Space for the mutex */
    region_sz += sizeof(*mutex);

    ptr = mmap(NULL, region_sz, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, 0, 0);
    if (ptr == MAP_FAILED) {
        perror("mmap(2) failed");
        exit(EXIT_FAILURE);
    }

    /* Set up everything */
    mutex = ptr;
    free_head = (struct list_node **) (((char *) ptr)+sizeof(*mutex));
    list_head = free_head+1;

    *free_head = (struct list_node *) (list_head+1);
    *list_head = NULL;

    /* Initialize free list */
    int i;
    struct list_node *curr;

    for (i = 0, curr = *free_head; i < MAX_LIST_SZ-1; i++, curr++) {
        curr->next = curr+1;
    }

    curr->next = NULL;

    pthread_mutexattr_t mutex_attr;
    if (pthread_mutexattr_init(&mutex_attr) < 0) {
        perror("Failed to initialize mutex attributes");
        exit(EXIT_FAILURE);
    }

    if (pthread_mutexattr_setpshared(&mutex_attr, PTHREAD_PROCESS_SHARED) < 0) {
        perror("Failed to change mutex attributes");
        exit(EXIT_FAILURE);
    }

    if (pthread_mutex_init(mutex, &mutex_attr) < 0) {
        perror("Failed to initialize mutex");
        exit(EXIT_FAILURE);
    }

    for (i = 0; i < WORKERS; i++) {
        pid_t pid;
        if ((pid = fork()) < 0) {
            perror("fork(2) error");
            exit(EXIT_FAILURE);
        }

        if (pid == 0) {
            do_work();
            return 0;
        }
    }

    for (i = 0; i < WORKERS; i++) {
        if (wait(NULL) < 0) {
            perror("wait(2) error");
        }
    }

    assert(*list_head == NULL);

    return 0;
}
Filipe Gonçalves
  • 20,783
  • 6
  • 53
  • 70
0

Do NOT use tabs for indenting, because every editor/wordprocessor has different tab widths/tab stops

mmap() returns a void* which can be assigned to any pointer, so

1) do not cast the return value.
2) always check the returned value for MAP_FAILED which indicates the operation failed

pid value after a call to fork() is being incorrectly used

1) when pid <  0 then an error occurred
2) when pid == 0 then child executing
3) when pid >  0 then parent executing

When using fork(), always check for all three conditions

the posted code is incorrectly using the pid value

Suggest reading the man pages for each system function used

user3629249
  • 16,402
  • 1
  • 16
  • 17
  • Thanks for the useful answer. I edited my fork calls but the code is stil not working. You can see in my question the updated code. Thanks – Federico Ponzi Jul 17 '15 at 19:19