0% found this document useful (0 votes)
20 views

Chapter2 - Processes and Threads

this is OS

Uploaded by

xq0drb2s
Copyright
© © All Rights Reserved
Available Formats
Download as PPTX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
20 views

Chapter2 - Processes and Threads

this is OS

Uploaded by

xq0drb2s
Copyright
© © All Rights Reserved
Available Formats
Download as PPTX, PDF, TXT or read online on Scribd
You are on page 1/ 96

Processes and Threads

Process
• Processes are one of the oldest and most important abstractions that
operating systems provide.
• They support the ability to have (pseudo) concurrent operation even
when there is only one CPU available. They turn a single CPU into
multiple virtual CPUs. Without the process abstraction, modern
computing could not exist
Process management
Process : is an instant of program in execution
The entity that can be assigned to and executed on a processor
Keeping track of multiple parallel activities is hard, So OS designer have evolved a conceptual
model where all the runnable software on the computer, sometimes including OS is
organized into a number of sequential processes

• Consists of three components


– An executable program
– Associated data needed by the program
– Execution context of the program
• All information the operating system needs to manage the process
Process and Program
Recipe of making cake is program ( an algorithm expressed in some suitable notation), the person is CPU
and the cake ingredients are the input data
The process is the activity consisting of baker reading the recipe, fetching the ingredients and baking the
cake
Now image that his son comes running and saying that he has been hurt by a bee. He records where he
was(the state of the current process)
Process is an activity of some kind. It has a program, input, output and a state
Process creation
In general purpose systems some way is needed to create and terminate processes as needed during operation
There are four principal events that cause process to be created
1. System initialization : when OS is booted, several processes are created. Some are foreground and some are
background (daemons). E.g. ps used to list running processes, In Windows, the task manager can be used.
2. Execution of a process creation system call by a running process :
Creating new processes is particularly useful when the work to be done can easily be formulated in terms of
several related, but otherwise independent interacting processes.
For example, if a large amount of data is being fetched over a network for subsequent processing, it may be
convenient to create one process to fetch the data and put them in a shared buffer while a second process
removes the data items and processes them.
3. A user request to create a new process : by typing a command or double clicking an icon
4. Initiation of a batch job found on large mainframes
Technically, in all these cases, a new process is created by having an existing
process execute a process creation system call
After the fork, the two processes, the parent and the child, have the same memory image, the same environment
strings, and the same open files. That is all there is. Usually, the child process then executes execve or a similar system
call to change its memory image and run a new program
Why two step process in UNIX? : The reason for this twostep process is to allow the child to manipulate its file
descriptors after the fork but before the execve in order to accomplish redirection of standard input, standard
output, and standard error.
In Windows, in contrast, a single Win32 function call, CreateProcess, handles both process creation and loading the
correct program into the new process. This call has 10 parameters
In both UNIX and Windows systems, after a process is created, the parent and
child have their own distinct address spaces
After a process has been created, it starts running and does whatever its job is.
However, nothing lasts forever, not even processes. Sooner or later the new process
will terminate, usually due to one of the following conditions:
Process termination
Process terminate due to one of the following conditions
1. Normal exit
2. Fatal error : e.g. no file found e.g. cc foo.c to compile the program foo.c and no such file exists, the
compiler simply announces this fact and exits.

3. Error exit : error cause by process Examples include executing an illegal instruction, referencing
nonexistent memory, or dividing by zero.
4. Killed by another process Unix : kill system call .Win32 function is TerminateProcess
Process Hierarchies
IN UNIX there is concepts of process group. In UNIX, a process and all of its children and further
descendants together form a process group. Tree structure
e.g. init at the root
Windows does not have any concept of process hierarchy. The only hint of a process hierarchy is that
when a process is created, the parent is given a special token (called a handle) that it can use to control
the child. However, it is free to pass this token to some other process, thus invalidating the hierarchy.
Processes in UNIX cannot disinherit their children.
Process States

• State : current activity of that process


• A process is typically in one of the three states
1. Running: has the CPU
2. Blocked: waiting for I/O or another thread
3. Ready to run: on the ready list, waiting for the CPU
Ready to running and running to ready state transition are cause by the scheduler

Figure 3.2
In the following process state transition diagram for a uniprocessor system,
assume that there are always some processes in the ready state: Now
consider the following statements:

I. If a process makes a transition D, it would result in


another process making transition A immediately.
II. A process P2 in blocked state can make transition E
while another process P1 is in running state.
III. The OS uses preemptive scheduling.
IV. The OS uses non-preemptive scheduling.

Which of the above statements are TRUE?

(A) I and II
(B) I and III
(C) II and III
(D) II and IV
Implementation of process
To implement the process model os maintains a table( an array of structures)called Process table, with one entry
per process (PCB or TCB)
This entry contains following information
State, its Pc, stack pointer, memory allocation, the status of its open files, its accounting and scheduling
information and everything else about the process that must be saved when the process is switched from running
to ready or blocked state, so that it can be restarted later
Suppose that user process 3 is running when a disk interrupt happens.

A process may be interrupted thousands of times during its execution, but the
key idea is that after each interrupt the interrupted process returns to precisely the
same state it was in before the interrupt occurred.
Modeling Multiprogramming
When multiprogramming is used, the CPU utilization can be improved.
Crudely put, if the average process computes only 20% of the time it is sitting in
memory, then with five processes in memory at once the CPU should be busy all
the time.

