0% found this document useful (0 votes)
26 views26 pages

Os Notes Unit 3

Uploaded by

Arib Ahmed
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
26 views26 pages

Os Notes Unit 3

Uploaded by

Arib Ahmed
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 26

UNIT-II

INTER-PROCESS COMMUNICATION
Processes executing concurrently in the operating system may be either independent
processes or cooperating processes.
1. Independent Process: Any process that does not share data with any other process. An
Independent process does not affect or be affected by the other processes executing in the
system.
2. Cooperating Process: Any process that shares data with other processes. A cooperating
process can affect or be affected by the other processes executing in the system.
Cooperating processes require an Inter-Process Communication (IPC) mechanism that
will allow them to exchange data and information.
Reasons for providing cooperative process environment:
 Information Sharing: Several users may be interested in the same piece of information
(i.e. a shared file), we must provide an environment to allow concurrent access to such
information.
 Computation Speedup: If we want a particular task to run faster, we must break it into
subtasks. Each task will be executing in parallel with the other tasks.
 Modularity: Dividing the system functions into separate processes or threads.
 Convenience: Even an individual user may work on many tasks at the same time. For
example a user may be editing, listening to music and compiling in parallel.
There are two models of IPC: Message passing and Shared memory.
Message-Passing Systems
In the message-passing model, communication takes place by means of messages exchanged
between the cooperating processes.

 Message passing provides a mechanism to allow processes to communicate and to


synchronize their actions without sharing the same address space.
 It is particularly useful in a distributed environment, where the communicating processes
may reside on different computers connected by a network.
 A message-passing facility provides two operations: send, receive.
 Messages sent by a process can be either fixed or variable in size.
Example: An Internet chat program could be designed so that chat participants communicate
with one another by exchanging messages.
P and Q are two processes wants to communicate with each other then they send and receive
messages to each other through a communication link such as Hardware bus or Network.
Methods for implementing a logical communication links are:
1. Naming
2. Synchronization
3. Buffering
Naming
Processes that want to communicate use either Direct or Indirect communication.
In Direct communication, each process that wants to communicate must explicitly name the
recipient or sender of the communication.
 send(P, message) — Send a message to process P.
 receive(Q, message) — Receive a message from process Q.
A communication link in direct communication scheme has the following properties:
 A link is established automatically between every pair of processes that want to
communicate. The processes need to know only each other’s identity to communicate.
 A link is associated with exactly two processes. (i.e.) between each pair of processes,
there exists exactly one link.
In Indirect communication, the messages are sent to and received from mailboxes or ports.
 A mailbox can be viewed abstractly as an object into which messages can be placed by
processes and from which messages can be removed.
 Each mailbox has a unique integer identification value.
A process can communicate with another process via a number of different mailboxes, but
two processes can communicate only if they have a shared mailbox.
 send (A, message) — Send a message to mailbox A.
 receive (A, message) — Receive a message from mailbox A.
In this scheme, a communication link has the following properties:
 A link is established between a pair of processes only if both members of the pair have a
shared mailbox.
 A link may be associated with more than two processes.
 Between each pair of communicating processes, a number of different links may exist,
with each link corresponding to one mailbox.
Synchronization
Message passing done in two ways:
1. Synchronous or Blocking
2. Asynchronous or Non-Blocking
 Blocking send: The sending process is blocked until the message is received by the
receiving process or by the mailbox.
 Non-blocking send: The sending process sends the message and resumes operation.
 Blocking receive: The receiver blocks until a message is available.
 Non-blocking receive: The receiver retrieves either a valid message or a null.
Buffering
Messages exchanged by communicating processes reside in a temporary queue. Those queues
can be implemented in three ways:
1. Zero Capacity: Zero-capacity is called as a message system with no buffering. The
sender must block until the recipient receives the message.
2. Bounded Capacity: The queue has finite length n. Hence at most n messages can reside
in it. If the queue is not full when a new message is sent, the message is placed in the
queue and the sender can continue execution without waiting. If the link is full, the sender
must block until space is available in the queue.
3. Unbounded Capacity: The queue’s length is potentially infinite. Hence any number of
messages can wait in it. The sender never blocks.
IPC - Pipes in UNIX
Pipes were one of the first IPC mechanisms in early UNIX systems.
The pipes in UNIX are categorized into two types: Ordinary Pipes and Named Pipes
Ordinary Pipes
 Ordinary pipes allow two processes to communicate in standard producer–consumer
fashion.
 The producer writes to one end of the pipe (write-end) and the consumer reads from the
other end (read-end).
 Ordinary pipes are unidirectional which allows only one-way communication. If two-way
communication is required, two pipes must be used. Each pipe transfer data in a different
direction.
On UNIX systems ordinary pipes are constructed using the function:
pipe(int fd[ ])
 This function creates a pipe that is accessed through the int fd[ ] file descriptors: fd[0] is
the read-end of the pipe and fd[1] is the write-end.
 UNIX treats a pipe as a special type of file. Thus, pipes can be accessed using ordinary
read( ) and write( ) system calls.

An ordinary pipe cannot be accessed from outside the process that created it.
 A parent process creates a pipe and uses it to communicate with a child process.
 A child process inherits open files from its parent. Since a pipe is a special type of file,
