0% found this document useful (0 votes)
20 views15 pages

Os Unit 3 Part 1

The document discusses process management and synchronization, focusing on race conditions and the critical section problem, which requires mutual exclusion, progress, and bounded waiting. It outlines solutions like disabling interrupts, special hardware instructions, and semaphores to manage access to critical sections. Additionally, it presents classic synchronization problems such as the bounded-buffer problem, readers-writers problem, and the dining-philosophers problem, along with their respective solutions.

Uploaded by

rakeshreddyt25
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)
20 views15 pages

Os Unit 3 Part 1

The document discusses process management and synchronization, focusing on race conditions and the critical section problem, which requires mutual exclusion, progress, and bounded waiting. It outlines solutions like disabling interrupts, special hardware instructions, and semaphores to manage access to critical sections. Additionally, it presents classic synchronization problems such as the bounded-buffer problem, readers-writers problem, and the dining-philosophers problem, along with their respective solutions.

Uploaded by

rakeshreddyt25
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/ 15

PROCESS MANAGEMENT AND SYNCHRONIZATION

Race Condition
Definition: A situation where 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 in some way.

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 or region, in which the process may be changing
common variables, updating a table, writing a file, and so on.
The important feature of the system is that, when one process is executing in its
critical section, no other process is allowed to execute in its critical section. That is, no two
processes are executing in their critical sections at the same time.
The critical-section problem is to design a protocol that the processes can use to
cooperate. Each process must request permission to enter its critical section. The section of
code implementing this request is the entry section. The critical section may be followed by
an exit section. The remaining code is the remainder section.
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. There exists a bound, or limit, on the number of times that other
processes are allowed to enter their critical sections after a process has made a request to
enter its critical section and before that request is granted.
General structure of process Pi

Synchronization Hardware
Software-based solutions such as Peterson’s does not guaranteed to work on modern
computer architectures. The following are several more solutions to the critical-section

10
problem using techniques ranging from hardware to software-based on the premise of
locking —that is, protecting critical regions through the use of locks.

Single Processor Systems: Disabling of Interrupts


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. In this way, 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 is often the approach taken by nonpreemptive kernels.

Disable Interrupts
Critical section
Enable Interrupts
Remainder section

Multi Processor Systems: Special hardware instructions


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.
Many modern computer systems therefore provide special hardware instructions like
“test _and _set” and “Compare _and_Swap” that allow us either to test and modify the
content of a word or to swap the contents of two words atomically—that is, as one
uninterruptible unit.
1. Test _and _Set
 The important characteristic of this instruction is that it is executed atomically.
 Thus, 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, initialized to false.
Definition of the test_and _set() instruction:
boolean test_and_set (boolean *target)
{
boolean rv = *target;
*target = TRUE;
return rv:
}
Mutual-exclusion implementation with test _and _set ()
do
{
while (test_and_set (&lock))
; /* do nothing */
/* critical section */
lock = false;
/* remainder section */
11
} while (true);

2. Compare _and_Swap
 The compare and swap () instruction, in contrast to the test and set () instruction,
operates on three operands.
 The operand value is set to new value only if the expression (*value == expected) is
true. Regardless, compare and swap () always returns the original value of the
variable value.
Definition of compare _and_swap
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 compare _and_swap
do
{
while (compare_and_swap(&lock, 0, 1) != 0)
; /* do nothing */
/* critical section */
lock = 0;
/* remainder section */
} while (true);

Although these algorithms satisfy the mutual-exclusion requirement, they do not


satisfy the bounded-waiting requirement. The following is another algorithm using the test
and set () instruction that satisfies all the critical-section requirements.
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;

12
/* remainder section */
} while (true);

Semaphores
Hardware based solutions to critical section problems are complicated, so we use a
more robust software tool called Semaphore.
Definition: A semaphore S is an integer variable that, apart from initialization, is accessed
only through two standard atomic operations: wait () and signal ().
Definition of the wait () operation

Wait(S)
{
while (S <= 0)
; // busy wait
S--;
}
Definition of the signal () operation
Signal(S)
{
S++;
}

1. Semaphore Usage
Operating systems often distinguish between counting and binary semaphores.
a. 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 (thereby decrementing the count).
 When a process releases a resource, it performs a signal () operation
(incrementing the count). When the count for the semaphore goes to 0, all
resources are being used. After that, processes that wish to use a resource will
block until the count becomes greater than 0.
b. Binary Semaphore
 The value of a binary semaphore can range only between 0 and 1.
 Example: 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 readily by letting P1 and P2 share
a common semaphore synch, initialized to 0.
In process P1, we insert the statements
S1;
signal (synch);
In process P2, we insert the statements
13
wait (synch);
S2;
Because synch is initialized to 0, P2 will execute S2 only after P1 has invoked signal
(synch), which is after statement S1 has been executed.