Better model is to look at CPU usage from a probabilistic viewpoint. Suppose


that a process spends a fraction p of its time waiting for I/O to complete. With
n processes in memory at once, the probability that all n processes are waiting for
I/O (in which case the CPU will be idle) is pn.
The CPU utilization is then given
by the formula
CPU utilization = 1 − pn
Modeling Multiprogramming
Suppose, for example, that a computer has 8 GB of memory, with the operating
system and its tables taking up 2 GB and each user program also taking up 2 GB.
These sizes allow three user programs to be in memory at once. With an 80% average
I/O wait, we have a CPU utilization (ignoring operating system overhead) of
1 -. 83 or about 49%.
Adding another 8 GB of memory allows the system to go from three-way multiprogramming
to seven-way multiprogramming, thus raising the CPU utilization to 79%.
In other words, the additional 8 GB will raise the throughput by 30%.

Adding yet another 8 GB would increase CPU utilization only from 79% to 91%, thus raising
the throughput by only another 12%.

Using this model, the computer’s owner might decide that the first addition was a good
investment but that the second was not.
Threads
Process Thread
Each process
resource has anand
grouping address space and a single thread
execution of control.
Execution So itincan
control do one
process task at a time.
or entities
Now in many
A process hasOs it is possible
address to have of
space consists multiple
text, threads of control
scheduled for in the same
execution onaddress
the CPUspace running in quasi parallel so
process
data andcan performlike
resources more
openthan one
files, task at a time Thread has Pc, registers which hold current
child
processes, pending alarm, signal handlers, working variables, and has a stack which
accounting information and more contains execution history
IPC required They have some of the properties of processes,
they are called lightweight processes
Thread share address space (code and data) so
they share global variables
No inter thread communication mechanism
required, no protection required (a process is
always owned by a single user, who has likely created
multiple threads so that they can cooperate, not fight.)

The main reason for having threads is that in many applications, multiple activities are going on at once. Some of these
may block from time to time. By decomposing such an application into multiple sequential threads that run in quasi-
parallel, the programming model becomes simpler
Each thread will generally call different procedures and thus have a different
execution history. This is why each thread needs its own stack

The process model is based on two independent


concepts: resource grouping and execution
Processes are used to group resources together; threads are the entities
scheduled for execution on the CPU.
Thread
Multithreading is used to describe the situation of allowing multiple threads in the same process.

Thread concept has the ability for multiple threads of execution to share a set of resources so they can work together
closely to perform some task
Thread state and state transition diagram is same as process

Process start with single thread. It has the ability to create new threads by calling a library procedure e.g.
thread_create and returns thread id
Thread_exit, thread_wait, thread_yield
In general thread creation and termination is very much like process creation and termination
Advantages of Multithreading
1. Responsiveness :Main reason for having threads is that in may applications multiple activates are going on
at once some of these may block from time to time. By decomposing such an application into multiple
sequential threads that run in quasi-parallel, the programming model becomes simpler and increasing
responsiveness
E.g. Multithreaded web browser could still allow user interaction in one thread while an image is being loaded
into another thread
2. Resource sharing :Sharing of address space and all of its data. Only now with threads we add a new element:
the ability for the parallel entities to share an address space and all of its data among themselves.
3.Economy:They do not have any resource attached to them they are easier to create and destroy than
process.in many systems creation of thread is 100times faster than creating a process. When number of
threads needed changes dynamically and rapidly this property is useful
4.Performace improvement :Threads yield no performance gain when all of them are CPU bound but when
there is sufficient computing and I/o, threads allows these activities to overlap thus speeding up the
application.
5.Utilization of multiprocessor architecture: Threads are useful on system with multiple CPUS, where real
parallelism is possible.
e.g. word processors
One thread interacts with user , one thread do formatting and third thread save entire file to disk every few minutes
Multiple process it will not work because here there is need of sharing document
If it is single thread then whenever a disk backup started commands from keyboard and mouse would be ignored until the
backup was finished
e.g. web server
Web server can be organized as
One thread called dispatcher read incoming requests for work
from the network
After examining the request it chooses an idle(i.e., blocked)
worker thread and hands it the request
When the worker thread run it check requested page
available in cache otherwise start read operation and block
until the disk operation completed
When the thread blocks on the disk operation, another
thread is chosen to run, possibly the dispatcher, in order to
acquire more work, or possibly another worker that
is now ready to run.
Consider webserver as single thread. The main loop of web
server gets a request, examines it and carries it out to
completion before getting the next one. While waiting for the
disk, the server is idle. So it can handle only few requests/sec
(throughput)
In this problem you are to compare reading a file using a single threaded file server and a multithreaded server.
It takes 15 msec to get a request for work, dispatch it and do the rest of the necessary processing assuming that
the data needed are in the block cache. If a disk operation is needed as in the case one third of the time an
additional 90msec is required during which time the thread sleeps. How many requests/sec can the server handle
if it is single threaded? If it is multithreaded?
In the single-threaded case, the cache hits take 15 msec and cache misses take
90 msec. The weighted average is 2/3 × 15 + 1/3 × 90. Thus the mean request
takes 40 msec and the server can do 25 per second.

For a multithreaded server, all the waiting for the disk is overlapped, so every request
takes 15msec, and the server can handle 66 .66 requests per second.
Implementation of threads
Two ways
1. User level
2. Kernel level

