Download as pdf or txt
Download as pdf or txt
You are on page 1of 42

21CSC202J - OPERATING SYSTEMS

Unit-2 – Process Management


Process Concept, Process Scheduling, Operations on Processes, Interprocess Communication,
Communication in Client– Server Systems, Threads: Multicore Programming, Multithreading
Models, Thread Libraries, Implicit Threading, Threading Issues. Process Synchronization: The
Critical-Section Problem, Peterson’s Solution, Synchronization Hardware, Mutex Locks,
Semaphores, Classic Problems of Synchronization, Monitors.

Process Concept
• A process is a program in execution.
• A program is a passive entity where as a process is an active entity.
• A program becomes a process when an executable file is loaded into memory
• Each process is represented in the operating system by a process control block (PCB)-
also called a task control block.
Process States:
• As a process executes, it changes state.
• The state of a process is defined in part by the current activity of that process. Process
State Transition Diagram is shown in Figure 1.
• Each process may be in one of the following states:
➢ New: The process is being created.
➢ Running: Instructions are being executed.
➢ Waiting: The process is waiting for some event to occur (such as an I/O completion
or reception of a signal).
➢ Ready: The process is waiting to be assigned to a processor.
➢ Terminated: The process has finished execution.

Figure 2.1. Process State Transition Diagram.

PREPARED BY DR J FARITHA BANU 1


Process Control Block
• Each process is represented in the operating system by a process control block (PCB)-
also called a task control block. Process Control Block is shown in figure 2.2
• A PCB defines a process to the operating system.
• It contains the entire information about a process.
• Some of the information a PCB contains are:
Process state: The state may be new, ready, running, waiting, halted, and So on.
Program counter: The counter indicates the address of the next instruction to be executed for
this process.
CPU registers: The registers vary in number and type, depending on the computer architecture.
They include accumulators, index registers, stack pointers, and general-purpose registers, plus
any condition-code information. Along with the program counter, this state information must
be saved when an interrupt occurs, to allow the process to be continued correctly afterward as
shown in Figure 2.3.
CPU-scheduling information: This information includes a process priority, pointers to
scheduling queues, and any other scheduling parameters.
Memory-management information: value of the base and limit registers, the page tables, or
the segment tables, depending on the memory system used by the operating system.
Accounting information: This information includes the amount of CPU and real time used,
time limits, account numbers, job or process numbers, and so on.
Status information: The information includes the list of I/O devices allocated to this process,
a list of open files, and so on.

Figure 2.2 Process Control Block.

PREPARED BY DR J FARITHA BANU 2


Figure 2.3. CPU switch from process to process
Threads
The single thread of control allows the process to perform only one task at a time. Most modern
operating systems have extended the process concept to allow a process to have multiple
threads of execution and thus to perform more than one task at a time. This feature is especially
beneficial on multicore systems, where multiple threads can run in parallel. On a system that
supports threads, the PCB is expanded to include information for each thread.
Process Scheduling
The process scheduling is the activity of the process manager that handles the removal of the
running process from the CPU and the selection of another process on the basis of a particular
strategy.
Process scheduling is an essential part of a Multiprogramming operating systems. Such
operating systems allow more than one process to be loaded into the executable memory at a
time and the loaded process shares the CPU using time multiplexing.

PREPARED BY DR J FARITHA BANU 3


Scheduling Queues
There are 3 types of scheduling queues. They are
1. Job Queue
2. Ready Queue
3. Device Queue
• As processes enter the system, they are put into a job queue.
• The processes that are residing in main memory and are ready and waiting to execute are
kept on a list called the ready queue.
• The list of processes waiting for an I/O device is kept in a device queue for that particular
device.
• A new process is initially put in the ready queue. It waits in the ready queue until it is
selected for execution (or dispatched).
• Once the process is assigned to the CPU and is executing, one of several events could
occur:
o The process could issue an I/O request, and then be placed in an I/O queue.
o The process could create a new subprocess and wait for its termination.
o The process could be removed forcibly from the CPU, as a result of an interrupt,
and be put back in the ready Queue.
• A common representation of process scheduling is a queueing diagram. It is shown in
figure 2.4.

Figure 2.4. Queuing Diagram Representation of Process Scheduling

Schedulers
• A process migrates between the various scheduling queues throughout its lifetime.
• The operating system must select, for scheduling purposes, processes from these queues
in some fashion.
• The selection process is carried out by the appropriate scheduler.
• There are three different types of schedulers. They are:
1. Long-term Scheduler or Job Scheduler
2. Short-term Scheduler or CPU Scheduler
3. Medium term Scheduler
PREPARED BY DR J FARITHA BANU 4
• The long-term scheduler, or job scheduler, selects which processes should be brought
into the ready queue from the pool and loads them into memory for execution. It is invoked
very infrequently. It controls the degree of multiprogramming.
• The short-term scheduler, or CPU scheduler, selects from among the processes that are
ready to execute, and allocates the CPU to one of them. It is invoked very frequently.
• Processes can be described as either I/O bound or CPU bound.
• An I\O-bound process spends more of its time doing I/O than it spends doing
computations.
• A CPU-bound process, on the other hand, generates I/O requests infrequently,using more
of its time doing computation than an I/O-bound process uses.
• The system with the best performance will have a combination of CPU-bound and I/O-
bound processes.

Medium term Scheduler

• Some operating systems, such as time-sharing systems, may introduce an additional,


intermediate level of scheduling. It is known as process swapping scheduler.
• The key idea is medium-term scheduler, to decrease the degree of multiple programming.
It is responsible for swapping of a process from the Main Memory to Secondary Memory
and vice-versa. This scheme is called swapping. Medium term scheduling is shown in
figure 2.5.

Figure 2.5. Medium term Scheduling

Context switch: CPU switch from process to process

A context switching is a process that involves switching of the CPU from one process or task
to another. In this phenomenon, the execution of the process that is present in the running state
is suspended by the kernel and another process that is present in the ready state is executed by
the CPU.

PREPARED BY DR J FARITHA BANU 5


The context of the process should be saved before putting any other process in the running
state.

It is one of the essential features of the multitasking operating system. The processes are
switched so fastly that it gives an illusion to the user that all the processes are being executed
at the same time.

A context is the contents of a CPU's registers and program counter at any point in time. Context
switching can happen due to the following reasons:

• When a process of high priority comes in the ready state. In this case, the execution of
the running process should be stopped and the higher priority process should be given
the CPU for execution.
• When an interruption occurs then the process in the running state should be stopped
and the CPU should handle the interrupt before doing something else.
• When a transition between the user mode and kernel mode is required then you have to
perform the context switching.

Steps involved in Context Switching

The process of context switching involves a number of steps. The figure 2.6 depicts the process
of context switching between the two processes P0 and P1.

Figure 2.6. CPU Context switch

PREPARED BY DR J FARITHA BANU 6


In the above figure, you can see that initially, the process P0 is in the running state and the
process P1 is in the ready state. Now, when some interruption occurs then you have to switch
the process P0 from running to the ready state after saving the context and the process P1 from
ready to running state. The following steps will be performed:

1. Firstly, the context of the process P0 i.e. the process present in the running state will be
saved in the Process Control Block of process P0 i.e. PCB0.
2. Now, you have to move the PCB0 to the relevant queue i.e. ready queue, I/O queue,
waiting queue, etc.
3. From the ready state, select the new process that is to be executed i.e. the process P1.
4. Now, update the Process Control Block of process P1 i.e. PCB1 by setting the process
state to running. If the process P1 was earlier executed by the CPU, then you can get
the position of last executed instruction so that you can resume the execution of P1.

