You are on page 1of 16

Chapter 5

Contents have been taken from Web Resources Namely

1. Tutorial Points
2. Geek for Geeks
3. Wikipedia
4. Lecture notes of experts as posted on Internet

Notes on Inter Process Communication


Intro
Ever since computers have had the ability to run multiple processes, there is a desire for the
processes to be able to communicate somehow. Communication and synchronization are closely
related challenges. Some of the most common and important problem types in computer
applications involve these challenges. Importantly, the same issues arise whether processes are
sharing resources (which usually must be done explicitly), or threads are sharing resources,
which they normally do by default.

Inter-Process Communication
The term IPC stands for inter-process communication, but it refers not only to communication
but synchronization, as well. Some examples of processes needing to communicate include:

• Reading from and writing to the same named pipe.


• Reading from and writing to the same file or device.
• Reading from and writing to an area of shared memory.

Basic information passing, as via signals and messages, does not ordinarily lead to problems, but
operating on shared resources and data certainly does.

Inter Process Communication (IPC)


A process can be of two type:

• Independent process.
• Co-operating process.

An independent process is not affected by the execution of other processes while a co-operating
process can be affected by other executing processes. Though one can think that those processes,
which are running independently, will execute very efficiently but in practical, there are many
situations when co-operative nature can be utilised for increasing computational speed,
convenience and modularity. Inter process communication (IPC) is a mechanism which allows
processes to communicate each other and synchronize their actions. The communication between
these processes can be seen as a method of co-operation between them. Processes can
communicate with each other using these two ways:

1. Shared Memory
2. Message passing

The Figure 1 below shows a basic structure of communication between processes via shared
memory method and via message passing.

An operating system can implement both method of communication. First, we will discuss the
shared memory method of communication and then message passing. Communication between
processes using shared memory requires processes to share some variable and it completely
depends on how programmer will implement it. One way of communication using shared
memory can be imagined like this: Suppose process1 and process2 are executing simultaneously
and they share some resources or use some information from other process, process1 generate
information about certain computations or resources being used and keeps it as a record in shared
memory. When process2 need to use the shared information, it will check in the record stored in
shared memory and take note of the information generated by process1 and act accordingly.
Processes can use shared memory for extracting information as a record from other process as
well as for delivering any specific information to other process.
Let’s discuss an example of communication between processes using shared memory method.

ii) Messaging Passing Method

Now, We will start our discussion for the communication between processes via message
passing. In this method, processes communicate with each other without using any kind of
shared memory. If two processes p1 and p2 want to communicate with each other, they proceed
as follow:

• Establish a communication link (if a link already exists, no need to establish it again.)
• Start exchanging messages using basic primitives.
We need at least two primitives:
– send(message, destinaion) or send(message)
– receive(message, host) or receive(message)

The message size can be of fixed size or of variable size. if it is of fixed size, it is easy for OS
designer but complicated for programmer and if it is of variable size then it is easy for
programmer but complicated for the OS designer. A standard message can have two parts:
header and body.
The header part is used for storing Message type, destination id, source id, message length and
control information. The control information contains information like what to do if runs out of
buffer space, sequence number, priority. Generally, message is sent using FIFO style.

Terms
We begin the discussion with a few important definitions:

• Atomic Operation: A function/action implemented as a sequence of instructions that


appears to be indivisible. No other process can see an intermediate state or interrupt the
operation. The sequence is guaranteed to execute as a group, or not execute at all.
• Critical Section: A section of code within a process that requires access to shared
resources, and must not be executed while another process is in a corresponding section
of code.
• Mutual Exclusion: When one process is in a critical section, no other process may be in
a critical section that accesses any of the same shared resources.
• Race Condition: A race condition exists when the outcome of a series of operations
depends not only on the data, but also on the order of execution.
• Deadlock: Two or more processes are unable to proceed because each is waiting for one
of the others to do something.
• Livelock: Two or more processes continuously change their states in response to changes
in the other process(es), without doing any useful work.
• Starvation: A runnable process is overlooked indefinitely by the scheduler. The process
is able to proceed, but it is never chosen.

Race Conditions
When a race condition exists, program correctness is likely to be violated. Consider two example
sections of code, called T1 and T2, which might be threads that operate on the shared variables a
and b.

// T1
a = a + 1;
b = b + 1;
// T2
b = b * 2;
a = a * 2;

If either T1 or T2 runs in isolation, the property a=b is preserved, as in the following examples:
// T1 then T2 (a=b=1 initially)
a = a + 1;
b = b + 1;
b = b * 2;
a = a * 2; // a=b=4
// T2 then T1 (a=b=1 initially)
b = b * 2;
a = a * 2;
a = a + 1;
b = b + 1; // a=b=4

