SlideShare a Scribd company logo
Chapter 5 
Concurrency: 
Mutual Exclusion 
and 
Synchronization 
Operating 
Systems: 
Internals 
and 
Design 
Principles 
Eighth Edition 
By William Stallings
Operating System design is 
concerned with the management of 
processes and threads: 
Multiprogramming 
Multiprocessing 
Distributed Processing
Multiple 
Applications 
invented to allow 
processing time to 
be shared among 
active applications 
Structured 
Applications 
extension of 
modular design 
and structured 
programming 
Operating 
System 
Structure 
OS themselves 
implemented as 
a set of 
processes or 
threads
Table 5.1 
Some Key 
Terms 
Related 
to 
Concurrency
 Interleaving and overlapping 
 can be viewed as examples of concurrent 
processing 
 both present the same problems 
 Uniprocessor – the relative speed of execution 
of processes cannot be predicted 
 depends on activities of other processes 
 the way the OS handles interrupts 
 scheduling policies of the OS
 Sharing of global resources 
 Difficult for the OS to manage the 
allocation of resources optimally 
 Difficult to locate programming errors as 
results are not deterministic and 
reproducible
 Occurs when multiple processes or 
threads read and write data items 
 The final result depends on the order 
of execution 
 the “loser” of the race is the process 
that updates last and will determine 
the final value of the variable
Operating System 
Concerns 
 Design and management issues raised by the 
existence of concurrency: 
 The OS must: 
be able to keep track of various processes 
allocate and de-allocate resources for each active process 
protect the data and physical resources of each process 
against interference by other processes 
ensure that the processes and outputs are independent of the 
processing speed
Degree of Awareness Relationship Influence that One 
Process Has on the 
Other 
Potential Control 
Problems 
Processes unaware of 
each other 
Competition •Results of one 
process independent 
of the action of 
others 
•Timing of process 
may be affected 
•Mutual exclusion 
•Deadlock (renewable 
resource) 
•Starvation 
Processes indirectly 
aware of each other 
(e.g., shared object) 
Cooperation by 
sharing 
•Results of one 
process may depend 
on information 
obtained from others 
•Timing of process 
may be affected 
•Mutual exclusion 
•Deadlock (renewable 
resource) 
•Starvation 
•Data coherence 
Processes directly 
aware of each other 
(have communication 
primitives available to 
them) 
Cooperation by 
communication 
•Results of one 
process may depend 
on information 
obtained from others 
•Timing of process 
may be affected 
•Deadlock 
(consumable 
resource) 
•Starvation 
Table 5.2 
Process 
Interaction
Resource Competition 
 
Concurrent processes come into conflict when 
they are competing for use of the same resource 
 
for example: I/O devices, memory, processor time, clock 
In the case of competing processes three 
control problems must be faced: 
• the need for mutual exclusion 
• deadlock 
• starvation
PROCESS 1 */ 
void P1 
{ 
while (true) { 
/* preceding code */; 
entercritical (Ra); 
/* critical section */; 
exitcritical (Ra); 
/* following code */; 
} 
} 
/* PROCESS 2 */ 
void P2 
{ 
while (true) { 
/* preceding code */; 
entercritical (Ra); 
/* critical section */; 
exitcritical (Ra); 
/* following code */; 
} 
} 
• • • 
/* PROCESS n */ 
void Pn 
{ 
while (true) { 
/* preceding code */; 
entercritical (Ra); 
/* critical section */; 
exitcritical (Ra); 
/* following code */; 
} 
} 
Figure 5.1 
Illustration of Mutual Exclusion
 Must be enforced 
 A process that halts must do so without 
interfering with other processes 
 No deadlock or starvation 
 A process must not be denied access to a critical 
section when there is no other process using it 
 No assumptions are made about relative process 
speeds or number of processes 
 A process remains inside its critical section for a finite 
time only
 Interrupt Disabling 
 uniprocessor system 
 disabling interrupts 
guarantees mutual 
exclusion 
 Disadvantages: 
 the efficiency of 
execution could be 
noticeably degraded 
 this approach will not 
work in a 
multiprocessor 
architecture
Compare&Swap Instruction 
 also called a “compare and exchange 
instruction” 
 a compare is made between a memory 
value and a test value 
 if the values are the same a swap occurs 
 carried out atomically
Figure 5.2 
Hardware Support for Mutual Exclusion 
/* program mutualexclusion */ 
const int n = /* number of processes */; 
int bolt; 
void P(int i) 
{ 
while (true) { 
while (compare_and_swap(&bolt, 0, 1) == 1) 
/* do nothing */; 
/* critical section */; 
bolt = 0; 
/* remainder */; 
} 
} 
void main() 
{ 
bolt = 0; 
parbegin (P(1), P(2), . . . ,P(n)); 
} 
/* program mutualexclusion */ 
int const n = /* number of processes*/; 
int bolt; 
void P(int i) 
{ 
while (true) { 
int keyi = 1; 
do exchange (&keyi, &bolt) 
while (keyi != 0); 
/* critical section */; 
bolt = 0; 
/* remainder */; 
} 
} 
void main() 
{ 
bolt = 0; 
parbegin (P(1), P(2), . . ., P(n)); 
} 
(a) Compare and swap instruction (b) Exchange instruction
 Applicable to any number of processes on 
either a single processor or multiple 
processors sharing main memory 
 Simple and easy to verify 
 It can be used to support multiple critical 
sections; each critical section can be 
defined by its own variable
Special Machine Instruction: 
Disadvantages 
 Busy-waiting is employed, thus while a 
process is waiting for access to a 
critical section it continues to consume 
processor time 
 Starvation is possible when a process 
leaves a critical section and more than 
one process is waiting 
 Deadlock is possible
Table 5.3 
Common 
Concurrenc 
y 
Mechanisms 
Semaphore An integer value used for signaling among processes. Only three 
operations may be performed on a semaphore, all of which are 
atomic: initialize, decrement, and increment. The decrement 
operation may result in the blocking of a process, and the increment 
operation may result in the unblocking of a process. Also known as a 
counting semaphore or a general semaphore 
Binary Semaphore A semaphore that takes on only the values 0 and 1. 
Mutex Similar to a binary semaphore. A key difference between the two is 
that the process that locks the mutex (sets the value to zero) must be 
the one to unlock it (sets the value to 1). 
Condition Variable A data type that is used to block a process or thread until a particular 
condition is true. 
Monitor A programming language construct that encapsulates variables, 
access procedures and initialization code within an abstract data type. 
The monitor's variable may only be accessed via its access 
procedures and only one process may be actively accessing the 
monitor at any one time. The access procedures are critical sections. 
A monitor may have a queue of processes that are waiting to access 
it. 
Event Flags A memory word used as a synchronization mechanism. Application 
code may associate a different event with each bit in a flag. A thread 
can wait for either a single event or a combination of events by 
checking one or multiple bits in the corresponding flag. The thread is 
blocked until all of the required bits are set (AND) or until at least 
one of the bits is set (OR). 
Mailboxes/Messages A means for two processes to exchange information and that may be 
used for synchronization. 
Spinlocks Mutual exclusion mechanism in which a process executes in an 
infinite loop waiting for the value of a lock variable to indicate 
availability.
Semaphore 
• There is no way to inspect or 
manipulate semaphores other 
than these three operations 
A variable that has 
an integer value 
upon which only 
three operations 
are defined: 
1) May be initialized to a nonnegative integer value 
2) The semWait operation decrements the value 
3) The semSignal operation increments the value
Consequences 
There is no way to 
know before a 
process decrements 
a semaphore 
whether it will block 
or not 
There is no way to 
know which process 
will continue 
immediately on a 
uniprocessor 
system when two 
processes are 
running concurrently 
You don’t know 
whether another 
process is waiting 
so the number of 
unblocked 
processes may be 
zero or one
Figure 
5.3 
A 
Definition 
of 
Semaphore 
Primitives
struct binary_semaphore { 
enum {zero, one} value; 
queueType queue; 
}; 
void semWaitB(binary_semaphore s) 
{ 
if (s.value == one) 
s.value = zero; 
else { 
/* place this process in s.queue */; 
/* block this process */; 
} 
} 
void semSignalB(semaphore s) 
{ 
if (s.queue is empty()) 
s.value = one; 
else { 
/* remove a process P from s.queue */; 
/* place process P on ready list */; 
} 
} 
Figure 5.4 
A Definition of Binary Semaphore Primitives
A queue is used to hold processes waiting on the semaphore 
Strong Semaphores 
• the process that has been blocked the longest is 
released from the queue first (FIFO) 
Weak Semaphores 
• the order in which processes are removed from 
the queue is not specified
A 
A issues semWait, later times out 
1 
B issues semWait 
D issues semSignal 
s = 1 
C D B 
Ready queue Processor 
2 
Blocked queue 
B 
D issues semSignal, later times out 
D 
B A C 
4 
C A B 
A B 
Figure 5.5 Example of Semaphore Mechanism 
s = 0 
Ready queue Processor 
Blocked queue 
B 
s = 0 
Ready queue 
Processor 
A C D 
Blocked queue 
5 
C 
C issues semWait 
s = 0 
Ready queue 
Processor 
D B A 
Blocked queue 
3 
D 
s = –1 
Ready queue 
Processor 
A C 
Blocked queue 
D issues semSignal 
7 
D 
s = –2 
Ready queue 
Processor 
C 
Blocked queue 
D issues semSignal 
6 
D 
s = –3 
Ready queue 
Processor 
Blocked queue
Figure 5.6 
Mutual Exclusion Using Semaphores
B –1 
C B 
C 
1 
A 
semWait(lock) 
Value of 
semaphore lock 
Queue for 
semaphore lock B C 
0 
–2 
–1 
0 
1 
semWait(lock) 
semSignal(lock) 
semWait(lock) 
semSignal(lock) 
semSignal(lock) 
Figure 5.7 Processes Accessing Shared Data 
Protected by a Semaphore 
Critical 
region 
Normal 
execution 
Blocked on 
semaphore 
lock 
Note that normal 
execution can 
proceed in parallel 
but that critical 
regions are serialized.
Producer/Consumer 
Problem 
General 
Statement: 
one or more producers are generating data and 
placing these in a buffer 
a single consumer is taking items out of the buffer 
one at a time 
only one producer or consumer may access the 
buffer at any one time 
The 
Problem: ensure that the producer can’t 
add data into full buffer and 
consumer can’t remove data from 
an empty buffer
0 1 2 3 4 
b[1] b[2] 
out 
b[3] b[4] b[5] 
in 
Note: shaded area indicates portion of buffer that is occupied 
Figure 5.8 Infinite Buffer for the Producer/Consumer Problem
/* program producerconsumer */ 
int n; 
binary_semaphore s = 1, delay = 0; 
void producer() 
{ 
while (true) { 
produce(); 
semWaitB(s); 
append(); 
n++; 
if (n==1) semSignalB(delay); 
semSignalB(s); 
} 
} 
void consumer() 
{ 
semWaitB(delay); 
while (true) { 
semWaitB(s); 
take(); 
n--; 
semSignalB(s); 
consume(); 
if (n==0) semWaitB(delay); 
} 
} 
void main() 
{ 
n = 0; 
parbegin (producer, consumer); 
} 
Figure 5.9 
An Incorrect 
Solution 
to the 
Infinite-Buffer 
Producer/Consu 
mer 
Problem Using 
Binary 
Semaphores
Table 5.4 
Possible Scenario for the Program of Figure 5.9 
Producer Consumer s n Delay 
1 1 0 0 
2 semWaitB(s) 0 0 0 
3 n++ 0 1 0 
4 if (n==1) 
(semSignalB(delay)) 
0 1 1 
5 semSignalB(s) 1 1 1 
6 semWaitB(delay) 1 1 0 
7 semWaitB(s) 0 1 0 
8 n-- 0 0 0 
9 semSignalB(s) 1 0 0 
10 semWaitB(s) 0 0 0 
11 n++ 0 1 0 
12 if (n==1) 
(semSignalB(delay)) 
0 1 1 
13 semSignalB(s) 1 1 1 
14 if (n==0) (semWaitB(delay)) 1 1 1 
15 semWaitB(s) 0 1 1 
16 n-- 0 0 1 
17 semSignalB(s) 1 0 1 
18 if (n==0) (semWaitB(delay)) 1 0 0 
19 semWaitB(s) 0 0 0 
20 n-- 0 –1 0 
21 semSignalB(s) 1 –1 0 
Note: White 
areas 
represent the 
critical 
section 
controlled by 
semaphore 
s.
Figure 5.10 
A Correct 
Solution to the 
Infinite-Buffer 
Producer/Con 
sumer 
Problem 
Using Binary 
Semaphores
/* program producerconsumer */ 
semaphore n = 0, s = 1; 
void producer() 
{ 
while (true) { 
produce(); 
semWait(s); 
append(); 
semSignal(s); 
semSignal(n); 
} 
} 
void consumer() 
{ 
while (true) { 
semWait(n); 
semWait(s); 
take(); 
semSignal(s); 
consume(); 
} 
} 
void main() 
{ 
parbegin (producer, consumer); 
} 
Figure 
5.11 
A Solution 
to the 
Infinite- 
Buffer 
Producer/C 
onsumer 
Problem 
Using 
Semaphore 
s
b[1] b[2] 
out 
b[3] b[4] b[5] b[n] 
(a) 
in 
b[1] b[2] 
b[5] b[n] 
out 
b[3] b[4] 
(b) 
in 
Figure 5.12 Finite Circular Buffer for the Producer/Consumer Problem
Figure 5.13 
A Solution to 
the Bounded- 
Buffer 
Producer/Con 
sumer 
Problem 
Using 
Semaphores
Implementation of 
Semaphores 
 Imperative that the semWait and 