Similarly, if you want to execute the process P0 again, then you have to follow the same steps
as mentioned above (from step 1 to 4).

Operations on Processes
1. Process Creation
2. Process Termination
3. Mechanisms involved in process creation on UNIX – illustration of fork(),wait(),exit()
Process Creation
• A process may create several new processes, during the course of execution.
• The creating process is called a parent process, whereas the new processes are called the
children of that process.
• When a process creates a new process, two possibilities exist in terms of execution:
o The parent continues to execute concurrently with its children.
o The parent waits until some or all of its children have terminated.
• There are also two possibilities in terms of the address space of the new process:
o The child process is a duplicate of the parent process.
o The child process has a program loaded into it.
• In UNIX, each process is identified by its process identifier, which is a unique integer. A
new process is created by the fork system call.
Process Termination
• A process terminates when it finishes executing its final statement and asks the operating
system to delete it by using the exit system call.

PREPARED BY DR J FARITHA BANU 7


• At that point, the process may return data (output) to its parent process (via the wait system
call).
• A process can cause the termination of another process via an appropriate system call.
• A parent may terminate the execution of one of its children for a variety of reasons, such
as these:
o The child has exceeded its usage of some of the resources that it has been
allocated.
o The task assigned to the child is no longer required.
o The parent is exiting, and the operating system does not allow a child to continue
if its parent terminates. On such systems, if a process terminates (either
normally or abnormally), then all its children must also be terminated. This
phenomenon, referred to as cascading termination, is normally initiated by the
operating system.
Understanding the system calls – fork(),wait(),exit()
Process creation using the fork() system call is shown in Figure 2.7.
Fork() System Call
• Process creation happens through the use of fork() system call, which creates a new
process(child process) by duplicating an existing one(parent process).
• The process that calls fork() is the parent, whereas the new process is the child.
• A non-zero value(Process ID of child) is returned to the parent.
• A value of zero is returned to the child.
• In case the child is not created successfully due to any issues like low memory, -1 is
returned to the fork().
• After the fork(), both parent and child share the memory address space of the parent.
Exec System Call
• The exec family of functions replaces the current running program (executable) with a new
executable code.
• This is very useful when you want the child process to run a different program than the
parent.
• If the code that the child will execute is within the program associated with parent, exec()
is not needed.

PREPARED BY DR J FARITHA BANU 8


