Chapter 5
Chapter 5
Chapter 5
Inter-Process Communication
Unfortunately, the solution is faulty. Suppose at the outset all the philosophers were hungry. Each
would pick up his left chopstick and wait for the right one to become free. They would wait for ever!
The system would become deadlocked.
We soon realise that the only way to prevent trouble is for the philosophers to communicate with each
other:-
24
Computer Engineering
‘‘If it so please you, my esteemed colleague, I would be obliged if one of your chopsticks were made
available to me in the forthcoming period of ten minutes.’’
‘‘Hey, Algernon, you’ve eaten too much. Gimme a chopstick!’’
This illustrates the fact that there must be inter-process communication in a multiprogrammed system,
whenever there are shared resources.
25
Computer Engineering
There is a global variable ‘‘count’ ’ , which indicates the number of items in the buffer. The Producer
process reads this value, and stops producing items when the buffer is full. Likewise the Consumer
process suspends taking items out of the buffer, when it is empty. The buffer is assumed to be a FIFO,
which does not leave spaces between items.
By comparing this scenario with the print spooler case, it can be seen that a race condition can occur
in just the same way. One process reads a value of ‘‘count’ ’ , and then is switched out by the
scheduler. When switched back to run again, it is unaware that the value of ‘‘count’ ’ has been changed
by the other process.
We will use this example in the discussions of some of the methods of preventing race conditions that
follow.
26
Computer Engineering
Although this looks attractive, it has a fatal flaw. The scheduler could switch processes after a process
has read the lock to be zero, but before it was set to one. This would produce a race condition in a
way similar to that discussed for the print spooler above.
5.5.3 Strict Alternation
A single integer globally-declared variable called ‘‘turn’ ’ is used to show which process is in its
critical region. After finishing its critical region, a process sets the variable so that other processes can
enter their critical regions.
To illustrate this, we consider a system with only two processes 0 and 1 executing. The argument can
easily be extended to a plurality of processes. The program fragment below contains a global
declaration of the variable ‘‘turn’ ’ , which indicates which process is in its critical region.
int turn; /* Globally declared variable */
Process i (i = 0 .. N-1):-
strict_alternation( int process_no) /* Run by all processes with access to “turn”
*/
{
while (1)
{
while (turn != process_no) ; /* Busy waiting - do nothing */
critical_section();
if (process_no == 0) turn = 1;
else turn = 0; /* Let other process have a turn */
noncritical_section();
}
}
The problem with Strict Alternation is that while a process is waiting to enter its critical region, it sits
in a loop, doing nothing. This is called busy waiting, and obviously wastes CPU time. Furthermore, it
is possible for the turn to be given to a process, which has no more critical region tasks to perform. In
that case the other process will wait a long time before entering its critical region, and possibly
indefinitely.
27
Computer Engineering
28
Computer Engineering
However, although we have overcome busy waiting, we have not provided mutual exclusion. There is
a race condition, as shown in this sequence of events:-
1. Consumer reads count to be zero.
2. Scheduler swaps from Consumer to Producer
3. Producer reads count to be zero, enters item, sets count to be one.
4. Producer thinks Consumer is asleep. Sends a Wakeup signal.
5. But Consumer is not asleep. The Wakeup call is lost.
6. Scheduler swaps to Consumer, which thinks the count is zero, and goes to sleep.
7. Producer thinks Consumer is awake. It fills the buffer and goes to sleep.
8. Both sleep for ever! R.I.P.
29
Computer Engineering
The trouble was that the wakeup signal to a process which was not asleep was lost. We need a better
method to prevent this.
5.6.2 Semaphores
To prevent lost wakeups, we can create a special data type called SEMAPHORE, which stores integers
indicating the number and identities of processes waiting for wakeups calls. Semaphores must be
manipulated by system calls which perform ‘‘atomic operations’ ’ to check and increment/decrement
the semaphore value. (Usually this is achieved by a call which disables interrupts for a few cycles.)
We therefore define two system calls, "Down(sema)" and "Up(sema)", which can take any semaphore
variable as argument. The semaphore variable is actually a structure with an integer field and a queue
(linked list) for processses it has blocked. Its definition would be of the form:-
struct sema
{
int value; /* Zero means process in CR; non-zero means not in CR */
int *process_queue;
}
The operation of Down( ) is to check whether the semaphore is greater than zero. If it is, then the
semaphore is decremented. If the semaphore is already zero, then the calling process is put to sleep
(which means it is put on the list of blocked processes). It will only be awakened (unblocked) when
the semaphore is incremented by another process. The operation can be expressed in pseudo-C as:-
Down(sema.value)
{
if sema.value > 0
sema.value--;
else (block calling process; put process on end of queue);
}
The operation of Up( ) on a semaphore is to increment the semaphore, thereby waking up one process
which is waiting on that semaphore. Its operation is:-
Up(sema.value)
{
if (there are any processes in the queue)
(unblock first process in queue);
else sema.value++;
}
5.6.3 Using semaphores in the Producer-Consumer problem
A valid solution to the Producer-Consumer problem can be achieved by using three semaphores,
‘‘empty’ ’ , ‘‘full’ ’ , ‘‘mutex’ ’ . The first two are simply integers which are manipulated atomically by
Down() and Up(), without having any values in their queue fields. The "mutex" is a full semaphore
which has a queue (of maximum length one!) of blocked processes, as well as an integer which
indicates whether another process is in its critical region. The variable names refer to the "value" field
in each case to save the expressions becoming too cumbersome.
#define N 100 /* No of spaces in the buffer */
semaphore mutex = 1; /* Mutual exclusion for critical regions */
semaphore empty = N; /* Counts empty buffer slots */
semaphore full = 0; /* Counts full buffer slots */
Process 1:-
producer()
{
while (1)
{
down(empty); /* Decrement empty slot count */
down(mutex); /* Enter CR */
enter_item(); /* Put item in buffer */
up(mutex); /* Leave CR */
up(full); /* Increment full slot count */
30
Computer Engineering
}
}
Process 2:-
consumer()
{
while (1)
{
down(full); /* Decrement full count */
down(mutex); /* Enter CR */
remove_item(); /* Take item from buffer */
up(mutex); /* Leave CR */
up(empty); /* Increment empty slot count */
}
}
31
Computer Engineering
32
Computer Engineering
33