the child inherits the pipe from its parent process.
 If a parent writes to the pipe then the child reads from pipe.
Named Pipes
Named pipe provides the bidirectional communication and no parent–child relationship is
required.
 Once a named pipe is established, several processes can use it for communication. A
named pipe has several writers.
 Named pipes continue to exist after communicating processes have finished.
 Both UNIX and Windows systems support named pipes.
Named pipes are referred to as FIFOs in UNIX systems.
 Once Named pipes are created, they appear as typical files in the file system.
 FIFO is created with the mkfifo( ) system call and manipulated with the ordinary open( ),
read( ), write( ) and close( ) system calls.
 It will continue to exist until it is explicitly deleted from the file system.
 Although FIFOs allow bidirectional communication, only one-way transmission is
permitted. If data must travel in both directions, two FIFOs are used. The communicating
processes must reside on the same machine.
 If inter-machine communication is required, sockets must be used.
Shared Memory Systems
In the shared-memory model, a region of memory will be shared by cooperating processes.
 Processes can exchange information by reading and writing data to the shared region.
 A shared-memory region (segment) resides in the address space of the process.
 Other processes that wish to communicate using this shared-memory segment must attach
it to their address space.

 Normally, the operating system tries to prevent one process from accessing another
process’s memory.
 Shared memory requires that two or more processes agree to remove this restriction. They
can then exchange information by reading and writing data in the shared areas.
 The form of the data and the location are determined by these processes and are not under
the operating system’s control.
 The processes are also responsible for ensuring that they are not writing to the same
location simultaneously.
Producer-Consumer Problem in Cooperative process
A producer process produces information that is consumed by a consumer process.
Example: A compiler may produce assembly code that is consumed by an assembler. The
assembler may produce object modules that are consumed by the loader.
One solution to the producer–consumer problem uses shared memory.
 To allow producer and consumer processes to run concurrently, we must have available a
buffer of items that can be filled by the producer and emptied by the consumer.
 This buffer will reside in a region of memory that is shared by the producer and consumer
processes.
 A producer can produce one item while the consumer is consuming another item.
 The producer and consumer must be synchronized, so that the consumer does not try to
consume an item that has not yet been produced.
Two types of buffers can be used. Unbounded Buffer and Bounded Buffer
 Unbounded Buffer: The size of the buffer is not limited. The consumer may have to wait
for new items, but the producer can always produce new items.
 Bounded Buffer: The buffer size is limited. The consumer must wait if the buffer is
empty and the producer must wait if the buffer is full.
Code for Producer and Consumer Process in Bounded Buffer IPC
Following variables reside in shared memory region by the producer and consumer processes:
#define BUFFER_SIZE 10
typedef struct {
...... ... ...
}item;
item buffer[BUFFER_SIZE];
int in = 0;
int out = 0;
Producer process code is as follows:
while (true)
{
/* produce an item in next_produced */
while (counter == BUFFER_SIZE) ; /*do nothing Buffer
full */
buffer[in] = next_produced;
in = (in + 1) % BUFFER_SIZE;
counter++;
}
Consumer process code is as follows:
while (true)
{
while (counter == 0); /* do nothing Buffer Empty */
next_consumed = buffer[out];
out = (out + 1) % BUFFER SIZE;
counter--;
/* consume the item in next_consumed */
}
The shared buffer is implemented as a circular array with two logical pointers: in and out.
 The variable ―in‖ points to the next free position in the buffer and ―out‖ points to the first
full position in the buffer.
 An integer variable counter is initialized to 0. Counter is incremented every time we add a
new item to the buffer and is decremented every time we remove one item from the
buffer.
 The buffer is empty when counter== 0 and the buffer is full when counter== Buffer_size.
 The producer process has a local variable next_produced in which the new item to be
produced is stored.
 The consumer process has a local variable next_consumed in which the item to be
consumed is stored.
Note: Although the producer and consumer routines shown above are correct separately, they
may not function correctly when executed concurrently.
Example: Let us consider the counter =5. Producer and consumer processes concurrently
execute the statements ―counter++‖ and ―counter--‖.
Let R1 and R2 are two registers. the statement ―counter++‖ may be implemented in machine
language as follows:
T0: R1 = counter
T1: R1 = R1 + 1
T2: counter = R1
where register1 is one of the local CPU registers. Similarly, the statement ―counter--‖ is
implemented as follows:
T3: R2 = counter
T4: R2 = R2 – 1
T5: counter = R2
We execute the statements in the order T0, T1, T2, T3, T4, T5,T6 then we get the accurate
counter value=5.
Now if these statements are executed concurrently by interleaving as follows:
T0: R1 = counter {R1 = 5, counter=5}
T1: R1 = R1 + 1 {R1 = 6, counter=5}
T2: R2 = counter {R2 = 5, counter=5}
T3: R2 = R2 − 1 {R2 = 4, counter=5}
T4: counter = R1 {counter = 6, R1=6}
T5: counter = R2 {counter = 4, R2=4}
Note that we have arrived at the incorrect state ―counter == 4‖, indicating that four buffer
locations are full but actually five buffer locations are full.
If we reversed the order of the statements at T4 and T5, we would arrive at the incorrect state
―counter == 6‖.
Race condition
 Several processes access and manipulate the same data concurrently and the outcome of