semSignal operations be implemented as 
atomic primitives 
 Can be implemented in hardware or 
firmware 
 Software schemes such as Dekker’s or 
Peterson’s algorithms can be used 
 Use one of the hardware-supported 
schemes for mutual exclusion
Two Possible Implementations of Semaphores 
semWait(s) 
{ 
Figure 5.14 
while (compare_and_swap(s.flag, 0 , 1) == 1) 
/* do nothing */; 
s.count--; 
if (s.count < 0) { 
/* place this process in s.queue*/; 
/* block this process (must also set s.flag to 0) 
*/; 
} 
s.flag = 0; 
} 
semSignal(s) 
{ 
while (compare_and_swap(s.flag, 0 , 1) == 1) 
/* do nothing */; 
s.count++; 
if (s.count <= 0) { 
/* remove a process P from s.queue */; 
/* place process P on ready list */; 
} 
s.flag = 0; 
} 
semWait(s) 
{ 
inhibit interrupts; 
s.count--; 
if (s.count < 0) { 
/* place this process in s.queue */; 
/* block this process and allow interrupts */; 
} 
else 
allow interrupts; 
} 
semSignal(s) 
{ 
inhibit interrupts; 
s.count++; 
if (s.count <= 0) { 
/* remove a process P from s.queue */; 
/* place process P on ready list */; 
} 
allow interrupts; 
} 
(a) Compare and Swap Instruction (b) Interrupts
Monitors 
 Programming language construct that provides 
equivalent functionality to that of semaphores and 
is easier to control 
 Implemented in a number of programming 
languages 
 including Concurrent Pascal, Pascal-Plus, Modula-2, 
Modula-3, and Java 
 Has also been implemented as a program library 
 Software module consisting of one or more 
procedures, an initialization sequence, and local 
data
Monitor Characteristics 
Local data variables are accessible only by the monitor’s 
procedures and not by any external procedure 
Process enters monitor by invoking one of its procedures 
Only one process may be executing in the monitor at a 
time
Synchronization 
 Achieved by the use of condition variables that 
are contained within the monitor and accessible 
only within the monitor 
 Condition variables are operated on by two 
functions: 
 cwait(c): suspend execution of the calling process 
on condition c 
 csignal(c): resume execution of some process 
blocked after a cwait on the same condition
Entrance 
queue of 
entering 
processes 
Exit 
monitor waiting area 
condition c1 
cwait(c1) 
condition cn 
cwait(cn) 
urgent queue 
csignal 
local data 
MONITOR 
condition variables 
Procedure 1 
Procedure k 
initialization code 
Figure 5.15 Structure of a Monitor
Figure 5.16 
A Solution to 
the Bounded- 
Buffer 
Producer/Cons 
umer Problem 
Using a Monitor
Concurrency: Mutual Exclusion and Synchronization
 When processes interact with one another two 
fundamental requirements must be satisfied: 
synchronization 
• to enforce 
mutual exclusion 
 Message Passing is one approach to providing 
both of these functions 
 works with distributed systems and shared memory multiprocessor and 
uniprocessor systems 
communication 
• to exchange 
information
Message Passing 
 The actual function is normally provided in the form 
of a pair of primitives: 
send (destination, message) 
receive (source, message) 
 A process sends information in the form of a 
message to another process designated by a 
destination 
 A process receives information by executing the 
receive primitive, indicating the source and the 
message
Synchronization 
Send 
blocking 
nonblocking 
Receive 
blocking 
nonblocking 
test for arrival 
Addressing 
Direct 
send 
receive 
explicit 
implicit 
Indirect 
static 
dynamic 
ownership 
Format 
Content 
Length 
fixed 
variable 
Queueing Discipline 
FIFO 
Priority 
Table 5.5 
Design Characteristics of Message Systems for 
Interprocess Communication and Synchronization
Concurrency: Mutual Exclusion and Synchronization
 Both sender and receiver are blocked until 
the message is delivered 
Sometimes referred to as a rendezvous 
 Allows for tight synchronization between 
processes
Nonblocking Send 
Nonblocking send, blocking receive 
• sender continues on but receiver is blocked until the 
requested message arrives 
• most useful combination 
• sends one or more messages to a variety of 
destinations as quickly as possible 
• example -- a service process that exists to provide a 
service or resource to other processes 
Nonblocking send, nonblocking receive 
• neither party is required to wait
 Schemes for specifying processes in 
send and receive primitives fall into 
two categories: 
Direct 
addressing 
Indirect 
addressing
Direct Addressing 
 Send primitive includes a specific 
identifier of the destination process 
 Receive primitive can be handled in one 
of two ways: 
 require that the process explicitly 
designate a sending process 
 effective for cooperating concurrent processes 
implicit addressing 
 source parameter of the receive primitive possesses 
a value returned when the receive operation has 
been performed
Indirect Addressing 
Messages are sent to a 
shared data structure 
consisting of queues that 
can temporarily hold 
messages 
Queues are 
referred to as 
mailboxes 
One process sends a 
message to the mailbox 
and the other process 
picks up the message 
from the mailbox 
Allows for 
greater flexibility 
in the use of 
messages
S1 
Sn 
R1 
Rm 
(b) Many to one 
Mailbox 
S1 
Sn 
Port R1 
S1 Mailbox R1 
Figure 5.18 Indirect Process Communication 
S1 
(a) One to one 
(d) Many to many 
R1 
Rm 
Mailbox 
(c) One to many
Message Type 
Destination ID 
Header Source ID 
Body 
Message Length 
Control Information 
Message Contents 
Figure 5.19 General Message Format
Figure 5.20 
Mutual Exclusion Using Messages
Figure 5.21 
A Solution to the 
Bounded-Buffer 
Producer/Consu 
mer Problem 
Using Messages
Readers/Writers 
Problem 
 A data area is shared among many processes 
 some processes only read the data area, 
(readers) and some only write to the data area 
(writers) 
 Conditions that must be satisfied: 
1. any number of readers may simultaneously 
read the file 
2. only one writer at a time may write to the 
file 
3. if a writer is writing to the file, no reader 
may read it
Figure 5.22 
A Solution to 
the 
Readers/Write 
rs Problem 
Using 
Semaphores: 
Readers Have 
Priority
Readers only in the system 
•wsem set 
•no queues 
Writers only in the system 
•wsem and rsem set 
•writers queue on wsem 
Both readers and writers with read first 
•wsem set by reader 
•rsem set by writer 
•all writers queue on wsem 
•one reader queues on rsem 
•other readers queue on z 
Both readers and writers with write first 
•wsem set by writer 
•rsem set by writer 
•writers queue on wsem 
•one reader queues on rsem 
•other readers queue on z 
Table 5.6 
State of the Process Queues for Program of Figure 5.23
Figure 5.23 
A Solution to the 
Readers/Writers 
Problem Using 
Semaphores: 
Writers Have 
Priority
void reader(int i) 
{ 
message rmsg; 
while (true) { 
rmsg = i; 
send (readrequest, rmsg); 
receive (mbox[i], rmsg); 
READUNIT (); 
rmsg = i; 
send (finished, rmsg); 
} 
} 
void writer(int j) 
{ 
message rmsg; 
while(true) { 
rmsg = j; 
send (writerequest, rmsg); 
receive (mbox[j], rmsg); 
WRITEUNIT (); 
rmsg = j; 
send (finished, rmsg); 
} 
} 
void controller() 
{ 
while (true) 
{ 
if (count > 0) { 
if (!empty (finished)) { 
receive (finished, msg); 
count++; 
} 
else if (!empty (writerequest)) { 
receive (writerequest, msg); 
writer_id = msg.id; 
count = count – 100; 
} 
else if (!empty (readrequest)) { 
receive (readrequest, msg); 
count--; 
send (msg.id, "OK"); 
} 
} 
if (count == 0) { 
send (writer_id, "OK"); 
receive (finished, msg); 
count = 100; 
} 
while (count < 0) { 
receive (finished, msg); 
count++; 
} 
} 
} 
Figure 5.24 
A Solution to the Readers/Writers Problem Using Message 
Passing
Summary 
 Monitors 
 Monitor with signal 
 Alternate model of monitors with 
notify and broadcast 
 Message passing 
 Synchronization 
 Addressing 
 Message format 
 Queueing discipline 
 Mutual exclusion 
 Readers/writers problem 
 Readers have priority 
 Writers have priority 
 Principles of concurrency 
 Race condition 
 OS concerns 
 Process interaction 
 Requirements for mutual 
exclusion 
 Mutual exclusion: hardware support 
 Interrupt disabling 
 Special machine instructions 
 Semaphores 
 Mutual exclusion 
 Producer/consumer problem 
 Implementation of semaphores

More Related Content

PPTX
Semaphore
PPT
Chapter 6 - Process Synchronization
PDF
5 process synchronization
PPT
Mutual exclusion and sync
DOC
Introduction to Operating System (Important Notes)
PPT
Inter-Process communication in Operating System.ppt
PDF
Semaphores
PPTX
Operating Systems - Processor Management
Semaphore
Chapter 6 - Process Synchronization
5 process synchronization
Mutual exclusion and sync
Introduction to Operating System (Important Notes)
Inter-Process communication in Operating System.ppt
Semaphores
Operating Systems - Processor Management

