Chapter 4
Chapter 4
Process Communication
Concurrency and Its Problems
A. Basic Concept
– The most fundamental task of modern operating systems is
management of multiple processes within uniprocessor,
multiprocessor or distributed computer systems
– The fundamental design issue in the management of
multiple processes is concurrency: simultaneous execution
of multiple processes
– Concurrency arises in three different contexts
• Multiple applications: concurrently running applications
• Structured applications: an application structured as a set of
concurrent processes (threads)
• OS structure: OS implemented as a set of processes
Concurrency and Its Problems …
B. Inter-process Communication
• There is frequent interaction among concurrently running
processes. There are three ways in which concurrent processes
interact with each other:
• Competition for Resources
It occurs when independent processes that are not intended
to work together compete for the use of the same or shared
resource, e.g. printer, memory, or file
There is no exchange of information between competing
processes
Processes are unaware of each other
Concurrency and Its Problems …
C. Concurrency Problems
• There are some serious problems associated with the interaction of
concurrently running processes:
i. Race Condition
– A situation that occurs when two or more processes are reading or
writing into some shared data and the final result depends on who
runs precisely when
– E.g. 1 Printer Spooler: when a process wants to print a file, it enters
the file name in a special spooler directory. Assume that the spooler
directory has a large number of slots, numbered 0,1,2,…
There are two globally shared variables
Outfile: points to the next file to be printed
Infile: points to the next free slot in the directory
Concurrency and Its Problems …
• There were some files in the spooler directory and assume the current value
of infile is 7 and that of outfile is 3
• Assume that simultaneously process A and process B decide they want to
queue a file for printing
– Process A reads infile and stores the value 7 in its local variable (x=infile)
– An interrupt occurs and the CPU decides that process A has run long
enough so it switches to process B
– Process B reads infile and stores the value 7 in its local variable (y=infile)
– Process B stores the name of its file in slot 7 and adjusts infile to be 8
– Eventually process A runs again, it checks x and finds 7 there, and writes
its file name in slot 7, erasing the name process B just put there, it updates
infile to be 8
• The printer daemon will now print the file of process A, process B file will
never get any output
Concurrency and Its Problems …
• E.g. 2 Character echo procedure: consider the following globally shared procedure
Void echo
{
chin=getchar(); //read a character from keyboard
chout=chin;
putchar (chout); //display the character on the screen
}
• Consider two processes (P1 & P2) trying to access the procedure
– Process P1 invokes the echo procedure and is interrupted immediately after
the conclusion of getchar function (chin =x)
– Process P2 is activated and invokes the echo procedure, which runs to
conclusion, inputting an displaying a single character, y
– Process P1 is resumed. By this time the value x ihas been overwritten in chin
and therefore lost. Instead chin contains y, and is displayed twice
Concurrency and Its Problems …
ii. Deadlock
• It is the permanent blocking of a set of processes that either compete for system
resources or communicate with each other. It involves conflicting needs for
resources by two or more processes.
• It refers to a situation in which a set of two or more processes are waiting for other
members of the set to complete an operation in order to proceed, but none of the
members is able to proceed.
• E.g. Traffic deadlock: consider a situation in which four cars have arrived at a four-
way stop intersection at the same time. The four quadrants of the intersection are
the resources over which control is needed. If all four cars proceed into the
intersection, then each car controls one resource (one quadrant) but cannot
proceed because the required second resource has already been controlled by
another car. Hence deadlock will occur. The main reason for this deadlock is
because each car needs exclusive use of both resources for certain period of time
• It is a difficult phenomenon to anticipate and there are no easy general solutions to
this problem
Concurrency and Its Problems …
iii. Starvation
– It referees to the situation in which a process is
ready to execute but is continuously denied access
to a processor in deference to other processes.
– E.g. suppose that there are three processes P1, P2,
and P3 and each require periodic access to resource
R. If the operating system grants the resource to P1
and P2 alternately, P3 may indefinitely be denied
access to the resource, thus starvation may occur.
– In large part, it is a scheduling issue
Concurrency and Its Problems …
D. Mutual Exclusion
• The key to preventing race condition is to enforce mutual
exclusion: It is the ability to exclude (prohibit) all other
processes from using a shared variable or file while one
process is using it.
• Part of a program where shared resource (critical resource) is
accessed is called critical region or critical section
Concurrency and Its Problems …
Concurrency and Its Problems …
• In this section, various proposals for achieving mutual exclusion with the help of
busy waiting are examined:
A. Disabling Interrupts
– An interrupt is an event that alters the sequence in which a process executes
instruction
– In this technique, each process disables all interrupts just after entering its
critical section and re-enables them just before leaving it. With interrupts
turned off the processor will not be switched to another process
– Disadvantages
• It is unwise to give processes the power to turn off interrupts. For
instance, a process can turn off interrupts but never turn them on again in
which the whole system freezes
• If the system is multiprocessor, disabling interrupts affects only the
processor that executed the disable instruction. The other ones will
continue running and can access the shared memory.
– Disabling interrupts is often a useful technique within the operating system
itself but is not appropriate as a general mutual exclusion mechanism for user
processes.
Implementing Mutual Exclusion with Busy Waiting …
B. Lock Variables
– Assume we have a single shared (lock) variable initially set to 0
– A process enters its critical section if this variable is 0, when it enters it
sets it to 1
– If the lock is already 1, the process waits until it 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
– Disadvantages
• 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 the two processes will be in their critical
region at the same time causing race condition
• Continuously testing a variable waiting for some value to appear is
called busy waiting. This technique wastes processor time
Implementing Mutual Exclusion with Busy Waiting …
C. Strict Alternation
– Strict alternation is shown in the program fragment below for two
processes, process 0 and process 1. This solution requires that the two
processes strictly alternate in entering their critical regions.
Implementing Mutual Exclusion with Busy Waiting …
D. Peterson’s Solution
– It is a software solution. It combines the idea of taking turns with the
idea of lock variables and warning variables. It does not require strict
alternation.
– Before using the shared variable, i.e. before entering its critical region,
each process calls enter_region procedure with its own process
number, 0 or 1 as a 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 procedure to indicate that it is done and
to allow the other process to enter, if it so desires. The code is shown
below:
Implementing Mutual Exclusion with Busy Waiting …
Implementing Mutual Exclusion with Busy Waiting …
E. TSL Instruction
– This technique requires a little help from the hardware. It uses the
hardware instruction TSL.
– TSL (Test and Set Lock) is an indivisible atomic instruction that copies
the content of a memory location into a register and stores a non-zero
value at the memory location. The operation of reading the word and
storing into it are guaranteed to be indivisible, i.e. no other processor
can access the memory word until the instruction is finished. The
processor executing the TSL instruction locks the memory bus to
prohibit other processors from accessing the memory until it is done.
– To implement mutual exclusion with TSL instruction, we use a shared
variable, lock, to coordinate access to shared memory. When lock is 0
the process may set it to 1 using TSL instruction and executes its critical
section. When it is done, the process sets lock into 0.
Implementing Mutual Exclusion with Busy Waiting …
• Both Peterson’s solution and the solution using TSL are correct, but both have the
defect of requiring busy waiting which wastes CPU time and which can also have
unexpected effects, like the priority inversion problem.
• Priority inversion problem: consider a computer with two processes, H with high
priority and L with low priority. The scheduling rules are such that H runs whenever it
is in the ready state. At a certain moment, with L in its critical region, H becomes ready
to run. H now begins busy waiting, but since L is never scheduled while H is running, L
never gets the chance to leave its critical region, so H loops forever.
• Now let us look at some inter-process communication primitives that block instead of
wasting CPU time when they are not allowed to enter their critical regions.
– Sleep: It is a system call that causes the caller to block, i.e. be suspended until
another process wakes it up.
– Wakeup: It is a system call that causes the process specified by the parameter to
wake up.
• As an example of how these primitives can be used let us consider the producer-
consumer problem (also known as the bounded buffer problem)
Implementing Mutual Exclusion without Busy Waiting …
• Producer-consumer problem
– Two processes share a common fixed-size buffer. One of, the
producers, puts information in the buffer, and the other one,
the consumer, takes it out.
– When the producer wants to put a new item in the buffer, it
checks the buffer, if it is full, it goes to sleep, to be awakened
when the consumer has removed one or more items.
– When the consumer wants to remove an item from the
buffer and sees that the buffer is empty, it goes to sleep until
the producer puts something in the buffer and wakes it up.
– Let us see the producer-consumer problem using c
programming
Implementing Mutual Exclusion without Busy Waiting …
• g Problem:
Readin
Assignme– Race condition can occur because access to count is unconstrained. Consider
nt: Other the following situation. The buffer is empty and the consumer has just read
classical count to see if it is 0. At that instant, the scheduler decides to stop running the
IPC consumer temporarily and start running the producer. The producer enters an
problems item in the buffer, increments count, and notices that it is now 1. Reasoning
– Readers the count was just 0, and thus the consumer must be sleeping, and the
and producer calls wakeup to wake the consumer up. Unfortunately, the consumer
Writers is not yet logically asleep, so the wakeup signal is lost. When the consumer
Problem, next runs, it will test the value of count it previously read, find it to be 0, and
The go to sleep. Sooner or later the producer will fill up the buffer and also go to
Sleeping sleep. Both will sleep forever.
Barber – The problem arises because the wakeup signal is lost. A quick fix is to add to
Problem, the rules by adding wakeup-waiting bit. It is a piggy bank for wakeup signals
The Dining – When a wakeup is sent to a process that is still awake, this bit is set.
Philosoph Later, when the process tries to go to sleep, if the wakeup-waiting bit
ers is on, it will be turned off, but the process will stay awake.
Problem
– The wakeup waiting bit cannot be a general solution, especially for any
random number of processes.
Implementing Mutual Exclusion without Busy Waiting …
A. Semaphores
• Semaphores solve the lost-wakeup problem
• A semaphore is a new integer variable type that counts the number of
wakeups saved for future use. A semaphore could have the value 0,
indicating that no wakeups were saved or some positive value if one or
more wakeups were pending.
• Two operations were proposed to implement semaphores: up and down
• DOWN operation
– It checks the value of the semaphore to see if the value is greater than 0. If so it
decrements the value and just continues. If the value is 0, the process is put to
sleep without completing the DOWN operation for the moment
– Checking the value, changing it and going to sleep is all done as a single,
indivisible atomic operation. Once a semaphore operation has started, no other
process can access the semaphore until the operation has completed or
blocked
Implementing Mutual Exclusion without Busy Waiting …
• UP operation
– It increments the value of the semaphore. If one or more processes were sleeping
on that semaphore, unable to complete an earlier DOWN operation, one of them
is chosen by the system and is allowed to complete its DOWN operation
– The process of incrementing the semaphore and waking up one process is also
indivisible.
• Semantics of DOWN and UP operations
void DOWN(s:semaphore)
{
if(s==0) sleep();
s=s-1;
}
void UP(s:semaphore)
{
s=s+1;
wakeup a sleeping process if any;
}
Implementing Mutual Exclusion without Busy Waiting …
• The solution uses three semaphores: full, to count the number of full
slots, empty, to count the number of empty slots and mutex, to make
sure the producer and the consumer do not access the buffer at the
same time
• Mutex is used for mutual exclusion, i.e. it is designed to guarantee that
only one process at a time will be reading or writing the buffer and the
associated variables
• Full/empty are used for synchronization, i.e. they are designed to
guarantee that certain event sequences do or do not occur
• The producer stops running when the buffer is full, and the consumer
stops running when it is empty.
• Semaphores that are initialized to 1 and are used by two or more
processes to ensure that only one of them can enter its critical region at
the same time are called binary semaphores
Implementing Mutual Exclusion without Busy Waiting …
• Problems
– Semaphores are too low-level and error prone. If you are not careful
when using them, errors like race condition, deadlocks and other
forms of unpredictable and irreproducible behavior can occur
– Suppose that the two downs in the producer’s code were
interchanged or reversed in order and suppose also the buffer was full
and mutex is 1
down (&mutex);
down (&empty);
– The producer does a down on mutex and mutex becomes 0 and then
the producer does a down on empty. The producer would block since
the buffer is full. Next time the consumer does a down on mutex and
it blocks since mutex is 0. Therefore both processes would block
forever and hence deadlock would occur.
Implementing Mutual Exclusion without Busy Waiting …
B. Monitors
• A monitor is a higher level of synchronization primitive proposed by Hoare
and Branch Hansen to make writing a correct program easier.
• A monitor is a collection of procedures, variables and data structures that are
all grouped together in a special kind of module or package.
• Rules associated with monitors
– Processes may call the procedures in a monitor whenever they want to, but they
can not directly access the monitor’s internal data structures
– Only one procedure can be active in a monitor at any instant. Monitors are
programming language construct, so the compiler knows that they are special and
can handle calls to a monitor procedures differently from other procedure calls
– It is the compiler that implements mutual exclusion. The person writing the
monitor does not have to be aware of how the compiler arranges for mutual
exclusion. It is sufficient to know that by turning all critical regions into monitor
procedure, no two processes will ever execute their critical regions at the same
time.
Implementing Mutual Exclusion without Busy Waiting …
• Monitors use condition variables, along with two operations on them, WAIT and
SIGNAL to block and wake up a process.
– WAIT
• When a monitor procedure discovers that it can not continue, it does a WAIT
on some condition variable that causes the calling procedure to block.
• It allows another process that had been previously prohibited from entering
the monitor to enter now.
– SIGNAL
• A process can wake up its sleeping partner by doing a SIGNAL on the condition
variable that its partner is waiting on
• A process doing a SIGNAL statement must exit the monitor immediately, i.e.
SIGNAL will be the final statement in a monitor procedure. This avoids having
two active processes in a monitor at the same time.
• If a SIGNAL is done on a condition variable on which several processes are
waiting, only one of them, determined by the system scheduler is revived.
– Condition variables are not counters. They do not accumulate signals for later use,
unlike semaphores. If a condition variable is signaled with no one waiting on it, the
signal is lost.
– The WAIT must always come before the SIGNAL
• An outline of the producer-consumer problem with monitors is shown
below
Implementing Mutual Exclusion without Busy Waiting …
– WAIT and SIGNAL are similar to SLEEP and WAKEUP but with one critical difference.
SLEEP and WAKEUP failed because while one process was trying to go to sleep, the other
one was trying to wake it up. With monitors, this cannot happen. The automatic mutual
exclusion on monitor procedures guarantees that if, say, the procedure inside a monitor
discovers that the buffer is full, it will be able to complete the WAIT operation without
having to worry about the possibility that the scheduler may switch to the consumer just
before the WAIT completes.
– Advantage of monitors
• By making mutual exclusion of critical regions automatic, monitors make parallel programming
much less error prone than with semaphores
– Drawback of monitors
• You need a language that has built-in monitors, but languages that have built-in monitors are
rare. But adding semaphores in C, C++ and other languages is easy.
– Drawback of monitors and semaphores
• They were designed for solving the mutual exclusion problem on one or more CPUs that all
have access to a common memory
• In a distributed system consisting of multiple CPUs, each with its own private memory,
connected by a local area network, these primitives become inapplicable
• Semaphores are too low level and monitors are not usable except in a few programming
languages. And none of them provide information exchange between machines
Implementing Mutual Exclusion without Busy Waiting …
C. Message Passing
– This technique uses two primitives: SEND and RECEIVE
– SEND and RECEIVE are system calls and they can be put as
library procedures: SEND(dest, &msg), RECEIVE(source, &msg)
– If no message is available, the receiver could block until one
arrives or could return with an error code
– The producer-consumer problem with message passing
• An outline for the solution of producer-consumer problem with
message passing is shown below
Implementing Mutual Exclusion without Busy Waiting …