the execution depends on the particular order in which the access takes place is called a
Race Condition.
 To guard against the race condition, we need to ensure that only one process at a time can
be manipulating the variable counter. To make such a guarantee, we require that the
processes be synchronized.
THE CRITICAL-SECTION PROBLEM
Consider a system consisting of n processes {P0, P1, ..., Pn−1}.
 Each process has a segment of code, called a Critical Section, in which the process may
be changing common variables, updating a table, writing a file and so on.
 When one process is executing in its critical section, no other process is allowed to
execute in its critical section.
do {
Entry section

Critical Section
Exit section

Remainder section
} while (true);
 Each process must request permission to enter its critical section. The section of code
implementing entering request is the Entry section.
 The critical section may be followed by an Exit section.
 The remaining code is the Remainder section.
 The entry section and exit section are enclosed in boxes to highlight these important
segments of code.
A solution to the critical-section problem must satisfy the following three requirements:
1. Mutual exclusion. If process Pi is executing in its critical section, then no other
processes can be executing in their critical sections.
2. Progress. If no process is executing in its critical section and some processes wish to
enter their critical sections, then only those processes that are not executing in their
remainder sections can participate in deciding which will enter its critical section next and
this selection cannot be postponed indefinitely.
3. Bounded waiting. After a process has made a request to enter its critical section and
before that request is granted, there exists a limit on the number of times that other
processes are allowed to enter their critical sections..
Two general approaches are used to handle critical sections in operating systems:
1. Preemptive Kernel allows a process to be preempted while it is running in kernel mode.
2. Non-preemptive Kernel does not allow a process running in kernel mode to be
preempted.
PETERSON’S SOLUTION
Peterson’s solution is a classic Software-Based Solution to the critical-section problem.
Peterson’s solution is restricted to two processes that alternate execution between their
critical sections and remainder sections.
The processes are numbered P0 and P1. Let Pi represents one process and Pj represents
other processes (i.e. j = i-1)
do
{
flag[i] = true;
turn = j;
while (flag[j] && turn == j);

Critical Section
flag[i] = false;

Remainder Section
} while (true);
Peterson’s solution requires the two processes to share two data items:
int turn;
boolean flag[2];
The variable turn indicates whose turn it is to enter its critical section. At any point of time
the turn value will be either 0 or 1 but not both.
 if turn == i, then process Pi is allowed to execute in its critical section.
 if turn == j, then process Pj is allowed to execute in its critical section.
 The flag array is used to indicate if a process is ready to enter its critical section.
Example: if flag[i] is true, this value indicates that Pi is ready to enter its critical section.
 To enter the critical section, process Pi first sets flag[i]=true and then sets turn=j,
thereby Pi checks if the other process wishes to enter the critical section, it can do so.
 If both processes try to enter at the same time, turn will be set to both i and j at the same
time. Only one of these assignments will be taken. The other will occur but will be
overwritten immediately.
 The eventual value of turn determines which of the two processes is allowed to enter its
critical section first.
The above code must satisfy the following requirements:
1. Mutual exclusion
2. The progress
3. The bounded-waiting
Check for Mutual Exclusion
 Each Pi enters its critical section only if either flag[j] == false or turn == i.
 If both processes can be executing in their critical sections at the same time, then flag[0]
== flag[1] == true. But the value of turn can be either 0 or 1 but cannot be both.
 Hence P0 and P1 could not have successfully executed their while statements at about the
same time.
 If Pi executed ―turn == j‖ and the process Pj executed flag[j]=true then Pj will have
successfully executed the while statement. Now Pj will enter into its Critical section.
 At this time, flag[j] == true and turn == j and this condition will persist as long as Pj is
in its critical section. As a result, mutual exclusion is preserved.
Check for Progress and Bounded-waiting
 The while loop is the only possible way that a process Pi can be prevented from entering
the critical section only if it is stuck in the while loop with the condition flag[j] == true
and turn == j.
 If Pj is not ready to enter the critical section, then flag[j] == false and Pi can enter its
critical section.
 If Pj has set flag[j] == true and is also executing in its while statement, then either turn
== i or turn == j.
 If turn == i, then Pi will enter the critical section. If turn == j, then Pj will enter the
critical section.
 Once Pj exits its critical section, it will reset flag[j] to false, allowing Pi to enter its
critical section.
 If Pj resets flag[j] to true, it must also set turn == i. Thus, since Pi does not change the
value of the variable turn while executing the while statement, Pi will enter the critical
section (Progress) after at most one entry by Pj (Bounded Waiting).
Problem with Peterson Solution
There are no guarantees that Peterson’s solution will work correctly on modern computer
architectures perform basic machine-language instructions such as load and store.
 The critical-section problem could be solved simply in a single-processor environment if
we could prevent interrupts from occurring while a shared variable was being modified.
This is an Non-preemptive kernel approach.
 We could be sure that the current sequence of instructions would be allowed to execute in
order without preemption.
 No other instructions would be run, so no unexpected modifications could be made to the