2. Semaphore Implementation
The previous definition of wait () and signal () has a problem of busy waiting which
wastes CPU cycles. To overcome this, we will modify the definition of wait () and signal ().
Wait ()
 When a process executes the wait () operation and finds that the semaphore value is not
positive, it must wait.
 However, 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.
 Then control is transferred to the CPU scheduler, which selects another process to
execute.

Signal ()
 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.
Defining a semaphore
typedef struct
{
int value;
struct process *list;
} semaphore;

wait () semaphore operation can be defined as,


wait (semaphore *S)
{
S->value--;
if(S->value<0)
{
add this process to S->list;
block ();
}
}

signal () semaphore operation can be defined as,


signal (semaphore *S)
{
S->value++;
14
if(S->value<=0)
{
remove a process P from S->list;
wakeup (P);
}
}

3. Deadlocks and Starvation


The implementation of a semaphore with a waiting queue may result in a situation
where two or more processes are waiting indefinitely for an event that can be caused only by
one of the waiting processes. The event in question is the execution of a signal () operation.
When such a state is reached, these processes are said to be deadlocked.
Example: consider a system consisting of two processes, P0 and P1, each accessing two
semaphores, S and Q, set to the value 1:

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.
Another problem related to deadlocks is indefinite blocking or starvation, a situation
in which processes wait indefinitely within the semaphore. Indefinite blocking may occur if
we remove processes from the list associated with a semaphore in LIFO (last-in, first-out)
order.

Classic Problems of Synchronization


1. The Bounded-Buffer Problem
The producer and consumer processes share the following data structures:
int n;
semaphore mutex = 1;
semaphore empty = n;
semaphore full = 0
We assume that the pool consists of n buffers, each capable of holding one item. The
mutex semaphore provides mutual exclusion for accesses to the buffer pool and is initialized
to the value 1. The empty and full semaphores count the number of empty and full buffers.
The semaphore empty is initialized to the value n; the semaphore full is initialized to the
value 0.
The structure of the producer process
do
{
...
/* produce an item in next_produced */
15
...
wait (empty);
wait (mutex);
...
/* add next produced to the buffer */
...
signal (mutex);
signal (full);
} while (true);

The structure of the consumer process


do
{
wait (full);
wait (mutex);
...
/* remove an item from buffer to next_consumed */
...
signal (mutex);
signal (empty);
...
/* consume the item in next consumed */
...
} while (true);

2. The Readers–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. Obviously,
if two readers access the shared data simultaneously, no adverse effects will result.
However, if a writer and some other process (either a reader or a writer) access the
database simultaneously, problems may ensue.
To ensure that these difficulties do not arise, we require that the writers have
exclusive access to the shared database while writing to the database. This
synchronization problem is referred to as the readers–writers problem.

Variations in readers–writers problem


The readers–writers problem has several variations, all involving priorities.
 The simplest one, referred to as the first readers–writers problem, requires that no reader
be kept waiting unless a writer has already obtained permission to use the shared object.
In other words, no reader should wait for other readers to finish simply because a writer
is waiting. In this writers may starve.

16
 The second readers –writer’s problem requires that, once a writer is ready, that writer
perform its write as soon as possible. In other words, if a writer is waiting to access the
object, no new readers may start reading. In this readers may starve.

Solution to the first readers–writers problem

Data structures:
semaphore rw mutex = 1;
semaphore mutex = 1;
int read count = 0;
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. It is also
used by the first or last reader that enters or exits the critical section. It is not used by readers
who enter or exit while other readers are in their critical sections.
The structure of a writer process
do
{
wait (rw_mutex);
...
/* writing is performed */
...
signal(rw_mutex);
} while (true);

The structure of a reader process


do
{
wait(mutex);
read_count++;
if (read_count == 1)
wait(rw_mutex);
signal(mutex);
...
/* reading is performed */
...
wait(mutex);
readcount--;
if (read_count == 0)
signal(rw_mutex);
signal(mutex);
} while (true);

17
Reader–Writer Locks
The readers–writers problem and its solutions have been generalized to provide
reader–writer locks on some systems. Acquiring a reader–writer lock requires specifying the
mode of the lock: either read or write access. When a process wishes only to read shared
data, it requests the reader–writer lock in read mode. A process wishing to modify the shared
data must request the lock in write mode. Multiple processes are permitted to concurrently
acquire a reader–writer lock in read mode, but only one process may acquire the lock for
writing, as exclusive access is required for writers.

Reader–writer locks are most useful in the following situations:


 In applications where it is easy to identify which processes only read shared data and
which processes only write shared data.
 In applications that have more readers than writers.