User level
User level threads: supported above the kernel and are implemented by a thread library at the user level. Kernel knows
nothing about them. e.g. POSIX threads, Java threads. Threads run on top of runtime system which is a collection of
procedures that manage threads
thread table to keep track of thread, which is managed by run time system
Advantages
1. Multithreading application run on top of os do not support threads by implementing thread package on top of an Os in
user level
2. Thread switching, creation is faster because no mode change take place
3. Thread scheduling is faster
4. Each process to have its own customized scheduling algorithm and they also scale better
Disadvantages
1. one thread execute system call, may involved blocking then entire process blocked. solution :1. non blocking system call.
2.before invoking system call it check is it safe to invoke system call? The code placed around the system call to do the
checking is called a jacket or wrapper. In most versions of UNIX, a system call, select, exists, which allows the caller to tell
whether a prospective read will block
Scheduler activation
When the kernel knows that a thread has blocked, the kernel notifies the process run time system, passing as
parameters on the stack the number of the thread and the description of the event. This mechanism is called an upcall
Once activated, the run-time system can reschedule its threads, typically by marking the current thread as blocked and
taking another thread from the ready list
Later, when the kernel learns that the original thread can run again (e.g., the pipe it was trying to read from now
contains data, or the page it faulted over has been brought in from disk), it makes another upcall to the run-time system
to inform it. The run-time system can either restart the blocked thread immediately or put it on the ready list to be run
later.
An objection to scheduler activations is the fundamental dependence on upcalls, a concept that violates the structure
inherent in any layered system. Normally, layer n offers certain services that layer n + 1 can call on, but layer n may not
call procedures in layer n + 1. Upcalls do not follow this fundamental principle.
2 one thread cause page fault, entire process block
3.if thread do not releases control of thread other thread will not get control
Solution : run time system request kernel to give notification at every second…
Kernel level
Kernel support threads and manage threads. No run time system is needed
Windows NT, Windows 2000, solaris 2,..
There is no thread table in each process. Instead the kernel has a thread table that keeps track of all the threads in
system. When a thread wants to create a new thread or destroy an existing thread, it makes a kernel call, which then
does the creation or destruction by updating the kernel thread table
Advantages : no run time system needed, when thread issue blocking system call that thread only block not whole
process , thread table is also managed by kernel
Disadvantage
Greater cost of creating and destroying threads. Some system use thread recycle i.e. when a thread is destroyed it is
marked as not runnable but its kernel data structures are kept as it is. When need arise this old thread is reactivated
While kernel threads solve some problems, they do not solve all problems. For example, what
happens when a multithreaded process forks?
Does the new process have as many threads as the old one did, or does it have just one?
In many cases, the best choice depends on what the process is planning to do next. If it is
going to call exec to start a new program, probably one thread is the correct choice,
but if it continues to execute, reproducing all the threads is probably best

Another issue is signals. Remember that signals are sent to processes, not to threads, at least in the classical model.
When a signal comes in, which thread should handle it?
Possibly threads could register their interest in certain signals, so when a signal came in it would be given to the thread
that said it wants it.
But what happens if two or more threads register for the same signal?
Hybrid
Various ways have been investigated to try to combine the advantages of user level threads with kernel level
threads
One to one mapping : drawback is creating a user thread requires creating the corresponding kernel thread
e.g. Windows NT, Windows 2000
Another way is multiplex many user level thread to a smaller or equal number of kernel thread. called many to
many e.g. Solaris 2
Kernel is aware of only kernel level threads and schedule them
Pop-UP threads
In distributed systems, how request for service is handled?
process or thread that is blocked on receive system call waiting for an incoming message. When
message arrives it accepts message and processes it

Another approach , in which arrival of message causes the system to create a new thread to
handle the message. Such a thread is called a pop-up thread. Advantage is such thread can be
created quickly because they are brand new, do not have any history. So the latency between
message arrival and start of processing can be made very short