What's hot (20)

PPT
Inter process communication
PDF
Concurrent/ parallel programming
PPTX
Context switching
DOCX
Operating System Process Synchronization
PPTX
Dead Lock in operating system
PPT
Part 1 - PROCESS CONCEPTS
PDF
Memory management
PPTX
Operating system - Process and its concepts
PPTX
Computer architecture page replacement algorithms
PPT
Introduction to System Calls
PPTX
Memory Management in OS
PPT
Thrashing allocation frames.43
PPT
Parallel processing
DOC
Distributed Mutual exclusion algorithms
PPT
Chapter 9 - Virtual Memory
PPTX
Deadlocks in operating system
PPTX
Lecture 14 run time environment
PPTX
CPU Scheduling in OS Presentation
PPTX
Difference Program vs Process vs Thread
Inter process communication
Concurrent/ parallel programming
Context switching
Operating System Process Synchronization
Dead Lock in operating system
Part 1 - PROCESS CONCEPTS
Memory management
Operating system - Process and its concepts
Computer architecture page replacement algorithms
Introduction to System Calls
Memory Management in OS
Thrashing allocation frames.43
Parallel processing
Distributed Mutual exclusion algorithms
Chapter 9 - Virtual Memory
Deadlocks in operating system
Lecture 14 run time environment
CPU Scheduling in OS Presentation
Difference Program vs Process vs Thread
Ad

Similar to Concurrency: Mutual Exclusion and Synchronization (20)

PPTX
Chapter 6 Concurrency: Deadlock and Starvation
PPTX
Chapter05 new
PDF
Process coordination
PPTX
Chapter 5. Concurrency: Mutual Exclusion and Synchronization
PPT
Inter process communication
PPTX
B.Tech. Computer Science Engineering OS Notes Unit 2
PPT
CChapter4.pptCChapter4.pptCChapter4.pptCChapter4.pptCChapter4.ppt
DOCX
Concurrency : Mutual Exclusion and Synchronization
PPTX
Process synchronization in Operating Systems
PPTX
CHAP4.pptx
PPTX
Interprocess Communication important topic in iOS .pptx
PPTX
Operating system 24 mutex locks and semaphores
PDF
OPERATING SYSTEM NOTESS ppt Unit 2.1.pdf
PPTX
Lecture 5 inter process communication
PPT
Chapter 5-Process Synchronization OS.ppt
PPT
Intro Basic of OS .ppt
PPTX
Operating system 27 semaphores
PPT
Classic synchronization
PDF
OS Process synchronization Unit3 synchronization
PPT
Lecture18-19 (1).ppt
Chapter 6 Concurrency: Deadlock and Starvation
Chapter05 new
Process coordination
Chapter 5. Concurrency: Mutual Exclusion and Synchronization
Inter process communication
B.Tech. Computer Science Engineering OS Notes Unit 2
CChapter4.pptCChapter4.pptCChapter4.pptCChapter4.pptCChapter4.ppt
Concurrency : Mutual Exclusion and Synchronization
Process synchronization in Operating Systems
CHAP4.pptx
Interprocess Communication important topic in iOS .pptx
Operating system 24 mutex locks and semaphores
OPERATING SYSTEM NOTESS ppt Unit 2.1.pdf
Lecture 5 inter process communication
Chapter 5-Process Synchronization OS.ppt
Intro Basic of OS .ppt
Operating system 27 semaphores
Classic synchronization
OS Process synchronization Unit3 synchronization
Lecture18-19 (1).ppt
Ad

Recently uploaded (20)

PDF
The scientific heritage No 166 (166) (2025)
PPTX
SCIENCE10 Q1 5 WK8 Evidence Supporting Plate Movement.pptx
PPTX
The KM-GBF monitoring framework – status & key messages.pptx
PPTX
EPIDURAL ANESTHESIA ANATOMY AND PHYSIOLOGY.pptx
PPTX
Vitamins & Minerals: Complete Guide to Functions, Food Sources, Deficiency Si...
PDF
VARICELLA VACCINATION: A POTENTIAL STRATEGY FOR PREVENTING MULTIPLE SCLEROSIS
PPTX
cpcsea ppt.pptxssssssssssssssjjdjdndndddd
PDF
Phytochemical Investigation of Miliusa longipes.pdf
PDF
Unveiling a 36 billion solar mass black hole at the centre of the Cosmic Hors...
PPTX
7. General Toxicologyfor clinical phrmacy.pptx
PDF
Formation of Supersonic Turbulence in the Primordial Star-forming Cloud
PDF
AlphaEarth Foundations and the Satellite Embedding dataset
PPTX
Comparative Structure of Integument in Vertebrates.pptx
PDF
ELS_Q1_Module-11_Formation-of-Rock-Layers_v2.pdf
PDF
Sciences of Europe No 170 (2025)
PDF
diccionario toefl examen de ingles para principiante
DOCX
Q1_LE_Mathematics 8_Lesson 5_Week 5.docx
PDF
SEHH2274 Organic Chemistry Notes 1 Structure and Bonding.pdf
PPTX
TOTAL hIP ARTHROPLASTY Presentation.pptx
PDF
MIRIDeepImagingSurvey(MIDIS)oftheHubbleUltraDeepField
The scientific heritage No 166 (166) (2025)
SCIENCE10 Q1 5 WK8 Evidence Supporting Plate Movement.pptx
The KM-GBF monitoring framework – status & key messages.pptx
EPIDURAL ANESTHESIA ANATOMY AND PHYSIOLOGY.pptx
Vitamins & Minerals: Complete Guide to Functions, Food Sources, Deficiency Si...
VARICELLA VACCINATION: A POTENTIAL STRATEGY FOR PREVENTING MULTIPLE SCLEROSIS
cpcsea ppt.pptxssssssssssssssjjdjdndndddd
Phytochemical Investigation of Miliusa longipes.pdf
Unveiling a 36 billion solar mass black hole at the centre of the Cosmic Hors...
7. General Toxicologyfor clinical phrmacy.pptx
Formation of Supersonic Turbulence in the Primordial Star-forming Cloud
AlphaEarth Foundations and the Satellite Embedding dataset
Comparative Structure of Integument in Vertebrates.pptx
ELS_Q1_Module-11_Formation-of-Rock-Layers_v2.pdf
Sciences of Europe No 170 (2025)
diccionario toefl examen de ingles para principiante
Q1_LE_Mathematics 8_Lesson 5_Week 5.docx
SEHH2274 Organic Chemistry Notes 1 Structure and Bonding.pdf
TOTAL hIP ARTHROPLASTY Presentation.pptx
MIRIDeepImagingSurvey(MIDIS)oftheHubbleUltraDeepField