shared variable.
This solution is not as feasible in a multiprocessor environment.
 Disabling interrupts on a multiprocessor can be time consuming, since the message is
passed to all the processors.
 This message passing delays entry into each critical section and system efficiency
decreases and if the clock is kept updated by interrupts there will be an effect on a
system’s clock.
SYNCHRONIZATION HARDWARE
Modern computer systems provide special hardware instructions that allow us either to test
and modify the content of a word or to swap the contents of two words atomically (i.e.) as
one uninterruptible unit.
There are two approaches in hardware synchronization:
1. test_and_set function
2. compare_and_swap function
test_and_set function
The test_and_set instruction is executed atomically (i.e.) if two test_and_set( ) instructions
are executed simultaneously each on a different CPU, they will be executed sequentially in
some arbitrary order.
If the machine supports the test_and_set( ) instruction, then we can implement mutual
exclusion by declaring a boolean variable lock. The lock is initialized to false.
The definition of test_and_set instruction for process Pi is given as:
boolean test_and_set(boolean *target)
{
boolean rv = *target;
*target = true;
return rv;
}
Below algorithm satisfies all the requirements of Critical Section problem for the process Pi
that uses two Boolean data structures: waiting[ ], lock.
boolean waiting[i] = false;
boolean lock = false;
do
{
waiting[i] = true;
key = true;
while (waiting[i] && key)
key = test and set(&lock);
waiting[i] = false;
/* critical section */
j = (i + 1) % n;
while ((j != i) && !waiting[j])
j = (j + 1) % n;
if (j == i)
lock = false;
else
waiting[j] = false;
/* remainder section */
} while (true);
Mutual Exclusion
 Process Pi can enter its critical section only if either waiting[i] == false or key == false.
 The value of key can become false only if the test_and_set( ) is executed.
 The first process to execute the test_and_set( ) will find key == false and all other
processes must wait.
 The variable waiting[i] can become false only if another process leaves its critical
section. Only one waiting[i] is set to false, maintaining the mutual-exclusion
requirement.
Progress
 A process exiting the critical section either sets lock==false or sets waiting[j]==false.
 Both allow a process that is waiting to enter its critical section to proceed.
 This requirement ensures progress property.
Bounded Waiting
 When a process leaves its critical section, it scans the array waiting in the cyclic ordering
(i+1, i+2, ..., n−1, 0, ..., i−1).
 It designates the first process in this ordering that is in the entry section (waiting[j] ==
true) as the next one to enter the critical section.
 Any process waiting to enter its critical section will thus do so within n−1 turns.
 This requirement ensures the bounded waiting property.
compare_and_swap function
compare_and_swap( ) is also executed atomically. The compare_and_swap( ) instruction
operates on three operands. The definition code as given below:
int compare_and_swap(int *value, int expected, int new_value)
{
int temp = *value;
if (*value == expected)
*value = new_value;
return temp;
}
Mutual-exclusion implementation with the compare and swap( ) instruction:
do
{
while (compare_and_swap(&lock, 0, 1) != 0); /* do nothing */
/* critical section */
lock = 0;
/* remainder section */
} while (true);
The operand value is set to new value only if the expression (*value == exected) is true.
Regardless, compare_and_swap( ) always returns the original value of the variable value.
Mutual Exclusion with compare_and_swap( )
 A global variable lock is declared and is initialized to 0 (i.e. lock=0).
 The first process that invokes compare_and_swap( ) will set lock=1. It will then enter its
critical section, because the original value of lock was equal to the expected value of 0.
 Subsequent calls to compare_and_swap( ) will not succeed, because lock now is not equal
to the expected value of 0. (lock==1).
 When a process exits its critical section, it sets lock back to 0 (lock ==0), which allows
another process to enter its critical section.
Problem with Hardware Solution:
The hardware-based solutions to the critical-section problem are complicated and they are
inaccessible to application programmers.
MUTEX LOCKS
Mutex Locks are short for Mutual Exclusive Locks.
 Mutex locks are software solution to the critical section problem.
 Mutex lock are used to protect critical regions and thus prevent race conditions.
 In Mutex locking, a process must acquire the lock before entering a critical section and it
releases the lock when it exits the critical section.
 The acquire( )function acquires the lock and the release( ) function releases the lock.
Critical section solution through Mutex locks:
do
{
acquire( )
{
while (!available); /* busy wait */
available = false;
}

Critical Section
release( )
{
available = true;
}

remainder section
} while (true);

Explanation of Algorithm:
 A mutex lock has a boolean variable available whose value indicates if the lock is
available or not.
 If the lock is available, a call to acquire( ) succeeds and the lock is then set unavailable to
other processes.
 A process that attempts to acquire an unavailable lock is blocked until the lock is
released. Calls to either acquire( ) or release( ) must be performed atomically.
Disadvantage: Mutex Locks implementation leads to Busy Waiting
 While a process is in its critical section, any other process that tries to enter its critical
section must loop continuously in the call to acquire( ).
 This type of mutex lock is also called a Spinlock because the process ―spins‖ while
waiting for the lock to become available.
 This continual looping is clearly a problem in a real multiprogramming system, where a