POSIX Threads
IEEE has defined a standard for threads in IEEE standard 1003.1c
The threads package it defines is called Pthreads.
Thread call Description
Pthread_create Create a new thread
Pthread_ exit Ter minate the calling thread
Pthread_ join Wait for a specific thread to exit
Pthread_ yield Release the CPU to let another thread run
Pthread_ attr- init Create and initialize a thread’s attribute structure
Pthread_ attr_ destroy Remove a thread’s attribute structure
pthread_t pthread_self(void);
int pthread_equal(pthread_t t1, pthread_t t2); If the two threads are equal, the function returns a
non-zero value otherwise zero.
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#define NUMBER OF THREADS 10
* *
void pr int hello world(void tid)
{
pr intf("Hello World. Greetings from thread %d\n", *(int *)tid);
pthread exit(NULL);
}
*
int main(int argc, char argv[])
{
pthread t threads[NUMBER OF THREADS];
int status, i;
for(i=0; i < NUMBER OF THREADS; i++) {
pr intf("Main here. Creating thread %d\n", i);
status = pthread create(&threads[i], NULL, print hello world, (void *)&i);
if (status != 0) {
pr intf("Oops. pthread create returned error code %d\n", status);
exit(-1);
}}
for(i=0; i < NUMBER OF THREADS; i++) {
pthread_join(threads[i],NULL)}
int min;
void *findmin(void *th)
{
int *array,i,s,mint;
array = (int *)th;
mint = array[0];
for(i=0;i<SIZE/NUM_THREADS;i++)
{
if(mint>array[i])
mint = array[i];
}
if(mint < min)
min = mint;
pthread_exit(NULL);
}

for(i=0;i<NUM_THREADS;i++)
pthread_create(&thread[i],NULL,findmin,(void*)A+i*(SIZE/NUM_THREADS));
Issues involved in Making single threaded code Multithreaded
a. Global variables : errno variable
b. Reentrant procedure : second call made to any given procedure while a previous call has not yet finished
A different solution is to provide each procedure with a jacket that sets a bit to mark the library as in use. Any attempt
for another thread to use a library procedure while a previous call has not yet completed is blocked. Although this
approach can be made to work, it greatly eliminates potential parallelism.
c. Signals are delivered to process : handling signals in single threaded programs is straightforward. However it is more
complicated in multithreaded programs because process may have several threads? Where then should a signal be
delivered? Synchronous signal need to be delivered to the thread that generated the signal
Following options exist
1. Deliver the signal to the thread to which the signal applies
2. Deliver the signal to every thread in process
3. Deliver the signal to certain threads in the process
4. Assign a specific thread to receive all signals for the process , which then delivers the signal to first thread, that is
not blocking signal, used by Solaris 2.
d. Fork : if one thread calls fork does the new process duplicate all threads or is the new process single-threaded?
Some unix systems have two version of fork , one duplicate all threads and other only the thread that invoked fork
e. Stack management : when process stack overflows, kernel provides that process , required memory so stack grow
automatically. When process has multiple threads it must also have multiple stacks. When threads are implemented in
user mode kernel may not even realize that a memory fault is related to stack growth
Conclusion : introducing thread in existing system required a fairly large system redesign. The semantics of system
calls may have to be redefine and libraries have to be rewritten at the very least by maintaining backward compatibility
IPC
IPC should handle following Three issues
1. How one process can pass information to another
• Shared memory
• Message passing
2. Two or more process do not get into each others way
3. Proper sequencing when dependencies are present
Issue 2 and 3 is also applicable to thread
Shared memory #include <sys/ipc.h>
#include <sys/shm.h>

#include <sys/ipc.h>
int main()
#include <sys/shm.h>
{
int *sum,id;
int main()
key_t key =
{ ftok("shmfile",65);
int *sum,id;
key_t key = ftok("shmfile",65); id = shmget(key, sizeof(int), IPC_CREAT |
id = shmget(key, sizeof(int), IPC_CREAT | 0666); 0666);
sum = (int *)shmat(id, 0, 0); sum = (int *)shmat(id, 0, 0);
*sum=10; printf(“%d\n”, *sum);
shmdt(sum); shmdt(sum);
} } shmctl(shmid,IPC_RMID,NULL);
Race condition
• Process that are working together may share some common storage
that each one can read and write. The shared storage may be in main
memory or it may be shared file
• Let discuss one example to understand the problem cause by this type
of IPC
Race condition
• Spooler directory
• Let there are two shared variables out and in
• Out : which points to the next file to be printed and in: which points to the next free slot in the
directory
• Situations like this, where two or more processes are reading and writing some shared data and
final results depends on who runs precisely when are called race conditions
• How do we avoid race conditions?
• Solution : Mutual exclusion, that is some way of making sure that if one process is using a shared
variable or file the other process will be excluded form doing the same thing.
• The part of the program where the shared memory is accessed is called critical region or section.
• If we could arrange matters such that no two process were ever in their critical section at the
same time we could avoid races
Mutual exclusion
• Mechanism available for mutual exclusion should satisfy following conditions
then we consider that solution as good solution
• 1. no two process may be simultaneously inside their critical regions
• 2. no assumptions may be made about speeds and number of CPUs
• 3. no process running outside its critical region may block other processes
• 4. no process should have to wait forever to enter its critical region
Mutual exclusion
• Disabling interrupts
Simplest solution is to have each process disable all interrupts just after entering its critical region
and re-enable them just before leaving it
Demerits
It is unwise to give user processes the power to turn off interrupts because some process disable all
interrupts and never turned them on again?
This scheme will not work for multiprocessor
Conclusion : disabling interrupts is often a useful technique within the Os itself but is not
appropriate as a general mutual exclusion mechanism for user processes
Mutual exclusion
Lock variables
• Consider single shared variable, initially 0
While(lock!=0);
Lock=1
Critical section
Lock=0

• Drawback : cannot avoid race condition


Mutual exclusion

Strict alternation
Let integer variable turn is initially zero

while (TRUE) { while (TRUE) {


while (turn != 0); / * loop */ while (turn != 1) ; /* loop */
cr itical region( ); cr itical region( );
turn = 1; tur n = 0;
noncr itical region( ); noncr itical region( );
} }

Continuously testing a variable until some value appears is called busy waiting. It should be usually
Avoided because it wastes CPU time. A lock that uses busy waiting is called spin lock
Drawback : process running outside critical region may block other processes to enter critical region
Not good idea when one of the process is much slower than the other
Peterson’s solution
#define FALSE 0
#define TRUE 1
#define N 2
int turn; /* whose turn is it? */
int interested[N]; /* all values initially 0 (FALSE) */

void enter region(int process); /* process is 0 or 1 */


{
int other; /* number of the other process */
other = 1 -process; /* the opposite of process */
interested[process] = TRUE; /* show that you are interested */
tur n =process; /* set flag */
while (turn == process && interested[other] == TRUE); /* null statement */
}

void leave region(int process) /* process: who is leaving */


{
interested[process] = FALSE; /* indicate departure from critical region */
}
Now consider the case that both processes call enter region almost simultaneously.
Both will store their process number in turn.
Whichever store is done last is the one that counts; the first one is overwritten and lost.
Suppose that process 1 stores last, so turn is 1.
When both processes come to the while statement, process 0 executes it zero times and enters
its critical region. Process 1 loops and does not enter its critical region until process 0 exits its
critical region.
Mutual exclusion
TSL instruction ( Test and Set Lock)
Little help from hardware. Processor support instruction
TSL RX, LOCK (test and set lock)
• It reads the contents of the memory word lock into register RX and then stores a nonzero value at the memory address lock.
The operation of reading the word and storing into it are guaranteed to be indivisible no other processor can access the memory word
until the instruction is finished. The CPU executing the TSL instruction locks the memory bus to prohibit other CPUs from accessing
memory until it is done
• enter region:
TSL REGISTER,LOCK copy lock to register and set lock to 1
CMP REGISTER,#0 was lock zero?
JNE enter region if it was not zero, lock was set, so loop
RET
• retur n to caller; critical region entered
• leave region:
• MOVE LOCK,#0 store a 0 in lock
• RET return to caller
• An alternative instruction to TSL is XCHG, which exchanges the contents of two locations atomically, for example, a register and a
memory word.
• All Intel x86 CPUs use XCHG instruction for low-level synchronization.
Mutual exclusion
enter region:
MOVE REGISTER,#1 | put a 1 in the register
LOCK: XCHG REGISTER,LOCK | swap the contents of the
register and lock var iable
CMP REGISTER,#0 | was lock zero?
JNE enter region | if it was non zero, lock was set, so loop
RET |retur n to caller; critical region entered

leave region:
MOVE LOCK,#0 | store a 0 in lock
RET
Mutual exclusion
• Both Peterson solution and solution using TSL are correct but both have requiring busy
waiting : continuously testing a variable until some value appears . Only when there is a
reasonable expectation that the wait will be short ,busy waiting used. A lock that uses
busy waiting is called a spin lock.
• It should be avoided because it wastes CPU time & it can also have unexpected effects.
• Consider a computer with two processes, H, with high priority, and L, with low priority.
• The scheduling rules are such that H is run whenever it is in ready state. At a certain
moment, with L in its critical region, H becomes ready to run (e.g., an I/O operation
completes). H now begins busy waiting, but since L is never scheduled while H is running,
L never gets the chance to leave its critical region, so H loops forever. This situation is
sometimes referred to as the priority inversion problem.
This can be solved by putting process in block state when it cannot enter critical section. Let
assume that system call sleep and wakeup available
Producer consumer problem
• It is also known as bounded buffer problem
• Two process share a common fixed size buffer
#define N 100 / * number of slots in the buffer */
int count = 0; /* number of items in the buffer */
void producer(void)
{
int item;
while (TRUE) { item = produce item( );
* if buffer is full, go to sleep */
if (count == N) sleep( ); /

inser t item(item); count = count + 1; /* increment count of items in buffer */

if (count == 1) wakeup(consumer); /* was buffer empty? */


}
}
void consumer(void)
{
int item;
while (TRUE) {
* if buffer is empty, got to sleep */
if (count == 0) sleep( ); /

item = remove item( ); count = count -1; /* decrement count of items in buffer */
if (count == N - 1) wakeup(producer);
consume item(item);}}
Producer consumer problem
• The buffer is empty and the consumer has just read count to see if it is 0. At that instant, the
scheduler decides to stop running the consumer temporarily and start running the producer.
• The producer inserts an item in the buffer, increments count, and notices that it is now 1.
Reasoning that count was just 0, and thus the consumer must be sleeping, the producer calls
wakeup to wake the consumer up.
• Unfortunately, the consumer is not yet logically asleep, so the wakeup signal is lost. When
the consumer next runs, it will test the value of count it previously read, find it to be 0, and
go to sleep.
• Sooner or later the producer will fill up the buffer and also go to sleep. Both will sleep
forever.
• Simple solution is wakeup waiting bit : When a wakeup is sent to a process that is still awake,
this bit is set. Later, when the process tries to go to sleep, if the wakeup waiting bit is on, it
will be turned off, but the process will stay awake. The wakeup waiting bit is a piggy bank for
storing wakeup signals.
Mutual exclusion
Semaphore
Dijkstra suggested using an integer variable to count the number of wakeups saved for future use
A new variable called a semaphore which is kernel variable. Semaphore could have value 0
indicating no wakeups were saved or some positive value if one or more wakeups were pending
He has also proposed two operations down and up
Down : check value of semaphore if it is zero process is put to sleep
Otherwise decrement it and continue. All done as single indivisible atomic action.
Up : increment the value of semaphore, and wakeup one of the process sleeping on that
semaphore
Again incrementing and waking one process is also indivisible operation
#define N 100 /* number of slots in the buffer */
typedef int semaphore; /* semaphores are a special kind of int */

semaphore s = 1; /* controls access to critical region */

semaphore empty = N; /* counts empty buffer slots */

semaphore full = 0; /* counts full buffer slots */


void producer(void)
{
int item;
while (TRUE) { item = produce item( ); / * generate something to put in buffer */
down(&empty);
down(&s); / * enter critical region */
inser t item(item); /* put new item in buffer */

up(&s); /* leave critical region */

up(&full); /* increment count of full slots */


}
}
void consumer(void) In his original paper he has used P and V instead of
{ down and up, respectively.
int item;
while (TRUE) {
* decrement full count */
down(&full); /

down(&s); /* enter critical region */

item = remove item( ); /* take item from buffer */

up(&s); /* leave critical region */

up(&empty); /* increment count of empty slots */

consume item(item); /* do something with the item */


}
}

Semaphore are used for Mutual exclusion and synchronization problem


Semaphore
• Down is implemented as indivisible atomic operation by disabling all interrupts while it is
testing the semaphore, updating it, and putting the process to sleep, if necessary. As all
of these actions take only a few instructions, no harm is done in disabling interrupts.
• If multiple CPUs are being used, each semaphore should be protected by a lock , with the
TSL or XCHG instructions used to make sure that only one CPU at a time examines the
semaphore.
• Semaphores that are initialized to 1 and used by two or more processes to ensure that
only one of them can enter its critical region at the same time are called binary
semaphores
• If each process does a down just before entering its critical region and an up just after
leaving it, mutual exclusion is guaranteed.
• Semaphore is also used for synchronization in previous example empty and full used for
synchronization
Exercise

For example, consider two concurrently running processes: P 1 with a statement


S1 and P2 with a statement S2 . Suppose we require that S2 be executed only
after S1 has completed
Semaphore
Example 1 : Let process A print 0 and B print 1 and we want output like 010101….
Example 2 : in cline-server game, Multiple client play game only all client start game simultaneously. Design client and server
program
While(1) server
{ {
i=0;
up(s1); while(i<N)
Down(s2); {
Game start down(s1);
} i++;}
i=0;
while(i<N)
{ up(s2); i++;}
game start;}
#include <stdio.h>
#include <sys/types.h> int main()
#include <sys/ipc.h> {
int main() int sem,i=0;
{
int sem,i=0; struct sembuf s;
struct sembuf s; sem = semget(100 2, IPC_CREAT | 0666);
sem = semget(100, 2, IPC_CREAT | 0666); semctl(sem, 0, SETVAL, 1);
semctl(sem, 0, SETVAL, 1); semctl(sem, 1, SETVAL, 0);
semctl(sem, 1, SETVAL, 0); while(i<5)
{
while(i < 5) down(sem, 1, &s);
{ printf("1\n");
down(sem, 0, &s); up(sem, 0, &s);
printf("0\n"); i++; }}
up(sem,1,&s);
i++; }}
#include <sys/sem.h>
#define KEY 1256
void down(int sem, int n, struct sembuf *s)
{
s->sem_num = n;
s->sem_flg = 0;
s->sem_op = -1;
semop(sem, s, 1);
}
void up(int sem, int n, struct sembuf *s)
{
s->sem_num = n;
s->sem_flg = 0;
s->sem_op = 1;
semop(sem, s, 1);
}
int main()
{ id = process_fork(p);
int id, no[10], n,p,i; for(i=id; i<n; i+=(p+1))
int *sum, semid; {
struct sembuf sb; down(semid, 0, &sb);
p=5; *sum += no[i];
n=10; up(semid, 0, &sb);
id = shmget(IPC_PRIVATE, sizeof(int), IPC_CREAT | 0666); }
sum = (int *)shmat(id, 0, 0); process_join(id,p);
semid = semget(KEY, 1, IPC_CREAT | 0666);
semctl(semid, 0, SETVAL, 1); if(id == 0)
for(i=0; i<n; i++) printf("Sum of %d to %d is %d\n",0,n, *sum);
no[i] = i+1; }
*sum = 0;
void process_join(int id, int n)
int process_fork(int n) {
{ int i;
int i,id; int s;
for(i=1; i<=n; i++)
{ if(id == 0)
id=fork(); {
if(id == 0) for(i=0; i<n; i++)
return i; wait(&s);
} }
return 0; else
} {
exit(0);
}
}
mutex
• When the semaphore’s ability to count is not needed, a simplified version of the
semaphore, called a mutex, is sometimes used. Mutexes are good only for
managing mutual exclusion to some shared resource or piece of code. They are
easy and efficient to implement, which makes them especially useful in thread
packages that are implemented entirely in user space.
• Mutex is a variable that can be in one of two states : unlocked or locked, 0 means
unlocked and 1 means locked
• When thread needs access to critical section it calls mutex_lock. If the mutex is
current unlocked then it succeeds and calling thread is free to enter critical
section. On the other hand if the mutex is already locked, the calling thread is
stopped until the other thread leave critical section
• Similar when thread leave critical section called mutex_unlock
mutex lock:
TSL REGISTER,MUTEX | copy mutex to register and set mutex to 1
CMP REGISTER,#0 | was mutex zero?
JZE ok | if it was zero, mutex was unlocked, so return
CALL thread yield | mutex is busy; schedule another thread
JMP mutex lock | tr y again
ok: RET | retur n to caller; critical region entered