3. 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 centre 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 that are between her and her left and right neighbours). 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 neighbour. 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
One simple solution is to represent each chopstick with a semaphore. A philosopher
tries to grab a chopstick by executing a wait () operation on that semaphore. She releases her
chopsticks by executing the signal () operation on the appropriate semaphores. Thus, the
shared data are
semaphore chopstick [5];
where all the elements of chopstick are initialized to 1.
The structure of Philosopher i:
do
{
wait (chopstick[i]);
wait (chopStick [(i + 1) % 5]);

18
// eat for a while
signal (chopstick[i] );
signal (chopstick[ (i + 1) % 5] );

// think for a while


} while (TRUE);

Although this solution guarantees that no two neighbours are eating simultaneously, it
nevertheless must be rejected because it could create a deadlock. Suppose that all five
philosophers become hungry at the same time and each grabs her left chopstick. All the
elements of chopstick will now be equal to 0. When each philosopher tries to grab her right
chopstick, she will be delayed forever.
Several possible remedies to the deadlock problem are replaced by:
 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).
 Use an asymmetric solution—that is, 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.

Monitors
Although semaphores provide a convenient and effective mechanism for process
synchronization, using them incorrectly can result in errors such as following,
 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.
 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.
 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.

19
To deal with such errors, researchers have developed one fundamental high-level
synchronization construct—the monitor type.

1. Monitor Usage
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 monitorname
{ /* shared variable declarations */
function P1 ( . . . ) { . . .
}
function P2 ( . . . ) { . . .
}
.
.
.
function Pn ( . . . ) { . . .
}
initialization code ( . . . ) { . . .
}
}
The monitor construct ensures that only one process at a time is active within the
monitor. Consequently, the programmer does not need to code this synchronization constraint
explicitly.

Condition Variables: The monitor construct is not sufficiently powerful for modelling some
synchronization schemes. For this purpose, we need to define additional synchronization
mechanisms. These mechanisms are provided by the condition construct,
condition x, y;
The only operations that can be invoked on a condition variable are wait () and signal
(). The operation
x.wait ();
means that the process invoking this operation is suspended until another process invokes
x.signal ();
20
The x.signal () operation resumes exactly one suspended process. If no process is suspended,
then the signal () operation has no effect.

2. Dining-Philosophers Solution Using Monitors


Monitor concepts presents a deadlock-free solution to the dining-philosophers
problem. This solution imposes the restriction that a philosopher may pick up her chopsticks
only if both of them are available.
The following are the data structure:
enum {THINKING, HUNGRY, EATING} state [5];
Philosopher i can set the variable state[i] = EATING only if her two neighbours 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.
monitor DiningPhilosophers
{
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;
}

21
}

3. Implementing a Monitor Using Semaphores


Variables
The following are the variables,
semaphore mutex=1;
For each monitor, a semaphore mutex (initialized to 1) is provided. A process must
execute wait (mutex) before entering the monitor and must execute signal (mutex) after
leaving the monitor.
semaphore next=0;
Since a signalling process must wait until the resumed process either leaves or waits,
an additional semaphore, next, is introduced, The signalling processes can use next to
suspend themselves
int next_count = 0;
An integer variable next count is also provided to count the number of processes
suspended on next.

Procedure
Each procedure F will be replaced by,
wait (mutex);
...
body of F
...
if (next _count > 0)
signal (next);
else
signal (mutex);
Mutual exclusion within a monitor is ensured.

Condition Variables
For each condition x, we introduce a semaphore x _sem and an integer variable x _count,
both initialized to 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)
{

22
next_count++;
signal (x_sem);
wait (next);
next_count--;
}

4. Resuming Processes within a Monitor


If several processes are suspended on condition x, and an x.signal () operation is
executed by some process, then we have to determine which of the suspended processes
should be resumed next.
First-Come, First-Served (FCFS)
One simple solution is to use a first-come, first-served (FCFS) ordering, so that the
process that has been waiting the longest is resumed first. This is a simple scheduling scheme
but not adequate.
Conditional-Wait
This construct has the form
x.wait(c);
where c is an integer expression that is evaluated when the wait () operation is
executed. The value of c, which is called a priority number, 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.
ResourceAllocator monitor
It controls the allocation of a single resource among competing processes. Each
process, when requesting an allocation of this resource, specifies the maximum time it plans
to use the resource. The monitor allocates the resource to the process that has the shortest
time-allocation request. A process that needs to access the resource in question must observe
the following sequence:
R.acquire(t);
...
access the resource;
...
R.release();
where R is an instance of type ResourceAllocator.
Monitor to allocate a single resource
monitor ResourceAllocator
{
boolean busy;
condition x;
void acquire(int time)
{
if (busy)
x.wait(time);
busy = true;
}

23
void release()
{
busy = false;
x.signal();
}
initialization code()
{
busy = false;
}
}
Problems
The following problems can occur:
 A process might access a resource without first gaining access permission to the
resource.
 A process might never release a resource once it has been granted access to the
resource.
 A process might attempt to release a resource that it never requested.
 A process might request the same resource twice (without first releasing the resource).
One possible solution is to include the resource access operations within the
ResourceAllocator monitor.

24

You might also like