However, suppose the code execution is somehow interleaved between T1 and T2. If initially
a=1 and b=1, the following interleaved execution could occur:
a = a + 1; // T1 (a=b=1)
b = b * 2; // T2
b = b + 1; // T1
a = a * 2; // T2 (a=4, b=3)

At the end of the interleaved execution, a=4 and b=3, so a=b is no longer true. We observe that
the value of the output depends on the order of execution, not just on the value of the input data.
It might be very rare that T1 and T2 ever execute in such a way, but when they do, the program
no longer executes correctly. This example involves integer variables, but can be applied in
principle to any shared data or object.

Critical Sections
In the previous example, the problem could have ben prevented if we could guarantee that, when
either T1 or T2 runs, it maintains exclusive access to the shared objects (a and b). This is the idea
behind the concept of a critical section. When a process (or thread) is executing in its own
critical section, no other process (or thread) may be execute in its own same critical section.
critical sections are a mechanism by which mutual exclusion can be enforced.

Mutual Exclusion
There are a number of requirements for mutual exclusion to be successful:

• No two processes may be simultaneously inside their critical sections, and a process
remains inside its critical section for a finite time
• A process that halts must do so without interfering with other processes
• A process must not be denied access to a critical resource when there is no other process
using it
• No assumptions are made about relative process speeds or the number of processes or
CPUs

Hardware-Based Approaches to Mutual Exclusion


In this section we briefly review a few hardware-based approaches to the problem of mutual
exclusion.
Disabling Interrupts

This is possibly the simplest approach. Disable all interrupts before a process/thread enters its
critical section, then re-enable them after the process/thread exits its critical section. On the good
side, this approach does actually enforce mutual exclusion. The running process can't be
interrupted in its critical section, even by the OS!
while (true)
{
/* disable interrupts */
/* critical section */
/* enable interrupts */

/* remainder */
}
However, there are two important limitations:

• It only works with a single processor. In a multi-processor design, it doesn't work.


• An infinite loop or error in the critical section could hang or crash the whole OS.

Besides disabling interrupts, there are other hardware-based mutual exclusion strategies. In each
case, there are one or more hardware instructions that, when properly used, can implement
mutual exclusion correctly.

Hardware Instructions -- Exchange, Test-and-Set

The Exchange method requires a hardware instruction that can perform a swap operation
atomically (indivisibly). A typical assembly representation of the machine instruction follows.
The special instruction is XCHG.

enter_critical:
MOV REGISTER, 1
XCHG REGISTER, LOCK ; swap contents of REGISTER and LOCK
CMP REGISTER, 0 ; was LOCK 0, before the swap?
JNE enter_critical ; no: busy loop (LOCK was 1)
RET ; enter critical_section

leave_critical:
MOV LOCK, 0 ; open the lock
RET ; leave critical section

And here is a C-style implementation:

int key = 1;
while (true) {
do exchange(&key, &lock) while (key != 0);
/* critical section */
lock = 0;
/* remainder */
}
As long as the lock is locked (i.e., the lock variable holds a '1'), then the action of swapping has
no effect -- it continually swaps a '1' with another '1'. Only when the lock variable is '0' does the
swap have an effect -- the lock is quickly reset to '1', and the calling function gets returned a '0'
value, indicating it has successfully gained access. The calling process knows it is the only one
gaining access, because the exchange is atomic. This is generally done by briefly locking the
memory bus in hardware. The Intel x86/x64 architecture implements a version of Exchange. It is
described in the architectural manual as follows:

Intel XCHG machine instruction for x86/x64. (source: Intel)

There are other similar instructions. One mentioned in the text, TSL (Test and Set Lock),
functions almost exactly the same as the Exchange example.

Hardware-based solutions can implement mutual exclusion correctly, but they rely on busy
waiting. They also fail if processes do not cooperate, and both deadlock and starvation are still
possible.

Early Software-Based Approaches to Mutual Exclusion


There are two software-based approaches to mutual exclusion that involve busy waiting. While
one process is in its critical section, other processes actively wait, repeatedly checking for their
own opportunity to proceed.

Lock Variables

In this approach, there is a shared 'lock' variable. If its value is 0, no process is in its critical
region. If the value is 1, a process is in its critical region and all others must wait. A process
needing to enter its critical section repeatedly checks the value, while the value is 1. As soon as
the lock value is 0 again, the process enters its critical section and sets the lock value to 1.

while (true) {
while (lock == 1)
/* busy wait: do nothing until lock==0 */ ;

// <- race condition here


lock = 1;
/* critical section */
lock = 0;
/* remainder */
}