Concurrency: Mutual Exclusion and Synchronization

  • 1. Chapter 5 Concurrency: Mutual Exclusion and Synchronization Operating Systems: Internals and Design Principles Eighth Edition By William Stallings
  • 2. Operating System design is concerned with the management of processes and threads: Multiprogramming Multiprocessing Distributed Processing
  • 3. Multiple Applications invented to allow processing time to be shared among active applications Structured Applications extension of modular design and structured programming Operating System Structure OS themselves implemented as a set of processes or threads
  • 4. Table 5.1 Some Key Terms Related to Concurrency
  • 5.  Interleaving and overlapping  can be viewed as examples of concurrent processing  both present the same problems  Uniprocessor – the relative speed of execution of processes cannot be predicted  depends on activities of other processes  the way the OS handles interrupts  scheduling policies of the OS
  • 6.  Sharing of global resources  Difficult for the OS to manage the allocation of resources optimally  Difficult to locate programming errors as results are not deterministic and reproducible
  • 7.  Occurs when multiple processes or threads read and write data items  The final result depends on the order of execution  the “loser” of the race is the process that updates last and will determine the final value of the variable
  • 8. Operating System Concerns  Design and management issues raised by the existence of concurrency:  The OS must: be able to keep track of various processes allocate and de-allocate resources for each active process protect the data and physical resources of each process against interference by other processes ensure that the processes and outputs are independent of the processing speed
  • 9. Degree of Awareness Relationship Influence that One Process Has on the Other Potential Control Problems Processes unaware of each other Competition •Results of one process independent of the action of others •Timing of process may be affected •Mutual exclusion •Deadlock (renewable resource) •Starvation Processes indirectly aware of each other (e.g., shared object) Cooperation by sharing •Results of one process may depend on information obtained from others •Timing of process may be affected •Mutual exclusion •Deadlock (renewable resource) •Starvation •Data coherence Processes directly aware of each other (have communication primitives available to them) Cooperation by communication •Results of one process may depend on information obtained from others •Timing of process may be affected •Deadlock (consumable resource) •Starvation Table 5.2 Process Interaction
  • 10. Resource Competition  Concurrent processes come into conflict when they are competing for use of the same resource  for example: I/O devices, memory, processor time, clock In the case of competing processes three control problems must be faced: • the need for mutual exclusion • deadlock • starvation
  • 11. PROCESS 1 */ void P1 { while (true) { /* preceding code */; entercritical (Ra); /* critical section */; exitcritical (Ra); /* following code */; } } /* PROCESS 2 */ void P2 { while (true) { /* preceding code */; entercritical (Ra); /* critical section */; exitcritical (Ra); /* following code */; } } • • • /* PROCESS n */ void Pn { while (true) { /* preceding code */; entercritical (Ra); /* critical section */; exitcritical (Ra); /* following code */; } } Figure 5.1 Illustration of Mutual Exclusion
  • 12.  Must be enforced  A process that halts must do so without interfering with other processes  No deadlock or starvation  A process must not be denied access to a critical section when there is no other process using it  No assumptions are made about relative process speeds or number of processes  A process remains inside its critical section for a finite time only
  • 13.  Interrupt Disabling  uniprocessor system  disabling interrupts guarantees mutual exclusion  Disadvantages:  the efficiency of execution could be noticeably degraded  this approach will not work in a multiprocessor architecture
  • 14. Compare&Swap Instruction  also called a “compare and exchange instruction”  a compare is made between a memory value and a test value  if the values are the same a swap occurs  carried out atomically
  • 15. Figure 5.2 Hardware Support for Mutual Exclusion /* program mutualexclusion */ const int n = /* number of processes */; int bolt; void P(int i) { while (true) { while (compare_and_swap(&bolt, 0, 1) == 1) /* do nothing */; /* critical section */; bolt = 0; /* remainder */; } } void main() { bolt = 0; parbegin (P(1), P(2), . . . ,P(n)); } /* program mutualexclusion */ int const n = /* number of processes*/; int bolt; void P(int i) { while (true) { int keyi = 1; do exchange (&keyi, &bolt) while (keyi != 0); /* critical section */; bolt = 0; /* remainder */; } } void main() { bolt = 0; parbegin (P(1), P(2), . . ., P(n)); } (a) Compare and swap instruction (b) Exchange instruction
  • 16.  Applicable to any number of processes on either a single processor or multiple processors sharing main memory  Simple and easy to verify  It can be used to support multiple critical sections; each critical section can be defined by its own variable
  • 17. Special Machine Instruction: Disadvantages  Busy-waiting is employed, thus while a process is waiting for access to a critical section it continues to consume processor time  Starvation is possible when a process leaves a critical section and more than one process is waiting  Deadlock is possible
  • 18. Table 5.3 Common Concurrenc y Mechanisms Semaphore An integer value used for signaling among processes. Only three operations may be performed on a semaphore, all of which are atomic: initialize, decrement, and increment. The decrement operation may result in the blocking of a process, and the increment operation may result in the unblocking of a process. Also known as a counting semaphore or a general semaphore Binary Semaphore A semaphore that takes on only the values 0 and 1. Mutex Similar to a binary semaphore. A key difference between the two is that the process that locks the mutex (sets the value to zero) must be the one to unlock it (sets the value to 1). Condition Variable A data type that is used to block a process or thread until a particular condition is true. Monitor A programming language construct that encapsulates variables, access procedures and initialization code within an abstract data type. The monitor's variable may only be accessed via its access procedures and only one process may be actively accessing the monitor at any one time. The access procedures are critical sections. A monitor may have a queue of processes that are waiting to access it. Event Flags A memory word used as a synchronization mechanism. Application code may associate a different event with each bit in a flag. A thread can wait for either a single event or a combination of events by checking one or multiple bits in the corresponding flag. The thread is blocked until all of the required bits are set (AND) or until at least one of the bits is set (OR). Mailboxes/Messages A means for two processes to exchange information and that may be used for synchronization. Spinlocks Mutual exclusion mechanism in which a process executes in an infinite loop waiting for the value of a lock variable to indicate availability.
  • 19. Semaphore • There is no way to inspect or manipulate semaphores other than these three operations A variable that has an integer value upon which only three operations are defined: 1) May be initialized to a nonnegative integer value 2) The semWait operation decrements the value 3) The semSignal operation increments the value
  • 20. Consequences There is no way to know before a process decrements a semaphore whether it will block or not There is no way to know which process will continue immediately on a uniprocessor system when two processes are running concurrently You don’t know whether another process is waiting so the number of unblocked processes may be zero or one
  • 21. Figure 5.3 A Definition of Semaphore Primitives
  • 22. struct binary_semaphore { enum {zero, one} value; queueType queue; }; void semWaitB(binary_semaphore s) { if (s.value == one) s.value = zero; else { /* place this process in s.queue */; /* block this process */; } } void semSignalB(semaphore s) { if (s.queue is empty()) s.value = one; else { /* remove a process P from s.queue */; /* place process P on ready list */; } } Figure 5.4 A Definition of Binary Semaphore Primitives
  • 23. A queue is used to hold processes waiting on the semaphore Strong Semaphores • the process that has been blocked the longest is released from the queue first (FIFO) Weak Semaphores • the order in which processes are removed from the queue is not specified
  • 24. A A issues semWait, later times out 1 B issues semWait D issues semSignal s = 1 C D B Ready queue Processor 2 Blocked queue B D issues semSignal, later times out D B A C 4 C A B A B Figure 5.5 Example of Semaphore Mechanism s = 0 Ready queue Processor Blocked queue B s = 0 Ready queue Processor A C D Blocked queue 5 C C issues semWait s = 0 Ready queue Processor D B A Blocked queue 3 D s = –1 Ready queue Processor A C Blocked queue D issues semSignal 7 D s = –2 Ready queue Processor C Blocked queue D issues semSignal 6 D s = –3 Ready queue Processor Blocked queue
  • 25. Figure 5.6 Mutual Exclusion Using Semaphores
  • 26. B –1 C B C 1 A semWait(lock) Value of semaphore lock Queue for semaphore lock B C 0 –2 –1 0 1 semWait(lock) semSignal(lock) semWait(lock) semSignal(lock) semSignal(lock) Figure 5.7 Processes Accessing Shared Data Protected by a Semaphore Critical region Normal execution Blocked on semaphore lock Note that normal execution can proceed in parallel but that critical regions are serialized.
  • 27. Producer/Consumer Problem General Statement: one or more producers are generating data and placing these in a buffer a single consumer is taking items out of the buffer one at a time only one producer or consumer may access the buffer at any one time The Problem: ensure that the producer can’t add data into full buffer and consumer can’t remove data from an empty buffer
  • 28. 0 1 2 3 4 b[1] b[2] out b[3] b[4] b[5] in Note: shaded area indicates portion of buffer that is occupied Figure 5.8 Infinite Buffer for the Producer/Consumer Problem
  • 29. /* program producerconsumer */ int n; binary_semaphore s = 1, delay = 0; void producer() { while (true) { produce(); semWaitB(s); append(); n++; if (n==1) semSignalB(delay); semSignalB(s); } } void consumer() { semWaitB(delay); while (true) { semWaitB(s); take(); n--; semSignalB(s); consume(); if (n==0) semWaitB(delay); } } void main() { n = 0; parbegin (producer, consumer); } Figure 5.9 An Incorrect Solution to the Infinite-Buffer Producer/Consu mer Problem Using Binary Semaphores
  • 30. Table 5.4 Possible Scenario for the Program of Figure 5.9 Producer Consumer s n Delay 1 1 0 0 2 semWaitB(s) 0 0 0 3 n++ 0 1 0 4 if (n==1) (semSignalB(delay)) 0 1 1 5 semSignalB(s) 1 1 1 6 semWaitB(delay) 1 1 0 7 semWaitB(s) 0 1 0 8 n-- 0 0 0 9 semSignalB(s) 1 0 0 10 semWaitB(s) 0 0 0 11 n++ 0 1 0 12 if (n==1) (semSignalB(delay)) 0 1 1 13 semSignalB(s) 1 1 1 14 if (n==0) (semWaitB(delay)) 1 1 1 15 semWaitB(s) 0 1 1 16 n-- 0 0 1 17 semSignalB(s) 1 0 1 18 if (n==0) (semWaitB(delay)) 1 0 0 19 semWaitB(s) 0 0 0 20 n-- 0 –1 0 21 semSignalB(s) 1 –1 0 Note: White areas represent the critical section controlled by semaphore s.
  • 31. Figure 5.10 A Correct Solution to the Infinite-Buffer Producer/Con sumer Problem Using Binary Semaphores
  • 32. /* program producerconsumer */ semaphore n = 0, s = 1; void producer() { while (true) { produce(); semWait(s); append(); semSignal(s); semSignal(n); } } void consumer() { while (true) { semWait(n); semWait(s); take(); semSignal(s); consume(); } } void main() { parbegin (producer, consumer); } Figure 5.11 A Solution to the Infinite- Buffer Producer/C onsumer Problem Using Semaphore s
  • 33. b[1] b[2] out b[3] b[4] b[5] b[n] (a) in b[1] b[2] b[5] b[n] out b[3] b[4] (b) in Figure 5.12 Finite Circular Buffer for the Producer/Consumer Problem
  • 34. Figure 5.13 A Solution to the Bounded- Buffer Producer/Con sumer Problem Using Semaphores
  • 35. Implementation of Semaphores  Imperative that the semWait and semSignal operations be implemented as atomic primitives  Can be implemented in hardware or firmware  Software schemes such as Dekker’s or Peterson’s algorithms can be used  Use one of the hardware-supported schemes for mutual exclusion
  • 36. Two Possible Implementations of Semaphores semWait(s) { Figure 5.14 while (compare_and_swap(s.flag, 0 , 1) == 1) /* do nothing */; s.count--; if (s.count < 0) { /* place this process in s.queue*/; /* block this process (must also set s.flag to 0) */; } s.flag = 0; } semSignal(s) { while (compare_and_swap(s.flag, 0 , 1) == 1) /* do nothing */; s.count++; if (s.count <= 0) { /* remove a process P from s.queue */; /* place process P on ready list */; } s.flag = 0; } semWait(s) { inhibit interrupts; s.count--; if (s.count < 0) { /* place this process in s.queue */; /* block this process and allow interrupts */; } else allow interrupts; } semSignal(s) { inhibit interrupts; s.count++; if (s.count <= 0) { /* remove a process P from s.queue */; /* place process P on ready list */; } allow interrupts; } (a) Compare and Swap Instruction (b) Interrupts
  • 37. Monitors  Programming language construct that provides equivalent functionality to that of semaphores and is easier to control  Implemented in a number of programming languages  including Concurrent Pascal, Pascal-Plus, Modula-2, Modula-3, and Java  Has also been implemented as a program library  Software module consisting of one or more procedures, an initialization sequence, and local data
  • 38. Monitor Characteristics Local data variables are accessible only by the monitor’s procedures and not by any external procedure Process enters monitor by invoking one of its procedures Only one process may be executing in the monitor at a time
  • 39. Synchronization  Achieved by the use of condition variables that are contained within the monitor and accessible only within the monitor  Condition variables are operated on by two functions:  cwait(c): suspend execution of the calling process on condition c  csignal(c): resume execution of some process blocked after a cwait on the same condition
  • 40. Entrance queue of entering processes Exit monitor waiting area condition c1 cwait(c1) condition cn cwait(cn) urgent queue csignal local data MONITOR condition variables Procedure 1 Procedure k initialization code Figure 5.15 Structure of a Monitor
  • 41. Figure 5.16 A Solution to the Bounded- Buffer Producer/Cons umer Problem Using a Monitor
  • 43.  When processes interact with one another two fundamental requirements must be satisfied: synchronization • to enforce mutual exclusion  Message Passing is one approach to providing both of these functions  works with distributed systems and shared memory multiprocessor and uniprocessor systems communication • to exchange information
  • 44. Message Passing  The actual function is normally provided in the form of a pair of primitives: send (destination, message) receive (source, message)  A process sends information in the form of a message to another process designated by a destination  A process receives information by executing the receive primitive, indicating the source and the message
  • 45. Synchronization Send blocking nonblocking Receive blocking nonblocking test for arrival Addressing Direct send receive explicit implicit Indirect static dynamic ownership Format Content Length fixed variable Queueing Discipline FIFO Priority Table 5.5 Design Characteristics of Message Systems for Interprocess Communication and Synchronization
  • 47.  Both sender and receiver are blocked until the message is delivered Sometimes referred to as a rendezvous  Allows for tight synchronization between processes
  • 48. Nonblocking Send Nonblocking send, blocking receive • sender continues on but receiver is blocked until the requested message arrives • most useful combination • sends one or more messages to a variety of destinations as quickly as possible • example -- a service process that exists to provide a service or resource to other processes Nonblocking send, nonblocking receive • neither party is required to wait
  • 49.  Schemes for specifying processes in send and receive primitives fall into two categories: Direct addressing Indirect addressing
  • 50. Direct Addressing  Send primitive includes a specific identifier of the destination process  Receive primitive can be handled in one of two ways:  require that the process explicitly designate a sending process  effective for cooperating concurrent processes implicit addressing  source parameter of the receive primitive possesses a value returned when the receive operation has been performed
  • 51. Indirect Addressing Messages are sent to a shared data structure consisting of queues that can temporarily hold messages Queues are referred to as mailboxes One process sends a message to the mailbox and the other process picks up the message from the mailbox Allows for greater flexibility in the use of messages
  • 52. S1 Sn R1 Rm (b) Many to one Mailbox S1 Sn Port R1 S1 Mailbox R1 Figure 5.18 Indirect Process Communication S1 (a) One to one (d) Many to many R1 Rm Mailbox (c) One to many
  • 53. Message Type Destination ID Header Source ID Body Message Length Control Information Message Contents Figure 5.19 General Message Format
  • 54. Figure 5.20 Mutual Exclusion Using Messages
  • 55. Figure 5.21 A Solution to the Bounded-Buffer Producer/Consu mer Problem Using Messages
  • 56. Readers/Writers Problem  A data area is shared among many processes  some processes only read the data area, (readers) and some only write to the data area (writers)  Conditions that must be satisfied: 1. any number of readers may simultaneously read the file 2. only one writer at a time may write to the file 3. if a writer is writing to the file, no reader may read it
  • 57. Figure 5.22 A Solution to the Readers/Write rs Problem Using Semaphores: Readers Have Priority
  • 58. Readers only in the system •wsem set •no queues Writers only in the system •wsem and rsem set •writers queue on wsem Both readers and writers with read first •wsem set by reader •rsem set by writer •all writers queue on wsem •one reader queues on rsem •other readers queue on z Both readers and writers with write first •wsem set by writer •rsem set by writer •writers queue on wsem •one reader queues on rsem •other readers queue on z Table 5.6 State of the Process Queues for Program of Figure 5.23
  • 59. Figure 5.23 A Solution to the Readers/Writers Problem Using Semaphores: Writers Have Priority
  • 60. void reader(int i) { message rmsg; while (true) { rmsg = i; send (readrequest, rmsg); receive (mbox[i], rmsg); READUNIT (); rmsg = i; send (finished, rmsg); } } void writer(int j) { message rmsg; while(true) { rmsg = j; send (writerequest, rmsg); receive (mbox[j], rmsg); WRITEUNIT (); rmsg = j; send (finished, rmsg); } } void controller() { while (true) { if (count > 0) { if (!empty (finished)) { receive (finished, msg); count++; } else if (!empty (writerequest)) { receive (writerequest, msg); writer_id = msg.id; count = count – 100; } else if (!empty (readrequest)) { receive (readrequest, msg); count--; send (msg.id, "OK"); } } if (count == 0) { send (writer_id, "OK"); receive (finished, msg); count = 100; } while (count < 0) { receive (finished, msg); count++; } } } Figure 5.24 A Solution to the Readers/Writers Problem Using Message Passing
  • 61. Summary  Monitors  Monitor with signal  Alternate model of monitors with notify and broadcast  Message passing  Synchronization  Addressing  Message format  Queueing discipline  Mutual exclusion  Readers/writers problem  Readers have priority  Writers have priority  Principles of concurrency  Race condition  OS concerns  Process interaction  Requirements for mutual exclusion  Mutual exclusion: hardware support  Interrupt disabling  Special machine instructions  Semaphores  Mutual exclusion  Producer/consumer problem  Implementation of semaphores