Wait System Call
• A process may wait on another process to complete its execution. The parent process may
issue a wait system call, which suspends the execution of the parent process while the child
executes. When the child process terminates, it returns an exit status to the operating
system, which is then returned to the waiting parent process. The parent process then
resumes execution.
• Why does parent waits for a child process?
• The parent can assign a task to it’s child and wait till it completes it’s task. Then it can
carry some other work.
• Once the child terminates, all the resources associated with child are freed except for the
process control block. Now, the child is in zombie state. Using wait(), parent can inquire
about the status of child and then ask the kernel to free the PCB.
• If a process exits with children still running (and doesn't kill its children), those children
are orphans. Orphaned children are immediately "adopted" by init system call.
• Zombie state: On Unix and Unix-like computer operating systems, a zombie process or
defunct process is a process that has completed execution (via the exit system call) but still
has an entry in the process table: it is a process in the "Terminated state". This occurs for
the child processes. Zombies only occupy space in the process table. They take no memory
or CPU.
Exit System Call
• A computer process terminates its execution by making an exit system call. When the child
process terminates (“dies”), either normally by calling exit, or abnormally due to a fatal
exception or signal (e.g., SIGTERM, SIGKILL), an exit status is returned to the operating
system and a SIGCHLD signal is sent to the parent process. The exit status can then be
retrieved by the parent process via the wait system call.
• Exit system call is not always implicit in a program. A process can also terminate/return if
control reaches the end of the function.

Figure 2.7. Process creation using the fork() system call.


PREPARED BY DR J FARITHA BANU 9
Example program 1:

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>

int main() {
fork();
printf("invoked fork() system call\n");
return 0;
}

Output:
invoked fork() system call
invoked fork() system call

// One printf statement, but output printed 2 times, parent 1 time and child 1 time, if you
invoke one more fork(), then one more time “invoked fork() system call”
will be printed.

Example program 2:

#include <stdio.h>
#include <stdlib.h>

void exitfunc() {
printf("Invoked cleanup function - exitfunc()\n");
return;
}

int main() {
atexit(exitfunc);
printf("Hello, World!\n");
exit (0);
}
Output:
Hello, World!
Invoked cleanup function - exitfunc()

Interprocess Communication
Inter-process communication (IPC) is a mechanism that allows processes to communicate with
each other and synchronize their actions. It enables resource and data sharing between the
processes without interference. Processes that execute concurrently in the operating system
may be either independent processes or cooperating processes.

PREPARED BY DR J FARITHA BANU 10


• Independent process does not share data with any other process.
• Cooperating process can affect or be affected by the other processes executing in the
system. That is any process that shares the data with another process is called a cooperative
process.
• Thus, Cooperating processes require an Interprocess communication (IPC) mechanism
that will allow them to exchange data and information.
Reasons for Process Cooperation
1. Information sharing − Several users are interested in sharing same piece of
information.
2. Computation speeds up − break task into subtasks, executing in parallel
3. Modularity − A system can be constructed in a modular fashion dividing the system
functions into separate processes or threads.
4. Convenience − An individual user may work on many tasks at the same time. For
example, a user may be editing, compiling, and printing in parallel.
There are two fundamental models of interprocess communication:
1. Shared memory
2. Message passing.
• In the shared-memory model, a region of memory that is shared by cooperating processes
is established. Processes can then exchange information by reading and writing data to the
shared region.
• In the message-passing model, communication takes place by means of messages
exchanged between the cooperating processes.
• Message passing is useful for exchanging smaller amounts of data, because no conflicts
need be avoided. Message passing is also easier to implement in a distributed system than
shared memory.

Figure 2.7. Communications models. (a) Message passing. (b) Shared memory.

PREPARED BY DR J FARITHA BANU 11


• Shared memory can be faster than message passing,
• Since message-passing systems are typically implemented using system calls. and thus
require the more time-consuming task of kernel intervention.
• In shared-memory systems, system calls are required only to establish shared memory
regions. Once shared memory is established, all accesses are treated as routine memory
accesses, and no assistance from the kernel is required. Shared memory suffers from cache
coherency issues, which arise because shared data migrate among the several caches.

IPC 1: Shared-Memory Systems


To illustrate the concept of cooperating processes, let’s consider the producer–consumer
problem, which is a common paradigm for cooperating processes.

Example: Producer – Consumer Problem

• A producer process produces information that is consumed by a consumer process.


• For example, a print program produces characters that are consumed by the printer driver.
A compiler may produce assembly code, which is consumed by an assembler.
• To allow producer and consumer processes to run concurrently, we must have available a
buffer of items that can be filled by the producer and emptied by the consumer.
• A producer can produce one item while the consumer is consuming another item. The
producer and consumer must be synchronized, so that the consumer does not try to
consume an item that has not yet been produced.
• Two types of buffers can be used
o unbounded-buffer: places no practical limit on the size of the buffer.
o bounded-buffer: assumes that there is a fixed buffer size.

The following variables reside in a region of memory shared by the producer and consumer
processes:
Shared data
#define BUFFER_SIZE 10
typedef struct {
...
} item;
item buffer[BUFFER_SIZE];
int in = 0;
int out = 0;

• The shared buffer is implemented as a circular array with two logical pointers: in and
out. The variable in points to the next free position in the buffer; out points to the first
full position in the buffer. The buffer is empty when in == out ; the buffer is full when
((in + 1) % BUFFERSIZE) == out.

The producer process using shared memory


Producer Process
item next produced;
PREPARED BY DR J FARITHA BANU 12
while (true)
{ /* produce an item in next produced */
while (((in + 1) % BUFFER_SIZE) == out);
/* do nothing */
buffer[in] = nextProduced;
in = (in + 1) % BUFFER_SIZE;
}

The consumer process using shared memory


Consumer process

item next consumed;


while (true)
{
while (in == out);
/* do nothing */
nextConsumed = buffer[out];
out = (out + 1) % BUFFER_SIZE;
/* consume the item in next consumed */
}
• local variable next produced in which the new item to be produced is stored. The
consumer process has a local variable next consumed in which the item to be consumed
is stored.

IPC 2: Message-Passing Systems

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


their actions without sharing the same address space. It is particularly useful in a distributed
environment, where the communicating processes may reside on different computers
connected by a network.
• A message-passing facility provides at least two operations:
o send(message)
o receive(message)
• Messages sent by a process can be either fixed or variable in size. If only fixed-sized
messages can be sent, the system-level implementation is straightforward.
• If processes P and Q want to communicate, they must send messages to and receive
messages from each other: a communication link must exist between them.
• Several methods for logically implementing a link and the send()/ receive() operations
are:
1. Naming - Direct or indirect communication
2. Buffering - Automatic or explicit buffering
3. Synchronization - Synchronous or asynchronous communication

1. Naming - Direct or indirect communication:


• Processes that want to communicate must have a way to refer to each other. They can use
either direct or indirect communication.
PREPARED BY DR J FARITHA BANU 13
• Direct Communication
o Each process that wants to communicate must explicitly name the recipient or sender
of the communication.
o Link: A link is established automatically between every pair of processes that want
to communicate. The processes need to know only each other’s identity to
communicate.
o A link is associated with exactly two processes.
o Two ways of addressing in direct Communication:
▪ Symmetry in addressing
▪ Asymmetry in addressing
o In symmetry in addressing, the send and receive primitives are defined as:
▪ send(P, message) - Send a message to process P
▪ receive(Q, message) - Receive a message from Q
o In asymmetry in addressing, the send & receive primitives are defined as:
▪ send (p, message) - send a message to process p
▪ receive (id, message) - receive message from any process, id is set to the
name of the process with which communication has taken place
• Indirect Communication
o With indirect communication, the messages are sent to and received from mailboxes,
or ports.
o The send and receive primitives are defined as follows:
▪ send (A, message) - Send a message to mailbox A.
▪ receive (A, message) - Receive a message from mailbox A.
o A communication link has the following properties:
▪ A link is established between a pair of processes only if both members of
the pair have a shared mailbox.
▪ A link may be associated with more than two processes.
▪ A number of different links may exist between each pair of communicating
processes, with each link corresponding to one mailbox.

2. Buffering - Automatic or explicit buffering


• A link has some capacity that determines the number of messages that can reside in it
temporarily. This property can be viewed as a queue of messages attached to the link.
• There are three ways that such a queue can be implemented.
o Zero capacity: Queue length of maximum is 0. No message is waiting in a queue.
The sender must wait until the recipient receives the message. (Message system with
no buffering)
o Bounded capacity: The queue has finite length n. Thus, at most n messages can
reside in it.
o Unbounded capacity: The queue has potentially infinite length. Thus, any number
of messages can wait in it. The sender is never delayed

3. Synchronization - Synchronous or asynchronous communication


• Message passing may be either blocking or nonblocking— also known as synchronous
and asynchronous.

PREPARED BY DR J FARITHA BANU 14


• Blocking Send - The sender blocks itself till the message sent by it is received by the
receiver.
• Non-blocking Send - The sender does not block itself after sending the message but
continues with its normal operation.
• Blocking Receive - The receiver blocks itself until it receives the message.
• Non-blocking Receive – The receiver does not block itself.

Communication in Client– Server Systems


There are three main strategies in client-server communication: sockets, remote procedure calls
(RPCs), and pipes. Usually multiple clients in communication with a single server. The clients
send requests to the server and the server responds to the client requests.
Sockets
Two processes communicating over a network often use a pair of connected sockets as a
communication channel. Software that is designed for client-server operation may also use
sockets for communication between two processes running on the same computer. A socket is
identified by an IP address concatenated with a port number, e.g. 200.100.50.5:80. Servers
implementing specific services (such as telnet, FTP, and HTTP) listen to well-known ports (a
telnet server listens to port 23; an FTP server listens to port 21; and a web, or HTTP, server
listens to port 80). All ports below 1024 are considered well known; we can use them to
implement standard services.
When a client process initiates a request for a connection, it is assigned a port by its host
computer. This port has some arbitrary number greater than 1024. For example, if a client on
host X with IP address 146.86.5.20 wishes to establish a connection with a web server (which
is listening on port 80) at address 161.25.19.8, host X may be assigned port 1625. The
connection will consist of a pair of sockets: (146.86.5.20:1625) on host X and (161.25.19.8:80)
on the web server. This situation is illustrated in Figure 2.8.
Java provides three different types of sockets.
1. Connection-oriented (TCP) sockets are implemented with the Socket class.
2. Connectionless (UDP) sockets use the DatagramSocket class.
3. MulticastSocket class is a subclass of the DatagramSocket class. A multicast socket
allows data to be sent to multiple recipients

Figure 2.8. Client server communication using sockets

PREPARED BY DR J FARITHA BANU 15


Remote Procedure Calls
These are interprocess communication techniques that are used for client-server based
applications. A remote procedure call is also known as a subroutine call or a function call. RPC
is to make procedure calls similarly to calling on ordinary local procedures, except the
procedure being called lies on a remote machine.
Implementation of RPC involves stubs on either end of the connection.
• The local process calls on the stub, much as it would call upon a local procedure.
• The RPC system packages up (marshals) the parameters to the procedure call, and
transmits them to the remote system.
• On the remote side, the RPC daemon accepts the parameters and calls upon the appropriate
remote procedure to perform the requested work.
• Any results to be returned are then packaged up and sent back by the RPC system to the
local system, which then unpackages them and returns the results to the local calling
procedure.
One potential difficulty is the formatting of data on local versus remote systems. (e.g. big-
endian versus little-endian) The resolution of this problem generally involves an agreed-upon
intermediary format, such as XDR (external data representation).

Figure 2.9. Execution of a remote procedure call (RPC).

PREPARED BY DR J FARITHA BANU 16


Another issue is identifying which procedure on the remote system a particular RPC is destined
for.
• Remote procedures are identified by ports, though not the same ports as the socket ports
described earlier.
• One solution is for the calling procedure to know the port number they wish to
communicate with on the remote system. This is problematic, as the port number would
be compiled into the code, and it makes it break down if the remote system changes their
port numbers.
• More commonly a matchmaker process is employed, which acts like a telephone directory
service. The local process must first contact the matchmaker on the remote system (at a
well-known port number), which looks up the desired port number and returns it. The local
process can then use that information to contact the desired remote procedure. This
operation involves an extra step, but is much more flexible. An example of the matchmaker
process is illustrated in Figure 2.9.
One common example of a system based on RPC calls is a networked file system. Messages
are passed to read, write, delete, rename, or check status, as might be made for ordinary local
disk access requests.
Pipes
Pipes are one of the earliest and simplest channels of communications between processes (in
UNIX). The two different types of pipes are ordinary pipes and named pipes. There are four
key considerations in implementing pipes:
• Unidirectional or Bidirectional communication?
• Is bidirectional communication half-duplex or full-duplex?
• Must a relationship such as parent-child exist between the processes?
• Can pipes communicate over a network, or only on the same machine?
Ordinary Pipes
Ordinary pipes are uni-directional, with a reading end and a writing end. If bidirectional
communications are needed, then a second pipe is required.
• In UNIX ordinary pipes are created with the system call "int pipe(int fd [ ])". The return
value is 0 on success, -1 if an error occurs.
• The int array must be allocated before the call, and the values are filled in by the pipe
system call:
o fd[ 0 ] is filled in with a file descriptor for the reading end of the pipe
o fd[ 1 ] is filled in with a file descriptor for the writing end of the pipe
• UNIX pipes are accessible as files, using standard read( ) and write( ) system calls.
• Ordinary pipes are only accessible within the process that created them. Typically a parent
creates the pipe before forking off a child. When the child inherits open files from its
parent, including the pipe file(s), a channel of communication is established.
• Each process (parent and child ) should first close the ends of the pipe that they are not
using. For example, if the parent is writing to the pipe and the child is reading, then the

PREPARED BY DR J FARITHA BANU 17


parent should close the reading end of its pipe after the fork and the child should close the
writing end.

Figure 2.10. File descriptors for an ordinary pipe


Named Pipes
Named pipes support bidirectional communication, communication between non parent-child
related processes, and persistence after the process which created them exits. Multiple
processes can also share a named pipe, typically one reader and multiple writers.
In UNIX, named pipes are termed FIFOs, and appear as ordinary files in the file system. A
FIFO is created with the mkfifo() system call and manipulated with the ordinary open(), read(),
write(), and close() system calls. It will continue to exist until it is explicitly deleted from the
file system. Although FIFOs allow bidirectional communication, only half-duplex transmission
is permitted. If data must travel in both directions, two FIFOs are typically used. UNIX named
pipes still require that all processes be running on the same machine. Otherwise sockets are
used.
Windows named pipes provide richer communications. Full-duplex is supported. Processes
may reside on the same or different machines Created and manipulated using
CreateNamedPipe( ), ConnectNamedPipe( ), ReadFile( ),and WriteFile( ).

Threads
A thread is a basic unit of CPU utilization. A thread is a flow of execution through the process
code. It comprises a thread ID, a program counter (keeps track of which instruction to execute
next), a register set(hold its current working variables), and a stack (execution history).
A thread shares with its peer threads few information like code segment, data segment and open
files. When one thread alters a code segment memory item, all other threads see that.
A thread is also called a lightweight process. Threads provide a way to improve application
performance through parallelism. Each thread belongs to exactly one process and no thread can
exist outside a process. Each thread represents a separate flow of control. A traditional (or
heavyweight) process has a single thread of control. Now a days process has many threads to
perform more than one task at a time. Figure 2.11 illustrates the difference between a traditional
single-threaded process and a multithreaded process.

PREPARED BY DR J FARITHA BANU 18


Figure 2.11. Single-threaded and multithreaded processes
Most software applications that run on modern computers are multithreaded. A web browser
might have one thread display images or text while another thread retrieves data from the
network, for example. A word processor may have a thread for displaying graphics, another
thread for responding to keystrokes from the user, and a third thread for performing spelling
and grammar checking in the background.
Advantages of Thread:
• Threads minimize the context switching time.
• Use of threads provides concurrency within a process.
• Efficient communication.
• It is more economical to create and context switch threads.
• Threads allow utilization of multiprocessor architectures to a greater scale and
efficiency.
Types of Thread
Threads are implemented in following two ways −
• User Level Threads − User managed threads.
• Kernel Level Threads − Operating System managed threads acting on kernel, an
operating system core.
Difference between Process and Thread
Process Thread
Process is heavy weight or resource Thread is light weight, taking lesser
intensive. resources than a process.
Process switching needs interaction with Thread switching does not need to interact
operating system. with operating system.

PREPARED BY DR J FARITHA BANU 19


Multicore Programming
multiple computing cores on a single chip. Each core appears as a separate processor to the
operating system. Whether the cores appear across CPU chips or within CPU chips, these
systems are known as multicore or multiprocessor systems.
On a system with a single computing core, concurrency merely means that the execution of the
threads will be interleaved over time, it is shown in figure 2.12. Because the processing core is
capable of executing only one thread at a time.

Figure 2.12. Concurrent execution on a single-core system


On a system with multiple cores, however, concurrency means that the threads can run in
parallel, because the system can assign a separate thread to each core, it is shown in figure 2.13.
A system is parallel if it can perform more than one task simultaneously (parallelism). In
contrast, a concurrent system supports more than one task by allowing all the tasks to make
progress. Thus, it is possible to have concurrency without parallelism.

Figure 2.13. Parallel execution on a multicore system


Programming Challenges
• Identifying tasks: - tasks that can be divided into separate, concurrent tasks, independent
of one another and thus can run in parallel on individual cores.
• Balance: - programmers must also ensure that the tasks perform equal work of equal value.
• Data splitting: - The data accessed and manipulated by the tasks must be divided to run on
separate cores.
• Data dependency: - When one task depends on data from another, programmers must
ensure that the execution of the tasks is synchronized to accommodate the data
dependency.
• Testing and debugging: - Testing and debugging concurrent programs in multiple core is
inherently more difficult than testing and debugging single-threaded applications.
Types of Parallelism
In general, there are two types of parallelism: data parallelism and task parallelism.
• Data parallelism involves the distribution of data across multiple cores. Example: On a
single-core system, one thread would simply sum the elements [0] . . . [N − 1]. On a dual-
core system, however, thread A, running on core 0, could sum the elements [0] . . . [N/2 −

PREPARED BY DR J FARITHA BANU 20


1] while thread B, running on core 1, could sum the elements [N/2] . . . [N − 1]. The two
threads would be running in parallel on separate computing cores.
• Task parallelism on the distribution of tasks across multiple cores. Each thread is
performing a unique operation.

Multithreading Models
Some operating system provide a combined user level thread and Kernel level thread facility.
Solaris is a good example of this combined approach. In a combined system, multiple threads
within the same application can run in parallel on multiple processors and a blocking system
call need not block the entire process. Multithreading models are three types. They are
• Many to many Model
• Many to one Model
• One to one Model
Many to Many Model
The many-to-many model multiplexes any number of user threads onto an equal or smaller
number of kernel threads is shown in figure 2.14.a and 2.14.b.. The figure 2.14a. shows the
many-to-many threading model where 6 user level threads are multiplexing with 6 kernel level
threads. In this model, developers can create as many user threads as necessary and the
corresponding Kernel threads can run in parallel on a multiprocessor machine. This model
provides the best accuracy on concurrency and when a thread performs a blocking system call,
the kernel can schedule another thread for execution.

Figure 2.14.a. Many to many model with 6 thread Figure 2.14.b Many-to-many model.

PREPARED BY DR J FARITHA BANU 21


Many to One Model
Many-to-one model maps many user level threads to one Kernel-level thread, shown in figure
2.15. Thread management is done in user space by the thread library. When thread makes a
blocking system call, the entire process will be blocked. Only one thread can access the Kernel
at a time, so multiple threads are unable to run in parallel on multiprocessors.
If the user-level thread libraries are implemented in the operating system in such a way that the
system does not support them, then the Kernel threads use the many-to-one relationship modes.

Figure 2.15. Many-to-one model


One to One Model
There is one-to-one relationship of user-level thread to the kernel-level thread, shown in figure
2.16. This model provides more concurrency than the many-to-one model. It also allows
another thread to run when a thread makes a blocking system call. It supports multiple threads
to execute in parallel on microprocessors.
Disadvantage of this model is that creating user thread requires the corresponding Kernel
thread. OS/2, windows NT and windows 2000 use one to one relationship model.

Figure 2.16. One-to-one model

PREPARED BY DR J FARITHA BANU 22


Thread Libraries
A thread library provides the programmer with an API for creating and managing threads. There
are two primary ways of implementing a thread library.
• User Level Threads − User managed threads.
• Kernel Level Threads − Operating System managed threads acting on kernel, an
operating system core.
The first approach is to provide a library entirely in user space with no kernel support. All code
and data structures for the library exist in user space. This means that invoking a function in
the library results in a local function call in user space and not a system call.
The second approach is to implement a kernel-level library supported directly by the operating
system. In this case, code and data structures for the library exist in kernel space. Invoking a
function in the API for the library typically results in a system call to the kernel.
Three main thread libraries are in use today:
• POSIX Pthreads
• Windows
• Java.
Pthreads, the threads extension of the POSIX standard, may be provided as either a user-level
or a kernel-level library.
The Windows thread library is a kernel-level library available on Windows systems.
The Java thread API allows threads to be created andmanaged directly in Java programs.
Asynchronous threading and Synchronous threading
• With asynchronous threading, once the parent creates a child thread, the parent resumes its
execution, so that the parent and child execute concurrently. Each thread runs
independently of every other thread, and the parent thread need not know when its child
terminates. Because the threads are independent, there is typically little data sharing
between threads.
• Synchronous threading occurs when the parent thread creates one or more children and
then must wait for all of its children to terminate before it resumes —the so-called fork-
join strategy.
Example Program for Pthread:
In this example, we will be creating a thread to perform a task. In this task, we will display the
sequence numbers from 1 to 5. The focus of this recipe is to learn how a thread is created and
how the main thread is asked to wait until the thread finishes its task.
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

void *runThread(void *arg)


{ int i; printf("Running Thread \n");
PREPARED BY DR J FARITHA BANU 23
for(i=1;i<=5;i++) printf("%d\n",i); return NULL; }

int main()
{ pthread_t tid;
printf("In main function\n");
pthread_create(&tid, NULL, runThread, NULL);
pthread_join(tid, NULL);
printf("Thread over\n"); return 0;
}
Output:
In main function
Running Thread
1
2
3
4
5
Thread over

Implicit Threading
To address the difficulties and better support the design of multithreaded applications is to
transfer the creation and management of threading from application developers to compilers
and run-time libraries.
Implicit threading is mainly the use of libraries or other language support to hide the creation
and management of threads from application developers. The most common implicit threading
library is OpenMP, in context of C.
Thread Pools
Whenever the server receives a request, it creates a separate thread to service the request.
Unlimited threads could exhaust system resources. One solution to this problem is to use a
thread pool.
The general idea behind a thread pool is to create a number of threads at process startup and
place them into a pool, where they sit and wait for work. When server receives a request, it
awakens a thread from this pool, and pass it the request for service. Once the thread completes
its service, it returns to the pool and awaits more work. If the pool contains no available thread,
the server waits until one becomes available.
Thread pool benefits
• Servicing a request with an existing thread is faster than waiting to create a thread.
• A thread pool limits the number of threads that exist at any one point.
• Separating the task to be performed from the mechanics of creating the task allows us
to use different strategies for running the task.

PREPARED BY DR J FARITHA BANU 24


OpenMP
OpenMP is a set of compiler directives as well as an API for programs written in C, C++, or
FORTRAN that provides support for parallel programming in shared-memory environments.
OpenMP identifies parallel regions as blocks of code that may run in parallel. Application
developers insert compiler directives into their code at parallel regions, and these directives
instruct the OpenMP run-time library to execute the region in parallel. The following C
program illustrates a compiler directive above the parallel region containing the printf()
statement:
#include <omp.h>
#include <stdio.h>
int main(int argc, char *argv[]){
/* sequential code */
#pragma omp parallel{
printf("I am a parallel region.");
}
/* sequential code */
return 0;
}
Output:
I am a parallel region.
When OpenMP encounters the directive #pragma omp parallel, It creates as many threads
according to the processing cores in the system. Thus, for a dual-core system, two threads are
created, for a quad-core system, four are created; and so forth. Then all the threads
simultaneously execute the parallel region.

