Skip to content
Snippets Groups Projects
memdemo.c 8.43 KiB
Newer Older
teuben's avatar
teuben committed
#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;
}