The problem with this approach is that it relies on a race condition. If two processes read the lock
value as 0 and both proceed into their critical sections before the lock value can be set to 1, then
mutual exclusion is not enforced successfully.

Strict Alternation

The integer variable turn keeps track of whose turn it is to enter the critical section. Each
process will busy wait until its own turn.
while (true) {
while (turn != 0)
/* busy wait */ ;

/* critical section */
turn = 1;
/* remainder */
}
while (true) {
while (turn != 1)
/* busy wait */ ;

/* critical section */
turn = 0;
/* remainder */
}

The problem with this approach is that, if one process is significantly faster, the slower process is
repeatedly blocked for long periods of time. Also, it becomes complex to scale beyond two
processes. The approach provides mutual exclusion, but is very inefficient.

Peterson's Solution

In 1981, G.L. Peterson devised a software-based solution to mutual exclusion that does not
require strict alternation. There are a few variations of how to code Peterson's Solution; the one
shown is from the Tanenbaum text.

There are two function calls, one each for entering and leaving a process' criticial region. Each
process must call enter_region with its own process number as an argument. This will cuase it
to wait, if necessary, until it is safe to enter. After accessing the shared variables, it calls
leave_region to exit the critical region.

#define FALSE 0
#define TRUE 1
#define N 2

int turn; // Whose turn is it?


int interested[N]; // All initially 0

void enter_region(int process)


{
int other;
other = 1 - process; // Number of the other process
interested[process] = TRUE; // Show interest
turn = process; // Set flag
// If both processes call enter_region() at the same time, the
// next statement forces the latter, who writes to turn _last_,
// to busy wait until the earlier process exits.
while(turn == process && interested[other] == TRUE) // Busy wait
}

void leave_region(int process)


{
interested[process] = FALSE; // Indicate departure from critical region
}

If both processes call enter_region simultaneously, they will each try to modify the variable
turn. The value of turn is overwritten by the second process, but by doing so it causes the
second process to hang on the while loop and busy wait, until the earlier processes finishes and
leaves its criticial region.

Peterson's solution works correctly if implemented properly, allowing mutual exclusion by two
cooperating processes. It can also be scaled to work with more than two processes. However,
Peterson's solution employs busy waiting, an inefficient use of the CPU.

Mutex vs Semaphore
What are the differences between Mutex vs Semaphore? When to use mutex and when to use
semaphore?

Concrete understanding of Operating System concepts is required to design/develop smart


applications. Our objective is to educate the reader on these concepts and learn from other expert
geeks.

As per operating system terminology, mutex and semaphore are kernel resources that provide
synchronization services (also called as synchronization primitives). Why do we need such
synchronization primitives? Won’t be only one sufficient? To answer these questions, we need to
understand few keywords. Please read the posts on atomicity and critical section. We will
illustrate with examples to understand these concepts well, rather than following usual OS
textual description.

The producer-consumer problem:

Note that the content is generalized explanation. Practical details vary with implementation.

Consider the standard producer-consumer problem. Assume, we have a buffer of 4096 byte
length. A producer thread collects the data and writes it to the buffer. A consumer thread
processes the collected data from the buffer. Objective is, both the threads should not run at the
same time.

Using Mutex:

A mutex provides mutual exclusion, either producer or consumer can have the key (mutex) and
proceed with their work. As long as the buffer is filled by producer, the consumer needs to wait,
and vice versa.

At any point of time, only one thread can work with the entire buffer. The concept can be
generalized using semaphore.
Using Semaphore:

A semaphore is a generalized mutex. In lieu of single buffer, we can split the 4 KB buffer into
four 1 KB buffers (identical resources). A semaphore can be associated with these four buffers.
The consumer and producer can work on different buffers at the same time.

Misconception:

There is an ambiguity between binary semaphore and mutex. We might have come across that a
mutex is binary semaphore. But they are not! The purpose of mutex and semaphore are different.
May be, due to similarity in their implementation a mutex would be referred as binary
semaphore.

Strictly speaking, a mutex is locking mechanism used to synchronize access to a resource. Only
one task (can be a thread or process based on OS abstraction) can acquire the mutex. It means
there is ownership associated with mutex, and only the owner can release the lock (mutex).

Semaphore is signaling mechanism (“I am done, you can carry on” kind of signal). For
example, if you are listening songs (assume it as one task) on your mobile and at the same time
your friend calls you, an interrupt is triggered upon which an interrupt service routine (ISR)
signals the call processing task to wakeup.

Classical problems of Synchronization with Semaphore Solution


