memdemo.c 8.43 KiB
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <getopt.h>
#include <errno.h>
#include <sys/types.h> /* for mmap() */
#include <sys/mman.h>
#include <signal.h>
/*
* This structure is declared "volatile" below
* to tell the compiler that its contents may change, even if this
* program doesn't change them.
*/
struct mappedmem {
volatile struct mappedmem *here; /* Address at which we'd like to be mapped.
* If you map this segment at a different
* address, any pointers must be adjusted!
* But maybe that's OK.
*/
long maplen; /* length of complete mmapped segment */
#define MAPPEDMEM_MAGIC 0xfeedc001
int magic; /* Magic number, so other files can't be
* confused with ours. This amounts to a
* version-number on this shared-memory area,
* so change it if the layout changes.
*/
/* Pass one message with this simple handshake sequence:
* Consumer increments wantseq to show that it's ready for data,
* then waits for gotseq to catch up.
* Producer waits for wantseq > gotseq, then updates message buffer,
* and sets gotseq = wantseq to show that it's caught up.
*/
int wantseq;
int gotseq;
/* Who's using this shared memory anyway?
* Since processes might attach or detach at any time,
* let's keep track of who used to be here.
*/
int producer_pid, consumer_pid;
/* The message to be passed */
int msglen;
char msgdata[1]; /* actually let's say it's longer than this,
* as given by msglen, but not to exceed
* the 'length' at the beginning of this header.
*/
};
/* Test whether process exists without harming it */
int is_alive(int pid) {
return(pid > 0 && !(kill(pid, 0) < 0 && errno == ESRCH));
}
/*
* Create new shared-memory segment and map it into memory.
*/
volatile struct mappedmem *
newshm(char *mapfile, long maplen)
{
static char zero = '\0';
volatile struct mappedmem *shm;
int fd;
fd = open(mapfile, O_RDWR|O_CREAT|O_TRUNC, 0666);
if(fd < 0) {
fprintf(stderr, "Can't create shared-memory file %s: ", mapfile);
perror("");
return 0;
}
/* Extend file to proper length */
lseek(fd, maplen-1, SEEK_SET);
write(fd, &zero, 1);
/* Map the file into memory. Anywhere will do. */
shm = (volatile struct mappedmem *)
mmap( NULL, maplen, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0 );
if( (void *)shm == (void *)MAP_FAILED ) {
fprintf(stderr, "Can't map %d bytes of shared-memory file %s: ",
maplen, mapfile);
perror("");
return 0;
}
/* Fill in blanks. */
shm->here = shm;
shm->maplen = maplen;
shm->wantseq = 0;
shm->gotseq = 0;
/* Mark as valid */
shm->magic = MAPPEDMEM_MAGIC;
return shm;
}
/*
* Open existing shared-memory segment and map it.
*/
volatile struct mappedmem *
oldshm(char *mapfile)
{
int fd = open(mapfile, O_RDWR, 0666);
volatile struct mappedmem *shm;
struct mappedmem mm;
long maplen;
if(fd < 0) {
fprintf(stderr, "Can't open shared-memory file %s: ", mapfile);
perror("");
return 0;
}
maplen = lseek(fd, 0, SEEK_END); /* find length of file */
lseek(fd, 0, SEEK_SET);
if(read(fd, &mm, sizeof(mm)) != sizeof(mm)) {
fprintf(stderr, "Can't even read %d-byte header from %s\n",
sizeof(mm), mapfile);
}
if(mm.magic != MAPPEDMEM_MAGIC) {
fprintf(stderr, "%s doesn't look like a shmem file: magic 0x%08x not 0x%08x\n",
mapfile, mm.magic, MAPPEDMEM_MAGIC);
return 0;
}
if(maplen < mm.maplen) {
fprintf(stderr, "%s is shorter (%d bytes) than its header claims (%d)!\n",
mapfile, maplen, mm.maplen);
return NULL;
}
/* Map the file into memory. Ask, but don't demand, that it be
* in the same place as the original creator had it mapped.
* If we actually used pointers inside this shared-mem segment,
* would MAP_SHARED below to MAP_SHARED|MAP_FIXED.
*/
shm = (volatile struct mappedmem *)
mmap( (void *)mm.here, mm.maplen,
PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0 );
if( (void *)shm == (void *)MAP_FAILED ) {
fprintf(stderr, "Can't map %d bytes of shared-memory file %s: ",
maplen, mapfile);
perror("");
return NULL;
}
return shm;
}
/*
* Handshake.
* This is the weak point of using shared memory,
* since the operating system doesn't offer any assistance.
* Could use another mechanism -- e.g. pushing bytes through
* a socket, or signalling, or a semaphore -- to
* let the OS do synchronization.
*/
int maxdelay = 50000;
int pollusec = 10000;
/* Wait for (*from - *to) >= thresh.
* First time, spin for up to "maxdelay" loops.
* After that, just poll every "pollusec" microseconds.
*/
int await(volatile int *from, volatile int *to, int thresh,
volatile int *peerp,
char *peername)
{
int count = maxdelay;
int peer = -1, deadpeer = -1;
do {
if(*from - *to >= thresh)
return 1;
} while(--count >= 0);
for(;;) {
usleep(pollusec);
if(*from - *to >= thresh)
return 2;
/*
* Try to detect whether original sender is still attached
* to this segment. Could exit if not, but maybe
* someone else will attach later.
*/
if(peerp && deadpeer != (peer = *peerp) && !is_alive(peer)) {
deadpeer = peer;
if(deadpeer > 0 && peername != NULL)
fprintf(stderr, "%s process %d is gone!\n",
peername, deadpeer);
}
}
}
int
produce(volatile struct mappedmem *shm) {
char line[512];
int len;
int gotany;
fprintf(stderr,
"Starting producer -- reading lines of text from standard input\n");
/* Is another producer already using this shm segment? */
if( is_alive(shm->producer_pid) ) {
fprintf(stderr, "Producer process %d is already using same segment!\n",
shm->producer_pid);
return 0;
}
shm->producer_pid = getpid();
do {
/* Get something to say */
gotany = (fgets(line, sizeof(line), stdin) != NULL);
/* Wait until receiver is ready: until shm->wantseq - shm->gotseq >= 1. */
await(&shm->wantseq, &shm->gotseq, 1, &shm->consumer_pid, "consumer");
/* If we got an end-of-file on input,
* pass EOF (msglen == -1) to consumer.
*/
if(gotany) {
shm->msglen = strlen(line);
strcpy(shm->msgdata, line);
} else {
shm->msglen = -1;
}
shm->gotseq = shm->wantseq;
} while(gotany);
return 1;
}
int
consume(volatile struct mappedmem *shm) {
fprintf(stderr, "Starting consumer -- passing received msgs to stdout\n");
/* Is another consumer already using this shm segment? */
if( is_alive(shm->consumer_pid) ) {
fprintf(stderr, "Consumer process %d is already using same segment!\n",
shm->consumer_pid);
return 0;
}
shm->consumer_pid = getpid();
/* Post initial request */
shm->wantseq = shm->gotseq + 1;
/* Wait until sender has filled our request: gotseq >= wantseq */
while(await(&shm->gotseq, &shm->wantseq, 0, &shm->producer_pid, "producer")) {
/* Consume our message */
if(shm->msglen < 0) {
printf("[EOF]\n");
break;
}
printf("[%d] %s", shm->msglen, shm->msgdata);
/* Ask sender for next message */
shm->wantseq = shm->gotseq + 1;
}
return 1;
}
main(int argc, char *argv[])
{
int maplen = 8*1024;
char *mapfile = "/var/tmp/Mapfile";
volatile struct mappedmem *shm = NULL;
int be_producer = 0;
int be_new = 0;
int c;
if(argc <= 1) {
fprintf(stderr, "Usage: %s [-p | -c] [-n] [nbytes] [filename]\n\
Demonstrate communicating via shared memory.\n\
memdemo -p starts a \"producer\" process, which reads\n\
lines of text from its standard input and sends\n\
them via shared memory to ...\n\
memdemo -c a \"consumer\" process, which takes those messages\n\
and prints them to standard output.\n\
Other options:\n\
-n \"new\" Create new shared-memory file, even if one\n\
already exists. (Default: only create if needed.)\n\
nbytes Length of shared-memory segment, default %d bytes.\n\
filename Name of shared-memory disk file, default %s\n",
argv[0], maplen, mapfile);
exit(1);
}
while((c = getopt(argc, argv, "cpn")) != EOF) {
switch(c) {
case 'c': be_producer = 0; break;
case 'p': be_producer = 1; break;
case 'n': be_new = 1; break;
}
}
if(argc > optind) maplen = atoi(argv[optind]);
if(argc > optind+1) mapfile = argv[optind+1];
/* Try to open existing shared-memory segment. If none, create it. */
if(!be_new && access(mapfile, W_OK) == 0)
shm = oldshm(mapfile);
if(shm == NULL)
shm = newshm(mapfile, maplen);
if(shm == NULL)
exit(1);
if(be_producer) {
produce(shm);
} else {
consume(shm);
}
return 0;
}