It does work when it's preemptive. That's sort of the point: despite the fact that it can be preempted in the middle of its operation, it still works.
It doesn't work in a non-preemptive environment. It doesn't yield the CPU in its busy-wait loop, so if it doesn't get preempted any process that gets stuck in that loop will never exit again. Of course, in a non-preemptive environment you don't need a mutual exclusion algorithm anyway since you've automatically got it unless you yield the processor.
Sure. Just put a 1 in the register, do a swap. The
variable now contains 1; the register contains its old value so you
can find out whether somebody else already held the lock.
The big thing here is that you need to use binary semaphores to
protect the operations on the counting semaphore. Let's make a
first try that just does the increment and decrement... as usual
with mutual exclusion variables, we'll asume mutex is
initialized to 1.
typedef struct { int count; BinarySemaphore mutex; } *CountingSemaphore;
void counting_down(CountingSemaphore sem) { binary_down(sem->mutex); count--; binary_up(sem->mutex); } void counting_up(CountingSemaphore sem) { binary_down(sem->mutex); count++; binary_up(sem->mutex); }
Of course, this isn't complete: it doesn't block when we try a
counting_down() when the count is already
0. So... let's try adding that. We'll use a second binary
semaphore to block on when we need to, and another counter to keep
track of how many processes are blocked on the counting semaphore.
queue and waiting assumed to be
initialized to 0.
typedef struct {
int count;
int waiting;
BinarySemaphore mutex;
BinarySemaphore queue;
} *CountingSemaphore;
void counting_down(CountingSemaphore sem)
{
binary_down(sem->mutex);
if (count > 0)
count--;
else {
sem->waiting++;
binary_down(sem->queue);
} else
binary_up(sem->mutex);
}
void counting_up(CountingSemaphore sem)
{
binary_down(sem->mutex);
if (sem->waiting == 0)
count++;
else {
sem->waiting--;
binary_up(sem->queue);
}
binary_up(sem->mutex);
}
Well, now we've gone from something that works but doesn't meet the
definition of a semaphore to something that's broken: we don't
release mutex before we block on queue.
So now no other process will be able to do a
counting_up(), and we're stuck forever.
So... let's try releasing mutex as our last
operation before blocking on queue.
typedef struct {
int count;
int waiting;
BinarySemaphore mutex;
BinarySemaphore queue;
} *CountingSemaphore;
void counting_down(CountingSemaphore sem)
{
binary_down(sem->mutex);
if (count > 0) {
count--;
} else {
sem->waiting++;
binary_up(sem->mutex);
binary_down(sem->queue);
}
binary_up(sem->mutex);
}
void counting_up(CountingSemaphore sem)
{
binary_down(sem->mutex);
if (sem->waiting == 0) {
count++;
binary_up(sem->mutex);
} else {
sem->waiting--;
binary_up(sem->queue);
}
}
So... if count is greater than 0,
counting_down() just decrements it and returns, all
protected by the mutex. If it's equal to 0 its value
doesn't change, but we increment the count of waiting processes,
release the mutex, and get blocked on queue.
In counting_up(), we check to see if anybody's
waiting. If not, we just increment
count. Otherwise, we decrement waiting and
wake up a process on queue.
Looking at this, we can see the two counters are well-protected by
mutex: we never modify either one unless we're in the
mutex-protected region.
The only time either procedure does anything after allowing other
processes to go is in counting_down(), where we release
mutex before we block on queue. Is there a
way for another process to get in and cause us trouble here?
Case 1: Another counting_down() happens before we
block. The new call takes mutex, and properly
updates waiting before it blocks. So we maintain
a proper count of blocked processes.
Case 2: counting_up() is called in between. It
will notice that waiting is non-zero, and perform a
binary_up on queue before the first
process has ever blocked. But this isn't a problem; it'll just
set queue to 1, so when the first process finally
gets through it won't block on queue.
But what happens if there are several calls to
counting_up() before we finally get around to
blocking? Looking carefully, it turns out that the call to
counting_up() doesn't release the
mutex, counting_down() does. So it
isn't possible to have more than one counting_up() try
to wake up blocked processes before the first one actual wakes up.
One last note on this is that it can be done a little bit more
efficiently by letting count go either positive or
negative; when it's positive it's the value of the semaphore, when
it's negative it's the count of blocked processes (see http://www.cs.jhu.edu/~yairamir/cs418/os3.ps for an example -- actually, that page is a pretty good presentation of mutual exclusion in general). I thought my
formulation was a little bit clearer, though.
Process 0 Process 1 turn = 1-me; flag[me] = 1; while (flag[1-me] && (turn == (1-me)));turn = 1-me; flag[me] = 1; while (flag[1-me] && (turn == (1-me)));
Process 0 sets turn to 1.
Process 1 sets turn to 0, sets flag[1] to 1,
and enters the while loop. Since
flag[0] is still 0, it exits the loop and enters the
critical section.
Process 2 sets flag[0] to 1 and enters the
while loop. Since turn is 0, it also
enters the critical section.
note: I'm pretty sure I was wrong about the order of the two
tests in the while loop being important.