single CPU is shared among many processes. Busy waiting wastes a lot of CPU cycles.
SEMAPHORES
Semaphores provides solution to the critical section problem.
A semaphore S is an integer variable that is accessed only through two standard atomic
operations: wait( ) and signal( ).
The definition of wait( ) and signal( ) are as follows:
wait(S)
{
while (S <= 0); // busy wait
S--;
}
signal(S)
{
S++;
}
All modifications to the integer value of the semaphore in the wait( ) and signal( ) operations
must be executed all at once.
 When one process modifies the semaphore value, no other process can simultaneously
modify that same semaphore value.
 In wait(S) function, the statements (S ≤ 0) and its possible modification (S--) must be
executed without interruption.
Semaphore Usage
Operating system provides two types of semaphores: Binary and Counting Semaphore.
Binary Semaphore
 The value of a binary semaphore can range only between 0 and 1.
 Binary semaphores behave similarly to mutex locks.
Counting Semaphore
 The value of a counting semaphore can range over an unrestricted domain.
 Counting semaphores can be used to control access to a given resource consisting of a
finite number of instances.
 The semaphore is initialized to the number of resources available.
 Each process that wishes to use a resource performs a wait( ) operation on the semaphore
and thereby decrementing the count value (S--).
 When a process releases a resource, it performs a signal( ) operation and incrementing the
count value (S++).
 When the count for the semaphore goes to 0 (S<=0), all resources are being used.
 After that, processes that wish to use a resource will block until the count becomes greater
than 0.
Semaphores provides solution for Synchronization problem
Consider two concurrently running processes: P1 with a statement S1 and P2 with a
statement S2. Suppose we require that S2 be executed only after S1 has completed.
We can implement this scheme by letting P1 and P2 share a common semaphore synch,
initialized to 0 (i.e. synch==0).
In process P1, we insert the statements.
S1;
signal(synch);
In process P2, we insert the statements
wait(synch);
S2 ;
Because synch is initialized to 0 (i.e. synch==0), P2 will execute S2 only after P1 has
invoked signal(synch), which is after statement S1 has been executed.
Semaphore Implementation
Definitions of the wait( ) and signal( ) semaphore operations leads to busy waiting problem.
To overcome the problem of busy waiting, the definitions of wait( ) and signal( ) must have
to modified as follows:
 When a process executes wait( ) operation and finds that the semaphore value is not
positive, it must wait. (i.e.) Rather than engaging in busy waiting, the process can block
itself.
 The block operation places a process into a waiting queue associated with the semaphore
and the state of the process is switched to the waiting state.
 Now control is transferred to CPU scheduler, which selects another process to execute.
 A process that is blocked, waiting on a semaphore S, should be restarted when some other
process executes a signal( ) operation.
 The process is restarted by a wakeup( ) operation, which changes the process from the
waiting state to the ready state. The process is then placed in the ready queue.
The semaphore definition has been changed to single integer variable to a Structure type with
two variables (i.e) Each semaphore has an integer value and list of processes.
When a process must wait on a semaphore, it is added to the list of processes. A signal( )
operation removes one process from the list of waiting processes and awakens that process.
typedef struct {
int value;
struct process *list;
} semaphore;
………….
wait(semaphore *S)
{
S->value--;
if (S->value < 0)
{
add this process to S->list;
block( );
}
}
…………..
..................
…………..
signal(semaphore *S)
{
S->value++;
if (S->value <= 0)
{
remove a process P from S->list;
wakeup(P);
}
}
 It is critical that semaphore operations wait( ) and signal( ) be executed atomically. This ensures
mutual exclusion property of Critical Section problem.
 The block( ) operation suspends the process that invokes it.
 The wakeup(P) operation resumes the execution of a blocked process P.
 The list of waiting processes can be easily implemented by a link field in each process
control block (PCB).
 Each semaphore contains an integer value and a pointer to a list of PCBs.
 A FIFO queue is used to add and remove a process from the list. It ensures the Bounded
Waiting property.
 The semaphore contains both head and tail pointers to the FIFO queue.
Note: The above algorithm does not eliminate the busy waiting problem completely instead
it limits the busy_waiting problem to very short amount of time.
Deadlocks and Starvation
A set of processes is in a deadlocked state when every process in the set is waiting for an
event that can be caused only by another process in the set. The events may be either resource
acquisition or resource release.
Note: 1. Deadlocks leads to a problem of indefinite blocking and starvation.
2. The implementation of a semaphore with a waiting queue may result in Deadlock.
Example: Consider a system consisting of two processes P0 and P1. Both are accessing two
semaphores S and Q. and S==Q==1.
P0 P1
wait(S); wait(Q);
wait(Q); wait(S);
. .
. .
. .
signal(S); signal(Q);
signal(Q); signal(S);
 Suppose that P0 executes wait(S) and then P1 executes wait(Q).
 When P0 executes wait(Q), it must wait until P1 executes signal(Q).
 Similarly, when P1 executes wait(S), it must wait until P0 executes signal(S).
 Since these signal( ) operations cannot be executed, P0 and P1 are deadlocked.
