Operating Systems - Sync/Race Conditions - Test 2
Semaphore Algorithm
#define Boolean int #define TRUE 1 #define FALSE 0 Shared variable:Semaphore mutex initialized to 1 P0: while(TRUE) { non-critical section wait(mutex); critical section signal(mutex); non-critical section } P1: while(TRUE) { non-critical section wait(mutex); critical section signal(mutex); non-critical section }
Semaphore
An abstract data type with a non-negative integer value, two valid atomic operations, apart from initialization with the following semantics: P/down/wait - value--; V/up/signal - value++;
Producer and Consumer problem
Circular buffer, count keeps track of no. of full buffers; incremented by producer after it produces an item and decremented by consumer after consuming an item.
Process Synchronization
Cooperating processes require an inter-process communication (IPC) mechanism to exchange information and data.
Race Condition
Cooperating processes, sharing same data structure can result in inconsistencies (race condition). Several processes access and manipulate same/shared data concurrently and outcome depends on particular order in which access takes place; usually due to preemptive process scheduling.
Counting semaphore
Counting semaphore are useful in controlling exclusive access to resources. The semaphore value is initialized to the number of instances of the resource available. The down and up operations correspond to resource acquisition and release, respectively.
Producer Process
while(TRUE) { ... produce an item in nextp // local variable ... while(count == BUFFER_SIZE); // do nothing; busy waiting buffer[in] = nextp; in = (in + 1) % BUFFER_SIZE; count = count + 1; .... }
Consumer Process
while(TRUE) { ... while(count == 0); // do nothing; busy waiting nextc = buffer[out]; out = (out + 1) % BUFFER_SIZE; count = count - 1; ... consume the next item in nextc; // local variable .... }
Producer Process with Semaphores
#define TRUE 1 #define FALSE 0 #define BUFFER_SIZE 100 //size of the buffer #define OBJTYPE ... //specify type here OBJTYPE buffer[BUFFER_SIZE]; //shared buffer int in = 0; //next free slot pointer int out = 0; //first full slot pointer int count = 0; //number of items in buffer semaphore mutex; //initialized to 1; binary Semaphore semaphore empty; //initialized to BUFFER-SIZE semaphore full; //initialized to 0 while (TRUE) { ... //non-critical section produce an item in nextp; //local variable ... //non-critical section wait (empty); wait (mutex); buffer [in] = nextp; in = (in + 1) % BUFFER_SIZE; count = count + 1; signal (mutex); signal (full); ... //non-critical section }
Consumer Process with Semaphores
#define TRUE 1 #define FALSE 0 #define BUFFER_SIZE 100 //size of the buffer #define OBJTYPE ... //specify type here OBJTYPE buffer[BUFFER_SIZE]; //shared buffer int in = 0; //next free slot pointer int out = 0; //first full slot pointer int count = 0; //number of items in buffer semaphore mutex; //initialized to 1; binary Semaphore semaphore empty; //initialized to BUFFER-SIZE semaphore full; //initialized to 0 while (TRUE) { ... //non-critical section wait (full); wait (mutex); nextc = buffer [out]; out = (out + 1) % BUFFER_SIZE; count = count - 1; signal (mutex); signal (empty); ... //non-critical section consume the item in nextc; //local variable ... //non-critical section }
Blocked Waiting
Busy waiting wastes CPU cycle time; blocked waiting is preferred (process put on waiting queue in waiting state and CPU selects another process to execute). Maintain a list of processes blocked on the semaphore; allow value to go negative to keep count of the number of processes waiting. Blocked process is restarted by a wakeup() and put on ready queue when some other process executes signal(). List of blocked processes may follow any queuing strategy e.g. LIFO, FIFO etc.
Peterson's Algorithm
If the other process wants to get in, let it in if it is it's turn. But if it doesn't want in, take its place. If turn == k, process k is allowed to enter its critical section. Process k will set flag[k]to TRUE if it wants to enter its critical section. #define Boolean int #define TRUE 1 #define FALSE 0 Shared variable: int turn -- initialized to 0 or 1 Boolean flag[2] -- Both initialized to FALSE P0: while(TRUE) { non-critical section flag[0] = TRUE; turn = 1; while(turn == 1 && flag[1] == TRUE); critical section flag[0] = FALSE; non-critical section } P1: while(TRUE) { non-critical section flag[1] = TRUE; turn = 0; while(turn == 0 && flag[0] == TRUE); critical section flag[1] = FALSE; non-critical section }
Solution to Critical-section problem must meet
Mutual Exclusion - If P1 is executing in its critical section, then no other processes can be executing in their critical sections. Progress - If no process is executing in its critical section and there exist some processes that wish to enter their critical section, then only those processes that want to enter can participate in the decision and the decision cannot be postponed indefinitely. Bounded Waiting - A bound must exist on the no. of times that other processes are allowed to enter their critical sections after a process has made a request to enter its critical section and before that request is granted (to prevent starvation).
Disabling Interrupts
No timer, no process switching, but dangerous, can be used by kernel processes, will not work in multicore and multiprocessor systems.
Structure for processes
P0: while(TRUE) { non-critical section entry section critical section exit section non-critical section } P1: while(TRUE) { non-critical section entry section critical section exit section non-critical section } Entry section: code implementing request to enter the critical section. Critical section: part of a process where it is manipulating shared memory. Exit section: may follow the critical section.
Critical Section
Part of a process where it is manipulating shard memory.
Shared Memory
Share memory to communicate between processes by creating shared memory (like any other memory).
Problem with the Produce and Consumer Code
The producer and consumer can concurrently execute the statements count++ and count-- resulting in incorrect values. Possible solutions: - count is shared and should be protected to avoid unpredictable race condition (output depends on order of execution) - only one process should be allowed to manipulate count at any time: need process synchronization and coordination.
Deadlock
Two or more processes are waiting indefinitely for an event that can be caused by only one of the waiting processes.
Producer Consumer with Semaphores
Use semaphore mutex to ensure mutual exclusion in the critical sections. Use semaphore empty for producer synchronization. Use semaphore full for consumer synchronization.
Atomic Operations
When one process is accessing Semaphore S, until that operation is complete, no other process can access it. View the semaphore value as the number of tokens or available resources. The P/down/wait operation allows us to seize a token. The V/up/signal operation allows us to return a token. The value S can be any arbitrary non-negative integer. For a binary semaphore, the value s can only be 0 or 1.
Semaphore definition
typedef struct{ int value; struct process *list; } semaphore; Implementation of wait: wait(semaphore *S) { S->value--; if (S->value < 0) { add this process to S->list; block(); } } Implementation of signal: signal(semaphore *S) { S->value++; if (S->value <= 0) { remove a process P from S->list; wakeup(P); } }