mutex unlock:
MOVE MUTEX,#0 | store a 0 in mutex
RET |retur n to caller

Here suppose thread do busy waiting then it will loop forever and never
acquired the lock
int min;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

void *findmin(void *th)


{
int *array,i,s,mint;
array = (int *)th;
mint = array[0];
for(i=0;i<SIZE/NUM_THREADS;i++)
{
if(mint>array[i])
mint = array[i];
}

pthread_mutex_lock(&mutex);
if(mint < min)
min = mint;
pthread_mutex_unlock(&mutex);
Futex
• Spin locks are fast if the wait is short, but waste CPU cycles if not.
• If there is much contention, it is therefore more efficient to block the
process and let the kernel unblock it only when the lock is free.
• it works well under heavy contention, but continuously switching to
the kernel is expensive if there is very little contention to begin with.
• Solution work in both case is : futex, or ‘‘fast user space mutex.’’
• A futex consists of two parts: a kernel service and a user library.
Futex
Futex is taken care in userspace. Then, depending on the value, we can use
the kernel only if we need to sleep or to wake other threads or processes.
futex_lock(unsigned int *futex)
{
• while (!cmpxchg(futex, UNLOCKED, LOCKED))
• futex(futex, FUTEX_WAIT, LOCKED);
•}
• cmpxchg() is an atomic compare-and-exchange operation, where if the
mutex value is UNLOCKED, it replaces it by LOCKED. It returns true if the
operation succeeded or false otherwise
Futex
futex_unlock(unsigned int *futex)

atomic_set(futex, UNLOCKED);

futex(futex, FUTEX_WAKE, 1);

}
Low level synchronization
• Interchange the order of down and up
• It cause dead lock
• Higher level synchronization called monitor proposed by hoare and Hansen
• Monitor is collection of procedures, variables and data structures that all are grouped together in a special kind
of module or package
• Process may call the procedure. Procedure declared inside monitor can access only those variables declared
locally within the monitor and not by any external procedure
• Only one process can be active in a monitor at any instant. Monitor are programming language construct so
compiler knows that they are special and handle calls to monitor procedures differently from other procedure
calls
• When a process call monitor procedure, the first few instructions of the procedure will check to see if any
process is currently active within the monitor
• It is upto the compiler to implement the mutual exclusion on monitor entries, they may use semaphore or mutex
• The compiler, not the programmer is arranging for mutual exclusion, it is much less likely that something will go
wrong
• Implement critical section as procedures of monitor
We also need a way for processes to block when they cannot proceed. In the producer-
consumer problem, it is easy enough to put all the tests for buffer-full and buffer-empty in
monitor procedures, but how
should the producer block when it finds the buffer full?

