Path: utzoo!attcan!uunet!snorkelwacker!tut.cis.ohio-state.edu!cs.utexas.edu!rutgers!njin!limonce From: limonce@pilot.njin.net (Tom Limoncelli) Newsgroups: comp.sys.amiga.tech Subject: Re: Semaphores (Why don't we use 'em?) Message-ID: Date: 3 Jul 90 04:00:47 GMT References: <709.268c893f@desire.wright.edu> <31309@cup.portal.com> Distribution: na Organization: Drew University/NJIN Lines: 570 People have asked me to post the docs I have on semaphores. I found this in a very old directory on my hard drive. I can't find a source, but I'm sure it was posted to Usenet. One of the files mentions that the author got help from the people at C-A, so I assume it was written by someone that isn't a C-A Employee. -Tom Here it is: >>>>>>>>>>sem.doc<<<<<<<<<< I've been looking through the Autodocs on message & signal semaphores and I find them to be a little bit lacking (I know, this has been mentioned before). So I was hoping that I might clarify the use of them just a bit. These programs demonstrate what I think is going on, I invite you all to try to find problems with them... They seem OK to me but semaphores in any O/S are a really touchy area. ---- Message based semaphores (usually refered to as just 'Semaphores') make use of the Message sending/receiving/waiting protocols. In order to use them you must initialize a 'struct Semaphore' if you examine just what a struct Semaphore is you'll see that it is defined as struct Semaphore { struct MsgPort sm_MsgPort; WORD sm_Bids; } (it's in like all of the semaphore definitions). To initialize it, you must allocate the memory for the structure and then do all of the usual MsgPort initialization things. Normally CreatePort() does all of this for you but you can't use it as you have do to a slightly different initialization for a semaphore. See the CreateSemaphore() function in the msgsem example. You can make a semaphore public by making its MsgPort public (i.e. giving the MsgPort a name & priority and using AddPort() to add the port to the system list of named ports). You can then find such a semaphore by using the FindPort() call... To remove the semaphore from the system you have do remove it from the public port list using RemPort() (iff you added it to that list) and then simply free the memory allocated for the structure. Assuming you have created the Semaphore, to use it you must allocate a message (to be used as your 'bid' for the semaphore) and port that this message will be sent to. Initialize the mn_ReplyPort field of your message to point to your port. You then use these two functions to lock/unlock the semaphore. Procure(Semaphore, BidMessage) struct Semaphore *Semaphore; struct Message *BidMessage; Procure() returns true if the semaphore can be locked for your use immediately. If the semaphore cannot be locked, your bid is queued up and when it becomes free your BidMessage will be sent the the port you allocated (the one that nm_ReplyPort in the BidMessage is supposed to point to). So if you need to get the Semaphore and you can't go on any further without it you must do something like if (!Procure(semaphore,message)) { WaitPort(message->mn_ReplyPort); GetMsg(message->nm_ReplyPort); } ... put code that needs the semaphore here ... This assumes that there is only one semaphore using this port for it's replies. Otherwise you have to be more complicated. Vacate(Semaphore) struct Semaphore *Semaphore; Vacate() unlocks the semaphore so that some other Procure() will succeed or if there is an outstanding bid then the bid message will sent to the appropriate port. So much for straight Semaphores. Now onto SignalSemaphores. ---- SignalSemaphores seem much easier to use on the surface only they are slightly broken which makes up for this. The AddSemaphore(), RemSemaphore() and FindSemaphore() calls all need special attention as they don't work as advertised in the Autodocs. Other than this difficulty, signal semaphores are very easy to deal with as there are no painful initialization rituals to remember as with message semaphores above... just allocate enough memory for the SignalSemaphore and you're off to the races. InitSemaphore(SignalSemaphore) struct SignalSemaphore *SignalSemaphore; Initializes a signal semaphore structure. Use this function for "private" semaphores (i.e. known only to tasks that have easy access to the creator's memory). AddSemaphore(SignalSemaphore) struct SignalSemaphore *SignalSemaphore; Adds the semaphore to the system list of semaphores. You must fill in the ss_Link.ln_{Type,Name,Pri} fields before calling this. AddSemaphore() calls InitSemaphore() so you don't need to do call this yourself if you use AddSemaphore(). N.B. The Exec version of this function seems to be broken. Use Dale's "hand crafted" version instead. (it's just a InitSemaphore() call followed by an atomic Enqueue on the system signal semaphore list). See example. RemSemaphore(SignalSemaphore) struct SignalSemaphore *SignalSemaphore; Removes the given semaphore from the system list of public semaphores. N.B. The amiga.lib and c[32].lib bindings for this function are broken. You must include your own bindings for this to work. See example. struct SignalSemaphore *FindSemaphore(name) char *name; Search the system list for semaphores for one with the given name. Returns a pointer to the semaphore if one is found, NULL otherwise. N.B. The amiga.lib and c[32].lib bindings for this function are broken. You must include your own bindings for this to work. See example. ObtainSemaphore(SignalSemaphore) struct SignalSemaphore *SignalSemaphore; This function locks the given semaphore for your use. If the semaphore is currently in use by someone else it blocks until it is Release'd by the current owner. A nesting count is maintained if you try to Obtain a semaphore that you already have. ReleaseSemaphore(SignalSemaphore) struct SignalSemaphore *SignalSemaphore; This function frees the given semaphore for use by someone else. If you Obtained the semaphore more than once the the nesting count is simply decremented rather than actually Releasing the semaphore. Bad things happen if you Release more times than you Obtain. AttemptSemaphore(SignalSemaphore) struct SignalSemaphore *SignalSemaphore; This function tries to Obtain the semaphore provided. If this can be done it locks the semaphore and returns true. If the semaphore is currently in use by someone else it returns false. Use this function if you don't want to block your task waiting for a semaphore. ObtainSemaphoreList(SemaphoreList) struct SemaphoreList *SemaphoreList; ReleaseSemaphoreList(SemaphoreList) struct SemaphoreList *SemaphoreList; Haven't played with these two yet... They're used for locking several semaphores in one atomic action. This ability can be used to prevent deadlock. ---- Note: The Kernel doesn't seem to do any kind of deadlock prevention or recovery. This means you have to decide for yourself if deadlock is an issue in your application. There are several good books which deal with deadlock avoidance and so forth so I won't go into that here. >>>>>>>>>>Makefile<<<<<<<<<< CFLAGS= +L all: msgsem sigsem msgsem: msgsem.o ln -o msgsem msgsem.o -lc32 sigsem: sigsem.o ln -o sigsem sigsem.o -lc32 >>>>>>>>>>msgsem.c<<<<<<<<<< #include #include #include #include #include struct MsgPort *CreatePort(); struct MsgPort *FindPort(); struct XSemaphore *CreateSemaphore(); void DeleteSemaphore(); void *AllocMem(); void cleanup(); struct XSemaphore { struct Semaphore s; WORD Users; }; struct MsgPort *port = 0; struct XSemaphore *sem = 0; struct Message msg; /* I made this extended semaphore so that there is an easy way to * keep track of when it is safe to delete it. If anyone can think * of a better way to do this, I'd love to hear it. */ extern int Enable_Abort; main() { int i; Enable_Abort = 0; /* the existance check/creation must be atomic --> Forbid/Permit */ Forbid(); if (!(sem = (struct XSemaphore *)FindPort("my semaphore"))) sem = CreateSemaphore("my semaphore", 0); else sem->Users++; Permit(); if (!sem) { printf("Error, can't find *or* create semaphore\n"); cleanup(); exit(100); } port = CreatePort(0,0); if (!port) { printf("Error, can't create my reply port\n"); cleanup(); } msg.mn_Node.ln_Type = NT_MESSAGE; msg.mn_Length = sizeof(struct Message); msg.mn_ReplyPort = port; /* for P & V fans, the Procure & Vacate names are mnemonic */ /* - - */ for (i=0;i<5;i++) { if (!Procure(sem, &msg)) { WaitPort(port); /* wait for the message to come */ GetMsg(port); /* get it */ } /* we now have exclusive access */ printf("\nTask $%06x owns the semaphore\n",FindTask(0)); /* pretend to so some action requiring semaphore */ Delay(1 + (rand()&31)); printf("\t\t...semaphore now released\n"); /* yield control to someone else */ Vacate(sem); /* pretend to so some action not requiring semaphore */ Delay(1 + (rand()&31)); } cleanup(); } void cleanup() { if (port) { DeletePort(port); port = 0; } /* * we'd like to use the semaphore to lock out other users here * but we'd have to somehow P and then not do a V but delete * the port instead for this to work... That hurts me more than * the Forbid() Permit() pair... */ if (sem) { /* the user check/delete must be atomic --> Forbid/Permit */ Forbid(); if (--sem->Users == 0) DeleteSemaphore(sem); Permit(); sem = 0; } } struct XSemaphore *CreateSemaphore(name,pri) char *name; int pri; { struct XSemaphore *s; char *buf; s = AllocMem(sizeof(struct XSemaphore), MEMF_PUBLIC|MEMF_CLEAR); if (!s) return(0); s->s.sm_Bids = -1; NewList(&s->s.sm_MsgPort.mp_MsgList); s->s.sm_MsgPort.mp_Flags = PA_IGNORE; s->s.sm_MsgPort.mp_Node.ln_Type = NT_SEMAPHORE; s->s.sm_MsgPort.mp_Node.ln_Pri = pri; s->Users = 1; /* Note that the name must be copied as the original creator * of the semaphore might exit. We can't leave a pointer to * the original data segment lying around after the program * has exited. */ if (name) { buf = AllocMem(strlen(name)+1, MEMF_PUBLIC); if (!buf) { FreeMem(s, sizeof(struct XSemaphore)); return(0); } strcpy(buf,name); s->s.sm_MsgPort.mp_Node.ln_Name = buf; AddPort(s); } return(s); } void DeleteSemaphore(s) struct XSemaphore *s; { char *name; if (!s) return; name = s->s.sm_MsgPort.mp_Node.ln_Name; if (name) { RemPort(s); /* remove from list of public semaphores */ FreeMem(name,strlen(name)+1); } FreeMem(s, sizeof(struct XSemaphore)); } >>>>>>>>>>sigsem.c<<<<<<<<<< /* * This program plays around with signal semaphores... * * Thanks to C-A for providing the new bindings & AddSemaphore code * */< #include #include #include #include #include #include struct XSSemaphore { struct SignalSemaphore s; WORD Users; }; void *AllocMem(); void cleanup(); void DeleteSSemaphore(); struct SignalSemaphore *myFindSemaphore(); struct XSSemaphore *CreateSSemaphore(); struct XSSemaphore *sem = 0; /* I made this extended semaphore so that there is an easy way to * keep track of when it is safe to delete it. If anyone can think * of a better way to do this, I'd love to hear it. */ extern int Enable_Abort; main() { int i; Enable_Abort = 0; /* the existance check/creation must be atomic --> Forbid/Permit */ Forbid(); if (!(sem = (struct XSSemaphore *)myFindSemaphore("sigSemaphore"))) sem = CreateSSemaphore("sigSemaphore",0); else sem->Users++; Permit(); if (!sem) { printf("Error, can't find *or* create semaphore\n"); cleanup(); exit(100); } for (i=0;i<5;i++) { /* get use of the semaphore */ ObtainSemaphore(sem); /* we now have exclusive access */ printf("\nTask $%06x owns the semaphore\n",FindTask(0)); /* pretend to so some action requiring semaphore */ Delay(1 + (rand()&31)); printf("\t\t...semaphore now released\n"); /* yield control to someone else */ ReleaseSemaphore(sem); /* pretend to so some action not requiring semaphore */ Delay(1 + (rand()&31)); } cleanup(); } void cleanup() { if (sem) { /* the user check/delete must be atomic --> Forbid/Permit */ Forbid(); if (--sem->Users == 0) DeleteSSemaphore(sem); Permit(); sem = 0; } } struct XSSemaphore *CreateSSemaphore(name,pri) char *name; int pri; { struct XSSemaphore *sem; char *buf; sem = AllocMem(sizeof(struct XSSemaphore), MEMF_PUBLIC|MEMF_CLEAR); if (!sem) return(0); sem->s.ss_Link.ln_Type = NT_SIGNALSEM; sem->s.ss_Link.ln_Pri = pri; /* Note that the name must be copied as the original creator * of the semaphore might exit. We can't leave a pointer to * the original data segment lying around after the program * has exited. */ if (name) { buf = AllocMem(strlen(name)+1, MEMF_PUBLIC); if (!buf) { FreeMem(sem, sizeof(struct XSSemaphore)); return(0); } strcpy(buf,name); sem->s.ss_Link.ln_Name = buf; myAddSemaphore(sem); } else InitSemaphore(sem); sem->Users = 1; return(sem); } void DeleteSSemaphore(sem) struct XSSemaphore *sem; { char *name; if (!sem) return; name = sem->s.ss_Link.ln_Name; if (name) { myRemSemaphore(sem); FreeMem(name, strlen(name) + 1 ); } FreeMem(sem, sizeof(struct XSSemaphore)); } /* The bindings for "AddSemaphore" are broken in 1.2 Amiga.lib * * Dale's handcrafted AddSemaphore(). */ myAddSemaphore(ss) struct SignalSemaphore *ss; { extern struct ExecBase *SysBase; InitSemaphore(ss); Forbid(); Enqueue(&SysBase->SemaphoreList,ss); Permit(); } /* The "C" interface code for the following semaphore routines is broken in * Amiga.lib and in the Aztec C release 3.4a. * * @ Lattice people should cut and paste the assembler into a separate file. */ #if AZTEC_C #asm ; The exec.library function "AddSemaphore" is broken in KickStart rel. 33.180 ; ; The Aztec bindings think that they should be using a0 to pass the ; argument instead of a1. This is likely the problem with the Lattice ; bindings. The Exec AddSemaphore function seems to be just plain broken. ; ; -Rico XREF _SysBase XREF _LVOFindSemaphore XREF _LVORemSemaphore XDEF _myFindSemaphore XDEF _myRemSemaphore _myFindSemaphore: move.l 4(sp),a1 move.l _SysBase,a6 jmp _LVOFindSemaphore(a6) _myRemSemaphore: move.l 4(sp),a1 move.l _SysBase,a6 jmp _LVORemSemaphore(a6) #endasm #endif -- tlimonce@drew.edu Tom Limoncelli tlimonce@drew.uucp +1 201 408 5389 tlimonce@drew.Bitnet limonce@pilot.njin.net