Operating-Systems, Threads and - Memory
Operating-Systems, Threads and - Memory
Source: https://www.studytonight.com/operating-system/system-calls
In this type of system, there is no direct interaction between user and the
computer.
The user has to submit a job (written on cards or tape) to a computer operator.
Then computer operator places a batch of several jobs on an input device.
Jobs are batched together by type of languages and requirement.
Then a special program, the monitor, manages the execution of each program in
the batch.
The monitor is always in the main memory and available for execution.
In this the operating system picks up and begins to execute one of the jobs from
memory.
Once this job needs an I/O operation operating system switches to another job
(CPU and OS always busy).
Jobs in the memory are always less than the number of jobs on disk(Job Pool).
If several jobs are ready to run at the same time, then the system chooses which
one to run through the process of CPU Scheduling.
Page 1 of 54
In Non-multiprogrammed system, there are moments when CPU sits idle and
does not do any work.
In Multiprogramming system, CPU will never be idle and keeps on processing.
Time Sharing Systems are very similar to Multiprogramming batch systems. In fact time
sharing systems are an extension of multiprogramming systems.
In Time sharing systems the prime focus is on minimizing the response time, while in
multiprogramming the prime focus is to maximize the CPU usage.
Multiprocessor Systems
A Multiprocessor system consists of several processors that share a common physical
memory. Multiprocessor system provides higher computing power and speed. In
multiprocessor system all processors operate under single operating system. Multiplicity
of the processors and how they do act together are transparent to the others.
Advantages of Multiprocessor Systems
1. Enhanced performance
2. Execution of several tasks by different processors concurrently, increases the
system's throughput without speeding up the execution of a single task.
3. If possible, system divides task into many subtasks and then these subtasks can
be executed in parallel in different processors. Thereby speeding up the execution
of single tasks.
Desktop Systems
Earlier, CPUs and PCs lacked the features needed to protect an operating system from
user programs. PC operating systems therefore were
neither multiuser nor multitasking. However, the goals of these operating systems
have changed with time; instead of maximizing CPU and peripheral utilization, the
systems opt for maximizing user convenience and responsiveness. These systems are
called Desktop Systems and include PCs running Microsoft Windows and the Apple
Macintosh. Operating systems for these computers have benefited in several ways from
the development of operating systems for mainframes.
Microcomputers were immediately able to adopt some of the technology developed for
larger operating systems. On the other hand, the hardware costs for microcomputers are
sufficiently low that individuals have sole use of the computer, and CPU utilization is no
longer a prime concern. Thus, some of the design decisions made in operating systems
for mainframes may not be appropriate for smaller systems.
Page 2 of 54
Distributed Operating System
The motivation behind developing distributed operating systems is the availability of
powerful and inexpensive microprocessors and advances in communication technology.
These advancements in technology have made it possible to design and develop
distributed systems comprising of many computers that are inter connected by
communication networks. The main benefit of distributed systems is its low
price/performance ratio.
Advantages Distributed Operating System
1. As there are multiple systems involved, user at one site can utilize the resources
of systems at other sites for resource-intensive tasks.
2. Fast processing.
3. Less load on the Host Machine.
1. Client-Server Systems
2. Peer-to-Peer Systems
Client-Server Systems
Centralized systems today act as server systems to satisfy requests generated
by client systems. The general structure of a client-server system is depicted in the
figure below:
Server Systems can be broadly categorized as: Compute Servers and File Servers.
Peer-to-Peer Systems
The growth of computer networks - especially the Internet and World Wide Web (WWW)
– has had a profound influence on the recent development of operating systems. When
PCs were introduced in the 1970s, they were designed for personal use and were
generally considered standalone computers. With the beginning of widespread public use
Page 3 of 54
of the Internet in the 1990s for electronic mail and FTP, many PCs became connected to
computer networks.
In contrast to the Tightly Coupled systems, the computer networks used in these
applications consist of a collection of processors that do not share memory or a clock.
Instead, each processor has its own local memory. The processors communicate with
one another through various communication lines, such as high-speed buses or
telephone lines. These systems are usually referred to as loosely coupled systems ( or
distributed systems). The general structure of a client-server system is depicted in the
figure below:
Clustered Systems
Clustered technology is rapidly changing. Clustered system's usage and it's features
should expand greatly as Storage Area Networks(SANs). SANs allow easy attachment
of multiple hosts to multiple storage units. Current clusters are usually limited to two or
four hosts due to the complexity of connecting the hosts to shared storage.
Page 4 of 54
It is defined as an operating system known to give maximum time for each of the critical
operations that it performs, like OS calls and interrupt handling.
The Real-Time Operating system which guarantees the maximum time for critical
operations and complete them on time are referred to as Hard Real-Time Operating
Systems.
While the real-time operating systems that can only guarantee a maximum of the time,
i.e. the critical task will get priority over other tasks, but no assurity of completeing it in a
defined time. These systems are referred to as Soft Real-Time Operating Systems.
Handheld Systems
Handheld systems include Personal Digital Assistants(PDAs), such as Palm-
Pilots or Cellular Telephones with connectivity to a network such as the Internet. They
are usually of limited size due to which most handheld devices have a small amount of
memory, include slow processors, and feature small display screens.
Some handheld devices may use wireless technology such as BlueTooth, allowing
remote access to e-mail and web browsing. Cellular telephones with connectivity to the
Internet fall into this category. Their use continues to expand as network connections
become more available and other options such as cameras and MP3 players, expand
their utility.
What is a Process?
A process is a program in execution. Process is not as same as program code but a lot
more than it. A process is an 'active' entity as opposed to program which is considered to
be a 'passive' entity. Attributes held by process include hardware state, memory, CPU
etc.
Process memory is divided into four sections for efficient working :
The Text section is made up of the compiled program code, read in from non-
volatile storage when the program is launched.
The Data section is made up the global and static variables, allocated and
initialized prior to executing the main.
The Heap is used for the dynamic memory allocation, and is managed via calls to
new, delete, malloc, free, etc.
Page 5 of 54
The Stack is used for local variables. Space on the stack is reserved for local
variables when they are declared.
Page 6 of 54
What is Process Scheduling?
The act of determining which process is in the ready state, and should be moved to
the running state is known as Process Scheduling.
The prime aim of the process scheduling system is to keep the CPU busy all the time
and to deliver minimum response time for all programs. For achieving this, the scheduler
must apply appropriate rules for swapping processes IN and OUT of CPU.
Scheduling fell into one of the two general categories:
All processes, upon entering into the system, are stored in the Job Queue.
Processes in the Ready state are placed in the Ready Queue.
Processes waiting for a device to become available are placed in Device Queues.
There are unique device queues available for each I/O 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 the following several events can occur:
The process could issue an I/O request, and then be placed in the I/O queue.
The process could create a new subprocess and wait for its termination.
The process could be removed forcibly from the CPU, as a result of an interrupt,
and be put back in the ready queue.
Page 7 of 54
In the first two cases, the process eventually switches from the waiting state to the ready
state, and is then put back in the ready queue. A process continues this cycle until it
terminates, at which time it is removed from all queues and has its PCB and resources
deallocated.
Types of Schedulers
There are three types of schedulers available:
Page 8 of 54
Addition of Medium-term scheduling to the queueing diagram.
1. Switching the CPU to another process requires saving the state of the old
process and loading the saved state for the new process. This task is known as
a Context Switch.
2. The context of a process is represented in the Process Control Block(PCB) of a
process; it includes the value of the CPU registers, the process state and memory-
management information. When a context switch occurs, the Kernel saves the
context of the old process in its PCB and loads the saved context of the new
process scheduled to run.
3. Context switch time is pure overhead, because the system does no useful
work while switching. Its speed varies from machine to machine, depending on
the memory speed, the number of registers that must be copied, and the
existence of special instructions(such as a single instruction to load or store all
registers). Typical speeds range from 1 to 1000 microseconds.
4. Context Switching has become such a performance bottleneck that programmers
are using new structures(threads) to avoid it whenever and wherever possible.
Operations on Process
Below we have discussed the two major operation Process Creation and Process
Termination.
Process Creation
Through appropriate system calls, such as fork or spawn, processes may create other
processes. The process which creates other process, is termed the parent of the other
process, while the created sub-process is termed its child.
Each process is given an integer identifier, termed as process identifier, or PID. The
parent PID (PPID) is also stored for each process.
On a typical UNIX systems the process scheduler is termed as sched, and is given PID
0. The first thing done by it at system start-up time is to launch init, which gives that
process PID 1. Further Init launches all the system daemons and user logins, and
becomes the ultimate parent of all other processes.
Page 9 of 54
A child process may receive some amount of shared resources with its parent depending
on system implementation. To prevent runaway children from consuming all of a certain
system resource, child processes may or may not be limited to a subset of the resources
originally allocated to the parent.
There are two options for the parent process after creating the child :
Wait for the child process to terminate before proceeding. Parent process makes
a wait() system call, for either a specific child process or for any particular child
process, which causes the parent process to block until the wait() returns. UNIX
shells normally wait for their children to complete before issuing a new prompt.
Run concurrently with the child, continuing to process without waiting. When a
UNIX shell runs a process as a background task, this is the operation seen. It is
also possible for the parent to run for a while, and then wait for the child later,
which might occur in a sort of a parallel processing operation.
There are also two possibilities in terms of the address space of the new process:
To illustrate these different implementations, let us consider the UNIX operating system.
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. The new process consists of a copy of
the address space of the original process. This mechanism allows the parent process to
communicate easily with its child process. Both processes (the parent and the child)
continue execution at the instruction after the fork system call, with one difference: The
return code for the fork system call is zero for the new(child) process, whereas
the(non zero) process identifier of the child is returned to the parent.
Typically, the execlp system call is used after the fork system call by one of the two
processes to replace the process memory space with a new program. The execlp system
call loads a binary file into memory - destroying the memory image of the program
containing the execlp system call – and starts its execution. In this manner the two
processes are able to communicate, and then to go their separate ways.
Below is a C program to illustrate forking a separate process using UNIX(made using
Ubuntu):
Page 10 of 54
#include<stdio.h>
void main(int argc, char *argv[])
{
int pid;
/* Fork another process */
pid = fork();
if(pid < 0)
{
//Error occurred
fprintf(stderr, "Fork Failed");
exit(-1);
}
else if (pid == 0)
{
//Child process
execlp("/bin/ls","ls",NULL);
}
else
{
//Parent process
//Parent will wait for the child to complete
wait(NULL);
printf("Child complete");
exit(0);
}
}
GATE Numerical Tip: If fork is called for n times, the number of child processes or new
processes created will be: 2n – 1.
Process Termination
By making the exit(system call), typically returning an int, processes may request their
own termination. This int is passed along to the parent if it is doing a wait(), and is
typically zero on successful completion and some non-zero code in the event of any
problem.
Processes may also be terminated by the system for a variety of reasons, including :
When a process ends, all of its system resources are freed up, open files flushed and
closed, etc. The process termination status and execution times are returned to the
Page 11 of 54
parent if the parent is waiting for the child to terminate, or eventually returned to init if the
process already became an orphan.
The processes which are trying to terminate but cannot do so because their parent is not
waiting for them are termed zombies. These are eventually inherited by init as orphans
and killed off.
Switching context
Switching to user mode
Jumping to the proper location in the user program to restart that program from
where it left last time.
The dispatcher should be as fast as possible, given that it is invoked during every
process switch. The time taken by the dispatcher to stop one process and start another
process is known as the Dispatch Latency. Dispatch Latency can be explained using
the below figure:
Non-Preemptive Scheduling
Under non-preemptive scheduling, once the CPU has been allocated to a process, the
process keeps the CPU until it releases the CPU either by terminating or by switching to
the waiting state.
This scheduling method is used by the Microsoft Windows 3.1 and by the Apple
Macintosh operating systems.
It is the only method that can be used on certain hardware platforms, because It does not
require the special hardware(for example: a timer) needed for preemptive scheduling.
Preemptive Scheduling
In this type of Scheduling, the tasks are usually assigned with priorities. At times it is
necessary to run a certain task that has a higher priority before another task although it is
running. Therefore, the running task is interrupted for some time and resumed later when
the priority task has finished its execution.
Page 13 of 54
The sum of the periods spent waiting in the ready queue amount of time a process has
been waiting in the ready queue to acquire get control on the CPU.
Load Average
It is the average number of processes residing in the ready queue waiting for their turn to
get into the CPU.
Response Time
Amount of time it takes from when a request was submitted until the first response is
produced. Remember, it is the time till the first response and not the completion of
process execution(final response).
In general CPU utilization and Throughput are maximized and other factors are reduced
for proper optimization.
Scheduling Algorithms
To decide which process to execute first and which process to execute last to achieve
maximum CPU utilisation, computer scientists have defined some algorithms, they are:
We will be discussing all the scheduling algorithms, one by one, in detail in the next
tutorials.
First Come First Serve, is just like FIFO(First in First out) Queue data structure,
where the data element which is added to the queue first, is the one who leaves
the queue first.
This is used in Batch Systems.
It's easy to understand and implement programmatically, using a Queue data
structure, where a new process enters through the tail of the queue, and the
scheduler selects process from the head of the queue.
A perfect real life example of FCFS scheduling is buying tickets at ticket
counter.
The GANTT chart above perfectly represents the waiting time for each process.
If a process with very least priority is being executed, more like daily routine
backup process, which takes more time, and all of a sudden some other high
priority process arrives, like interrupt to avoid system crash, the high priority
process will have to wait, and hence in this case, the system will crash, just
because of improper process scheduling.
Page 16 of 54
{
int wt[n], tat[n], total_wt = 0, total_tat = 0;
// function to find waiting time of all processes
findWaitingTime(processes, n, bt, wt);
// function to find turn around time for all processes
findTurnAroundTime(processes, n, bt, wt, tat);
// display processes along with all details
cout << "Processes "<< " Burst time "<< " Waiting time " << " Turn around time\n";
// calculate total waiting time and total turn around time
for (int i = 0; i < n; i++)
{
total_wt = total_wt + wt[i];
total_tat = total_tat + tat[i];
cout << " " << i+1 << "\t\t" << bt[i] <<"\t "<< wt[i] <<"\t\t " << tat[i] <<endl;
}
cout << "Average waiting time = "<< (float)total_wt / (float)n;
cout << "\nAverage turn around time = "<< (float)total_tat / (float)n;
}
// main function
int main()
{
// process ids
int processes[] = { 1, 2, 3, 4};
int n = sizeof processes / sizeof processes[0];
// burst time of all processes
int burst_time[] = {21, 3, 6, 2};
findAverageTime(processes, n, burst_time);
return 0;
}
Here we have simple formulae for calculating various times for given processes:
Completion Time: Time taken for the execution to complete, starting from arrival time.
Turn Around Time: Time taken to complete after arrival. In simple words, it is the
difference between the Completion time and the Arrival time.
Waiting Time: Total time the process has to wait before it's execution begins. It is the
difference between the Turn Around time and the Burst time of the process.
For the program above, we have considered the arrival time to be 0 for all the
processes, try to implement a program with variable arrival times.
Page 18 of 54
As you can see in the GANTT chart above, the process P4 will be picked up first as it
has the shortest burst time, then P2, followed by P3 and at last P1.
We scheduled the same set of processes using the First come first serve algorithm in the
previous tutorial, and got average waiting time to be 18.75 ms, whereas with SJF, the
average waiting time comes out 4.5 ms.
As you can see in the GANTT chart above, as P1 arrives first, hence it's execution starts
immediately, but just after 1 ms, process P2 arrives with a burst time of 3 ms which is
less than the burst time of P1, hence the process P1(1 ms done, 20 ms left) is
preemptied and process P2 is executed.
As P2 is getting executed, after 1 ms, P3 arrives, but it has a burst time greater than that
of P2, hence execution of P2 continues. But after another millisecond, P4 arrives with a
burst time of 2 ms, as a result P2(2 ms done, 1 ms left) is preemptied and P4 is
executed.
Page 19 of 54
After the completion of P4, process P2 is picked up and finishes, then P2 will get
executed and at last P1.
The Pre-emptive SJF is also known as Shortest Remaining Time First, because at any
given point of time, the job with the shortest remaining time is executed first.
Page 21 of 54
{
cout << proc[i].pid <<" ";
}
findAverageTime(proc, n);
return 0;
}
1. Preemptive Priority Scheduling: If the new process arrived at the ready queue
has a higher priority than the currently running process, the CPU is preempted,
Page 22 of 54
which means the processing of the current process is stoped and the incoming
new process with higher priority gets the CPU for its execution.
2. Non-Preemptive Priority Scheduling: In case of non-preemptive priority
scheduling algorithm if a new process arrives with a higher priority than the current
running process, the incoming process is put at the head of the ready queue,
which means after the execution of the current process it will be processed.
As you can see in the GANTT chart that the processes are given CPU time just on the
basis of the priorities.
Page 24 of 54
// calculating turnaround time by adding
// bt[i] + wt[i]
for (int i = 0; i < n ; i++)
tat[i] = proc[i].bt + wt[i];
}
//Function to calculate average time
void findavgTime(Process proc[], int n)
{
int wt[n], tat[n], total_wt = 0, total_tat = 0;
//Function to find waiting time of all processes
findWaitingTime(proc, n, wt);
//Function to find turn around time for all processes
findTurnAroundTime(proc, n, wt, tat);
//Display processes along with all details
cout << "\nProcesses "<< " Burst time "
<< " Waiting time " << " Turn around time\n";
// Calculate total waiting time and total turn
// around time
for (int i=0; i<n; i++)
{
total_wt = total_wt + wt[i];
total_tat = total_tat + tat[i];
cout << " " << proc[i].pid << "\t\t"
<< proc[i].bt << "\t " << wt[i]
<< "\t\t " << tat[i] <<endl;
}
cout << "\nAverage waiting time = "
<< (float)total_wt / (float)n;
cout << "\nAverage turn around time = "
<< (float)total_tat / (float)n;
}
void priorityScheduling(Process proc[], int n)
{
// Sort processes by priority
sort(proc, proc + n, sortProcesses);
Page 26 of 54
A multi-level queue scheduling algorithm partitions the ready queue into several separate
queues. The processes are permanently assigned to one queue, generally based on
some property of the process, such as memory size, process priority, or process type.
Each queue has its own scheduling algorithm.
For example: separate queues might be used for foreground and background
processes. The foreground queue might be scheduled by Round Robin algorithm, while
the background queue is scheduled by an FCFS algorithm.
In addition, there must be scheduling among the queues, which is commonly
implemented as fixed-priority preemptive scheduling. For example: The foreground
queue may have absolute priority over the background queue.
1. System Processes
2. Interactive Processes
3. Interactive Editing Processes
4. Batch Processes
5. Student Processes
Each queue has absolute priority over lower-priority queues. No process in the batch
queue, for example, could run unless the queues for system processes, interactive
processes, and interactive editing processes were all empty. If an interactive editing
process entered the ready queue while a batch process was running, the batch process
will be preempted.
The definition of a multilevel feedback queue scheduler makes it the most general CPU-
scheduling algorithm. It can be configured to match a specific system under design.
Unfortunately, it also requires some means of selecting values for all the parameters to
define the best scheduler. Although a multilevel feedback queue is the most general
scheme, it is also the most complex.
Page 28 of 54
Types of Thread
There are two types of threads:
1. User Threads
2. Kernel Threads
User threads, are above the kernel and without kernel support. These are the threads
that application programmers use in their programs.
Kernel threads are supported within the kernel of the OS itself. All modern OSs support
kernel level threads, allowing the kernel to perform multiple simultaneous tasks and/or to
service multiple kernel system calls simultaneously.
Multithreading Models
The user threads must be mapped to kernel threads, by one of the following strategies:
In the many to one model, many user-level threads are all mapped onto a single
kernel thread.
Thread management is handled by the thread library in user space, which is
efficient in nature.
The one to one model creates a separate kernel thread to handle each and every
user thread.
Most implementations of this model place a limit on how many threads can be
created.
Linux and Windows from 95 to XP implement the one-to-one model for threads.
Page 29 of 54
Many to Many Model
The many to many model multiplexes any number of user threads onto an equal
or smaller number of kernel threads, combining the best features of the one-to-
one and many-to-one models.
Users can create any number of the threads.
Blocking the kernel system calls does not block the entire process.
Processes can be split across multiple processors.
Page 30 of 54
Benefits of Multithreading
1. Responsiveness
2. Resource sharing, hence allowing better utilization of resources.
3. Economy. Creating and managing threads becomes easier.
4. Scalability. One thread runs on one CPU. In Multithreaded processes, threads can
be distributed over a series of processors to scale.
5. Context Switching is smooth. Context switching refers to the procedure followed
by CPU to change from one task to another.
Multithreading Issues
Below we have mentioned a few issues related to multithreading. Well, it's an old
saying, All good things, come at a price.
Thread Cancellation
Thread cancellation means terminating a thread before it has finished working. There can
be two approaches for this, one is Asynchronous cancellation, which terminates the
target thread immediately. The other is Deferred cancellation allows the target thread to
periodically check if it should be cancelled.
Signal Handling
Signals are used in UNIX systems to notify a process that a particular event has
occurred. Now in when a Multithreaded process receives a signal, to which thread it must
be delivered? It can be delivered to all, or a single thread.
fork() System Call
fork() is a system call executed in the kernel through which a process creates a copy of
itself. Now the problem in Multithreaded process is, if one thread forks, will the entire
process be copied or not?
Security Issues
Yes, there can be security issues because of extensive sharing of resources between
multiple threads.
There are many other issues that you might face in a multithreaded process, but there
are appropriate solutions available for them. Pointing out some issues here was just to
study both sides of the coin.
Process Synchronization
Process Synchronization means sharing system resources by processes in a such a way
that, Concurrent access to shared data is handled thereby minimizing the chance of
inconsistent data. Maintaining data consistency demands mechanisms to ensure
synchronized execution of cooperating processes.
Process Synchronization was introduced to handle problems that arose while multiple
process executions. Some of the problems are discussed below.
Page 31 of 54
given point of time, only one process must be executing its critical section. If any other
process also wants to execute its critical section, it must wait until the first one finishes.
Synchronization Hardware
Many systems provide hardware support for critical section code. The critical section
problem could be solved easily in a single-processor environment if we could disallow
interrupts to occur while a shared variable or resource is being modified.
In this manner, we could be sure that the current sequence of instructions would be
allowed to execute in order without pre-emption. Unfortunately, this solution is not
feasible in a multiprocessor environment.
Disabling interrupt on a multiprocessor environment can be time consuming as the
message is passed to all the processors.
This message transmission lag, delays entry of threads into critical section and the
system efficiency decreases.
Page 32 of 54
Mutex Locks
As the synchronization hardware solution is not easy to implement for everyone, a strict
software approach called Mutex Locks was introduced. In this approach, in the entry
section of code, a LOCK is acquired over the critical resources modified and used inside
critical section, and in the exit section that LOCK is released.
As the resource is locked while a process executes its critical section hence no other
process can access it.
Introduction to Semaphores
In 1965, Dijkstra proposed a new and very significant technique for managing concurrent
processes by using the value of a simple integer variable to synchronize the progress of
interacting processes. This integer variable is called semaphore. So it is basically a
synchronizing tool and is accessed only through two low standard atomic
operations, wait and signal designated by P(S) and V(S) respectively.
In very simple words, semaphore is a variable which can hold only a non-negative
Integer value, shared between all the threads, with operations wait and signal, which
work as follow:
P(S): if S ≥ 1 then S := S - 1
else <block and enqueue the process>;
Wait: Decrements the value of its argument S, as soon as it would become non-
negative(greater than or equal to 1).
Signal: Increments the value of its argument S, as there is no more process
blocked on the queue.
Properties of Semaphores
Types of Semaphores
Semaphores are mainly of two types:
1. Binary Semaphore:
Page 33 of 54
It is a special form of semaphore used for implementing mutual exclusion, hence it
is often called a Mutex. A binary semaphore is initialized to 1 and only takes the
values 0 and 1 during execution of a program.
2. Counting Semaphores:
Example of Use
Here is a simple step wise implementation involving declaration and usage of
semaphore.
Shared var mutex: semaphore = 1;
Process i
begin
.
.
P(mutex);
execute CS;
V(mutex);
.
.
End;
Limitations of Semaphores
Page 34 of 54
This problem is generalised in terms of the Producer Consumer problem, where
a finite buffer pool is used to exchange messages between producer and
consumer processes.
Because the buffer pool has a maximum size, this problem is often called
the Bounded buffer 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.
In this problem there are some processes(called readers) that only read the
shared data, and never change it, and there are other processes(called writers)
who may change the data in addition to reading, or instead of reading it.
There are various type of readers-writers problem, most centred on relative
priorities of readers and writers.
Page 35 of 54
A producer tries to insert data into an empty slot of the buffer. A consumer tries to
remove data from a filled slot in the buffer. As you might have guessed by now, those
two processes won't produce the expected output if they are being executed
concurrently.
There needs to be a way to make the producer and consumer work in an independent
manner.
Here's a Solution
One solution of this problem is to use semaphores. The semaphores which will be used
here are:
At any instant, the current value of empty represents the number of empty slots in the
buffer and full represents the number of occupied slots in the buffer.
// release lock
signal(mutex);
// increment 'full'
signal(full);
}
while(TRUE)
Looking at the above code for a producer, we can see that a producer first waits
until there is atleast one empty slot.
Then it decrements the empty semaphore because, there will now be one less
empty slot, since the producer is going to insert data in one of those slots.
Then, it acquires lock on the buffer, so that the consumer cannot access the buffer
until producer completes its operation.
Page 36 of 54
After performing the insert operation, the lock is released and the value of full is
incremented because the producer has just filled a slot in the buffer.
The consumer waits until there is atleast one full slot in the buffer.
Then it decrements the full semaphore because the number of occupied slots will
be decreased by one, after the consumer completes its operation.
After that, the consumer acquires lock on the buffer.
Following that, the consumer completes the removal operation so that the data
from one of the full slots is removed.
Then, the consumer releases the lock.
Finally, the empty semaphore is incremented by 1, because the consumer has
just removed data from an occupied slot, thus making it empty.
Page 37 of 54
Dining Philosophers Problem
Page 38 of 54
But if all five philosophers are hungry simultaneously, and each of them pickup one
chopstick, then a deadlock situation occurs because they will be waiting for another
chopstick forever. The possible solutions for this are:
A philosopher must be allowed to pick up the chopsticks only if both the left and
right chopsticks are available.
Allow only four philosophers to sit at the table. That way, if all the four
philosophers pick up four chopsticks, there will be one chopstick left on the table.
So, one philosopher can start eating and eventually, two chopsticks will be
available. In this way, deadlocks can be avoided.
The Solution
From the above problem statement, it is evident that readers have higher priority than
writer. If a writer wants to write to the resource, it must wait until there are no readers
currently accessing that resource.
Here, we use one mutex m and a semaphore w. An integer variable read_count is used
to maintain the number of readers currently accessing the resource. The
variable read_count is initialized to 0. A value of 1 is given initially to m and w.
Instead of having the process to acquire lock on the shared resource, we use the
mutex m to make the process to acquire and release lock whenever it is updating
the read_count variable.
The code for the writer process looks like this:
while(TRUE)
{
wait(w);
signal(w);
}
And, the code for the reader process looks like this:
while(TRUE)
Page 39 of 54
{
//acquire lock
wait(m);
read_count++;
if(read_count == 1)
wait(w);
//release lock
signal(m);
// acquire lock
wait(m);
read_count--;
if(read_count == 0)
signal(w);
// release lock
signal(m);
}
As seen above in the code for the writer, the writer just waits on the w semaphore
until it gets a chance to write to the resource.
After performing the write operation, it increments w so that the next writer can
access the resource.
On the other hand, in the code for the reader, the lock is acquired whenever
the read_count is updated by a process.
When a reader wants to access the resource, first it increments
the read_count value, then accesses the resource and then decrements
the read_count value.
The semaphore w is used by the first reader which enters the critical section and
the last reader which exits the critical section.
The reason for this is, when the first readers enters the critical section, the writer is
blocked from the resource. Only new readers can access the resource now.
Similarly, when the last reader exits the critical section, it signals the writer using
the w semaphore because there are zero readers now and a writer can have the
chance to access the resource.
What is a Deadlock?
Page 40 of 54
Deadlocks are a set of blocked processes each holding a resource and waiting to acquire
a resource held by another process.
Handling Deadlock
The above points focus on preventing deadlocks. But what to do once a deadlock has
occured. Following three strategies can be used to remove deadlock after its occurrence.
1. Preemption
We can take a resource from one process and give it to other. This will resolve the
deadlock situation, but sometimes it does causes problems.
2. Rollback
In situations where deadlock is a real possibility, the system can periodically make
a record of the state of each process and when deadlock occurs, roll everything
back to the last checkpoint, and restart, but allocating resources differently so that
deadlock does not occur.
3. Kill one or more processes
This is the simplest way, but it works.
What is a Livelock?
There is a variant of deadlock called livelock. This is a situation in which two or more
processes continuously change their state in response to changes in the other
Page 41 of 54
process(es) without doing any useful work. This is similar to deadlock in that no progress
is made but differs in that neither process is blocked or waiting for anything.
A human example of livelock would be two people who meet face-to-face in a corridor
and each moves aside to let the other pass, but they end up swaying from side to side
without making any progress because they always move the same way at the same time.
Swapping
A process needs to be in memory for execution. But sometimes there is not enough main
memory to hold all the currently active processes in a timesharing system. So, excess
process are kept on disk and brought in to run dynamically. Swapping is the process of
bringing in each process in main memory, running it for a while and then putting it back to
the disk.
Memory Protection
Memory protection is a phenomenon by which we control memory access rights on a
computer. The main aim of it is to prevent a process from accessing memory that has not
been allocated to it. Hence prevents a bug within a process from affecting other
processes, or the operating system itself, and instead results in a segmentation fault or
storage violation exception being sent to the disturbing process, generally killing of
process.
Memory Allocation
Memory allocation is a process by which computer programs are assigned memory or
space. It is of three types :
1. First Fit:
The first hole that is big enough is allocated to program.
Page 42 of 54
2. Best Fit:
The smallest hole that is big enough is allocated to program.
3. Worst Fit:
The largest hole that is big enough is allocated to program.
Fragmentation
Fragmentation occurs in a dynamic memory allocation system when most of the free
blocks are too small to satisfy any request. It is generally termed as inability to use the
available memory.
In such situation processes are loaded and removed from the memory. As a result of
this, free holes exists to satisfy a request but is non contiguous i.e. the memory is
fragmented into large no. Of small holes. This phenomenon is known as External
Fragmentation.
Also, at times the physical memory is broken into fixed size blocks and memory is
allocated in unit of block sizes. The memory allocated to a space may be slightly larger
than the requested memory. The difference between allocated and required memory is
known as Internal fragmentation i.e. the memory that is internal to a partition but is of
no use.
Paging
A solution to fragmentation problem is Paging. Paging is a memory management
mechanism that allows the physical address space of a process to be non-contagious.
Here physical memory is divided into blocks of equal size called Pages. The pages
belonging to a certain process are loaded into available memory frames.
Page Table
A Page Table is the data structure used by a virtual memory system in a computer
operating system to store the mapping between virtual address and physical addresses.
Virtual address is also known as Logical address and is generated by the CPU. While
Physical address is the address that actually exists on memory.
Segmentation
Segmentation is another memory management scheme that supports the user-view of
memory. Segmentation allows breaking of the virtual address space of a single process
into segments that may be placed in non-contiguous areas of physical memory.
Segment numbers(S)
Page number (P)
The displacement or offset number (D)
Error handling code is not needed unless that specific error occurs, some of which
are quite rare.
Arrays are often over-sized for worst-case scenarios, and only a small fraction of
the arrays are actually used in practice.
Certain features of certain programs are rarely used.
Initially only those pages are loaded which will be required the process immediately.
The pages that are not moved into the memory, are marked as invalid in the page table.
For an invalid entry the rest of the table is empty. In case of pages that are loaded in the
memory, they are marked as valid along with the information about where to find the
swapped out page.
Page 44 of 54
When the process requires any of the page that is not loaded into the memory, a page
fault trap is triggered and following steps are followed,
1. The memory address which is requested by the process is first checked, to verify
the request made by the process.
2. If its found to be invalid, the process is terminated.
3. In case the request by the process is valid, a free frame is located, possibly from a
free-frame list, where the required page will be moved.
4. A new operation is scheduled to move the necessary page from disk to the
specified memory location. ( This will usually block the process on an I/O wait,
allowing some other process to use the CPU in the meantime. )
5. When the I/O operation is complete, the process's page table is updated with the
new frame number, and the invalid bit is changed to valid.
6. The instruction that caused the page fault must now be restarted from the
beginning.
There are cases when no pages are loaded into the memory initially, pages are only
loaded when demanded by the process by generating page faults. This is called Pure
Demand Paging.
The only major issue with Demand Paging is, after a new page is loaded, the process
starts execution from the beginning. Its is not a big issue for small programs, but for
larger programs it affects performance drastically.
Page Replacement
As studied in Demand Paging, only certain pages of a process are loaded initially into the
memory. This allows us to get more number of processes into the memory at the same
time. but what happens when a process requests for more pages and no free memory is
available to bring them in. Following steps can be taken to deal with this problem :
1. Put the process in the wait queue, until any other process finishes its execution
thereby freeing frames.
2. Or, remove some other process completely from the memory to free frames.
3. Or, find some pages that are not being used right now, move them to the disk to
get free frames. This technique is called Page replacement and is most
commonly used. We have some great algorithms to carry on page replacement
efficiently.
Find the location of the page requested by ongoing process on the disk.
Find a free frame. If there is a free frame, use it. If there is no free frame, use a
page-replacement algorithm to select any existing frame to be replaced, such
frame is known as victim frame.
Write the victim frame to disk. Change all related page tables to indicate that this
page is no longer in memory.
Move the required page and store it in the frame. Adjust all related page and
frame tables to indicate the change.
Restart the process that was waiting for this page.
Page 45 of 54
A very simple way of Page replacement is FIFO (First in First Out)
As new pages are requested and are swapped in, they are added to tail of a
queue and the page which is at the head becomes the victim.
Its not an effective way of page replacement but can be used for small systems.
Thrashing
A process that is spending more time paging than executing is said to be thrashing. In
other words it means, that the process doesn't have enough frames to hold all the pages
for its execution, so it is swapping pages in and out very frequently to keep executing.
Sometimes, the pages which will be required in the near future have to be swapped out.
Initially when the CPU utilization is low, the process scheduling mechanism, to increase
the level of multiprogramming loads multiple processes into the memory at the same
time, allocating a limited amount of frames to each process. As the memory fills up,
process starts to spend a lot of time for the required pages to be swapped in, again
leading to low CPU utilization because most of the proccesses are waiting for pages.
Hence the scheduler loads more processes to increase CPU utilization, as this continues
at a point of time the complete system comes to a stop.
To prevent thrashing we must provide processes with as many frames as they really
need "right now".
File Structure
A file has various kinds of structure. Some of them can be :
Attributes of a File
Page 46 of 54
Following are some of the attributes of a file :
2. Direct Access
What is a Directory?
Information about files is maintained by Directories. A directory can contain multiple files.
It can even have directories inside of them. In Windows we also call these directories as
folders.
Following is the information maintained in a directory :
1. Available
It is an array of length m. It represents the number of available resources of each type.
If Available[j] = k, then there are k instances available, of resource type R(j).
2. Max
It is an n x m matrix which represents the maximum number of instances of each
resource that a process can request. If Max[i][j] = k, then the process P(i) can request
atmost k instances of resource type R(j).
3. Allocation
It is an n x m matrix which represents the number of resources of each type currently
allocated to each process. If Allocation[i][j] = k, then process P(i) is currently
allocated k instances of resource type R(j).
4. Need
It is an n x m matrix which indicates the remaining resource needs of each process.
If Need[i][j] = k, then process P(i) may need k more instances of resource type R(j) to
complete its task.
Need[i][j] = Max[i][j] - Allocation [i][j]
1. If number of requested instances of each resource is less than the need (which
was declared previously by the process), go to step 2.
Page 48 of 54
2. If number of requested instances of each resource type is less than the available
resources of each type, go to step 3. If not, the process has to wait because
sufficient resources are not available yet.
3. Now, assume that the resources have been allocated. Accordingly do,
This step is done because the system needs to assume that resources have been
allocated. So there will be less resources available after allocation. The number of
allocated instances will increase. The need of the resources by the process will reduce.
That's what is represented by the above three operations.
After completing the above three steps, check if the system is in safe state by applying
the safety algorithm. If it is in safe state, proceed to allocate the requested resources.
Else, the process has to wait longer.
Safety Algorithm
10. If Finish[i] == true for all i, then the system is in a safe state.
That means if all processes are finished, then the system is in safe state.
Page 49 of 54
Secondary storage devices are those devices whose memory is non volatile, meaning,
the stored data will be intact even if the system is turned off. Here are a few things worth
noting about secondary storage.
Transfer rate: This is the rate at which the data moves from disk to the computer.
Random access time: It is the sum of the seek time and rotational latency.
Seek time is the time taken by the arm to move to the required track. Rotational
latency is defined as the time taken by the arm to reach the required sector in the track.
Even though the disk is arranged as sectors and tracks physically, the data is logically
arranged and addressed as an array of blocks of fixed size. The size of a block can
Page 50 of 54
be 512 or 1024 bytes. Each logical block is mapped with a sector on the disk,
sequentially. In this way, each sector in the disk will have a logical address.
SCAN algorithm
Page 51 of 54
This algorithm is also called the elevator algorithm because of it's behavior. Here, first
the head moves in a direction (say backward) and covers all the requests in the path.
Then it moves in the opposite direction and covers the remaining requests in the path.
This behavior is similar to that of an elevator. Let's take the previous example,
98, 183, 37, 122, 14, 124, 65, 67
Assume the head is initially at cylinder 56. The head moves in backward direction and
accesses 37 and 14. Then it goes in the opposite direction and accesses the cylinders as
they come in the path.
Kernel Mode
When CPU is in kernel mode, the code being executed can access any memory
address and any hardware resource.
Hence kernel mode is a very privileged and powerful mode.
If a program crashes in kernel mode, the entire system will be halted.
User Mode
When CPU is in user mode, the programs don't have direct access to memory
and hardware resources.
Page 52 of 54
In user mode, if any program crashes, only that particular program is halted.
That means the system will be in a safe state even if a program in user mode
crashes.
Hence, most programs in an OS run in user mode.
System Call
When a program in user mode requires access to RAM or a hardware resource, it must
ask the kernel to provide access to that resource. This is done via something called
a system call.
When a program makes a system call, the mode is switched from user mode to kernel
mode. This is called a context switch.
Then the kernel provides the resource which the program requested. After that, another
context switch happens which results in change of mode from kernel mode back to user
mode.
Generally, system calls are made by the user level programs in the following situations:
In a typical UNIX system, there are around 300 system calls. Some of them which are
important ones in this context, are described below.
Fork()
The fork() system call is used to create processes. When a process (a program in
execution) makes a fork() call, an exact copy of the process is created. Now there are
two processes, one being the parent process and the other being the child process.
The process which called the fork() call is the parent process and the process which is
created newly is called the child process. The child process will be exactly the same as
the parent. Note that the process state of the parent i.e., the address space, variables,
open files etc. is copied into the child process. This means that the parent and child
processes have identical but physically different address spaces. The change of values
in parent process doesn't affect the child and vice versa is true too.
Both processes start execution from the next line of code i.e., the line after the fork() call.
Let's look at an example:
// example.c
#include <stdio.h>
void main()
{
int val;
val = fork(); // line A
printf("%d", val); // line B
}
Page 53 of 54
When the above example code is executed, when line A is executed, a child process is
created. Now both processes start execution from line B. To differentiate between the
child process and the parent process, we need to look at the value returned by
the fork() call.
The difference is that, in the parent process, fork() returns a value which represents
the process ID of the child process. But in the child process, fork() returns the value 0.
This means that according to the above program, the output of parent process will be
the process ID of the child process and the output of the child process will be 0.
Exec()
The exec() system call is also used to create processes. But there is one big difference
between fork() and exec() calls. The fork() call creates a new process while preserving
the parent process. But, an exec() call replaces the address space, text segment, data
segment etc. of the current process with the new process.
It means, after an exec() call, only the new process exists. The process which made the
system call, wouldn't exist.
There are many flavors of exec() in UNIX, one being exec1() which is shown below as an
example:
// example2.c
#include <stdio.h>
void main()
{
execl("/bin/ls", "ls", 0); // line A
printf("This text won't be printed unless an error occurs in exec().");
}
As shown above, the first parameter to the execl() function is the address of the program
which needs to be executed, in this case, the address of the ls utility in UNIX. Then it is
followed by the name of the program which is ls in this case and followed by optional
arguments. Then the list should be terminated by a NULL pointer (0).
When the above example is executed, at line A, the ls program is called and executed
and the current process is halted. Hence the printf() function is never called since the
process has already been halted. The only exception to this is that, if the execl() function
causes an error, then the printf() function is executed.
Page 54 of 54