Priority Inversion
Priority inversion is a scheduling problem. It occurs only in systems with more than two
priorities.
Consider there are three processes L, M, H whose order of priorities are given as L< M < H
and all are wanted to access the resource R.
 When a higher-priority process (H) needs to read or modify kernel data resource R that
are currently being accessed by a lower-priority process (L).
 Since kernel data are protected with a lock, the higher-priority process (H) will have to
wait for a lower-priority (L) to finish with the resource.
 The situation becomes more complicated if the lower-priority process (L) is preempted in
favor of another process (M) with a higher priority.
 Now the higher priority process (H) must have to wait until the process M has to release
the lock and then L has to release the lock then only the process H can access the kernel
data resource R.
 This problem is called Priority Inversion.
One solution for priority inversion is that the system will have only two priorities.
 These systems solve the priority inversion problem by implementing a Priority
Inheritance Protocol.
 In this protocol, all processes that are accessing resources needed by a higher-priority
process inherit the higher priority until they are finished with the resources in question.
 When they are finished, their priorities revert to their original values.
Example:
 A priority-inheritance protocol would allow process L to temporarily inherit the priority
of process H, thereby preventing process M from preempting process L’s execution.
 When process L had finished using resource R, it would relinquish its inherited priority
from H and assume its original priority.
 Because resource R would now be available, process H will get the resource instead of
process M.
Note: This solution is inefficient for systems with more than one priority given for processes.
CLASSIC PROBLEMS OF SYNCHRONIZATION
There are three classic problem that are related synchronization when processes are executing
concurrently.
1. The Bounded-Buffer Problem
2. The Readers–Writers Problem
3. The Dining-Philosophers Problem
The Bounded-Buffer Problem
The major problem in Producer-consumer process is The Bounded-Buffer problem.
Let the Buffer pool consists of n locations, each location stores one item.
The Solution for Producer-Consumer Process can be given as:
int n;
semaphore empty = n; semaphore full = 0
semaphore mutex = 1;
Producer process: do {
/* produce an item in next produced */
wait(empty); /* If any one location on buffer is empty */
wait(mutex);
/* add next produced item to the buffer */
signal(mutex);
signal(full);
} while (true);
Consumer process: do {
wait(full); /* If any one location in buffer is full */
wait(mutex);
/* remove an item from buffer to next consumed */
signal(mutex);
signal(empty);
/* consume the item in next consumed */
} while (true);
Explanation of above algorithm
There is one integer variable and three semaphore variables are declared in the definition.
 Integer variable n represents the size of the buffer.
 The mutex semaphore provides mutual exclusion for accesses to the buffer pool and is
initialized to the value 1. Mutex variable allows only either producer process or consumer
process allows to access the buffer at a time.
 The empty semaphore counts the no. of free location in buffer, it is initialized to n.
 The full semaphore counts the number of full locations buffers, is initialized to 0.
 empty=n and full=0 represents that initially all locations in the buffer are empty.
Producer Process
 wait(empty): Any location in the buffer is free, then wait(empty) operation is successful
and producer process will put an item into buffer. If wait(empty) is false then the
producer process will be blocked.
 wait(mutex): It allows the producer to use the buffer and produce an item into buffer.
 signal(mutex): Buffer will be released by producer process.
 signal(full): It indicates one or more item added to the buffer if signal(full) is successful.
Consumer process
 wait(full): If wait(full) < 0 then the buffer is empty and there are no item in the buffer to
consume. Hence the consumer process will be blocked. Otherwise the buffer is having
some items the consumer process can consume an item.
 wait(mutex): It allows consumer to use buffer.
 signal(mutex): Buffer is released by consumer.
 signal(empty): Consumer consumed an item. Hence one more location in buffer is free.
The Readers–Writers Problem
The Reader-Writer problem occurs in the following situation:
 Consider there is a shared file that is shared by two processes called Reader process and
Writer process.
 The reader process will read the data from the file and the writer process will modifies the
contents of the file.
 A reader process does not modify the shared file contents. Hence if two reader processes
access the shared data simultaneously then there will be no problem of inconsistency.
 If a writer process is writing the data and any other process wants to read or write the
shared file simultaneously that may result the inconsistency of data. Hence we can’t allow
more than one writer process to access the shared file.
 If one writer process is modifying the contents of the shared file, no other reader or writer
process will read or write the data of shared file.
 Hence the writer process has exclusive access to the shared file while performing write
operation.
 This synchronization problem is referred to as the Readers–Writers problem.
Solution to Reader-Writer Problem
semaphore rw_mutex = 1;
semaphore mutex = 1;
int read_count = 0;
Code for Reading the data:
do
{
wait(mutex);
read_count++;
if (read_count == 1)
wait(rw_mutex);
signal(mutex);
...
/* reading is performed */
...
wait(mutex);
read_count--;
if (read_count == 0)
signal(rw_mutex);
signal(mutex);
} while (true);
Code for Writing data:
do
{
wait(rw_mutex);
...
/* writing is performed */
...
signal(rw_mutex);
} while (true);
 The semaphores mutex and rw_mutex are initialized to 1. read_count is initialized to 0.
 The semaphore rw_mutex is common to both reader and writer processes.
 The mutex semaphore is used to ensure mutual exclusion when the variable read_count is
