- There Is No Spoon
- Will You Remember Something for Me?
- One for You, One for Me
- Caveats
Will You Remember Something for Me?
The exit(3)/wait(2) mechanism is a simple way of returning an integer from a child process. It doesn’t work so well for larger amounts of data. Fortunately, POSIX gives us a few mechanisms for moving data between processes. The most conceptually simple of these is shared memory.
The traditional way of sharing memory on UNIX systems is to use System V shared memory. This has largely been superseded by POSIX shared memory. The main difference between the System V and POSIX shared memory interfaces is that System V used keys (key_t, usually some form of integer) to identify shared memory regions, while POSIX uses file system references. The POSIX system is more in keeping with the UNIX philosophy of everything being a file.
The POSIX shared memory functions have a close relationship to mmap(2). The mmap(2) system call maps a file (or a subset of a file) into a process’ address space. If two processes have the same file mapped into their address spaces, this is the same as sharing a memory region
To make things easier, mmap(2) provides a way of mapping an anonymous region of memory. If we take a pointer to an anonymous region of memory mapped with mmap(2), and then fork, the pointer will point to the same physical memory region in both cases.
Anonymous regions of memory are mapped like this:
int * anInt = mmap(0,sizeof(int), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANON, -1,0);
This statement will create a pointer to an integer backed by some memory that will be shared across forks. The PROT_READ and PROT_WRITE parts indicate that you should have both read and write (but not execute) permissions on this memory block. The MAP_SHARED and MAP_ANON flags tell it to map an anonymous region of memory and make changes public. Since we’re mapping anonymous memory (that is, memory not backed by a real filesystem entity), we can pass -1 instead of a real file descriptor.
Note that mapping a region this small typically isn’t very efficient, since the granularity of memory pages is at least 4KB on most systems.
The following example gives a simple demonstration of how this process works:
#include <stdlib.h> #include <stdio.h> #include <sys/types.h> #include <sys/mman.h> #include <unistd.h> int main(void) { //Shared memory region: int * anInt = mmap(0,sizeof(int), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANON, -1,0); *anInt = 0; pid_t pid = fork(); if(pid == 0) { printf("Child setting anInt to 1\n"); *anInt = 1; return 0; } int childVal; wait(&childVal); printf("anInt is %d in parent\n", *anInt); return 0; }
The parent forks, the child sets the integer, and then the parent checks that it worked.