(from analysis from Marcus DeGruttola, edited to line up with code here) 1. Until created, the array entry for a semaphore for a given process, psem, is equal to NOSEM, later a real sem id. This is one state variable. A flag phasmsg is set when there is a message to be received. This is another state variable. After the semaphore exists, it is possible to block on a semaphore. This is yet another state. The semaphore count is always either 0 or -1. 2. State Transition diagram for one mailbox Receive runs in two parts, receive1 to the psem wait, then it holds that state for a while (if necessary) and then runs its second part, receive2, where the message is picked up and psem is released. Send runs without pause (with respect to this system, i.e, under mutex), so it has only one part. If a message is already sitting there for a process, receive runs without wait, labelled simply "receive" here. Actually, send can lose the CPU in signal, but it has finished modifying kernel variables at that point, so it doesn't count here as another transition. Waiting for message ------------- --------------- | sem exists | | no sem exists | Idle | scount=-1 |<---------| no msg | | no msg | receive1 ---------------- ------------- ^ | ^ | | | | | | | | |send receive2 | | send | | ------ | |receive | | V | V | ------------- |------------| | | sem exists |<-- --> | sem exists | | | scount=1 | | send send | | scount=0 |-- | has msg |--- --- | has msg | ------------- |------------| Has message, no Has message not yet receive seen yet picked up by ongoing receive (but receiver is ready-to-run, so will run pretty soon) Note that is is impossible for receive1 or receive2 to be generated from the blocked state "waiting-for-message", because receive only receives for its own process, and it is blocked. 3. case 1: 2 senders sending to process without a message. Both user and kernel impl's guarantee that only one of the two sends will get through. user does it with a master mutex lock. kernel does it with disable/restore. In user impl, both sends will hit the wait on the master mutex. Only one will get through while all others block. The master mutex is not released until the message is setup and a signal is sent off to the potential receiving process. Once released, the second send will come awake and find the message hold full, returning an error. In kernel impl, both sends will hit the disable. Whoever hits the atomic disable first is garanteed the cpu. Restore is not called until the message is in place. Once restored, the second send will be eligable for the CPU and will find a message in the message hold and return an error. case 2: a receiver about to return, sender sends new message. The receiver copies the old message to a local var, i.e., a process-local storage spot while still under mutex. Then it marks the message slot free and releases the mutex, and at this moment the sender can get to run, before the receiver actually returns to its caller. But it can't hurt the old message, safely held in process-local storage--it just drops off the new message in the array. case 3: a receiver has to release mutex before waiting on psem, and a sender can send it a new message before the receiver gets to call wait. Here is where the semaphores work so well--the signal can unhang a future wait just as well as a current wait, so as long as a signal goes with the send (in this implementation), all is well. 4. It is impossible for user package to clean up on a kill. A user process never sees itself get killed and isn't given the time to clean up. This means that a process can receive a message that was intended for the previous process with that PID. Here a leftover psem is reset in receive (which expects no sem there, having cleaned up after its last receive) but can be fooled in send. In the user package, we could "package up" kill into, say, msgkill, that cleans up and then calls kill, but that is a messy solution. The kernel package can do better, because we could edit kill and/or create to make sure the process starts off clean. Yes, each semaphore is independent of the others, so an app can create a different semaphore and use it in any normal way.