updated.
 The read_count variable keeps track of how many processes are currently reading the
object.
 The semaphore rw_mutex functions as a mutual exclusion semaphore for the writers.
 rw_mutex is also used by the first reader process that enters the critical section or last
reader process that exits the critical section.
 rw_mutex is not used by readers who enter or exit while other readers are in their critical
sections.
Reader-Writer process code Explanation
 Let us consider the process P1 that is executing wait(mutex) operation that increments
the read_count to 1 (i.e. now read_count=1).
 The if condition has been satisfied by P1 and it can invoke wait(rw_mutex) and P1 will
enter into the critical section and modify (write) the contents of the shared file.
 Later P1 executes signal(rw_mutex) operation and the control pointer returns to
readering process and executes signal(mutex).
 The signal(mutex) operation allows another process P2 to enter into reader process and
executes wait(mutex) and increments read_count value by 1 (i.e. now read_count=2).
 Now P2 fails to satify the if condition because the read_count value.
 Hence P2 can only reads the file but not writes on the file because P1 is still in critical
section and reading the updated data.
 If P1 coming out of the critical section and executes wait(mutex) and decrements the
read_count value by 1. (now read_count =1) and executes signal(mutex) to allow P2.
 If P2 now executes if condition, then the condition is satisfied and P2 will enter into
writing process and writes the contents of the file.
 This is how the semaphore variable solves the synchronization problem of critical section.
The Dining-Philosophers Problem
Consider five philosophers who spend their lives thinking and eating.
 The philosophers share a circular table surrounded by five chairs, each belonging to one
philosopher.
 In the center of the table is a bowl of rice and the table is laid with five single chopsticks.
 When a philosopher thinks, she does not interact with her colleagues.
 From time to time, a philosopher gets hungry and tries to pick up the two chopsticks that
are closest to her. The chopsticks are placed between her and her left and right neighbors.
 A philosopher may pick up only one chopstick at a time. Obviously, she cannot pick up a
chopstick that is already in the hand of a neighbor.
 When a hungry philosopher has both her chopsticks at the same time, she eats without
releasing the chopsticks.
 When she is finished eating, she puts down both chopsticks and starts thinking again.
Solution with semaphores:
semaphore chopstick[5];
do {
wait(chopstick[i]);
wait(chopstick[(i+1) % 5]);
/* eat for a while */
signal(chopstick[i]);
signal(chopstick[(i+1) % 5]);
/* think for a while */
} while (true);
 Each chopstick is represented with a semaphore and all the elements of chopstick are
initialized to 1.
 A philosopher tries to grab a chopstick by executing a wait( ) operation on that
semaphore.
 A philosopher releases chopsticks by executing the signal( ) operation on the appropriate
semaphores.
Note: Although this solution guarantees that no two neighbors are eating simultaneously, it
nevertheless must be rejected because it could create a deadlock.
1. Suppose that all five philosophers become hungry at the same time and each grabs her left
chopstick.
2. All the elements of chopstick will now be equal to 0.
3. When each philosopher tries to grab her right chopstick, she will be delayed forever.
Possible remedies to the Deadlock problem:
 Allow at most four philosophers to be sitting simultaneously at the table.
 Allow a philosopher to pick up her chopsticks only if both chopsticks are available. To do
this, she must pick them up in a critical section.
 An Asymmetric solution will be used, an odd-numbered philosopher picks up first her left
chopstick and then her right chopstick, whereas an even numbered philosopher picks up
her right chopstick and then her left chopstick.
PROBLEM WITH SEMAPHORES
Semaphores provide a convenient and effective mechanism for process synchronization but
using semaphores incorrectly can result in timing errors that are difficult to detect.
These errors happen only if particular execution sequences take place and these sequences do
not always occur.
Examples: 1
Suppose that a process interchanges the order in which the wait( ) and signal( ) operations on
the semaphore mutex are executed, resulting in the following execution:
signal(mutex);
...
critical section
...
wait(mutex);
In this situation, several processes may be executing in their critical sections simultaneously,
violating the mutual-exclusion requirement. This error may be discovered only if several
processes are simultaneously active in their critical sections.
Example:2
Suppose that a process replaces signal(mutex) with wait(mutex). That is, it executes
wait(mutex);
...
critical section
...
wait(mutex);
In this case, a deadlock will occur.
Example:3
Suppose that a process omits the wait(mutex) or the signal(mutex), or both. In this case,
either mutual exclusion is violated or a deadlock will occur.
MONITORS
Monitors are developed to deal with semaphore errors. Monitors are high-level language
synchronization constructs.
 A monitor type is an ADT that includes a set of programmer defined operations that are
provided with mutual exclusion within the monitor.
 The monitor type also declares the variables whose values define the state of an instance
of that type, along with the bodies of functions that operate on those variables.
Monitor Syntax:
monitor monitor_ name
{
/* shared variable declarations */
function P1 ( . . . )
{
...
}
function P2 ( . . . )
{
...
}
function Pn ( . . . )
{
...
}
initialization code ( . . . )
{
...
}
}
The representation of a monitor type cannot be used directly by the various processes.
 A function defined within a monitor can access only those variables declared locally