Threading Issues
we discuss some of the issues to consider in designing multithreaded programs.
• The fork() and exec() System Calls
• Thread Cancellation
• Signal Handling
• Thread Pool
• Thread Specific Data

1. fork() and exec() System Calls


Consider that a thread of the multithreaded program has invoked the fork(). So, the fork() would
create a new duplicate process. Here the issue is whether the
• Here the issue is, new duplicate process created by fork() will duplicate all the threads
of the parent process or the duplicate process would be single-threaded.

PREPARED BY DR J FARITHA BANU 25


Solution: There are two versions of fork() in some of the UNIX systems. Either the fork() can
duplicate all the threads of the parent process in the child process or the fork() would only
duplicate that thread from parent process that has invoked it. Which version of fork() must be
used totally depends upon the application.
exec() system call when invoked by a single thread then it replaces the entire program along
with all its threads with the program that is specified in the parameter to exec().
• Here the issue is if the exec() system call is lined up just after the fork() system call
then duplicating all the threads of parent process in the child process by fork() is useless.
In such case, the version of fork() that duplicates only the thread that invoked the fork()
would be appropriate.
• is if the exec() system call is not called after the fork(), then the process should
duplicate all threads.
2. Thread cancellation
Termination of the thread in the middle of its execution it is termed as ‘thread cancellation’.
Let us understand this with the help of an example. Consider that there is a multithreaded
program which has let its multiple threads to search through a database for some information.
However, if one of the thread returns with the desired result the remaining threads will be
cancelled.
Now a thread which we want to cancel is termed as target thread. Thread cancellation can be
performed in two ways:
• Asynchronous Cancellation: In asynchronous cancellation, a thread is employed to
terminate the target thread instantly.
• Deferred Cancellation: In deferred cancellation, the target thread is scheduled to check
itself at regular interval whether it can terminate itself or not.
The issue related to the target threads are listed below:
• What if the resources had been allotted to the cancel target thread?
• What if the target thread is terminated when it was updating the data, it was sharing with
some other thread.
Here the asynchronous cancellation of the thread where a thread immediately cancels the target
thread without checking whether it is holding any resources or not creates troublesome.
However, in deferred cancellation, the thread that indicates the target thread about the
cancellation, the target thread crosschecks its flag in order to confirm that it should it be
cancelled immediately or not. The thread cancellation takes place where they can be cancelled
safely such points are termed as cancellation points by Pthreads.
3. Signal Handling
Signal handling is more convenient in the single-threaded program as the signal would be
directly forwarded to the process. But when it comes to multithreaded program, the issue
arrives to which thread of the program the signal should be delivered.