The solution lies in the introduction of condition variables, along with two operations on them, wait and signal.
When a monitor procedure discovers that it cannot continue (e.g., the producer finds the buffer full), it does a wait on
some condition variable, say, full. This action causes the calling process to block. It also allows another process that
had been previously prohibited from entering the monitor to enter now

This other process, for example, the consumer, can wake up its sleeping partner by doing a signal on the condition
variable that its partner is waiting on.

To avoid having two active process in monitor at the same time, we need a rule telling what happens after a signal
1. Hoarse proposed telling the newly awaked process run, suspending the other one
2. Hansen proposed that a process doing a signal must exit the monitor immediately. Signal statement may appear
as final statement in the monitor procedure
3. Process doing signal continue to run and allow waiting process to run only after that process leave monitor (not
proposed by either)
Imaginary language, Pidgin Pascal
monitor ProducerConsumer
condition full, empty;
integer count;
procedure insert(item: integer);
begin
if count = N then wait(full);
insert item(item);
count := count + 1;
if count = 1 then signal(empty)
end;

function remove: integer;


begin
if count = 0 then wait(empty);
remove = remove item;
count := count + 1;
if count = N - 1 then signal(full)
end;
count := 0;
end monitor;
procedure producer;
begin
while true do
begin
item = produce item;
ProducerConsumer.insert(item)
end
end;