In this article, we will see number of classical problems of synchronization as examples of a
large class of concurrency-control problems. In our solutions to the problems, we use
semaphores for synchronization, since that is the traditional way to present such solutions.
However, actual implementations of these solutions could use mutex locks in place of binary
semaphores.

These problems are used for testing nearly every newly proposed synchronization scheme. The
following problems of synchronization are considered as classical problems:

1. Bounded-buffer (or Producer-Consumer) Problem,


2. Dining-Philosphers Problem,
3. Readers and Writers Problem,
4. Sleeping Barber Problem

These are summarized, for detailed explanation, you can view the linked articles for each.
1. Bounded-buffer (or Producer-Consumer) Problem:
Bounded Buffer problem is also called producer consumer problem. This problem is generalized
in terms of the Producer-Consumer problem. Solution to this problem is, creating two counting
se aphores full a d e pty to keep tra k of the urre t u er of full and empty buffers
respectively. Producers produce a product and consumers consume the product, but both use of
one of the containers each time.

2. Dining-Philosphers Problem:
The Dining Philosopher Problem states that K philosophers seated around a circular table with
one chopstick between each pair of philosophers. There is one chopstick between each
philosopher. A philosopher may eat if he can pickup the two chopsticks adjacent to him. One
chopstick may be picked up by any one of its adjacent followers but not both. This problem
involves the allocation of limited resources to a group of processes in a deadlock-free and
starvation-free manner.

3. Readers and Writers Problem:


Suppose that a database is to be shared among several concurrent processes. Some of these
processes may want only to read the database, whereas others may want to update (that is, to
read and write) the database. We distinguish between these two types of processes by referring
to the former as readers and to the latter as writers. Precisely in OS we call this situation as the
readers-writers problem. Problem parameters:
o One set of data is shared among a number of processes.
o Once a writer is ready, it performs its write. Only one writer may write at a time.
o If a process is writing, no other process can read it.
o If at least one reader is reading, no other process can write.
o Readers may not write and only read.

4. Sleeping Barber Problem:


Barber shop with one barber, one barber chair and N chairs to wait in. When no customers the
barber goes to sleep in barber chair and must be woken when a customer comes in. When
barber is cutting hair new custmers take empty seats to wait, or leave if no vacancy.

Monitor vs Semaphore
Both semaphores and monitors are used to solve the critical section problem (as they allow
processes to access the shared resources in mutual exclusion) and to achieve process
synchronization in the multiprocessing environment.
Monitor:
A Monitor type high-level synchronization construct. It is an abstract data type. The Monitor
type contains shared variables and the set of procedures that operate on the shared variable.

When any process wishes to access the shared variables in the monitor, it needs to access it
through the procedures. These processes line up in a queue and are only provided access when
the previous process release the shared variables. Only one process can be active in a monitor at
a time. Monitor has condition variables.

Syntax:

monitor {

//shared variable declarations


data variables;
Procedure P1() { ... }
Procedure P2() { ... }
.
.
.
Procedure Pn() { ... }

Semaphore:
A Semaphore is a lower-level object. A semaphore is a non-negative integer variable. The value
of Semaphore indicates the number of shared resources available in the system. The value of
semaphore can be modified only by two functions, namely wait() and signal() operations (apart
from the initialization).

When any process accesses the shared resources, it performs the wait() operation on the
semaphore and when the process releases the shared resources, it performs the signal() operation
on the semaphore. Semaphore does not have condition variables. When a process is modifying
the value of the semaphore, no other process can simultaneously modify the value of the
semaphore.

The Semaphore is further divided into 2 categories:

1. Binary semaphore
2. Counting semaphore

Syntax:

// Wait Operation
wait(Semaphore S) {
while (S<=0);
S--;
}
// Signal Operation
signal(Semaphore S) {
S++;
}

Advantages of Monitors:

• Monitors are easy to implement than semaphores.


• Mutual exclusion in monitors is automatic while in semaphores, mutual exclusion needs to be
implemented explicitly.
• Monitors can overcome the timing errors that occur while using semaphores.
• Shared variables are global to all processes in the monitor while shared variables are hidden in
semaphores.

Advantages of Semaphores:

• Semaphores are machine independent (because they are implemented in the kernel services).
• Semaphores permit more than one thread to access the critical section, unlike monitors.
• In semaphores there is no spinning, hence no waste of resources due to no busy waiting.

What is Semaphore?

Semaphore is simply a variable that is non-negative and shared between threads. A semaphore
is a signaling mechanism, and a thread that is waiting on a semaphore can be signaled by another
thread. It uses two atomic operations, 1)wait, and 2) signal for the process synchronization.