PREPARED BY DR J FARITHA BANU 26


Let’s say the signal would be delivered to:
• All the threads of the process.
• To some specific threads in a process.
• To the thread to which it applies
• Or you can assign a thread to receive all the signals.
Well, how the signal would be delivered to the thread would be decided, depending upon the
type of generated signal. The generated signal can be classified into two type’s synchronous
signal and asynchronous signal.
• Synchronous signals: are generated / forwarded by the same process, it would be
delivered to the specific thread.
• Asynchronous signals are generated by the event external to the running process thus
the running process receives the signals asynchronously. Thus all threads of the process
receives the signal.
However, the Window operating system does not support the concept of the signal instead it
uses asynchronous procedure call (ACP) which is similar to the asynchronous signal of the
UNIX system.
4. Thread-Local Storage
Threads belonging to a process share the data of the process. However, in some circumstances,
each thread might need its own copy of certain data known as thread-local storage (TLS).
The difference between TLS and local variables is,
• Local variables are visible only during a single function invocation,
• whereas TLS data are visible across function invocations. In some ways, TLS is similar
to static data.
• The difference is that TLS data are unique to each thread. Most thread libraries—including
Windows and Pthreads—provide some form of support for thread-local storage; Java
provides support as well.
5. Scheduler Activations
Many systems implementing either the many-to-many or the two-level model place an
intermediate data structure between the user and kernel threads. This data structure—typically
known as a lightweight process, or LWP is shown in Figure 2.17. To the user-thread library, the
LWP appears to be a virtual processor on which the application can schedule a user thread to
run. Each LWP is attached to a kernel thread, and it is kernel threads that the operating system
schedules to run on physical processors. If a kernel thread blocks (such as while waiting for an
I/O operation to complete), the LWP blocks as well.
One scheme for communication between the user-thread library and the kernel is known as
scheduler activation. It works as follows: The kernel provides an application with a set of
virtual processors (LWPs), and the application can schedule user threads onto an available
virtual processor. Furthermore, the kernel must inform an application about certain events. This
procedure is known as an upcall. Upcalls are handled by the thread library with an upcall
handler, and upcall handlers must run on a virtual processor.

