CS473 HW2 Solutions

From the Book

  1. 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.

  2. 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.

  3. 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?

Not From the Book

Process 0Process 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)));

  1. Process 0 sets turn to 1.

  2. 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.

  3. 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.


Last modified: Tue Oct 4 12:01:45 MDT 2005