procedure consumer;
begin
while true do
begin
item = ProducerConsumer.remove;
consume item(item)
end
end
some real programming languages also support monitors, although not always in the
form designed by Hoare and Brinch Hansen. One such language is Java.
Java is an object-oriented language that supports user-level threads and also allows
methods (procedures) to be grouped together into classes. By adding the keyword
synchronized to a method
declaration, Java guarantees that once any thread has started executing that method,
no other thread will be allowed to start executing any other synchronized method
of that object.
Synchronized methods in Java differ from classical monitors in an essential
way: Java does not have condition variables built in. Instead, it offers two procedures,
wait and notify, which are the equivalent of sleep and wakeup except that
when they are used inside synchronized methods, they are not subject to race
conditions.
public class ProducerConsumer {
static final int N = 100; // constant giving the buffer size
static producer p = new producer( ); // instantiate a new producer thread
static consumer c = new consumer( ); // instantiate a new consumer thread
static our monitor mon = new our monitor( ); // instantiate a new monitor
public static void main(String args[ ]) {
p.star t( ); // star t the producer thread
c.star t( ); // star t the consumer thread
}
static class producer extends Thread {
public void run( ) { // run method contains the thread code
int item;
while (true) { // producer loop
item = produce item( );
mon.inser t(item);}
pr ivate int produce item( ) { ... } // actually produce
}
static class consumer extends Thread {
public void run( ) { run method contains the thread code
int item;
while (true) { // consumer loop
item = mon.remove( );
consume item (item);
}}
pr ivate void consume item(int item) { ... } // actually consume
static class our monitor { // this is a monitor
pr ivate int buffer[ ] = new int[N];
pr ivate int count = 0, lo = 0, hi = 0; // counters and indices
public synchronized void insert(int val) {
if (count == N) go to sleep( ); // if the buffer is full, go to sleep
buffer [hi] = val; // inser t an item into the buffer
hi = (hi + 1) % N; // slot to place next item in
count = count + 1; // one more item in the buffer now
if (count == 1) notify( ); // if consumer was sleeping, wake it up
}
public synchronized int remove( ) {
int val;
if (count == 0) go to sleep( ); // if the buffer is empty, go to sleep
val = buffer [lo]; // fetch an item from the buffer
lo = (lo + 1) % N; // slot to fetch next item from
count = count − 1; // one few items in the buffer
if (count == N − 1) notify( ); // if producer was sleeping, wake it up
retur n val;
}
pr ivate void go to sleep( ) { try{wait( );} catch(Interr uptedException exc) {};}
}
}
IPC problems
• Reader and writer problem
e.g. airline reservation
Many readers and writers
2. Dining philosopher problem
3. The sleeping barber problem
Barber shop has One barber, one barber chair and n chairs for waiting
Dining philosopher problem
Message passing
Shared memory : process running on same machine
• Semaphore or monitor can be used for mutual exclusion on if process
are running on the same machine
• But if process running on different machine then how they
communicate?
Message passing : useful in distributed systems, shared memory
multiprocessor and uniprocessor systems
• Two primitive operations
• Send( destination, &message) and receive(source, &message)
Message passing
Design issues
1. Synchronization : receiver cannot receive message until it has been sent by
some other process. Thus both the sender and receiver can be blocking or
non-blocking. Three combinations are common
a. Blocking send and blocking receive : Both the sender and receiver are
blocked until the message is delivered. this is called rendezvous. It is easier to
implement than a buffered message scheme but is less flexible since the
sender and receiver are forced to run in lockstep
b.Non blocking send and blocking receive : most useful combination.
Processes must employ reply messages (acknowledgment)
c. Non blocking send and non blocking receive
2. Addressing
Direct and indirect addressing
Direct : it include specific identifier of the destination process. Helpful for cooperative
Concurrent processes.
Indirect : not sent directly from sender to receiver, but messages are sent to shared data
structure called mailbox which consists of queues that can temporary hold messages. Mailbox is
a place to buffer a certain number of messages
The relation between sender and receiver may be One to one, many to one or many to many
One to one : private communication link is set up between two processes
Many to one: client/server communication. In this case mailbox is also known as port
One to many : broadcast messages
Many to many : multiple servers and multiple clients
The association of mailbox to process may be static or dynamic
port is static assignment i.e. port is created and assigned to the process permanently
When there are many senders, the association of sender to mail box may be dynamic
In case of dynamic association connect and disconnect operation may required
Port is owned by receiving process. In general mailbox may owned by OS
3.Message format : some preferred short, fix size messages or it can be variable size (header
and body)
4. Queuing discipline : default is FIFO or it can be priority based

5. Mutual exclusion : it can also be used for mutual exclusion


Message passing is commonly used in parallel system. One well known message passing system
Unix
• Id=msgget(key,IPC_CREAT|0666)
• msgsnd(id,&message,len,0)
• msgrcv(id,&buff,len,type,0);

struct msgbuf {
long mtype; /* message type, must be > 0 */
char mtext[1]; /* message data */
};
Barriers
• Some applications are divided into phases and have rule that no
process may proceed into the next phase until all process are ready to
proceed the next phase
• This behavior may be achieved by placing barrier at the end of each
phase. When a process reaches the barrier, it is blocked until all
process have reached the barrier
• int main()
• {
• int sem1, sem2, id, shid;
• int i,pi;
• int array[N];
• float *avg, *dev, x;

• for(i = 0; i<N; i++)
• array[1] = 1+1;

• shid = shmget(IPC_PRIVATE, sizeof(float)*(PROCESS+1),IPC_CREAT | 0666);

• avg = (float *)shmat(shid, 0, 0);
• *avg=0;
• shid = shmget(IPC_PRIVATE, sizeof(float), IPC_CREAT | 0666);
• dev = (float *)shmat(shid, 0, 0);

• *dev = 0;
• sem1 = semget(KEY, 2, IPC_CREAT | 0666);
• semctl(sem1, 0, SETVAL, PROCESS+1);
id = process_fork(PROCESS);
sum=0;
for(i=id; i<N; i += PROCESS+1)
{
sum=sum+array[i];
}
down(sem1,1,&s);
*avg = *avg + sum/N;
up(sem1,1,&s);
barrier(sem1);
find deviation
join
print deviation
void waitforzero(int semid, int n, struct sembuf *s)
{
s->sem_num = n;
s->sem_flg = 0;
s->sem_op = 0,
semop(semid, s, 1);
}

void barrier(int semid)


{
down(semid, 0, &s),
waitforzero(semid, 0, &s);
}
Pipe
• Used for inter process communication
• It is implemented same as file so known as pseudo file
• Two types
• Named and unnamed pipe
• Unnamed pipe is created using pipe system call
• Named pipe is created using mknod system call
Unnamed pipe
• Main()
• {
• Int fd[2];
• pipe(fd)
• Id=fork()
• If(id==0)
• {
• Close(fd[1]);
• read(fd[0],buff,5)
• Printf(“%s”,buff)
• }
• Else
• {
• Close(fd[0])
• Write(fd[1],”hello”,5);
• }
Named pipe
• P1
• mknod(“/home/student/p”, 010777,0);
• fd1=open(“home/student/p”,O_RDONLY);
• Read(fd1,buff,5)
• P2
• mknod(“/home/student/p”, 010777,0);
• fd2=open(“home/student/p”,O_WRONLY);
• write(fd2,buff,5)

You might also like