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