A semaphore either allows or disallows access to the resource, which depends on how it is set
up.

Characteristic of Semaphore
Here, are characteristic of a semaphore:

• It is a mechanism that can be used to provide synchronization of tasks.


• It is a low-level synchronization mechanism.
• Semaphore will always hold a non-negative integer value.
• Semaphore can be implemented using test operations and interrupts, which should be
executed using file descriptors.

Types of Semaphores
The two common kinds of semaphores are
• Counting semaphores
• Binary semaphores.

Counting Semaphores

This type of Semaphore uses a count that helps task to be acquired or released numerous times. If
the initial count = 0, the counting semaphore should be created in the unavailable state.

However, If the count is > 0, the semaphore is created in the available state, and the number of
tokens it has equals to its count.

Binary Semaphores

The binary semaphores are quite similar to counting semaphores, but their value is restricted to 0
and 1. In this type of semaphore, the wait operation works only if semaphore = 1, and the signal
operation succeeds when semaphore= 0. It is easy to implement than counting semaphores.

Example of Semaphore
The below-given program is a step by step implementation, which involves usage and
declaration of semaphore.

Shared var mutex: semaphore = 1;


Process i
begin
.
.
P(mutex);
execute CS;
V(mutex);
.
.
End;

Wait and Signal Operations in Semaphores


Both of these operations are used to implement process synchronization. The goal of this
semaphore operation is to get mutual exclusion.

Wait for Operation


This type of semaphore operation helps you to control the entry of a task into the critical section.
However, If the value of wait is positive, then the value of the wait argument X is decremented.
In the case of negative or zero value, no operation is executed. It is also called P(S) operation.

After the semaphore value is decreased, which becomes negative, the command is held up until
the required conditions are satisfied.

Copy CodeP(S)
{
while (S<=0);
S--;
}

Signal operation

This type of Semaphore operation is used to control the exit of a task from a critical section. It
helps to increase the value of the argument by 1, which is denoted as V(S).

Copy CodeP(S)
{
while (S>=0);
S++;
}

Counting Semaphore vs. Binary Semaphore


Here, are some major differences between counting and binary semaphore:

Counting Semaphore Binary Semaphore


No mutual exclusion Mutual exclusion
Any integer value Value only 0 and 1
More than one slot Only one slot
Provide a set of Processes It has a mutual exclusion mechanism.

Difference between Semaphore vs. Mutex


Parameters Semaphore Mutex
Mechanism It is a type of signaling mechanism. It is a locking mechanism.
Data Type Semaphore is an integer variable. Mutex is just an object.
The wait and signal operations can It is modified only by the process that
Modification
modify a semaphore. may request or release a resource.
If no resource is free, then the process If it is locked, the process has to wait.
Resource requires a resource that should execute The process should be kept in a queue.
management wait operation. It should wait until the This needs to be accessed only when
count of the semaphore is greater than 0. the mutex is unlocked.
You can have multiple program
Thread You can have multiple program threads. threads in mutex but not
simultaneously.
Object lock is released only by the
Value can be changed by any process
Ownership process, which has obtained the lock
releasing or obtaining the resource.
on it.
Types of Semaphore are counting
Types Mutex has no subtypes.
semaphore and binary semaphore and
Semaphore value is modified using wait
Operation Mutex object is locked or unlocked.
() and signal () operation.
It is occupied if all resources are being
In case if the object is already locked,
used and the process requesting for
Resources the process requesting resources waits
resource performs wait () operation and
Occupancy and is queued by the system before
blocks itself until semaphore count
lock is released.
becomes >1.

Advantages of Semaphores
Here, are pros/benefits of using Semaphore:

• It allows more than one thread to access the critical section


• Semaphores are machine-independent.
• Semaphores are implemented in the machine-independent code of the microkernel.
• They do not allow multiple processes to enter the critical section.
• As there is busy waiting in semaphore, there is never a wastage of process time and
resources.
• They are machine-independent, which should be run in the machine-independent code of
the microkernel.
• They allow flexible management of resources.

Disadvantage of semaphores
Here, are cons/drawback of semaphore

• One of the biggest limitations of a semaphore is priority inversion.


• The operating system has to keep track of all calls to wait and signal semaphore.
• Their use is never enforced, but it is by convention only.
• In order to avoid deadlocks in semaphore, the Wait and Signal operations require to be
executed in the correct order.
• Semaphore programming is a complicated, so there are chances of not achieving mutual
exclusion.
• It is also not a practical method for large scale use as their use leads to loss of modularity.
• Semaphore is more prone to programmer error.
• It may cause deadlock or violation of mutual exclusion due to programmer error.

You might also like