PREPARED BY DR J FARITHA BANU 27


Figure 2.17. Lightweight process (LWP).

Process Synchronization
It is the task of coordinating the execution of processes in such a way that no two processes
can have access to the same shared data and resources at the same time and ensures orderly
execution of the process.
Concurrent access to shared data may result in data inconsistency. Therefore need to ensure
orderly execution of cooperating processes. There are various solutions for the same such as
semaphores, mutex locks, synchronization hardware, etc.
Race condition
Where several processes access and manipulate the same data concurrently and the outcome of
the execution depends on the particular order in which the access takes place, is called a race
condition. To prevent race conditions, concurrent processes must be synchronized.

The Critical-Section Problem


There are n processes that are competing to use some shared data. Each process has a code
segment, called critical section, in which the shared data is accessed. To ensure that when one
process is executing in its critical section, no other process is allowed to execute in its critical
section. Each process must request permission to enter its critical section. The section of code
implementing this request is knowns as entry section. The critical section may be followed
by an exit section. The remaining code is the remainder section. The general structure of a
typical process Pi is shown in Figure 2.18. The entry section and exit section are enclosed in
boxes to highlight these important segments of code.

PREPARED BY DR J FARITHA BANU 28


Figure 2.18. General structure of a typical process Pi.
A solution to the critical-section problem must satisfy the following three requirements
1. Mutual Exclusion - If process Pi is executing in its critical section, then no other
processes can be executing in their critical sections.
2. Progress - If no process is executing in its critical section and some processes wish to
enter their critical sections, then only those processes that are not executing in their
remainder sections can participate in deciding which will enter its critical section next,
and this selection cannot be postponed indefinitely.
3. Bounded Waiting - There exists a bound, or limit, on the number of times that other
processes are allowed to enter their critical sections after a process has made a request
to enter its critical section and before that request is granted.
Peterson’s Solution
A classic software-based solution to the critical-section problem is known as Peterson’s
solution.
The structure of process Pi in Peterson’s solution is as follows:
do
{
flag[i] = true; // when pi wants to enter it sets its
turn = j; flag as true and sets turn = j, gives
the chance to other process.
while (flag[j] && turn == j);
/* while is wait loop */

critical section

flag[i] = false;

remainder section
} while (true);

PREPARED BY DR J FARITHA BANU 29


This solution is restricted to two processes that alternate execution between their critical
sections and remainder sections. The processes are numbered P0 and P1. For convenience, one
process is pi and other one pj and j equals 1 – i. Peterson’s solution requires the two processes
to share two data items –
int turn;
boolean flag[2];
• The variable turn denotes whose turn it is to enter its critical section. i.e., if turn == i,
then process Pi is allowed to execute in its critical section.
• flag array is used to indicate - a process is ready to enter its critical section. For E.g.,
if flag[i] is true, this value indicates that Pi is ready to enter its critical section.
We now prove that this solution is correct. We need to show that
• Mutual exclusion is preserved.
• The progress requirement is satisfied.
• The bounded-waiting requirement is met.
To prove 1, we note that each Pi enters its critical section only if either flag[j] == false or turn
== i. Also note that, if both processes can be executing in their critical sections at the same
time, then flag[0] == flag[1] == true. These two observations indicate that P0 and P1 could not
have successfully executed their while statements at about the same time, since the value of
turn can be either 0 or 1 but cannot be both. Hence, one of the processes — say, Pj — must
have successfully executed the while statement, whereas Pi had to execute at least one
additional statement (“turn == j”). However, at that time, flag[j] == true and turn == j, and this
condition will persist as long as Pj is in its critical section; as a result, mutual exclusion is
preserved.
To prove properties 2 and 3, we note that if a process is stuck in the while loop with the
condition flag[j] == true and turn == j, process Pi can be prevented from entering the
critical section only; this loop is the only one possible. flag[j] will be == false, and Pi can enter
its critical section if Pj is not ready to enter the critical section. If Pj has set, flag[j] = true and
is also executing in its while statement, then either turn == i or turn == j. If turn == i, Pi will
enter the critical section then. Pj will enter the critical section, If turn == j. Although once Pj
exits its critical section, it will reset flag[j] to false, allowing Pi to enter its critical section. Pj
must also set turn to i, if Pj resets flag[j] to true. Hence, since Pi does not change the value of
the variable turn while executing the while statement, Pi will enter the critical section (progress)
after at most one entry by Pj (bounded waiting).

PREPARED BY DR J FARITHA BANU 30


Disadvantage
• Peterson’s solution works for two processes. This solution is also a busy waiting
solution so CPU time is wasted.
• Software-based solutions such as Peterson’s are not guaranteed to work on modern
computer architectures.

int turn boolean flag[2]

Indicates whose turn it is to enter in Indicates process is ready to enter in


critical section critical section

Structure of process Pi Structure of process Pj


do { do {
flag[i] = true;
flag[j] = true;
turn = j;
turn = i;
while (flag[j] && turn == j);
while (flag[i] && turn == i);
critical section
critical section
flag[i] = false;
flag[j] = false;
remainder section
remainder section
} while (true); } while (true);

Figure 2.19. Peterson’s solution for process Pi and Pj

Synchronization Hardware
Understanding the two-process solution and the benefits of the synchronization hardware

