630 likes | 651 Views
Learn about race conditions, critical regions, mutual exclusion, and techniques to synchronize processes and avoid conflicts in shared resources. Understand spooler problem resolution using critical regions.<br>
E N D
Race Conditions: An Example Global Variables Process A somefile.txt 4 4 out 5 list.c (next job to print) scores.txt 6 in 7 7 (next available slot in queue) 8 Process B . . . spooler directory
spooler directory Global Variables Process A somefile.txt 4 4 out 5 list.c (next to print) scores.txt 6 in 7 7 7 7 (next available slot) 8 Process B . . . Process A gets a time slice. It does some work. Then it wants to print. It gets the next available slot in the spooler directory. Then it gets swapped out.
spooler directory Global Variables 7 Process A somefile.txt 4 4 out 5 list.c (next to print) scores.txt 6 in 7 7 7 (next available slot) 8 Process B . . . b_file.txt Process B now gets a time slice. It also wants to print. It gets the next available slot in the spooler directory and stores the path to the file there. Then it gets swapped out.
spooler directory Global Variables 7 Process A somefile.txt 4 4 out a_file.txt 5 list.c (next to print) scores.txt 6 7 7 in 7 b_file.txt (next available slot) 8 Process B . . . Process A now gets a time slice. It has what it believes is the next available slot in the spooler directory. It stores the path to it’s file there...
spooler directory Process A 4 7 out 5 (next to print) 6 12 in 7 a_file.txt (next available slot) 8 Process B . . . Eventually, the spooler prints the file pointed to by entry 7 in the spooler directory. User A gets their printout. User B has no clue what happened.
One way to address the problem of race conditions is to make sure that while one program is using a shared variable or a shared file, other programs are excluded from doing so. This is called mutual exclusion. The part of a program where a shared resource is accessed is called a critical region. If we could guarantee that two programs were not both executing their critical regions at the same time, we could avoid race conditions. Critical Regions
The following four conditions must hold to avoid a race condition: * No two processes may be simultaneously inside their critical regions * No assumptions may be made about speeds, scheduling, or the number of CPUs * No process running outside of its critical region may block another process * No process should have to wait forever to enter its critical region.
Solving the Spooler Problem Using Critical Regions
Process A leaves its critical region Process A enters its critical region Process A Blocked Process B Process B enters its critical region Process A is swapped out. Process B attempts to enter its critical region
High Level Language Statements are not atomic! Process B Process A balance = balance - amount; balance = balance + amount; load r1, balance load r2, amount add r1, r2 store r1, balance load r1, balance load r2, amount sub r1, r2 store r1, balance shared variable “balance” balancea balancenew balancea r1 restore the registers
with busy waiting Achieving Mutual Exclusion Disabling Interrupts Lock Variables Strict Alternation Petersen’s Solution The TSL Instruction
Let process A disable interrupts just as it enters its critical section. Now it can examine and update shared resources without concern. The timer interrupt won’t be caught until interrupts are enabled again. Process A then enables interrupts as it leaves its critical section. Problem: What if a user program disables interrupts, but for some reason never enables them again? Disabling Interrupts It is okay for the kernel to disable interrupts, but not for a user program to do so.
Lock Variables Consider having a single shared variable that all processes use to test whether or not they can enter their critical section. If the value of the lock is 0, a process sets it to 1 and enters its critical region. If it is 1, it waits until the lock becomes 0.
Lock Variables Consider having a single shared variable that all processes use to test whether or not they can enter their critical section. If the value of the lock is 0, a process sets it to 1 and enters its critical region. If it is 1, it waits until the lock becomes 0. while (lock == 1); lock = 1; // critical region . . . what happens if a context switch occurs here?
Strict Alternation Again, use a single shared variable as a lock variable. However, this time we allow process A to enter only when the value of the lock is 0 and process B can enter only when the lock is 1.
0 lock variable Process A checks the lock variable. It is 0, so it enters its critical region Process A Process B
0 lock variable Process A checks the lock variable. It is 0, so it enters its critical region Process A busy wait Process B Process B checks the lock variable. It is 0, so it cannot enter its critical region. It loops, waiting for the lock to change to 1.
1 lock variable Process A exits its critical region and changes the lock to 1. Process A busy wait Process B Process B notices that the lock is now set to 1 and enters its critical region.
0 lock variable What are the problems here? Busy waiting (a spin lock) wastes CPU time Processes must alternate. Process A busy wait Process B When it leaves its critical region, process B changes to lock to 0. Recall that process A can enter only when the value of the lock is 0. If A leaves it’s critical section and B never executes it’s critical section, the lock never gets set back to 0!
Consider the following code: #define N 2 int lock; int interested[N]; void enter_region(int process) { int other; other = 1 – process; // flips it interested[process] = 1; lock = process; while (lock == process && interested[other] == 1); } void leave_region(int process) { interested[process] = 0; } Each process stores its number here when it gets the lock All values initially zero Petersen’s Solution Processes indicate an interest in entering their shared region by calling this function. The parameter is either process 1 or 0 Process calls this function when it leaves its critical region.
lock 0 interested[0] 0 Process 0 calls enter_region(0) 0 interested[1] Process 0 Process 1
lock 0 interested[0] 0 0 interested[1] ? other Process 0 Process 1 void enter_region(int process) { int other; other = 1 – process; interested[process] = 1; lock = process; while (lock == process && interested[other] == 1); }
lock 0 interested[0] 0 0 interested[1] 1 other Process 0 Process 1 void enter_region(int process) { int other; other = 1 – process; // if I’m 0 the other process is 1 interested[process] = 1; lock = process; while (lock == process && interested[other] == 1); }
lock 0 interested[0] 1 0 interested[1] 1 other Process 0 Process 1 void enter_region(int process) { int other; other = 1 – process; interested[process] = 1; // Mark me as interested lock = process; while (lock == process && interested[other] == 1); } Note: Once process0 has set interested[0] to 1, process1cannot enter its critical region!
lock 0 interested[0] 1 0 interested[1] 1 other Process 0 Process 1 void enter_region(int process) { int other; other = 1 – process; interested[process] = 1; lock = process; // give me the lock while (lock == process && interested[other] == 1); }
lock 0 interested[0] 1 Since interested[1] = 0, the while executes once and the function exits immediately. Process 0 is now in its critical region. 0 interested[1] 1 other Process 0 Process 1 void enter_region(int process) { int other; other = 1 – process; interested[process] = 1; lock = process; while (lock == process && interested[other] == 1); } Note: if process1 were in its critical region, interested[1] would be 1 and process0 would be blocked from entering its critical region.
lock 0 interested[0] 1 0 interested[1] Process 0 Process 1 Process 1 calls enter_region(1)
lock 0 interested[0] 1 0 interested[1] ? other Process 0 Process 1 void enter_region(int process) { int other; other = 1 – process; interested[process] = 1; lock = process; while (lock == process && interested[other] == 1); }
lock 0 interested[0] 1 0 interested[1] 0 other Process 0 Process 1 void enter_region(int process) { int other; other = 1 – process; // I’m 1 so other is 0 interested[process] = 1; lock = process; while (lock == process && interested[other] == 1); } 0
lock 0 interested[0] 1 1 interested[1] 0 other Process 0 Process 1 void enter_region(int process) { int other; other = 1 – process; interested[process] = 1; // mark me as interested lock = process; while (lock == process && interested[other] == 1); } 0
lock 1 interested[0] 1 1 interested[1] 0 other Process 0 Process 1 void enter_region(int process) { int other; other = 1 – process; interested[process] = 1; lock = process; // give me the lock while (lock == process && interested[other] == 1); }
lock 1 interested[0] 1 1 interested[1] 0 other Process 0 Process 1 void enter_region(int process) { int other; other = 1 – process; interested[process] = 1; lock = process; while (lock == process && interested[other] == 1); } The process stays in this loop until interested[0] changes to 0.
lock 1 interested[0] 0 1 interested[1] Process 0 calls leave_region(0) Process 0 Process 1 void leave_region(int process) { interested[process] = 0; }
lock 1 interested[0] 0 1 interested[1] 0 other Process 0 Process 1 void enter_region(int process) { int other; other = 1 – process; interested[process] = 1; lock = process; while (lock == process && interested[other] == 1); } Process 1 drops through the loop and exits. It now is in its critical region.
Many computers have an instruction of the form tsl rx, lock The instruction does two things: * It stores the current value of lock in the register rx * It stores a non-zero value in lock The key is that this instruction is guaranteed to be atomic, that is, no other instruction can be executed until both parts of the instruction are done. The TSL Instruction
Consider the following code in assembly language. The lock must be zero to enter the critical section: enter_region: tsl r1, lock cmp r1, #0 jne enter_region ret leave_region: move lock, #0 ret stores current value of lock in r1 and a non-zero value in lock test to see if original value of lock was zero, Loop back to beginning of loop if not. If it is zero, drop out of the loop and return. The lock is now non-zero. set the value of lock to 0
Both Petersen’s Solution and the TSL instruction work, … but require busy waiting. * Wasteful of CPU time * Priority Inversion Problem
Priority Inversion Problem Process H Process L Process H has high priority. The scheduling algorithm guarantees that it will always be run next if it is in a ready state.
Process H blocks, waiting for some I/O. Process H Process L
Process H Process L Process L now gets the cpu. It enters it’s critical region.
I/O completes and process H unblocks Process H Process L
Process H tries to enter its critical region. It calls enter_region and loops until lock is set to 0 by Process L Process H Process L
Because process H has a high priority, it gets all of the cpu time. It continues to loop, waiting for the lock to turn to 0. Process H Process L Process L, because it has a low priority, never gets any cpu time it and so never exits its critical region. The lock never clears.
Consumer-Producer Problems Producer Fixed Size (bounded) buffer Consumer Consumer-Producer problems are very common in system code.
Producer Fixed Size buffer The consumer blocks if the buffer is empty. The producer blocks if the buffer is full Consumer A count is required so each can test the buffer condition. count
if count == 0 block else remove item and decrement count if producer is asleep (blocked) wake producer Producer Fixed Size buffer if count == max block else add item and increment count if consumer is asleep (blocked) wake consumer Consumer count
The consumer does not get a chance to block. It gets swapped out. if count == 0 block else remove item and decrement count if producer is asleep wake producer Race Condition Producer Fixed Size buffer if count == max block else add item and increment count if consumer is asleep wake consumer Consumer 0 1
if count == 0 block else remove item & decrement count if producer is asleep wake producer Producer Fixed Size buffer if count == max block else add item & increment count if consumer is asleep wake consumer Consumer Since count was == 0, the producer thinks that the consumer is asleep, so it sends a wake up signal. 1