within the monitor and its formal parameters.
 Similarly, the local variables of a monitor can be accessed by only the local functions.
 The monitor construct ensures that only one process at a time is active within the monitor.
 the programmer does not need to code this synchronization constraint explicitly
To solve the problem of synchronization problem along with monitors a construct called
Condition has been implemented. Condition construct defines one or more variables of type
“condition”. The syntax of condition is:
condition x,y;
The only operations that can be invoked on a condition variable are wait( ) and signal( ).
x.wait( ): Process invokes x.wait( ) is suspended until another process invokes x.signal( ).
x.signal( ): It resumes exactly one suspended process.

Dining-Philosophers Solution Using Monitors


By using monitor we can have a deadlock free solution for dining philosopher’s problem.
This solution imposes the restriction that a philosopher may pick up her chopsticks only if
both of them are available.
There are 3 states for each philosopher: Thinking, Hungry, Eating.
enum { THINKING, HUNGRY, EATING } state[5];
Philosopher i can set the variable state[i] = EATING only if her two neighbors are not eating:
(state[(i+4) % 5] != EATING) and (state[(i+1) % 5] != EATING)
We also need to declare: condition self[5];
This allows philosopher i to delay herself when she is hungry but is unable to obtain the
chopsticks she needs.
Solution through monitor:
monitor Dining_Philosophers {
enum {THINKING, HUNGRY, EATING} state[5];
condition self[5];
void pickup(int i)
{
state[i] = HUNGRY;
test(i);
if (state[i] != EATING)
self[i].wait( );
}
void putdown(int i)
{
state[i] = THINKING;
test((i + 4) % 5);
test((i + 1) % 5);
}
void test(int i)
{
if ((state[(i + 4) % 5] != EATING) && (state[i] == HUNGRY) && (state[(i + 1) % 5]
!= EATING))
{
state[i] = EATING;
self[i].signal( );
}
}
initialization_code( ) {
for (int i = 0; i < 5; i++)
state[i] = THINKING;
}
}
The distribution of the chopsticks is controlled by the monitor Dining_Philosophers.
 Each philosopher before starting to eat, must invoke the operation pickup( ). This act may
result in the suspension of the philosopher process.
 After the successful completion of the pickup( ) operation, the philosopher may eat.
 After pickup( ) operation the philosopher invokes the putdown( ) operation.
Thus, philosopher i must invoke pickup( ) & putdown( ) operations in following sequence:
DiningPhilosophers.pickup(i);
Eat
DiningPhilosophers.putdown(i);
This solution ensures that no two neighbors are eating simultaneously and that no deadlocks
will occur.
Implementing a Monitor Using Semaphores
Monitor uses three variables: semaphore mutex=1; semaphore next=0; int next_count;
 For each monitor, a semaphore mutex is provided. A process must execute wait(mutex)
before entering the monitor and must execute signal(mutex) after leaving the monitor.
 The signaling processes can use next variable to suspend themselves, because signaling
process must wait until the resumed process exit or leave.
 next count is an integer variable used to count the number of processes suspended on
next.
Each external function F is replaced by:
wait(mutex);
...
body of F
...
if (next count > 0)
signal(next);
else
signal(mutex);
The above code ensures the mutual exclusion property.
Implementing condition variables
For each condition x, we introduce a semaphore x_sem and an integer variable x_count.
semaphore x_sem=0;
int x_count=0;
The operation x.wait( ) can now be implemented as
x_count++;
if (next_count > 0)
signal(next);
else
signal(mutex);
wait(x_sem);
x_count--;
The operation x.signal( ) can be implemented as
if (x_count > 0)
{
next_count++;
signal(x_sem);
wait(next);
next_count--;
}
Resuming Processes within a Monitor
Consider a situation, if several processes are suspended on condition x, and an x.signal( )
operation is executed by some process, then how do we determine which of the suspended
processes should be resumed next?
We have two solutions: FCFS and Priority mechanism.
We use first-come, first-served (FCFS) ordering, so that the process that has been waiting the
longest is resumed first.
In priority mechanism the conditional-wait construct can be used.
x.wait(c);
where c is an integer priority numbers that is evaluated when the wait( ) operation is
executed. The c value is then stored with the name of the process that is suspended.
When x.signal( ) is executed, the process with the smallest priority number is resumed next.
Consider the below code the Resource_Allocator monitor that controls the allocation of a
single resource among competing processes.
monitor Resource_Allocator
{
boolean busy;
condition x;
void acquire(int time)
{
if (busy)
x.wait(time);
busy = true;
}
void release( )
{
busy = false;
x.signal( );
}
initialization_code( )
{
busy = false;
}
}
 Each process, when requesting an allocation of this resource, specifies the maximum time
it plans to use the resource.
 Monitor allocates the resource to the process that has the shortest time-allocation request.
The process that needs to access the resource must observe the following sequence:
R.acquire(t); /* R is an instance of type ResourceAllocator */
access the resource;
R.release( );
Problems with monitor
1. A process might access a resource without first gaining access permission to the resource.
2. A process might never release a resource once it has been granted access to the resource.
3. A process might attempt to release a resource that it never requested.
4. A process might request the same resource twice (without first releasing the resource).

You might also like