Synchronization hardware is a hardware-based solution to resolve the critical section problem.


hardware-based solution for the critical section problem which introduces the hardware
instructions that can be used to resolve the critical section problem effectively. Hardware
solutions are often easier and also improves the efficiency of the system.

The hardware-based solution to critical section problem is based on a simple tool i.e. lock. The
solution implies that before entering into the critical section the process must acquire a lock
and must release the lock when it exits its critical section. Using of lock also prevent the race
condition.

PREPARED BY DR J FARITHA BANU 31


The hardware synchronization provides two kinds of hardware instructions that are TestAndSet
and Swap.
TestAndSet Hardware Instruction:
The TestAndSet() hardware instruction is atomic instruction. Atomic means both the test
operation and set operation are executed in one machine cycle at once. If the two different
processes are executing TestAndSet() simultaneously each on different CPU. Still, they will be
executed sequentially in some random order.
The TestAndSet() instruction can be defined as in the code below:

do { boolean TestAndSet(boolean *target){


while (TestAndSet(&lock)); boolean rv = *target;
// critical section *target = true;
lock = FALSE; return rv;
// remainder section }
} while (TRUE);

Let’s say process P0 wants to enter the critical section it executes the code above which let
while loop invokes TestAndSet() instruction. Using the TestAndSet() instruction the P0
modifies the lock value to true to acquire the lock and enters the critical section. Now, when
P0 is already in its critical section process P1 also wants to enter in its critical section. So it
will execute the do-while loop and invoke TestAndSet() instruction only to see that the lock is
already set to true which means some process is in the critical section which will make P1
repeat while loop unless P0 turns the lock to false.

Once the process P0 complete executing its critical section its will turn the lock variable to
false. Then P1 can modify the lock variable to true using TestAndSet() instruction and enter its
critical section. This is how you can achieve mutual exclusion with the do-while structure
above i.e. it let only one process to execute its critical section at a time.

PREPARED BY DR J FARITHA BANU 32


Test and Set Lock boolean TestAndSet(boolean *target){

boolean rv = *target;

*target = true;

return rv;

The definition of TestAndSet() struction

do { do {
while (TestAndSet(&lock)); Process while (TestAndSet(&lock));
P1
// critical section // critical section
lock = FALSE; lock = FALSE;
Process
// remainder section // remainder section
P2
} while (TRUE); } while (TRUE);

Figure 2.20. Test and set Lock - solution for process P1 and P2
Compare and Swap or Swap Hardware Instruction:
Like TestAndSet() instruction the swap() hardware instruction is also an atomic instruction.
With a difference that it operates on two variables provided in its parameter. The structure of
swap() instruction is:

do {
key = TRUE;
while (key == TRUE)
Swap(&lock, &key);
// critical section
lock = FALSE;
// remainder section
} while (TRUE);

The structure above operates on one global shared Boolean variable lock and another local
Boolean variable key. Both of which are initially set to false. The process P0 interested in
executing its critical section execute code above and set lock as true and enter its critical
section. Thus refrain other processes from executing their critical section satisfying mutual
exclusion.

PREPARED BY DR J FARITHA BANU 33


Advantages of Hardware Instruction
• Hardware instructions are easy to implement and improves the efficiency of the
system.
• Supports any number of processes may it be on the single or multiple processor
system.
• With hardware instructions, you can implement multiple critical sections each defined
with a unique variable.

Disadvantages of Hardware Instruction


• Processes waiting for entering their critical section consumes a lot of processors time
which increases busy waiting.
• As the selection of processes to enter their critical section is arbitrary. It may happen
that some processes are waiting for the indefinite time which leads to process starvation.
• Deadlock is also possible.

SPIN LOCK : While a process is in its critical section, any other process that tries to enter its
critical section must loop continuously in the call to critical section. This type of mutex lock is
also called a spinlock because the process “spins” while waiting for the lock to become
available.

Mutex Locks
The hardware-based solutions to the critical-section problem presented synchoronization
hardware are complicated as well as generally inaccessible to application programmers.
The simplest of these tools is the mutex lock is the software based solution to the critical-
section (mutex is short for mutual exclusion) .
We use the mutex lock to protect critical regions and thus prevent race conditions. That is, a
process must acquire the lock before entering a critical section; it releases the lock when it exits
the critical section. The acquire()function acquires the lock, and the release() function releases
the lock, as illustrated in Figure 2.21.
A mutex lock has a boolean variable available whose value indicates if the lock is available or
not. If the lock is available, a call to acquire() succeeds, and the lock is then considered
unavailable. A process that attempts to acquire an unavailable lock is blocked until the lock is
released.
The definition of acquire() is as follows:
acquire() {
while (!available)
; /* busy wait */
available = false;;
}

PREPARED BY DR J FARITHA BANU 34


The definition of release() is as follows:
release() {
available = true;
}
Calls to either acquire() or release() must be performed atomically.

Figure 2.21. Mutex lock


The main disadvantage of the implementation given here is that it requires busy waiting. While
a process is in its critical section, any other process that tries to enter its critical section must
loop continuously in the call to acquire(). In fact, this type of mutex lock is also called a
spinlock because the process “spins” while waiting for the lock to become available. (We see
the same issue with the code examples illustrating the test and set() instruction and the compare
and swap() instruction.) This continual looping is clearly a problem in a real multiprogramming
system, where a single CPU is shared among many processes. Busy waiting wastes CPU cycles
that some other process might be able to use productively.
Spinlocks do have an advantage, however, in that no context switch is required when a process
must wait on a lock, and a context switch may take considerable time. Thus, when locks are
expected to be held for short times, spinlocks are useful. They are often employed on
multiprocessor systems where one thread can “spin” on one processor while another thread
performs its critical section on another processor.

Semaphores
It is a synchronization tool that is used to generalize the solution to the critical section
problem in complex situations.

A Semaphore s is an integer variable that can only be accessed via two indivisible
(atomic) operations namely

1. wait or P operation ( to test )


2. signal or V operation ( to increment )
The wait() operation was originally termed P (from the Dutch proberen, “to test”); signal() was
originally called V (from verhogen, “to increment”).

PREPARED BY DR J FARITHA BANU 35


The definition of wait() is as follows:

wait (s)
{
while(s<=0);
s--;
}
The definition of signal() is as follows:
signal (s)
{
s++;
}
All modifications to the integer value of the semaphore in the wait() and signal() operations
must be executed indivisibly. That is, when one process modifies the semaphore value, no other
process can simultaneously modify that same semaphore value.

Semaphore Usage:
The two common kinds of semaphores are Counting semaphores and Binary semaphores.
Binary Semaphores: In Binary semaphores, the value of the semaphore variable will be 0 or
1. Initially, the value of semaphore variable is set to 1 and if some process wants to use some
resource then the wait() function is called and the value of the semaphore is changed to 0 from
1. The process then uses the resource and when it releases the resource then the signal()
function is called and the value of the semaphore variable is increased to 1. If at a particular
instant of time, the value of the semaphore variable is 0 and some other process wants to use
the same resource then it has to wait for the release of the resource by the previous process. In
this way, process synchronization can be achieved.
Counting Semaphores: In Counting semaphores, firstly, the semaphore variable is initialized
with the number of resources available. After that, whenever a process needs some resource,
then the wait() function is called and the value of the semaphore variable is decreased by one.
The process then uses the resource and after using the resource, the signal() function is called
and the value of the semaphore variable is increased by one. So, when the value of the
semaphore variable goes to 0 i.e all the resources are taken by the process and there is no
resource left to be used, then if some other process wants to use resources then that process has
to wait for its turn. In this way, we achieve the process synchronization.
Implementation - Gaining the knowledge of the usage of the semaphores for the Mutual
exclusion mechanisms
Semaphore Implementation
• When a process executes the wait operation and finds that the semaphore value
is not positive, the process can block itself. The block operation places the
process into a waiting queue associated with the semaphore.
• A process that is blocked waiting on a semaphore should be restarted when some
other process executes a signal operation. The blocked process should be
PREPARED BY DR J FARITHA BANU 36
restarted by a wakeup operation which put that process into ready queue.
• To implemented the semaphore, we define a semaphore as a record as:

typedef
struct { int
value;
struct process *L;
} semaphore;


Assume two simple operations:
− block suspends the process that invokes it.
− wakeup(P) resumes the execution of a blocked process P.

Semaphore operations now defined as
wait(S)
{
S.value--;

if (S.value <= 0) {
add this process to
S.L; block;
}
signal(S)
{
S.value++;
remove a process P from S.L;
wakeup(P);
}

Classic Problems of Synchronization


The classical problems of synchronization are as follows:
• Bound-Buffer problem or Producer-Consumer problem.
• Readers and writers problem
• Dining Philosophers problem.

Readers writers problem, Bounded Buffer problem gives Good understanding of


synchronization mechanisms.
Dining Philosophers problem (Monitor) gives understanding the synchronization of limited
resources among multiple processes.

PREPARED BY DR J FARITHA BANU 37


The Bounded-Buffer Problem
Bounded-buffer (or Producer-Consumer) Problem:
Bounded Buffer problem is also called producer consumer problem. This problem is
generalized in terms of the Producer-Consumer problem. Solution to this problem is, creating
two counting semaphores “full” and “empty” to keep track of the current number of full and
empty buffers respectively. Producers produce a product and consumers consume the product,
but both use of one of the containers each time.
Readers and Writers Problem:
Suppose that a database is to be shared among several concurrent processes. Some of these
processes may want only to read the database, whereas others may want to update (that is, to
read and write) the database. We distinguish between these two types of processes by referring
to the former as readers and to the latter as writers. Precisely in OS we call this situation as the
readers-writers problem. Problem parameters:
One set of data is shared among a number of processes.
• Once a writer is ready, it performs its write. Only one writer may write at a time.
• If a process is writing, no other process can read it.
• If at least one reader is reading, no other process can write.
• Readers may not write and only read.

Dining-Philosophers Problem:
Consider five philosophers who spend their lives thinking and eating. The philosophers share
a circular table surrounded by five chairs, each belonging to one philosopher. In the center of
the table is a bowl of rice, and the table is laid with five single chopsticks When a philosopher
thinks, she does not interact with her colleagues. From time to time, a philosopher gets hungry
and tries to pick up the two chopsticks that are closest to her (the chopsticks that are between
her and her left and right neighbors). A philosopher may pick up only one chopstick at a time.
Obviously, she cannot pick up a chopstick that is already in the hand of a neighbor. When a
hungry philosopher has both her chopsticks at the same time, she eats without releasing the
chopsticks. When she is finished eating, she puts down both chopsticks and starts thinking
again.

Fig: The dining philosophers Problem


PREPARED BY DR J FARITHA BANU 38
This problem involves the allocation of limited resources to a group of processes in a deadlock-
free and starvation-free manner.
Semaphore Solution:
One simple solution is to represent each chopstick with a semaphore. A philosopher tries to
grab a chopstick by executing a wait() operation on that semaphore. She releases her chopsticks
by executing the signal() operation on the appropriate semaphores. Thus, the shared data are
semaphore chopstick[5];
The structure of philosopher i is shown
do {
wait(chopstick[i]);
wait(chopstick[(i+1) % 5]);
...
/* eat for awhile */
...
signal(chopstick[i]);
signal(chopstick[(i+1) % 5]);
...
/* think for awhile */
...
} while (true);
Suppose that all five philosophers become hungry at the same time and each grabs her left
chopstick. All the elements of chopstick will now be equal to 0. When each philosopher tries
to grab her right chopstick, she will be delayed forever. This situation is known as Deadlock.
Several possible remedies to the deadlock problem are:
• Allow at most four philosophers to be sitting simultaneously at the table.
• Allow a philosopher to pick up her chopsticks only if both chopsticks are available (to
do this, she must pick them up in a critical section).
• Use an asymmetric solution—that is, an odd-numbered philosopher picks up first her
left chopstick and then her right chopstick, whereas an even numbered philosopher
picks up her right chopstick and then her left chopstick

Monitors
Monitors
• One fundamental high-level synchronization construct
• A monitor is a synchronization construct that supports mutual exclusion and the ability
to wait /block until a certain condition becomes true.
• A monitor is an abstract datatype that encapsulates data with a set of functions to operate
on the data.

Characteristics of Monitor
• The local variables of a monitor can be accessed only by the local functions.

PREPARED BY DR J FARITHA BANU 39


• A function defined within a monitor can only access the local variables of a monitor
and its formal parameter.
• Only one process may be active within the monitor at a time.
Syntax of a Monitor
monitor monitor-name
{
// shared variable declarations
function P1 ( . . . ) {
...
}
function P2 ( . . . ) {
...
}
.
.
.
function Pn ( . . . ) {
...
}
initialization code ( . . . ) {
...
}
}
• A monitor type is an ADT that includes a set of programmer defined operations that are
provided with mutual exclusion within the monitor.
• Instead of lock-based protection, monitors use a shared condition variable for
synchronization, they are declared as

condition x, y;

Fig: Schematic view of a monitor.

• Two operations on a condition variable:


1. x.wait () –a process that invokes the operation is suspended.
2. x.signal () –resumes one of the suspended processes (if any)

PREPARED BY DR J FARITHA BANU 40


Dining-Philosophers Solution Using Monitors
monitor DP
{
enum { THINKING; HUNGRY, EATING) state [5] ; condition self [5];
void pickup (int i) { state[i] = HUNGRY; test(i);
if (state[i] != EATING) self [i].wait;
}
void putdown (int i) { state[i] = THINKING;
// test left and right neighbors test((i + 4) % 5);
test((i + 1) % 5);
}
void test (int i) {
if ( (state[(i + 4) % 5] != EATING) && (state[i] == HUNGRY) &&
(state[(i + 1) % 5] != EATING) ) { state[i] = EATING ;
self[i].signal () ;
} }
initialization_code() { for (int i = 0; i < 5; i++) state[i] = THINKING;
}
}
We also need to declare condition self[5];
Each philosopher, before starting to eat, must invoke the operation pickup() followed by eating
and finally invoke putdown().
DiningPhilosophers.pickup(i);
...
eat
...
DiningPhilosophers.putdown(i);

This solution ensures that no two neighbors are eating simultaneously and that no deadlocks
will occur. However, with this solution it is possible for a philosopher to starve to death.

PREPARED BY DR J FARITHA BANU 41


References:
http://operatingsystempoly.blogspot.com/p/operations-on-processes.html
https://youtu.be/5oZYS5dTrmk?si=8f3dGIW6UUnLVPpe - NESO Academy
https://www.youtube.com/watch?v=5oZYS5dTrmk
https://www.youtube.com/watch?v=E6KGDpY_XoI

PREPARED BY DR J FARITHA BANU 42

You might also like