[comp.sys.amiga] Semaphores: a tutorial & examples

rico@oscvax.UUCP (Rico Mariani) (12/31/87)

This article contains basically everything I've been able to find out
about the two kinds of semaphores on the Amiga.  The example programs
are written such that you can run several copies of them and they won't
mix each others output up (that's what the semaphores are used for). 
The last copy of the programs to exit cleans up all the semaphore goo
that was needed to make it all work.  There are two programs provided,
one that uses straight semaphores and one that uses signal semaphores. 
These programs (especially the 2nd) are based on the examples/info
provided by CATS.  Thanks CATS! 

(As far as I know there aren't any bugs in these but I've been wrong before...)


: -------cut here-----------cut here------------cut here--------
: This is a shar archive.  Extract with sh, not csh.
: Extraction terminated by end of archive message.
: The rest of this file will extract:
: Makefile msgsem.c sem.doc sigsem.c
echo x - Makefile
sed 's/^X//' > Makefile << '/*EOF'
X
XCFLAGS= +L
X
Xall: msgsem sigsem
X
Xmsgsem:	msgsem.o
X	ln -o msgsem msgsem.o -lc32 
X
Xsigsem:	sigsem.o
X	ln -o sigsem sigsem.o -lc32 
/*EOF
echo x - msgsem.c
sed 's/^X//' > msgsem.c << '/*EOF'
X#include <exec/types.h>
X#include <exec/nodes.h>
X#include <exec/ports.h>
X#include <exec/semaphores.h>
X#include <exec/memory.h>
X
Xstruct MsgPort *CreatePort();
Xstruct MsgPort *FindPort();
Xstruct XSemaphore *CreateSemaphore();
Xvoid DeleteSemaphore();
Xvoid *AllocMem();
Xvoid cleanup();
X
Xstruct XSemaphore {
X	struct Semaphore s;
X	WORD Users;
X};
X
Xstruct MsgPort *port = 0;
Xstruct XSemaphore *sem = 0;
Xstruct Message msg;
X
X/* I made this extended semaphore so that there is an easy way to
X * keep track of when it is safe to delete it.  If anyone can think
X * of a better way to do this, I'd love to hear it.
X */
X
Xextern int Enable_Abort;
X
Xmain()
X{
X	int i;
X	Enable_Abort = 0;
X
X	/* the existance check/creation must be atomic --> Forbid/Permit */
X	Forbid();
X	if (!(sem = (struct XSemaphore *)FindPort("my semaphore")))
X		sem = CreateSemaphore("my semaphore", 0);
X	else
X		sem->Users++;
X
X	Permit();
X
X	if (!sem) {
X		printf("Error, can't find *or* create semaphore\n");
X		cleanup();
X		exit(100);
X	}
X
X	port = CreatePort(0,0);
X	if (!port) {
X		printf("Error, can't create my reply port\n");
X		cleanup();
X	}
X
X	msg.mn_Node.ln_Type = NT_MESSAGE;
X	msg.mn_Length	    = sizeof(struct Message);
X	msg.mn_ReplyPort    = port;
X
X	/* for P & V fans, the Procure & Vacate names are mnemonic */
X	/*		       -         -		           */
X
X	for (i=0;i<5;i++) {
X		if (!Procure(sem, &msg)) {
X			WaitPort(port);	/* wait for the message to come */
X			GetMsg(port);	/* get it */
X		}
X
X		/* we now have exclusive access */
X
X		printf("\nTask $%06x owns the semaphore\n",FindTask(0));
X
X		/* pretend to so some action requiring semaphore */
X		Delay(1 + (rand()&31));
X
X		printf("\t\t...semaphore now released\n");
X
X		/* yield control to someone else */
X		Vacate(sem);
X
X		/* pretend to so some action not requiring semaphore */
X		Delay(1 + (rand()&31));
X
X	}
X
X	cleanup();
X
X}
X
Xvoid cleanup()
X{
X	if (port) {
X		DeletePort(port);
X		port = 0;
X	}
X
X	/*
X	 * we'd like to use the semaphore to lock out other users here 
X	 * but we'd have to somehow P and then not do a V but delete
X	 * the port instead for this to work... That hurts me more than
X	 * the Forbid() Permit() pair...
X	 */
X
X	if (sem) {
X		/* the user check/delete must be atomic --> Forbid/Permit */
X
X		Forbid();
X		if (--sem->Users == 0)
X			DeleteSemaphore(sem);
X		Permit();
X
X		sem = 0;
X	}
X}
X
Xstruct XSemaphore *CreateSemaphore(name,pri)
Xchar *name;
Xint pri;
X{
X	struct XSemaphore *s;
X	char *buf;
X
X	s = AllocMem(sizeof(struct XSemaphore), MEMF_PUBLIC|MEMF_CLEAR);
X	if (!s)
X		return(0);
X
X	s->s.sm_Bids = -1;
X	NewList(&s->s.sm_MsgPort.mp_MsgList);
X	s->s.sm_MsgPort.mp_Flags	= PA_IGNORE;
X	s->s.sm_MsgPort.mp_Node.ln_Type	= NT_SEMAPHORE;
X	s->s.sm_MsgPort.mp_Node.ln_Pri	= pri;
X	s->Users			= 1;
X
X	/* Note that the name must be copied as the original creator
X	 * of the semaphore might exit.  We can't leave a pointer to
X	 * the original data segment lying around after the program
X	 * has exited.  
X	 */
X	if (name) {
X		buf = AllocMem(strlen(name)+1, MEMF_PUBLIC);
X		if (!buf) {
X			FreeMem(s, sizeof(struct XSemaphore));
X			return(0);
X		}
X		strcpy(buf,name);
X		s->s.sm_MsgPort.mp_Node.ln_Name = buf;
X		AddPort(s);	
X	}
X	return(s);
X}
X
Xvoid DeleteSemaphore(s)
Xstruct XSemaphore *s;
X{
X	char *name;
X
X	if (!s) return;
X
X	name =  s->s.sm_MsgPort.mp_Node.ln_Name;
X
X	if (name) {
X		RemPort(s);	/* remove from list of public semaphores */
X		FreeMem(name,strlen(name)+1);
X	}
X
X	FreeMem(s, sizeof(struct XSemaphore));
X}
/*EOF
echo x - sem.doc
sed 's/^X//' > sem.doc << '/*EOF'
XI've been looking through the Autodocs on message & signal semaphores
Xand I find them to be a little bit lacking (I know, this has been
Xmentioned before).  So I was hoping that I might clarify the
Xuse of them just a bit.  These programs demonstrate what I think is
Xgoing on, I invite you all to try to find problems with them...  They seem
XOK to me but semaphores in any O/S are a really touchy area.
X
X----
X
XMessage based semaphores (usually refered to as just 'Semaphores') make use
Xof the Message sending/receiving/waiting protocols.  In order to use
Xthem you must initialize a 'struct Semaphore'  if you examine just what
Xa struct Semaphore is you'll see that it is defined as
X
Xstruct Semaphore {
X	struct MsgPort sm_MsgPort;
X	WORD sm_Bids;
X}
X
X(it's in <exec/semaphores.h> like all of the semaphore definitions).
X
XTo initialize it, you must allocate the memory for the structure and
Xthen do all of the usual MsgPort initialization things.  Normally
XCreatePort() does all of this for you but you can't use it as you have
Xdo to a slightly different initialization for a semaphore.  See the
XCreateSemaphore() function in the msgsem example.  You can make a
Xsemaphore public by making its MsgPort public (i.e. giving the MsgPort
Xa name & priority and using AddPort() to add the port to the system list
Xof named ports).  You can then find such a semaphore by using the FindPort()
Xcall... To remove the semaphore from the system you have do remove it from 
Xthe public port list using RemPort() (iff you added it to that list) and
Xthen simply free the memory allocated for the structure.
X
XAssuming you have created the Semaphore, to use it you must allocate a
Xmessage (to be used as your 'bid' for the semaphore) and port that this
Xmessage will be sent to.  Initialize the mn_ReplyPort field of your
Xmessage to point to your port.  You then use these two functions
Xto lock/unlock the semaphore.
X
XProcure(Semaphore, BidMessage)
Xstruct Semaphore *Semaphore;
Xstruct Message *BidMessage;
X
X	Procure() returns true if the semaphore can be locked for your
X	use immediately.  If the semaphore cannot be locked, your
X	bid is queued up and when it becomes free your BidMessage
X	will be sent the the port you allocated (the one that nm_ReplyPort
X	in the BidMessage is supposed to point to).  So if you need
X	to get the Semaphore and you can't go on any further without
X	it you must do something like
X
X	if (!Procure(semaphore,message)) {
X		WaitPort(message->mn_ReplyPort);
X		GetMsg(message->nm_ReplyPort);
X	}
X
X	... put code that needs the semaphore here ...
X
X	This assumes that there is only one semaphore using this port
X	for it's replies.  Otherwise you have to be more complicated.
X
XVacate(Semaphore)
Xstruct Semaphore *Semaphore;
X
X	Vacate() unlocks the semaphore so that some other Procure()
X	will succeed or if there is an outstanding bid then the
X	bid message will sent to the appropriate port.
X
X	So much for straight Semaphores.  Now onto SignalSemaphores.
X
X----
X
XSignalSemaphores seem much easier to use on the surface only they
Xare slightly broken which makes up for this.  The AddSemaphore(),
XRemSemaphore() and FindSemaphore() calls all need special attention
Xas they don't work as advertised in the Autodocs.  Other than this
Xdifficulty, signal semaphores are very easy to deal with as
Xthere are no painful initialization rituals to remember as with
Xmessage semaphores above... just allocate enough memory for the
XSignalSemaphore and you're off to the races.
X
XInitSemaphore(SignalSemaphore)
Xstruct SignalSemaphore *SignalSemaphore;
X
X	Initializes a signal semaphore structure.  Use this function
X	for "private" semaphores (i.e. known only to tasks that
X	have easy access to the creator's memory).
X
XAddSemaphore(SignalSemaphore)
Xstruct SignalSemaphore *SignalSemaphore;
X
X	Adds the semaphore to the system list of semaphores.  You
X	must fill in the ss_Link.ln_{Type,Name,Pri} fields before
X	calling this.  AddSemaphore() calls InitSemaphore() so
X	you don't need to do call this yourself if you use AddSemaphore().
X
X	N.B.  The Exec version of this function seems to be broken.
X	Use Dale's "hand crafted" version instead.  (it's just a
X	InitSemaphore() call followed by an atomic Enqueue on the
X	system signal semaphore list).  See example.
X
X
XRemSemaphore(SignalSemaphore)
Xstruct SignalSemaphore *SignalSemaphore;
X
X	Removes the given semaphore from the system list of public
X	semaphores.
X
X	N.B. The amiga.lib and c[32].lib bindings for this function
X	are broken.  You must include your own bindings for this
X	to work.  See example.
X
X
Xstruct SignalSemaphore *FindSemaphore(name)
Xchar *name;
X
X	Search the system list for semaphores for one with the given
X	name.  Returns a pointer to the semaphore if one is found,
X	NULL otherwise.
X
X	N.B. The amiga.lib and c[32].lib bindings for this function
X	are broken.  You must include your own bindings for this
X	to work.  See example.
X
X
XObtainSemaphore(SignalSemaphore)
Xstruct SignalSemaphore *SignalSemaphore;
X
X	This function locks the given semaphore for your use.  If the
X	semaphore is currently in use by someone else it blocks until
X	it is Release'd by the current owner.  A nesting count is
X	maintained if you try to Obtain a semaphore that you already
X	have.
X
XReleaseSemaphore(SignalSemaphore)
Xstruct SignalSemaphore *SignalSemaphore;
X
X	This function frees the given semaphore for use by someone
X	else.  If you Obtained the semaphore more than once the
X	the nesting count is simply decremented rather than
X	actually Releasing the semaphore.  Bad things happen if
X	you Release more times than you Obtain.
X
X
XAttemptSemaphore(SignalSemaphore)
Xstruct SignalSemaphore *SignalSemaphore;
X
X	This function tries to Obtain the semaphore provided.  If this
X	can be done it locks the semaphore and returns true.  If the
X	semaphore is currently in use by someone else it returns false.
X	Use this function if you don't want to block your task waiting
X	for a semaphore.
X
X
XObtainSemaphoreList(SemaphoreList)
Xstruct SemaphoreList *SemaphoreList;
X
XReleaseSemaphoreList(SemaphoreList)
Xstruct SemaphoreList *SemaphoreList;
X
X	Haven't played with these two yet... They're used for locking
X	several semaphores in one atomic action.  This ability can
X	be used to prevent deadlock.
X
X----
X
XNote:
X	The Kernel doesn't seem to do any kind of deadlock prevention
X	or recovery.  This means you have to decide for yourself if
X	deadlock is an issue in your application.  There are several
X	good books which deal with deadlock avoidance and so forth
X	so I won't go into that here.
/*EOF
echo x - sigsem.c
sed 's/^X//' > sigsem.c << '/*EOF'
X/*
X * This program plays around with signal semaphores...
X *
X * Thanks to C-A for providing the new bindings & AddSemaphore code
X *
X */
X
X#include <exec/types.h>
X#include <exec/nodes.h>
X#include <exec/ports.h>
X#include <exec/semaphores.h>
X#include <exec/memory.h>
X#include <exec/execbase.h>
X
Xstruct XSSemaphore {
X	struct SignalSemaphore s;
X	WORD Users;
X};
X
Xvoid *AllocMem();
Xvoid cleanup();
Xvoid DeleteSSemaphore();
Xstruct SignalSemaphore *myFindSemaphore();
Xstruct XSSemaphore *CreateSSemaphore();
X
Xstruct XSSemaphore *sem = 0;
X
X/* I made this extended semaphore so that there is an easy way to
X * keep track of when it is safe to delete it.  If anyone can think
X * of a better way to do this, I'd love to hear it.
X */
X
Xextern int Enable_Abort;
X
Xmain()
X{
X	int i;
X	Enable_Abort = 0;
X
X	/* the existance check/creation must be atomic --> Forbid/Permit */
X
X	Forbid();
X
X	if (!(sem = (struct XSSemaphore *)myFindSemaphore("sigSemaphore")))
X		sem = CreateSSemaphore("sigSemaphore",0);
X	else
X		sem->Users++;
X
X	Permit();
X
X	if (!sem) {
X		printf("Error, can't find *or* create semaphore\n");
X		cleanup();
X		exit(100);
X	}
X
X	for (i=0;i<5;i++) {
X
X		/* get use of the semaphore */
X		ObtainSemaphore(sem);
X
X		/* we now have exclusive access */
X
X		printf("\nTask $%06x owns the semaphore\n",FindTask(0));
X
X		/* pretend to so some action requiring semaphore */
X		Delay(1 + (rand()&31));
X
X		printf("\t\t...semaphore now released\n");
X
X		/* yield control to someone else */
X		ReleaseSemaphore(sem);
X
X		/* pretend to so some action not requiring semaphore */
X		Delay(1 + (rand()&31));
X
X	}
X
X	cleanup();
X}
X
Xvoid cleanup()
X{
X	if (sem) {
X		/* the user check/delete must be atomic --> Forbid/Permit */
X
X		Forbid();
X		if (--sem->Users == 0)
X			DeleteSSemaphore(sem);
X		Permit();
X
X		sem = 0;
X	}
X}
X
Xstruct XSSemaphore *CreateSSemaphore(name,pri)
Xchar *name;
Xint pri;
X{
X	struct XSSemaphore *sem;
X	char *buf;
X
X	sem = AllocMem(sizeof(struct XSSemaphore), MEMF_PUBLIC|MEMF_CLEAR);
X
X	if (!sem) return(0);
X
X	sem->s.ss_Link.ln_Type = NT_SIGNALSEM;
X	sem->s.ss_Link.ln_Pri  = pri;
X
X	/* Note that the name must be copied as the original creator
X	 * of the semaphore might exit.  We can't leave a pointer to
X	 * the original data segment lying around after the program
X	 * has exited.  
X	 */
X	if (name) {
X		buf = AllocMem(strlen(name)+1, MEMF_PUBLIC);
X		if (!buf) {
X			FreeMem(sem, sizeof(struct XSSemaphore));
X			return(0);
X		}
X		strcpy(buf,name);
X		sem->s.ss_Link.ln_Name = buf;
X		myAddSemaphore(sem);
X	}
X	else
X		InitSemaphore(sem);
X
X	sem->Users = 1;
X
X	return(sem);
X}
X
Xvoid DeleteSSemaphore(sem)
Xstruct XSSemaphore *sem;
X{
X	char *name;
X
X	if (!sem) return;
X
X	name = sem->s.ss_Link.ln_Name;
X
X	if (name) {
X		myRemSemaphore(sem);
X		FreeMem(name, strlen(name) + 1 );
X	}
X
X	FreeMem(sem, sizeof(struct XSSemaphore));
X}
X
X
X/* The bindings for "AddSemaphore" are broken in 1.2 Amiga.lib
X *
X * Dale's handcrafted AddSemaphore(). 
X */
X
XmyAddSemaphore(ss)
Xstruct SignalSemaphore *ss;
X{
X    extern struct ExecBase *SysBase;
X
X    InitSemaphore(ss);
X    Forbid();
X    Enqueue(&SysBase->SemaphoreList,ss);
X    Permit();
X}
X
X/* The "C" interface code for the following semaphore routines is broken in
X * Amiga.lib and in the Aztec C release 3.4a.
X *
X * @ Lattice people should cut and paste the assembler into a separate file.
X */
X 
X#if AZTEC_C     
X#asm
X; The exec.library function "AddSemaphore" is broken in KickStart rel. 33.180
X;
X; The Aztec bindings think that they should be using a0 to pass the
X; argument instead of a1.  This is likely the problem with the Lattice
X; bindings.  The Exec AddSemaphore function seems to be just plain broken.
X;
X;		-Rico
X        
X        XREF    _SysBase
X        XREF    _LVOFindSemaphore
X	XREF    _LVORemSemaphore
X
X
X	XDEF    _myFindSemaphore
X	XDEF    _myRemSemaphore
X
X_myFindSemaphore:
X	move.l 	4(sp),a1
X        move.l 	_SysBase,a6
X	jmp 	_LVOFindSemaphore(a6)
X
X_myRemSemaphore:
X	move.l 	4(sp),a1
X        move.l 	_SysBase,a6
X	jmp 	_LVORemSemaphore(a6)
X#endasm
X#endif
/*EOF
echo end of archive
exit 0
-- 
		...{watmath|allegra|decvax|ihnp4|linus}!utzoo!oscvax!rico
		or oscvax!rico@gpu.toronto.EDU   if you're lucky

[NSA food: terrorist, cryptography, DES, drugs, CIA, secret, decode]
[CSIS food: supermailbox, tuna, fiberglass coffins, Mirabel, microfiche]
[Cat food: Nine Lives, Cat Chow, Meow Mix, Crave]