Whether you use completely separate programs or fork depends on how much common ground there is between the processes. At the very least, they would be compiled with a common object file for the mmap and list handling. That's all there is to my demo, so I used fork.
In real life I would do this kind of thing with threads, so this was a good exercise for me as I've never done it. It works, but there could be an easier way, and there could be problems with this I don't see.
The big issue with mmap is that you have to decide how much space you will need in advance, because
I actually did try a version that dynamically grew the file, it definitely led to "unspecified", lol.
The basic idea here is that the first 4 bytes of the mmap contain an offset to the end of the used space, and after those four bytes a pointer value to the current list head. This would make deletions very awkward; it would be a better idea to write a memory pool using the mmap space and then have the linked list use that interface. Which is a lot more work.
The sleep() call is not necessary, but without it, what will most likely happen is one process will accomplish everything it needs to do in one go (it will keep re-aquiring the lock it releases) because the task here is so simple. So for the demo, sleep() allows them to alternate.
There is not much point to this unless you actually have something for the processes to do outside the locked section, eg, rather than being handed all the input up front, they might be reading it from a socket or other external source.
But anything that actually involves the mmap (which is where the list is) must be done inside the locked section.
This must be linked to the "real time" lib for semaphores, so compile -lrt. You may also need -std=gnu99 because of the declarations.
Code:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <semaphore.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <inttypes.h>
#include <sys/wait.h>
/* compile -lrt */
#define MMAPSZ 1024
typedef struct _node {
char *data;
struct _node *next;
} node;
int addNode (node *addr, node *head, char *data) {
fprintf(stderr,"Adding %s...\n", data);
addr->data = strcpy((char*)(addr + sizeof(node)), data);
addr->next = head;
return sizeof(node) + strlen(data) + 1;
}
void process (int fd, sem_t *lock, void *map, char *words[], int n) {
uint32_t *offset = map;
int i;
node **head = map + 4, *cur;
for (i = 0; i < n; i++) {
sem_wait(lock);
/********* LOCKED SECTION *********/
// set address for new node
cur = map + *offset;
// add node
*offset += addNode(cur, *head, words[i]);
// set head to new node address
*head = cur;
sem_post(lock);
/***********************************/
sleep(1);
}
}
int main(int argc, const char *argv[]) {
// file for map
int mfd = open("mmap", O_CREAT | O_RDWR | O_TRUNC);
int32_t i;
if (mfd < 0) {
perror("open() 'mmap'");
return -1;
}
// file must sized before mmap call
for (i = 0; i < MMAPSZ; i += 4) write(mfd, &i, 4);
// semaphore for locking list
sem_unlink("/listlock");
sem_t *listLock = sem_open("/listlock", O_CREAT | O_EXCL, S_IRUSR | S_IWUSR, 0);
if (listLock == SEM_FAILED) {
perror("sem_open() '/listLock'");
return -1;
}
sem_post(listLock); // unlocked
// mmap file
void *map = mmap(0, MMAPSZ, PROT_READ | PROT_WRITE, MAP_SHARED, mfd, 0);
if (map == MAP_FAILED) {
perror("mmap()");
return -1;
}
// use first 4 bytes to indicate current offset
i = 4 + sizeof(node*);
memcpy(map, &i, 4);
// then pointer to list head, initially NULL
memset(map+4, 0, sizeof(node*));
// fork into two processes
pid_t pid = fork();
if (!pid) {
// child
char *words[5] = { "one", "two", "three", "four", "five" };
process(mfd, listLock, map, words, 5);
exit(0);
} else {
//parent
char *words[5] = { "six", "seven", "eight", "nine", "ten" };
int status;
process(mfd, listLock, map, words, 5);
waitpid(pid, &status, 0);
// everybody's done, walk list
node *cur;
memcpy(&cur, map+4, sizeof(node*));
while (cur) {
puts(cur->data);
cur = cur->next;
}
sem_close(listLock);
sem_unlink("/listLock");
close(mfd);
}
return 0;
}
Example output:
Adding six...
Adding one...
Adding seven...
Adding two...
Adding eight...
Adding three...
Adding nine...
Adding four...
Adding ten...
Adding five...
five
ten
four
nine
three
eight
two
seven
one
six
Notice the list has a FILO ordering.
You should consult the linux man pages for all the unfamiliar commands. I'd also recommend the POSIX pages as they provide more depth:
The Open Group Base Specifications Issue 7