In this section we will examine various proposals for achieving mutual exclusion, so that while one process is busy updating shared memory in its critical region, no other process will enter its critical region and cause trouble.

1. Disabling Interrupts

On a single-processor system, the simplest solution is to have each process disable all interrupts just after entering its critical region and re-enable them just before leaving it. With interrupts disabled, no clock interrupts can occur.

The CPU is only switched from process to process as a result of clock or other interrupts, after all, and with interrupts turned off the CPU will not be switched to another process. Thus, once a process has disabled interrupts, it can examine and update the shared memory without fear that any other process will intervene.

This approach is generally unattractive because it is unwise to give user processes the power to turn off interrupts. Suppose that one of them did it, and never turned them on again? That could be the end of the system.

Furthermore, if the system is a multiprocessor (with two or possibly more CPUs) disabling interrupts affects only the CPU that executed the disable instruction. The other ones will continue running and can access the shared memory.

2. Lock Variables

As a second attempt, let us look for a software solution. Consider having a single, shared (lock) variable, initially 0. When a process wants to enter its critical region, it first tests the lock. If the lock is 0, the process sets it to 1 and enters the critical region.

If the lock is already 1, the process just waits until becomes 0. Thus, a 0 means that no process is in its critical region, and a 1 means that some process is in its critical region.

Unfortunately, this idea contains exactly the same fatal flaw that we saw in the spooler directory. Suppose that one process reads the lock and sees that it is 0.

Before it can set the lock to 1, another process is scheduled, runs, and sets the lock to 1. When the first process runs again, it will also set the lock to 1, and two processes will be in their critical regions at the same time.

3. Strict Alternation

Two Process Solution

#define FALSE 0                                    #define FALSE 0

#define TRUE 1                                     #define TRUE 1

while (TRUE)                                         while (TRUE)

                                                             { 

{                                                            while (turn != 1); 

while (turn != 0) ;                                  critical _region();

critical _region();                                   turn = 0;

turn = 1;                                                noncritical _region();

noncritical_region();                              }                

}                                                            

Figure 2-23. A proposed solution to the critical region problem, (a) Process 0 (b) Process 1.

The integer variable turn, initially 0, keeps track of whose turn it is to enter the critical region and examine or update the shared memory. Initially, process 0 inspects turn, finds it to be 0, and enters its critical region. Process 1 also finds it to be 0 and therefore sits in a tight loop continually testing turn to see when it becomes 1.

When process 0 leaves the critical region, it sets turn to 1, to allow process 1 to enter its critical region. Suppose that process 1 finishes its critical region quickly, so that both processes are in their noncritical regions, with turn set to 0.

Now process 0 executes its whole loop quickly, exiting its critical region and setting turn to 1. At this point turn is 1 and both processes are executing in their noncritical regions.

4. Peterson’s Solution

Two Process Solution

#define FALSE 0

#defineTRUE 1

#define N 2                                                             /* number of processes */

int turn;                                                                    /* whose turn is it? */

int interested[N];                                                    /* all values initially 0 (FALSE) */

void enter_region(int process);                               /* process is O or 1 */

{

int other;                                                                  /* number of the other process */

other = 1 - process;                                                 /* the opposite of process */

interested[process] = TRUE;                                  /* show that you are interested */

turn = process;                                                         /* set flag */

while (turn == process && interested[other] == TRUE); /* null statement */

}

void leave_region(int process)                                 /* process: who is leaving */

{

interested[process] - FALSE;                                 /* indicate departure from critical region /

}

Before using the shared variables (i.e., before entering its critical region), each process calls enter_region with its own process number, 0 or 1, as parameter. This call will cause it to wait, if need be, until it is safe to enter. After it has finished with the shared variables, the process calls leave_region to indicate that it is done and to allow the other process to enter, if it so desires.

Initially neither process is in its critical region. Now process 0 calls enter_region. It indicates its interest by setting its array element and sets turn to 0.

Since process 1 is not interested, enter region returns immediately. If process 1 now makes a call to enter_region, it will hang there until interested [0] goes to FALSE, an event that only happens when process 0 calls leave_region to exit the critical region.

5. The TSL Instruction

enter_region:

TSL REGISTER.LOCK                                 copy lock to register and set lock to 1

CMP REGISTER, #0                                     was lock zero?

JNE enter_region                                            if it was nonzero, lock was set, so loop

RET                                                                 return to caller; critical region entered

leave_region:

MOVE LOCK, #0                                        store a 0 in lock

RET                                                                return to caller

To use the TSL instruction, we will use a shared variable, lock, to coordinate access to shared memory. When lock is 0, any process may set it to 1 using the TSL instruction and then read or write the shared memory. When it is done, the process sets lock back to 0 using an ordinary move instruction.

The first instruction copies the old value of lock to the register and then sets lock to 1. Then the old value is compared with 0. If it is nonzero, the lock was already set, so the program just goes back to the beginning and tests it again.

Sooner or later it will become 0 (when the process currently in its critical region is done with its critical region), and the subroutine returns, with the lock set. Clearing the lock is very simple. The program just stores a 0 in lock. No special synchronization instructions are needed.