Editor's Notes

  • #2: “Operating Systems: Internal and Design Principles”, 8/e, by William Stallings, Chapter 5 “Concurrency: Mutual Exclusion and Synchronization”.
  • #3: The central themes of operating system design are all concerned with the management of processes and threads: • Multiprogramming: The management of multiple processes within a uniprocessor system • Multiprocessing : The management of multiple processes within a multiprocessor • Distributed processing: The management of multiple processes executing on multiple, distributed computer systems. The recent proliferation of clusters is a prime example of this type of system.
  • #4: Fundamental to all of these areas, and fundamental to OS design, is concurrency. Concurrency encompasses a host of design issues, including communication among processes, sharing of and competing for resources (such as memory, files, and I/O access), synchronization of the activities of multiple processes, and allocation of processor time to processes. We shall see that these issues arise not just in multiprocessing and distributed processing environments but even in single-processor multiprogramming systems. Concurrency arises in three different contexts: • Multiple applications: Multiprogramming was invented to allow processing time to be dynamically shared among a number of active applications. • Structured applications: As an extension of the principles of modular design and structured programming, some applications can be effectively programmed as a set of concurrent processes. Operating system structure: The same structuring advantages apply to systems programs, and we have seen that operating systems are themselves often implemented as a set of processes or threads.
  • #5: Table 5.1 lists some key terms related to concurrency. A set of animations that illustrate concepts in this chapter is available online. Click on the rotating globe at this book’s Web site at WilliamStallings. com/OS/OS7e.html for access.
  • #6: At first glance, it may seem that interleaving and overlapping represent fundamentally different modes of execution and present different problems. In fact, both techniques can be viewed as examples of concurrent processing, and both present the same problems. In the case of a uniprocessor, the problems stem from a basic characteristic of multiprogramming systems: The relative speed of execution of processes cannot be predicted. It depends on the activities of other processes, the way in which the OS handles interrupts, and the scheduling policies of the OS.
  • #7: The following difficulties arise: 1. The sharing of global resources is fraught with peril. For example, if two processes both make use of the same global variable and both perform reads and writes on that variable, then the order in which the various reads and writes are executed is critical. An example of this problem is shown in the following subsection. 2. It is difficult for the OS to manage the allocation of resources optimally. For example, process A may request use of, and be granted control of, a particular I/O channel and then be suspended before using that channel. It may be undesirable for the OS simply to lock the channel and prevent its use by other processes; indeed this may lead to a deadlock condition, as described in Chapter 6 . 3. It becomes very difficult to locate a programming error because results are typically not deterministic and reproducible (e.g., see [LEBL87, CARR89, SHEN02] for a discussion of this point). All of the foregoing difficulties present themselves in a multiprocessor system as well, because here too the relative speed of execution of processes is unpredictable. A multiprocessor system must also deal with problems arising from the simultaneous execution of multiple processes. Fundamentally, however, the problems are the same as those for uniprocessor systems. This should become clear as the discussion proceeds.
  • #8: A race condition occurs when multiple processes or threads read and write data items so that the final result depends on the order of execution of instructions in the multiple processes. Let us consider two simple examples. As a first example, suppose that two processes, P1 and P2, share the global variable a . At some point in its execution, P1 updates a to the value 1, and at some point in its execution, P2 updates a to the value 2. Thus, the two tasks are in a race to write variable a . In this example, the “loser” of the race (the process that updates last) determines the final value of a . For our second example, consider two process, P3 and P4, that share global variables b and c , with initial values b = 1 and c = 2 . At some point in its execution, P3 executes the assignment b = b + c , and at some point in its execution, P4 executes the assignment c = b + c . Note that the two processes update different variables. However, the final values of the two variables depend on the order in which the two processes execute these two assignments. If P3 executes its assignment statement first, then the final values are b = 3 and c = 5 . If P4 executes its assignment statement first, then the final values are b = 4 and c = 3 . Appendix A includes a discussion of race conditions using semaphores as an example.
  • #9: What design and management issues are raised by the existence of concurrency? We can list the following concerns: 1. The OS must be able to keep track of the various processes. This is done with the use of process control blocks and was described in Chapter 4 . 2. The OS must allocate and de-allocate various resources for each active process. At times, multiple processes want access to the same resource. These resources include • Processor time: This is the scheduling function, discussed in Part Four. • Memory: Most operating systems use a virtual memory scheme. The topic is addressed in Part Three. • Files: Discussed in Chapter 12 . • I/O devices: Discussed in Chapter 11 . 3. The OS must protect the data and physical resources of each process against unintended interference by other processes. This involves techniques that relate to memory, files, and I/O devices. A general treatment of protection is found in Part Seven . 4. The functioning of a process, and the output it produces, must be independent of the speed at which its execution is carried out relative to the speed of other concurrent processes. This is the subject of this chapter.
  • #10: We can classify the ways in which processes interact on the basis of the degree to which they are aware of each other’s existence. Table 5.2 lists three possible degrees of awareness plus the consequences of each: • Processes unaware of each other: These are independent processes that are not intended to work together. The best example of this situation is the multiprogramming of multiple independent processes. These can either be batch jobs or interactive sessions or a mixture. Although the processes are not working together, the OS needs to be concerned about competition for resources. For example, two independent applications may both want to access the same disk or file or printer. The OS must regulate these accesses. • Processes indirectly aware of each other: These are processes that are not necessarily aware of each other by their respective process IDs but that share access to some object, such as an I/O buffer. Such processes exhibit cooperation in sharing the common object. • Processes directly aware of each other: These are processes that are able to communicate with each other by process ID and that are designed to work jointly on some activity. Again, such processes exhibit cooperation . Conditions will not always be as clear-cut as suggested in Table 5.2 . Rather, several processes may exhibit aspects of both competition and cooperation. Nevertheless, it is productive to examine each of the three items in the preceding list separately and determine their implications for the OS.
  • #11: Concurrent processes come into conflict with each other when they are competing for the use of the same resource. In its pure form, we can describe the situation as follows. Two or more processes need to access a resource during the course of their execution. Each process is unaware of the existence of other processes, and each is to be unaffected by the execution of the other processes. It follows from this that each process should leave the state of any resource that it uses unaffected. Examples of resources include I/O devices, memory, processor time, and the clock. There is no exchange of information between the competing processes. However, the execution of one process may affect the behavior of competing processes. In particular, if two processes both wish access to a single resource, then one process will be allocated that resource by the OS, and the other will have to wait. Therefore, the process that is denied access will be slowed down. In an extreme case, the blocked process may never get access to the resource and hence will never terminate successfully. In the case of competing processes three control problems must be faced. First is the need for mutual exclusion . Suppose two or more processes require access to a single non-sharable resource, such as a printer. During the course of execution, each process will be sending commands to the I/O device, receiving status information, sending data, and/or receiving data. We will refer to such a resource as a critical resource , and the portion of the program that uses it as a critical section of the program. It is important that only one program at a time be allowed in its critical section. We cannot simply rely on the OS to understand and enforce this restriction because the detailed requirements may not be obvious. In the case of the printer, for example, we want any individual process to have control of the printer while it prints an entire file. Otherwise, lines from competing processes will be interleaved. The enforcement of mutual exclusion creates two additional control problems. One is that of deadlock . For example, consider two processes, P1 and P2, and two resources, R1 and R2. Suppose that each process needs access to both resources to perform part of its function. Then it is possible to have the following situation: the OS assigns R1 to P2, and R2 to P1. Each process is waiting for one of the two resources. Neither will release the resource that it already owns until it has acquired the other resource and performed the function requiring both resources. The two processes are deadlocked. A final control problem is starvation . Suppose that three processes (P1, P2, P3) each require periodic access to resource R. Consider the situation in which P1 is in possession of the resource, and both P2 and P3 are delayed, waiting for that resource. When P1 exits its critical section, either P2 or P3 should be allowed access to R. Assume that the OS grants access to P3 and that P1 again requires access before P3 completes its critical section. If the OS grants access to P1 after P3 has finished, and subsequently alternately grants access to P1 and P3, then P2 may indefinitely be denied access to the resource, even though there is no deadlock situation.
  • #12: Control of competition inevitably involves the OS because it is the OS that allocates resources. In addition, the processes themselves will need to be able to express the requirement for mutual exclusion in some fashion, such as locking a resource prior to its use. Any solution will involve some support from the OS, such as the provision of the locking facility. Figure 5.1 illustrates the mutual exclusion mechanism in abstract terms. There are n processes to be executed concurrently. Each process includes (1) a critical section that operates on some resource Ra, and (2) additional code preceding and following the critical section that does not involve access to Ra. Because all processes access the same resource Ra, it is desired that only one process at a time be in its critical section. To enforce mutual exclusion, two functions are provided: entercritical and exitcritical . Each function takes as an argument the name of the resource that is the subject of competition. Any process that attempts to enter its critical section while another process is in its critical section, for the same resource, is made to wait. It remains to examine specific mechanisms for providing the functions entercritical and exitcritical . For the moment, we defer this issue while we consider the other cases of process interaction.
  • #13: Any facility or capability that is to provide support for mutual exclusion should meet the following requirements: 1. Mutual exclusion must be enforced: Only one process at a time is allowed into its critical section, among all processes that have critical sections for the same resource or shared object. 2. A process that halts in its noncritical section must do so without interfering with other processes. 3. It must not be possible for a process requiring access to a critical section to be delayed indefinitely: no deadlock or starvation. 4. When no process is in a critical section, any process that requests entry to its critical section must be permitted to enter without delay. 5. No assumptions are made about relative process speeds or number of processors. 6. A process remains inside its critical section for a finite time only. There are a number of ways in which the requirements for mutual exclusion can be satisfied. One approach is to leave the responsibility with the processes that wish to execute concurrently. Processes, whether they are system programs or application programs, would be required to coordinate with one another to enforce mutual exclusion, with no support from the programming language or the OS. We can refer to these as software approaches. Although this approach is prone to high processing overhead and bugs, it is nevertheless useful to examine such approaches to gain a better understanding of the complexity of concurrent processing. This topic is covered in Appendix A . A second approach involves the use of special purpose machine instructions. These have the advantage of reducing overhead but nevertheless will be shown to be unattractive as a general-purpose solution; they are covered in Section 5.2 . A third approach is to provide some level of support within the OS or a programming language. Three of the most important such approaches are examined in Sections 5.3 through 5.5 .
  • #14: In a uniprocessor system, concurrent processes cannot have overlapped execution; they can only be interleaved. Furthermore, a process will continue to run until it invokes an OS service or until it is interrupted. Therefore, to guarantee mutual exclusion, it is sufficient to prevent a process from being interrupted. This capability can be provided in the form of primitives defined by the OS kernel for disabling and enabling interrupts. Because the critical section cannot be interrupted, mutual exclusion is guaranteed. The price of this approach, however, is high. The efficiency of execution could be noticeably degraded because the processor is limited in its ability to interleave processes. Another problem is that this approach will not work in a multiprocessor architecture. When the computer includes more than one processor, it is possible (and typical) for more than one process to be executing at a time. In this case, disabled interrupts do not guarantee mutual exclusion.
  • #15: This version of the instruction checks a memory location ( *word ) against a test value ( testval ). If the memory location’s current value is testval, it is replaced with newval ; otherwise it is left unchanged. The old memory value is always returned; thus, the memory location has been updated if the returned value is the same as the test value. This atomic instruction therefore has two parts: A compare is made between a memory value and a test value; if the values are the same, a swap occurs. The entire compare&swap function is carried out atomically—that is, it is not subject to interruption. Another version of this instruction returns a Boolean value: true if the swap occurred; false otherwise. Some version of this instruction is available on nearly all processor families (x86, IA64, sparc, IBM z series, etc.), and most operating systems use this instruction for support of concurrency.
  • #16: Figure 5.2a shows a mutual exclusion protocol based on the use of this instruction. A shared variable bolt is initialized to 0. The only process that may enter its critical section is one that finds bolt equal to 0. All other processes attempting to enter their critical section go into a busy waiting mode. The term busy waiting , or spin waiting , refers to a technique in which a process can do nothing until it gets permission to enter its critical section but continues to execute an instruction or set of instructions that tests the appropriate variable to gain entrance. When a process leaves its critical section, it resets bolt to 0; at this point one and only one of the waiting processes is granted access to its critical section. The choice of process depends on which process happens to execute the compare & swap instruction next. Figure 5.2b shows a mutual exclusion protocol based on the use of an exchange instruction. A shared variable bolt is initialized to 0. Each process uses a local variable key that is initialized to 1. The only process that may enter its critical section is one that finds bolt equal to 0. It excludes all other processes from the critical section by setting bolt to 1. When a process leaves its critical section, it resets bolt to 0, allowing another process to gain access to its critical section.
  • #17: The use of a special machine instruction to enforce mutual exclusion has a number of advantages: • It is applicable to any number of processes on either a single processor or multiple processors sharing main memory. • It is simple and therefore easy to verify. • It can be used to support multiple critical sections; each critical section can be defined by its own variable.
  • #18: There are some serious disadvantages: • Busy waiting is employed: Thus, while a process is waiting for access to a critical section, it continues to consume processor time. Starvation is possible: When a process leaves a critical section and more than one process is waiting, the selection of a waiting process is arbitrary. Thus, some process could indefinitely be denied access. • Deadlock is possible: Consider the following scenario on a single-processor system. Process P1 executes the special instruction (e.g., compare & swap, exchange ) and enters its critical section. P1 is then interrupted to give the processor to P2, which has higher priority. If P2 now attempts to use the same resource as P1, it will be denied access because of the mutual exclusion mechanism. Thus, it will go into a busy waiting loop. However, P1 will never be dispatched because it is of lower priority than another ready process, P2. Because of the drawbacks of both the software and hardware solutions just outlined, we need to look for other mechanisms.
  • #19: We now turn to OS and programming language mechanisms that are used to provide concurrency. Table 5.3 summarizes mechanisms in common use. We begin, in this section, with semaphores. The next two sections discuss monitors and message passing. The other mechanisms in Table 5.3 are discussed when treating specific operating system examples, in Chapters 6 and 13 .
  • #20: The fundamental principle is this: Two or more processes can cooperate by means of simple signals, such that a process can be forced to stop at a specified place until it has received a specific signal. Any complex coordination requirement can be satisfied by the appropriate structure of signals. For signaling, special variables called semaphores are used. To transmit a signal via semaphore s , a process executes the primitive semSignal(s) . To receive a signal via semaphore s , a process executes the primitive semWait(s) ; if the corresponding signal has not yet been transmitted, the process is suspended until the transmission takes place. To achieve the desired effect, we can view the semaphore as a variable that has an integer value upon which only three operations are defined: 1. A semaphore may be initialized to a nonnegative integer value. 2. The semWait operation decrements the semaphore value. If the value becomes negative, then the process executing the semWait is blocked. Otherwise, the process continues execution. 3. The semSignal operation increments the semaphore value. If the resulting value is less than or equal to zero, then a process blocked by a semWait operation, if any, is unblocked. Other than these three operations, there is no way to inspect or manipulate semaphores. We explain these operations as follows. To begin, the semaphore has a zero or positive value. If the value is positive, that value equals the number of processes that can issue a wait and immediately continue to execute. If the value is zero, either by initialization or because a number of processes equal to the initial semaphore value have issued a wait, the next process to issue a wait is blocked, and the semaphore value goes negative. Each subsequent wait drives the semaphore value further into minus territory. The negative value equals the number of processes waiting to be unblocked. Each signal unblocks one of the waiting processes when the semaphore value is negative.
  • #21: [DOWN08] points out three interesting consequences of the semaphore definition: • In general, there is no way to know before a process decrements a semaphore whether it will block or not. After a process increments a semaphore and another process gets woken up, both processes continue running concurrently. There is no way to know which process, if either, will continue immediately on a uniprocessor system. • When you signal a semaphore, you don’t necessarily know whether another process is waiting, so the number of unblocked processes may be zero or one.
  • #22: Figure 5.3 suggests a more formal definition of the primitives for semaphores. The semWait and semSignal primitives are assumed to be atomic.
  • #23: A more restricted version, known as the binary semaphore , is defined in Figure 5.4 . A binary semaphore may only take on the values 0 and 1 and can be defined by the following three operations: 1. A binary semaphore may be initialized to 0 or 1. 2. The semWaitB operation checks the semaphore value. If the value is zero, then the process executing the semWaitB is blocked. If the value is one, then the value is changed to zero and the process continues execution. 3. The semSignalB operation checks to see if any processes are blocked on this semaphore (semaphore value equals 0). If so, then a process blocked by a semWaitB operation is unblocked. If no processes are blocked, then the value of the semaphore is set to one. In principle, it should be easier to implement the binary semaphore, and it can be shown that it has the same expressive power as the general semaphore (see Problem 5.16). To contrast the two types of semaphores, the nonbinary semaphore is often referred to as either a counting semaphore or a general semaphore . A concept related to the binary semaphore is the mutual exclusion lock (mutex) . A mutex is a programming flag used to grab and release an object. When data are acquired that cannot be shared or processing is started that cannot be performed simultaneously elsewhere in the system, the mutex is set to lock (typically zero), which blocks other attempts to use it. The mutex is set to unlock when the data are no longer needed or the routine is finished. A key difference between the a mutex and a binary semaphore is that the process that locks the mutex (sets the value to zero) must be the one to unlock it (sets the value to 1). In contrast, it is possible for one process to lock a binary semaphore and for another to unlock it.
  • #24: For both counting semaphores and binary semaphores, a queue is used to hold processes waiting on the semaphore. The question arises of the order in which processes are removed from such a queue. The fairest removal policy is first-in-first-out (FIFO): The process that has been blocked the longest is released from the queue first; a semaphore whose definition includes this policy is called a strong semaphore . A semaphore that does not specify the order in which processes are removed from the queue is a weak semaphore .
  • #25: Figure 5.5 is an example of the operation of a strong semaphore. Here processes A, B, and C depend on a result from process D. Initially (1), A is running; B, C, and D are ready; and the semaphore count is 1, indicating that one of D’s results is available. When A issues a semWait instruction on semaphore s , the semaphore decrements to 0, and A can continue to execute; subsequently it rejoins the ready queue. Then B runs (2), eventually issues a semWait instruction, and is blocked, allowing D to run (3). When D completes a new result, it issues a semSignal instruction, which allows B to move to the ready queue (4). D rejoins the ready queue and C begins to run (5) but is blocked when it issues a semWait instruction. Similarly, A and B run and are blocked on the semaphore, allowing D to resume execution (6). When D has a result, it issues a semSignal , which transfers C to the ready queue. Later cycles of D will release A and B from the Blocked state.
  • #26: For the mutual exclusion algorithm discussed in the next subsection and illustrated in Figure 5.6 , strong semaphores guarantee freedom from starvation, while weak semaphores do not. We will assume strong semaphores because they are more convenient and because this is the form of semaphore typically provided by operating systems. Figure 5.6 shows a straightforward solution to the mutual exclusion problem using a semaphore s (compare Figure 5.1 ). Consider n processes, identified in the array P ( i ), all of which need access to the same resource. Each process has a critical section used to access the resource. In each process, a semWait(s) is executed just before its critical section. If the value of s becomes negative, the process is blocked. If the value is 1, then it is decremented to 0 and the process immediately enters its critical section; because s is no longer positive, no other process will be able to enter its critical section. The semaphore is initialized to 1. Thus, the first process that executes a semWait will be able to enter the critical section immediately, setting the value of s to 0. Any other process attempting to enter the critical section will find it busy and will be blocked, setting the value of s to –1. Any number of processes may attempt entry; each such unsuccessful attempt results in a further decrement of the value of s . When the process that initially entered its critical section departs, s is incremented and one of the blocked processes (if any) is removed from the queue of blocked processes associated with the semaphore and put in a Ready state. When it is next scheduled by the OS, it may enter the critical section.
  • #27: Figure 5.7, based on one in [BACO03], shows a possible sequence for three processes using the mutual exclusion discipline of Figure 5.6. In this example three processes (A, B, C) access a shared resource protected by the semaphore lock . Process A executes semWait (lock) ; because the semaphore has a value of 1 at the time of the semWait operation, A can immediately enter its critical section and the semaphore takes on the value 0. While A is in its critical section, both B and C perform a semWait operation and are blocked pending the availability of the semaphore. When A exits its critical section and performs semSignal (lock) , B, which was the first process in the queue, can now enter its critical section.
  • #28: We now examine one of the most common problems faced in concurrent processing: the producer/consumer problem. The general statement is this: There are one or more producers generating some type of data (records, characters) and placing these in a buffer. There is a single consumer that is taking items out of the buffer one at a time. The system is to be constrained to prevent the overlap of buffer operations. That is, only one agent (producer or consumer) may access the buffer at any one time. The problem is to make sure that the producer won’t try to add data into the buffer if it’s full and that the consumer won’t try to remove data from an empty buffer. We will look at a number of solutions to this problem to illustrate both the power and the pitfalls of semaphores.
  • #29: Figure 5.8 illustrates the structure of buffer b . The producer can generate items and store them in the buffer at its own pace. Each time, an index ( in ) into the buffer is incremented. The consumer proceeds in a similar fashion but must make sure that it does not attempt to read from an empty buffer. Hence, the consumer makes sure that the producer has advanced beyond it ( in > out ) before proceeding.
  • #30: Let us try to implement this system using binary semaphores. Figure 5.9 is a first attempt. Rather than deal with the indices in and out , we can simply keep track of the number of items in the buffer, using the integer variable n (= in – out ). The semaphore s is used to enforce mutual exclusion; the semaphore delay is used to force the consumer to semWait if the buffer is empty. This solution seems rather straightforward. The producer is free to add to the buffer at any time. It performs semWaitB(s) before appending and semSignalB(s) afterward to prevent the consumer or any other producer from accessing the buffer during the append operation. Also, while in the critical section, the producer increments the value of n . If n = 1, then the buffer was empty just prior to this append, so the producer performs semSignalB(delay) to alert the consumer of this fact. The consumer begins by waiting for the first item to be produced, using semWaitB(delay) . It then takes an item and decrements n in its critical section. If the producer is able to stay ahead of the consumer (a common situation), then the consumer will rarely block on the semaphore delay because n will usually be positive. Hence both producer and consumer run smoothly. There is, however, a flaw in this program. When the consumer has exhausted the buffer, it needs to reset the delay semaphore so that it will be forced to wait until the producer has placed more items in the buffer. This is the purpose of the statement: if n == 0 semWaitB(delay) .
  • #31: Consider the scenario outlined in Table 5.4 . In line 14, the consumer fails to execute the semWaitB operation. The consumer did indeed exhaust the buffer and set n to 0 (line 8), but the producer has incremented n before the consumer can test it in line 14. The result is a semSignalB not matched by a prior semWaitB . The value of –1 for n in line 20 means that the consumer has consumed an item from the buffer that does not exist. It would not do simply to move the conditional statement inside the critical section of the consumer because this could lead to deadlock (e.g., after line 8 of Table 5.4 ).
  • #32: A fix for the problem is to introduce an auxiliary variable that can be set in the consumer’s critical section for use later on. This is shown in Figure 5.10 . A careful trace of the logic should convince you that deadlock can no longer occur.
  • #33: A somewhat cleaner solution can be obtained if general semaphores (also called counting semaphores) are used, as shown in Figure 5.11 . The variable n is now a semaphore. Its value still is equal to the number of items in the buffer. Suppose now that in transcribing this program, a mistake is made and the operations semSignal(s) and semSignal(n) are interchanged. This would require that the semSignal(n) operation be performed in the producer’s critical section without interruption by the consumer or another producer. Would this affect the program? No, because the consumer must wait on both semaphores before proceeding in any case.
  • #34: Finally, let us add a new and realistic restriction to the producer/consumer problem: namely, that the buffer is finite. The buffer is treated as a circular storage ( Figure 5.12 ), and pointer values must be expressed modulo the size of the buffer.
  • #35: Figure 5.13 shows a solution using general semaphores. The semaphore e has been added to keep track of the number of empty spaces.
  • #36: As was mentioned earlier, it is imperative that the semWait and semSignal operations be implemented as atomic primitives. One obvious way is to implement them in hardware or firmware. Failing this, a variety of schemes have been suggested. The essence of the problem is one of mutual exclusion: Only one process at a time may manipulate a semaphore with either a semWait or semSignal operation. Thus, any of the software schemes, such as Dekker’s algorithm or Peterson’s algorithm ( Appendix A ), could be used; this would entail a substantial processing overhead. Another alternative is to use one of the hardware-supported schemes for mutual exclusion.
  • #37: Figure 5.14a shows the use of a compare&swap instruction. In this implementation, the semaphore is again a structure, as in Figure 5.3, but now includes a new integer component, s.flag . Admittedly, this involves a form of busy waiting. However, the semWait and semSignal operations are relatively short, so the amount of busy waiting involved should be minor. For a single-processor system, it is possible to inhibit interrupts for the duration of a semWait or semSignal operation, as suggested in Figure 5.14b. Once again, the relatively short duration of these operations means that this approach is reasonable.
  • #38: The monitor is a programming-language construct that provides equivalent functionality to that of semaphores and that is easier to control. The concept was first formally defined in [HOAR74]. The monitor construct has been implemented in a number of programming languages, including Concurrent Pascal, Pascal-Plus, Modula-2, Modula-3, and Java. It has also been implemented as a program library. This allows programmers to put a monitor lock on any object. In particular, for something like a linked list, you may want to lock all linked lists with one lock, or have one lock for each list, or have one lock for each element of each list. A monitor is a software module consisting of one or more procedures, an initialization sequence, and local data.
  • #39: The chief characteristics of a monitor are the following: 1. The local data variables are accessible only by the monitor’s procedures and not by any external procedure. 2. A process enters the monitor by invoking one of its procedures. 3. Only one process may be executing in the monitor at a time; any other processes that have invoked the monitor are blocked, waiting for the monitor to become available. The first two characteristics are reminiscent of those for objects in object-oriented software. Indeed, an object-oriented OS or programming language can readily implement a monitor as an object with special characteristics. By enforcing the discipline of one process at a time, the monitor is able to provide a mutual exclusion facility. The data variables in the monitor can be accessed by only one process at a time. Thus, a shared data structure can be protected by placing it in a monitor. If the data in a monitor represent some resource, then the monitor provides a mutual exclusion facility for accessing the resource. To be useful for concurrent processing, the monitor must include synchronization tools. For example, suppose a process invokes the monitor and, while in the monitor, must be blocked until some condition is satisfied. A facility is needed by which the process is not only blocked but releases the monitor so that some other process may enter it. Later, when the condition is satisfied and the monitor is again available, the process needs to be resumed and allowed to reenter the monitor at the point of its suspension.
  • #40: A monitor supports synchronization by the use of condition variables that are contained within the monitor and accessible only within the monitor. Condition variables are a special data type in monitors, which are operated on by two functions: • cwait(c) : Suspend execution of the calling process on condition c . The monitor is now available for use by another process. • csignal(c) : Resume execution of some process blocked after a cwait on the same condition. If there are several such processes, choose one of them; if there is no such process, do nothing. Note that monitor wait and signal operations are different from those for the semaphore. If a process in a monitor signals and no task is waiting on the condition variable, the signal is lost.
  • #41: Figure 5.15 illustrates the structure of a monitor. Although a process can enter the monitor by invoking any of its procedures, we can think of the monitor as having a single entry point that is guarded so that only one process may be in the monitor at a time. Other processes that attempt to enter the monitor join a queue of processes blocked waiting for monitor availability. Once a process is in the monitor, it may temporarily block itself on condition x by issuing cwait(x) ; it is then placed in a queue of processes waiting to reenter the monitor when the condition changes, and resume execution at the point in its program following the cwait(x) call. If a process that is executing in the monitor detects a change in the condition variable x , it issues csignal(x) , which alerts the corresponding condition queue that the condition has changed.
  • #42: As an example of the use of a monitor, let us return to the bounded-buffer producer/consumer problem. Figure 5.16 shows a solution using a monitor. The monitor module, boundedbuffer , controls the buffer used to store and retrieve characters. The monitor includes two condition variables (declared with the construct cond ): notfull is true when there is room to add at least one character to the buffer, and notempty is true when there is at least one character in the buffer. A producer can add characters to the buffer only by means of the procedure append inside the monitor; the producer does not have direct access to buffer . The procedure first checks the condition notfull to determine if there is space available in the buffer. If not, the process executing the monitor is blocked on that condition. Some other process (producer or consumer) may now enter the monitor. Later, when the buffer is no longer full, the blocked process may be removed from the queue, reactivated, and resume processing. After placing a character in the buffer, the process signals the notempty condition. A similar description can be made of the consumer function. This example points out the division of responsibility with monitors compared to semaphores. In the case of monitors, the monitor construct itself enforces mutual exclusion: It is not possible for both a producer and a consumer simultaneously to access the buffer. However, the programmer must place the appropriate cwait and csignal primitives inside the monitor to prevent processes from depositing items in a full buffer or removing them from an empty one. In the case of semaphores, both mutual exclusion and synchronization are the responsibility of the programmer. Note that in Figure 5.16 , a process exits the monitor immediately after executing the csignal function. If the csignal does not occur at the end of the procedure, then, in Hoare’s proposal, the process issuing the signal is blocked to make the monitor available and placed in a queue until the monitor is free. One possibility at this point would be to place the blocked process in the entrance queue, so that it would have to compete for access with other processes that had not yet entered the monitor. However, because a process blocked on a csignal function has already partially performed its task in the monitor, it makes sense to give this process precedence over newly entering processes by setting up a separate urgent queue ( Figure 5.15 ). One language that uses monitors, Concurrent Pascal, requires that csignal only appear as the last operation executed by a monitor procedure. If there are no processes waiting on condition x , then the execution of csignal( x ) has no effect. As with semaphores, it is possible to make mistakes in the synchronization function of monitors. For example, if either of the csignal functions in the boundedbuffer monitor are omitted, then processes entering the corresponding condition queue are permanently hung up. The advantage that monitors have over semaphores is that all of the synchronization functions are confined to the monitor. Therefore, it is easier to verify that the synchronization has been done correctly and to detect bugs. Furthermore, once a monitor is correctly programmed, access to the protected resource is correct for access from all processes. In contrast, with semaphores, resource access is correct only if all of the processes that access the resource are programmed correctly.
  • #43: Hoare’s definition of monitors [HOAR74] requires that if there is at least one process in a condition queue, a process from that queue runs immediately when another process issues a csignal for that condition. Thus, the process issuing the csignal must either immediately exit the monitor or be blocked on the monitor. Lampson and Redell developed a different definition of monitors for the language Mesa [LAMP80]. Their approach overcomes the problems just listed and supports several useful extensions. The Mesa monitor structure is also used in the Modula-3 systems programming language [NELS91]. In Mesa, the csignal primitive is replaced by cnotify , with the following interpretation: When a process executing in a monitor executes cnotify(x), it causes the x condition queue to be notified, but the signaling process continues to execute. The result of the notification is that the process at the head of the condition queue will be resumed at some convenient future time when the monitor is available. However, because there is no guarantee that some other process will not enter the monitor before the waiting process, the waiting process must recheck the condition. For example, the procedures in the boundedbuffer monitor would now have the code of Figure 5.17. An advantage of Lampson/Redell monitors over Hoare monitors is that the Lampson/Redell approach is less prone to error. In the Lampson/Redell approach, because each procedure checks the monitor variable after being signaled, with the use of the while construct, a process can signal or broadcast incorrectly without causing an error in the signaled program. The signaled program will check the relevant variable and, if the desired condition is not met, continue to wait. Another advantage of the Lampson/Redell monitor is that it lends itself to a more modular approach to program construction.
  • #44: When processes interact with one another, two fundamental requirements must be satisfied: synchronization and communication. Processes need to be synchronized to enforce mutual exclusion; cooperating processes may need to exchange information. One approach to providing both of these functions is message passing. Message passing has the further advantage that it lends itself to implementation in distributed systems as well as in shared-memory multiprocessor and uniprocessor systems.
  • #45: Message-passing systems come in many forms. In this section, we provide a general introduction that discusses features typically found in such systems. The actual function of message passing is normally provided in the form of a pair of primitives: send (destination, message) receive (source, message) This is the minimum set of operations needed for processes to engage in message passing. A process sends information in the form of a message to another process designated by a destination . A process receives information by executing the receive primitive, indicating the source and the message .
  • #46: A number of design issues relating to message-passing systems are listed in Table 5.5 , and examined in the remainder of this section.
  • #47: The communication of a message between two processes implies some level of synchronization between the two: The receiver cannot receive a message until it has been sent by another process. In addition, we need to specify what happens to a process after it issues a send or receive primitive. Consider the send primitive first. When a send primitive is executed in a process, there are two possibilities: Either the sending process is blocked until the message is received, or it is not. Similarly, when a process issues a receive primitive, there are two possibilities: 1. If a message has previously been sent, the message is received and execution continues. 2. If there is no waiting message, then either (a) the process is blocked until a message arrives, or (b) the process continues to execute, abandoning the attempt to receive.
  • #48: Thus, both the sender and receiver can be blocking or nonblocking. Three combinations are common, although any particular system will usually have only one or two combinations implemented: • Blocking send, blocking receive: Both the sender and receiver are blocked until the message is delivered; this is sometimes referred to as a rendezvous . This combination allows for tight synchronization between processes.
  • #49: Nonblocking send, blocking receive: Although the sender may continue on, the receiver is blocked until the requested message arrives. This is probably the most useful combination. It allows a process to send one or more messages to a variety of destinations as quickly as possible. A process that must receive a message before it can do useful work needs to be blocked until such a message arrives. An example is a server process that exists to provide a service or resource to other processes. • Nonblocking send, nonblocking receive: Neither party is required to wait. The nonblocking send is more natural for many concurrent programming tasks. For example, if it is used to request an output operation, such as printing, it allows the requesting process to issue the request in the form of a message and then carry on. One potential danger of the nonblocking send is that an error could lead to a situation in which a process repeatedly generates messages. Because there is no blocking to discipline the process, these messages could consume system resources, including processor time and buffer space, to the detriment of other processes and the OS. Also, the nonblocking send places the burden on the programmer to determine that a message has been received: Processes must employ reply messages to acknowledge receipt of a message. For the receive primitive, the blocking version appears to be more natural for many concurrent programming tasks. Generally, a process that requests a message will need the expected information before proceeding. However, if a message is lost, which can happen in a distributed system, or if a process fails before it sends an anticipated message, a receiving process could be blocked indefinitely. This problem can be solved by the use of the nonblocking receive . However, the danger of this approach is that if a message is sent after a process has already executed a matching receive , the message will be lost. Other possible approaches are to allow a process to test whether a message is waiting before issuing a receive and allow a process to specify more than one source in a receive primitive. The latter approach is useful if a process is waiting for messages from more than one source and can proceed if any of these messages arrive.
  • #50: Clearly, it is necessary to have a way of specifying in the send primitive which process is to receive the message. Similarly, most implementations allow a receiving process to indicate the source of a message to be received. The various schemes for specifying processes in send and receive primitives fall into two categories: direct addressing and indirect addressing.
  • #51: With direct addressing , the send primitive includes a specific identifier of the destination process. The receive primitive can be handled in one of two ways. One possibility is to require that the process explicitly designate a sending process. Thus, the process must know ahead of time from which process a message is expected. This will often be effective for cooperating concurrent processes. In other cases, however, it is impossible to specify the anticipated source process. An example is a printer server process, which will accept a print request message from any other process. For such applications, a more effective approach is the use of implicit addressing. In this case, the source parameter of the receive primitive possesses a value returned when the receive operation has been performed.
  • #52: The other general approach is indirect addressing . In this case, messages are not sent directly from sender to receiver but rather are sent to a shared data structure consisting of queues that can temporarily hold messages. Such queues are generally referred to as mailboxes . Thus, for two processes to communicate, one process sends a message to the appropriate mailbox and the other process picks up the message from the mailbox. A strength of the use of indirect addressing is that, by decoupling the sender and receiver, it allows for greater flexibility in the use of messages.
  • #53: The relationship between senders and receivers can be one to one, many to one, one to many, or many to many ( Figure 5.18 ). A one-to-one relationship allows a private communications link to be set up between two processes. This insulates their interaction from erroneous interference from other processes. A many-to-one relationship is useful for client/server interaction; one process provides service to a number of other processes. In this case, the mailbox is often referred to as a port . A one-to-many relationship allows for one sender and multiple receivers; it is useful for applications where a message or some information is to be broadcast to a set of processes. A many-to-many relationship allows multiple server processes to provide concurrent service to multiple clients. 1) A one-to-one relationship allows a private communications link to be set up between two processes. This insulates their interaction from erroneous interference from other processes. 2) A many-to-one relationship is useful for client/server interaction; one process provides service to a number of other processes. In this case, the mailbox is often referred to as a port. 3) A one-to-many relationship allows for one sender and multiple receivers; it is useful for applications where a message or some information is to be broadcast to a set of processes. 4) A many-to-many relationship allows multiple server processes to provide concurrent service to multiple clients. The association of processes to mailboxes can be either static or dynamic. Ports are often statically associated with a particular process; that is, the port is created and assigned to the process permanently. Similarly, a one-to-one relationship is typically defined statically and permanently. When there are many senders, the association of a sender to a mailbox may occur dynamically. Primitives such as connect and disconnect may be used for this purpose. A related issue has to do with the ownership of a mailbox. In the case of a port, it is typically owned by and created by the receiving process. Thus, when the process is destroyed, the port is also destroyed. For the general mailbox case, the OS may offer a create-mailbox service. Such mailboxes can be viewed either as being owned by the creating process, in which case they terminate with the process, or as being owned by the OS, in which case an explicit command will be required to destroy the mailbox.
  • #54: The format of the message depends on the objectives of the messaging facility and whether the facility runs on a single computer or on a distributed system. For some operating systems, designers have preferred short, fixed-length messages to minimize processing and storage overhead. If a large amount of data is to be passed, the data can be placed in a file and the message then simply references that file. A more flexible approach is to allow variable-length messages. Figure 5.19 shows a typical message format for operating systems that support variable-length messages. The message is divided into two parts: a header, which contains information about the message. The header may contain an identification of the source and intended destination of the message, a length field, and a type field to discriminate among various types of messages. additional control information, e.g. pointer field so a linked list of messages can be created; a sequence number, to keep track of the number and order of messages passed between source and destination; and a priority field. a body, which contains the actual contents of the message.
  • #55: Figure 5.20 shows one way in which message passing can be used to enforce mutual exclusion (compare Figures 5.1 , 5.2 , and 5.6 ). We assume the use of the blocking receive primitive and the nonblocking send primitive. A set of concurrent processes share a mailbox, box , which can be used by all processes to send and receive. The mailbox is initialized to contain a single message with null content. A process wishing to enter its critical section first attempts to receive a message. If the mailbox is empty, then the process is blocked. Once a process has acquired the message, it performs its critical section and then places the message back into the mailbox. Thus, the message functions as a token that is passed from process to process. The preceding solution assumes that if more than one process performs the receive operation concurrently, then: • If there is a message, it is delivered to only one process and the others are blocked, or • If the message queue is empty, all processes are blocked; when a message is available, only one blocked process is activated and given the message. These assumptions are true of virtually all message-passing facilities.
  • #56: As an example of the use of message passing, Figure 5.21 is a solution to the bounded-buffer producer/consumer problem. Using the basic mutual-exclusion power of message passing, the problem could have been solved with an algorithmic structure similar to that of Figure 5.13 . Instead, the program of Figure 5.21 takes advantage of the ability of message passing to be used to pass data in addition to signals. Two mailboxes are used. As the producer generates data, it is sent as messages to the mailbox mayconsume . As long as there is at least one message in that mailbox, the consumer can consume. Hence mayconsume serves as the buffer; the data in the buffer are organized as a queue of messages. The “size” of the buffer is determined by the global variable capacity . Initially, the mailbox mayproduce is filled with a number of null messages equal to the capacity of the buffer. The number of messages in mayproduce shrinks with each production and grows with each consumption. This approach is quite flexible. There may be multiple producers and consumers, as long as all have access to both mailboxes. The system may even be distributed, with all producer processes and the mayproduce mailbox at one site and all the consumer processes and the mayconsume mailbox at another.
  • #57: The readers/writers problem is defined as follows: There is a data area shared among a number of processes. The data area could be a file, a block of main memory, or even a bank of processor registers. There are a number of processes that only read the data area (readers) and a number that only write to the data area (writers). The conditions that must be satisfied are as follows: 1. Any number of readers may simultaneously read the file. 2. Only one writer at a time may write to the file. 3. If a writer is writing to the file, no reader may read it. Thus, readers are processes that are not required to exclude one another and writers are processes that are required to exclude all other processes, readers and writers alike.
  • #58: Figure 5.22 is a solution using semaphores, showing one instance each of a reader and a writer; the solution does not change for multiple readers and writers. The writer process is simple. The semaphore wsem is used to enforce mutual exclusion. As long as one writer is accessing the shared data area, no other writers and no readers may access it. The reader process also makes use of wsem to enforce mutual exclusion. However, to allow multiple readers, we require that, when there are no readers reading, the first reader that attempts to read should wait on wsem . When there is already at least one reader reading, subsequent readers need not wait before entering. The global variable readcount is used to keep track of the number of readers, and the semaphore x is used to assure that readcount is updated properly.
  • #59: For readers, one additional semaphore is needed. A long queue must not be allowed to build up on rsem ; otherwise writers will not be able to jump the queue. Therefore, only one reader is allowed to queue on rsem , with any additional readers queuing on semaphore z , immediately before waiting on rsem . Table 5.6 summarizes the possibilities.
  • #60: In the previous solution, readers have priority. Once a single reader has begun to access the data area, it is possible for readers to retain control of the data area as long as there is at least one reader in the act of reading. Therefore, writers are subject to starvation. Figure 5.23 shows a solution that guarantees that no new readers are allowed access to the data area once at least one writer has declared a desire to write. For writers, the following semaphores and variables are added to the ones already defined: • A semaphore rsem that inhibits all readers while there is at least one writer desiring access to the data area • A variable writecount that controls the setting of rsem • A semaphore y that controls the updating of writecount
  • #61: An alternative solution, which gives writers priority and which is implemented using message passing, is shown in Figure 5.24 . In this case, there is a controller process that has access to the shared data area. Other processes wishing to access the data area send a request message to the controller, are granted access with an “OK” reply message, and indicate completion of access with a “finished” message. The controller is equipped with three mailboxes, one for each type of message that it may receive. The controller process services write request messages before read request messages to give writers priority. In addition, mutual exclusion must be enforced. To do this the variable count is used, which is initialized to some number greater than the maximum possible number of readers. In this example, we use a value of 100. The action of the controller can be summarized as follows: • If count > 0, then no writer is waiting and there may or may not be readers active. Service all “finished” messages first to clear active readers. Then service write requests and then read requests. • If count = 0, then the only request outstanding is a write request. Allow the writer to proceed and wait for a “finished” message. If count < 0, then a writer has made a request and is being made to wait to clear all active readers. Therefore, only “finished” messages should be serviced.
  • #62: Summary of Chapter 5.