Name- Midhun Nath K R
Reg. No.-22BAI10358
in partial fulfilment for the award of the degree
of
BACHELOR OF TECHNOLOGY
in
PROGRAM OF STUDY
SCHOOL OF COMPUTING SCIENCE AND ENGINEERING
VIT BHOPAL UNIVERSITY
KOTHRIKALAN, SEHORE
MADHYA PRADESH - 466114
MAY 2024
UNDER GUIDANCE OF:
Dr. Jitendra Pratap Singh Mathur
School of Computing Science and Engineering
Table of Contents
Sl. No. Name of the experiment Date Page No
Study of Hardware/Software re-
1 quirement of various operating 12-4-24 3 - 12
system.
Implement CPU scheduling poli-
2 cies 19-4-24 13 -24
Implementation of Contiguous al-
3 location techniques: 26-4-24 25 - 33
Implementation of resource allo-
4 cation graph (RAG).
1-5-24 34 - 46
Implementation of Banker’s Algo- 8-5-24
5 rithm.
47 - 57
Inter process Communication –
6 Semaphore
14-5-24 58 - 67
Experiment 1:
Study of Hardware/Software requirement of various oper-
ating system.
(I. (a) Study of hardware and software requirements of
different operating systems (UNIX, LINUX, WINDOWS XP,
WINDOWS 7/8). (1 Lab session)
(b) Execute various UNIX system calls for (1 Lab session)
1. Process management
2. File management
3. Input / Output System calls
IMPLEMENTATION DETAILS AND ASSUMPTIONS:
(i) The OBJECTIVE of this practical is to obtain a general overview of
various popular OS.
(a) Their development and distribution
(b) Compatibility
(c) Security issues and Thread detection and its solution
(d) The GUI etc...
(ii) Along with the above-mentioned activities, execution of various
UNIX commands is also helpful: cat, cd, cp, chmod, df, less, ls,
mkdir, more, mv, pwd, rmdir, rm, man, uname, who, ps, vi, cal, date,
echo, bc, grep
Objective: - Study of hardware and software requirements of different oper-
ating systems (UNIX, LINUX, WINDOWS XP, WINDOWS7/8
Content:
Hardware requirements: The most common set of requirements defined by
any operating system or software application is the physical computer re-
sources, also known as hardware, A hardware requirements list is often ac-
companied by a hardware compatibility list (HCL), especially in case of oper-
ating systems.
Hardware and Software Minimum Requirements
1. Windows 10
i. Processor: 1 gigahertz (GHz) or faster processor or SoC
ii. RAM: 1 gigabyte (GB) for 32-bit or 2 GB for 64-bit
iii. Hard disk space: 16 GB for 32-bit OS or 20 GB for 64-bit OS
iv. Graphics card: DirectX 9 or later with WDDM 1.0 driver
v. Display: 800 x 6009 with WDDM driver
2. WINDOWS XP
The minimum hardware requirements for Windows XP Home Edition are:
i. Pentium 233-megahertz (MHz) processor or faster (300 MHz is rec-
ommended)
ii. At least 64 megabytes (MB) of RAM (128 MB is recommended)
iii. At least 1.5 gigabytes (GB) of available space on the hard disk
iv. CD-ROM or DVD-ROM drive
v. Keyboard and a Microsoft Mouse or some other compatible pointing
device
vi. Video adapter and monitor with Super VGA (800 x 600)or higher
resolution
vii. Sound card
viii. Speakers or headphones
3. UNIX OS
· RAM: 1 GB
· Processor: IBM 604e processor with a clock speed of 375 MHz or faster
· Free disk space: /tmp must have I GB free disk space. If Tivoli Identity
Manager installs WebSphere Application Server, (WAS_HOME) must
have 800 MB free disk space and /var must have 300 MB free disk
space. Allocate 500 MB for /itim45.
4. LINUX
• 32-bit Intel-compatible processor running at 2 0Hz or greater
• 512 MB RAM • Disk space: 2.5 GB for Pipeline Pilot server plus compo-
nents
• A DVD-ROM drive
Objective: - Execute various UNIX system calls for
i. Process management
ii. File management
iii. Input/output Systems calls
Content:
The interface between a process and an operating system is provided by sys-
tem calls. In general, system calls are available as assembly language in-
structions. They are also included in the manuals used by the assembly level
programmers.
Unix System Calls
System calls in Unix are used for file system control, process control, inter-
process communication etc. Access to the Unix kernel is only available
through these system calls. Generally, system calls are similar to function
calls, the only difference is that they remove the control from the user
process. There are around 80 system calls in the Unix interface currently.
Details about some of the important ones are given as follows-
System Call Description
access() This checks if a calling process has access to the required file
chdir() The chdir command changes the current directory of the system
chmod() The mode of a file can be changed using this command
chown() This changes the ownership of a particular file
kill() This system call sends kill signal to one or more processes
link() A new 'lie name is linked to an existing file using link system call.
Open() This opens a file for the reading or writing process
Pause() The pause call suspends a file until a particular signal occurs.
Slime() This system call sets the correct time.
times() Gets the parent and child process times
alarm() The alarm system call sets the alarm clock of a process
fork() A new process is created using this command
chroot() This changes the root directory of a file.
Creating a new file named “sample1 and data in it :-
Code:-
cat > sample1
This is a sample file, by Prakarsh
Joshi
cat > sample1
View the content of “sample1”:-
Code:-
cat sample1
List files and directories in the current location :-
Code:-
$ ls
Display the current working directory :-
Code:-
Create a new directory named “example” :-
Code:-
Create empty files “file1”, “file2”, “file3” :-
Code:-
Copy a file/command:-
Code:-
Move Command:-
Code
Remove
files:-
Code
Experiment 2:
I. Implement CPU scheduling policies :
(a) SJF
(b) Priority
(c) FCFS
(d) Multi-level queue
IMPLEMENTATION DETAILS AND ASSUMPTIONS: INPUT/s:
(i) The number of processes/jobs in the system (computed through random
functions in C)
(ii) The CPU Burst (based on past history), priority (initially, compute
through random function), arrival time of process.
STEPS TO PERFORM:
(a) For SJF algorithm,
(i) We randomly generate the number of jobs. There must be a limit on the
number of jobs in a system.
(ii) The execution time of the generated jobs is also not known. Here, we
are generating the CPU burst of each job making use of the past history.
(iii) All the jobs are then arranged in a queue where searching is done to
find the one with the least CPU burst. There may be two jobs in queue with
the same execution time then FCFS approach is to be performed.
Case a) If the algorithm is non preemptive in nature, then the newly arriv-
ing job is to be added to the job queue even though it is of lesser execu-
tion time than the one running on the processor.
Case b) Otherwise preemption is performed.
(b) For Priority scheduling,
(i) We again prefer to compute the CPU burst from past history.
(ii) Priority may be assigned on the basis of their CPU burst (simplest ap-
proach)
(iii)Priority may be assigned through some random function (within a spe-
cific range say 1 to 100).
(iv) Priority has to be changed dynamically (to perform aging in order to
avoid starvation).
Priority (preemption) and priority (non preemption) nature of priority
scheduling is performed.
(c) The FCFS scheduling is performed on the basis of arrival time irrespec-
tive of their other parameters.
(d) In multi-level queue scheduling, different queues are to be created.
OUTPUT/s:
The average throughput, waiting time of process/s
a) SJF:
ALGORITHM:
Step 1: Start the process
Step 2: Accept the number of processes in the ready Queue
Step 3: For each process in the ready Q, assign the process id and accept the CPU
burst time
Step 4: Start the Ready Q according the shortest Burst time by sorting according to
lowest to highest burst time.
Step 5: Set the waiting time of the first process as Jr and its turnaround time as its
burst time.
Step 6: Sort the processes names based on their Burt time
Step 7: For each process in the ready queue, calculate
a) Waiting time(n)= waiting time (n-1) + Burst time (n-1)
b) Turnaround time (n)= waiting time(n)+Burst time(n)
Step 8: Calculate
c) Average waiting time = Total waiting Time / Numbcr of proccss
d)Average Turnaround time = Total Turnaround Time / Number ofprocess
Step 9: Stop the process
(b) Priority
ALGORITHM:
Step 1: Start the process
Step 2: Accept the number of processes in the ready Queue Step
3: For each process in the ready Q, assign the process id and accept the CPU burst
time
Step 4: Sort the ready queue according to the priority number.
Step 5: Set the waiting of the first process as _0° and its burst time as its turn-
around time
Step 6: Arrange the processes based on process priority
Step 7: For each process in the Ready Q calculate
Step 8: for each process in the Ready Q calculate
a) Waiting time(n)= waiting time (n-1) + Burst time (n-1)
b) Turnaround time (n)= waiting time(n)+Burst_time(n)
Step 9: Calculate
c) Average waiting time = Total waiting Time / Number of process
d) Average Turnaround time = Total Turnaround Time / Number of process
Print the results in an order.
Step10: Stop
(c) FCFS
ALGORITHM:
Step 1: Start the process
Step 2: Accept the number of processes in the ready Queue
Step 3: For each process in the ready Q, assign the process name and the burst
time
Step 4: Set the waiting of the first process as _O‘and its burst time as its turnaround
time
Step 5: for each process in the Ready Q calculate
a). Waiting time (n) = waiting time (n-1) + Burst time (n-1)
b). Turnaround time (n)= waiting time(n)+Burst time(n)
Step 6: Calculate
a) Average waiting time = Total waiting Time / Number of process
b) Average Turnaround time = Total Turnaround Time / Number of process
Step 7: Stop the process
(d) Multi-level queue
SOURCE CODE
#include<stdio.h>
int main()
{
int p[20],bt[20], su[20], wt[20],tat[20],i, k, n, temp;
float wtavg, tatavg;
printf("Enter the number of processes:");
scanf("%d",&n);
for(i=0;i<n;i++)
{
p[i] = i;
printf("Enter the Burst Time of Process%d:", i);
scanf("%d",&bt[i]);
printf("System/User Process (0/1) ? ");
scanf("%d", &su[i]);
}
for(i=0;i<n;i++)
for(k=i+1;k<n;k++)
if(su[i] > su[k])
{
temp=p[i];
p[i]=p[k];
p[k]=temp;
temp=bt[i];
bt[i]=bt[k];
bt[k]=temp;
temp=su[i];
su[i]=su[k];
su[k]=temp;
}
wtavg = wt[0] = 0;
tatavg = tat[0] = bt[0];
for(i=1;i<n;i++)
{
wt[i] = wt[i-1] + bt[i-1];
tat[i] = tat[i-1] + bt[i];
wtavg = wtavg + wt[i];
tatavg = tatavg + tat[i];
}
printf("\nPROCESS\t\t SYSTEM/USER PROCESS \tBURST TIME\tWAITING TIME\
tTURNAROUND TIME");
for(i=0;i<n;i++)
printf("\n%d \t\t %d \t\t %d \t\t %d \t\t %d ",p[i],su[i],bt[i],wt[i],tat[i]);
printf("\nAverage Waiting Time is --- %f",wtavg/n);
printf("\nAverage Turnaround Time is --- %f",tatavg/n);
return 0;
}
APPROACHES
Multilevel Feedback Queue (MLFQ) − The MLFQ approach is a variation of the
multilevel queue scheduling algorithm. It employs multiple queues with varying pri-
orities and allocates time slices to processes in each queue. However, the MLFQ al -
gorithm allows processes to move between queues based on their behavior.
Processes that use excessive CPU time or have higher priority may be moved to a
higher-priority queue, while processes that wait for I/O or exhibit low CPU usage
may be demoted to a lower-priority queue. This dynamic behavior allows the sys-
tem to adapt to changing process requirements.
Multilevel Priority Queue − In the multilevel priority queue approach, each
queue is assigned a different priority level, and processes are scheduled based on
their priority. Processes with higher priority are given preference and are executed
before lower-priority processes. This approach is suitable for systems where priority-
based scheduling is critical, such as real-time systems.
INPUT - OUTPUT
Experiment 3
Contiguous Allocation Techniques
I. Implementation of Contiguous allocation techniques:
(a) Worst-Fit
(b) Best-Fit
(c) First-Fit
IMPLEMENTATION DETAILS:
INPUT/s:
(i) Free space list of blocks from system (as created in experiment
3)
(ii) List processes and files from the system (as in experiment 3)
STEPS TO PERFORM:
(i) We consider the same free space list and files/processes as cre-
ated in experiment 3 for our system.
(ii) Implement the above mentioned three contiguous allocation
techniques. Also, the free space list is updated from the free blocks
left out after performing allocation.
(a) Worst-fit: In worst fit technique largest available block/partition
which will hold the page is selected. Blocks are sorted according to
their size in descending order.
(b) Best-fit: Best-fit is one of the optimal technique in which page is
stored in the block/partition which is large enough to hold it. Blocks
are sorted according to their size in ascending order.
(c) First-fit: In first-fit technique page is stored in the block which is
encountered first that is big enough to hold it.
(iii) Also, the free space list is updated from the free blocks left out
after performing allocation.
OUTPUT/s:
Processes and files allocated to free blocks. List of processes and
files which are not allocated memory. The remaining free space list
left out after performing allocation.
(a) Worst-Fit
PROGRAM
#include<stdio.h>
#include<conio.h>
#define max 25
void main()
int frag[max],b[max],f[max],i,j,nb,nf,temp;
static int bf[max],ff[max];
clrscr();
printf("\n\tMemory Management Scheme - First Fit");
printf("\nEnter the number of blocks:");
scanf("%d",&nb);
printf("Enter the number of files:");
scanf("%d",&nf);
printf("\nEnter the size of the blocks:-\n");
for(i=1;i<=nb;i++)
printf("Block %d:",i);
scanf("%d",&b[i]);
printf("Enter the size of the files :-\n");
for(i=1;i<=nf;i++)
printf("File %d:",i);
scanf("%d",&f[i]);
for(i=1;i<=nf;i++)
{
for(j=1;j<=nb;j++)
if(bf[j]!=1)
temp=b[j]-f[i];
if(temp>=0)
ff[i]=j;
break;
frag[i]=temp;
bf[ff[i]]=1;
printf("\nFile_no:\tFile_size :\tBlock_no:\tBlock_size:\tFragement");
for(i=1;i<=nf;i++)
printf("\n%d\t\t%d\t\t%d\t\t%d\t\t%d",i,f[i],ff[i],b[ff[i]],frag[i]);
getch();
INPUT
Enter the number of blocks: 3
Enter the number of files: 2
Enter the size of the blocks:-
Block 1: 5
Block 2: 2
Block 3: 7
Enter the size of the files:-
File 1: 1
File 2: 4
OUTPUT
File No File Size Block No Block Size Fragment
1 1 1 5 4
2 4 3 7 3
(b) Best-Fit
PROGRAM
#include<stdio.h>
#include<conio.h>
#define max 25
void main()
int frag[max],b[max],f[max],i,j,nb,nf,temp,lowest=10000;
static int bf[max],ff[max];
clrscr();
printf("\nEnter the number of blocks:");
scanf("%d",&nb);
printf("Enter the number of files:");
scanf("%d",&nf);
printf("\nEnter the size of the blocks:-\n");
for(i=1;i<=nb;i++)
{
printf("Block %d:",i);
scanf("%d",&b[i]);
printf("Enter the size of the files :-\n");
for(i=1;i<=nf;i++)
printf("File %d:",i);
scanf("%d",&f[i]);
for(i=1;i<=nf;i++)
for(j=1;j<=nb;j++)
if(bf[j]!=1)
temp=b[j]-f[i];
if(temp>=0)
if(lowest>temp)
ff[i]=j;
lowest=temp;
frag[i]=lowest;
bf[ff[i]]=1;
lowest=10000;
printf("\nFile No\tFile Size \tBlock No\tBlock Size\tFragment");
for(i=1;i<=nf && ff[i]!=0;i++)
printf("\n%d\t\t%d\t\t%d\t\t%d\t\t%d",i,f[i],ff[i],b[ff[i]],frag[i]);
getch();
INPUT
Enter the number of blocks: 3
Enter the number of files: 2
Enter the size of the blocks:-
Block 1: 5
Block 2: 2
Block 3: 7
Enter the size of the files:-
File 1: 1
File 2: 4
OUTPUT
File No File Size Block No Block Size Fragment
1 1 2 2 1
2 4 1 5 1
(c) First-Fit
PROGRAM
#include<stdio.h>
#include<conio.h>
#define max 25
void main()
int frag[max],b[max],f[max],i,j,nb,nf,temp,highest=0;
static int bf[max],ff[max];
clrscr();
printf("\n\tMemory Management Scheme - Worst Fit");
printf("\nEnter the number of blocks:");
scanf("%d",&nb);
printf("Enter the number of files:");
scanf("%d",&nf);
printf("\nEnter the size of the blocks:-\n");
for(i=1;i<=nb;i++)
printf("Block %d:",i);
scanf("%d",&b[i]);
printf("Enter the size of the files :-\n");
for(i=1;i<=nf;i++)
printf("File %d:",i);
scanf("%d",&f[i]);
for(i=1;i<=nf;i++)
for(j=1;j<=nb;j++)
{
if(bf[j]!=1) //if bf[j] is not allocated
temp=b[j]-f[i];
if(temp>=0)
if(highest<temp)
ff[i]=j;
highest=temp;
frag[i]=highest;
bf[ff[i]]=1;
highest=0;
printf("\nFile_no:\tFile_size :\tBlock_no:\tBlock_size:\tFragement");
for(i=1;i<=nf;i++)
printf("\n%d\t\t%d\t\t%d\t\t%d\t\t%d",i,f[i],ff[i],b[ff[i]],frag[i]);
getch();
INPUT
Enter the number of blocks: 3
Enter the number of files: 2
Enter the size of the blocks:-
Block 1: 5
Block 2: 2
Block 3: 7
Enter the size of the files:-
File 1: 1
File 2: 4
OUTPUT
File No File Size Block No Block Size Fragment
1 1 3 7 6
2 4 1 5 1
Experiment 4:
Resource Allocation Graph (RAG)
Implementation of resource allocation graph (RAG).
IMPLEMENTATION DETAILS:
INPUT/s:
(i) List of resources
(ii) Instance of each resource (for case 2 only)
(iii) List of processes
(iv) Resource allocated by each process
STEPS TO PERFORM:
(i) List the processes and resources.
(ii) We read input from user for each [Pi , Ri] and also how many instances
of each resource to a particular process (for multiple instances case).
(iii) While user completes the input, we end up constructing adjacency ma-
trices/ list. Two cases to be considered:
Case 1– Each resource has single instance (simpler problem).
Case 2– Multiple instances of resources (complex problem).
a. Methods used for representing graph:
(i) Adjacency matrix: A 2-D array of size N x N where N is the number of
vertices in the graph (includes processes and resources). For each adj[i][j]
= 1 indicates that there is an edge from vertex i to vertex j. Since resource
allocation graph is directed graph, hence it is not necessary to be symmet-
ric.
(ii) Adjacency list: An array of linked list is used. Size of the array is equal
to number of vertices (processes) in the graph. An entry arr[i] represents
the linked list of vertices (resources requested by process) adjacent to the
ith vertex.
OUTPUT/s:
Output is a Resource allocation graph through matrices/list.
Resource Allocation Graph (RAG)
A resource allocation graphs shows which resource is held by which process and
which process is waiting for a resource of a specific kind. It is amazing and straight –
forward tool to outline how interacting processes can deadlock. Therefore, resource
allocation graph describe what the condition of the system as far as process and re-
sources are concern like what number of resources are allocated and what is the re-
quest of each process. Everything can be represented in terms of graph. One of the
benefit of having a graph is, sometimes it is conveivable to see a deadlock straight
forward by utilizing RAG and however you probably won’t realize that by taking a
glance at the table. Yet tables are better if the system contains bunches of process
and resource and graph is better if the system contains less number of process and
resource.
So, resource allocation graph is explained to us what is the state of the system in
terms of processes and resources. Like how many resources are available, how
many are allocated and what is the request of each process. Everything can be rep-
resented in terms of the diagram. One of the advantages of having a diagram is,
sometimes it is possible to see a deadlock directly by using RAG, but then you
might not be able to know that by looking at the table. But the tables are better if
the system contains lots of process and resource and Graph is better if the system
contains less number of process and resource. We know that any graph contains
vertices and edges.
Types of Vertices in RAG
So RAG also contains vertices and edges. In RAG vertices are two types
1. Process Vertex: Every process will be represented as a process vertex. Generally,
the process will be represented with a circle.
2. Resource Vertex: Every resource will be represented as a resource vertex. It is
also two types:
Single instance type resource: It represents as a box, inside the box, there will
be one dot.So the number of dots indicate how many instances are present of each
resource type.
Multi-resource instance type resource: It also represents as a box, inside the
box, there will be many dots present.
How many Types of Edges are there in RAG?
Now coming to the edges of RAG.There are two types of edges in RAG –
Assign Edge: If you already assign a resource to a process then it is called Assign
edge.
Request Edge: It means in future the process might want some resource to com-
plete the execution, that is called request edge.
So, if a process is using a resource, an arrow is drawn from the resource node to the
process node. If a process is requesting a resource, an arrow is drawn from the
process node to the resource node.
Example 1 (Single instances RAG)
If there is a cycle in the Resource Allocation Graph and each resource in the cycle
provides only one instance, then the processes will be in deadlock. For example, if
process P1 holds resource R1, process P2 holds resource R2 and process P1 is wait-
ing for R2 and process P2 is waiting for R1, then process P1 and process P2 will be
in deadlock.
Here’s another example, that shows Processes P1 and P2 acquiring resources R1
and R2 while process P3 is waiting to acquire both resources. In this example, there
is no deadlock because there is no circular dependency. So cycle in single-instance
resource type is the sufficient condition for deadlock.
Example 2 (Multi-instances RAG)
From the above example, it is not possible to say the RAG is in a safe state or in an
unsafe state.So to see the state of this RAG, let’s construct the allocation matrix
and request matrix.
The total number of processes are three; P1, P2 & P3 and the total number of re-
sources are two; R1 & R2.
Allocation matrix –
For constructing the allocation matrix, just go to the resources and see to which
process it is allocated.
R1 is allocated to P1, therefore write 1 in allocation matrix and similarly, R2 is allo-
cated to P2 as well as P3 and for the remaining element just write 0.
Request matrix –
In order to find out the request matrix, you have to go to the process and see the
outgoing edges.
P1 is requesting resource R2, so write 1 in the matrix and similarly, P2 requesting
R1 and for the remaining element write 0.
So now available resource is = (0, 0).
Checking deadlock (safe or not) –
So, there is no deadlock in this RAG.Even though there is a cycle, still there is no
deadlock.Therefore in multi-instance resource cycle is not sufficient condition for
deadlock.
Above example is the same as the previous example except that, the process P3 re-
questing for resource R1. So the table becomes as shown in below.
So,the Available resource is = (0, 0), but requirement are (0, 1), (1, 0) and (1, 0).So
you can’t fulfill any one requirement.Therefore, it is in deadlock. Therefore, every
cycle in a multi-instance resource type graph is not a deadlock, if there has to be a
deadlock, there has to be a cycle.So, in case of RAG with multi-instance resource
type, the cycle is a necessary condition for deadlock, but not sufficient.
SOURCE CODE
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#define MAX_RESOURCES 10
#define MAX_PROCESSES 10
// Define node types for the graph
typedef enum { PROCESS, RESOURCE } NodeType;
// Define structure for graph nodes
typedef struct Node {
char name[20]; // Name of the node
NodeType type; // Type of the node (PROCESS or RESOURCE)
bool visited; // Flag to track visited nodes
struct Node* next; // Pointer to the next node in the adjacency list
} Node;
// Define structure for the adjacency list
typedef struct Graph {
Node* nodes[MAX_PROCESSES + MAX_RESOURCES]; // Array of pointers to
nodes
} Graph;
// Function to initialize a new node
Node* createNode(char name[], NodeType type) {
Node* newNode = (Node*)malloc(sizeof(Node));
if (newNode == NULL) {
printf("Memory allocation failed.\n");
exit(1);
strcpy(newNode->name, name);
newNode->type = type;
newNode->visited = false;
newNode->next = NULL;
return newNode;
// Function to add a new edge to the graph
void addEdge(Graph* graph, char from[], char to[]) {
Node* fromNode = createNode(from, PROCESS);
Node* toNode = createNode(to, RESOURCE);
// Add the new nodes to the graph if they don't exist
if (graph->nodes[fromNode->name[0] - 'A'] == NULL)
graph->nodes[fromNode->name[0] - 'A'] = fromNode;
if (graph->nodes[toNode->name[0] - 'A'] == NULL)
graph->nodes[toNode->name[0] - 'A'] = toNode;
// Connect the nodes
fromNode->next = toNode;
// Function to check if the graph has a cycle using DFS
bool hasCycleUtil(Graph* graph, Node* node) {
if (node->visited) // If this node is already visited, then there is a cycle
return true;
node->visited = true;
// Recur for all the nodes adjacent to this node
Node* adjacent = node->next;
while (adjacent != NULL) {
if (hasCycleUtil(graph, graph->nodes[adjacent->name[0] - 'A']))
return true;
adjacent = adjacent->next;
// Reset visited flag for backtracking
node->visited = false;
return false;
}
// Function to check if the graph has a cycle
bool hasCycle(Graph* graph) {
for (int i = 0; i < MAX_PROCESSES + MAX_RESOURCES; i++) {
if (graph->nodes[i] != NULL) {
if (hasCycleUtil(graph, graph->nodes[i]))
return true;
return false;
int main() {
// Initialize the graph
Graph* graph = (Graph*)malloc(sizeof(Graph));
if (graph == NULL) {
printf("Memory allocation failed.\n");
return 1;
for (int i = 0; i < MAX_PROCESSES + MAX_RESOURCES; i++)
graph->nodes[i] = NULL;
// Add edges to the graph (example)
addEdge(graph, "P1", "R1");
addEdge(graph, "P2", "R2");
addEdge(graph, "P1", "R2");
addEdge(graph, "R2", "P3");
// Check for cycles
if (hasCycle(graph))
printf("The graph has a cycle.\n");
else
printf("The graph does not have a cycle.\n");
// Free allocated memory
for (int i = 0; i < MAX_PROCESSES + MAX_RESOURCES; i++) {
Node* current = graph->nodes[i];
while (current != NULL) {
Node* temp = current;
current = current->next;
free(temp);
free(graph);
return 0;
}
Experiment 5:
Bankers Algorithm
Implementation of Banker’s Algorithm.
IMPLEMENTATION DETAILS:
INPUT/s:
Basic input required to implement the Banker's Algorithm:
(i) Available
(ii) Max
(iii) Allocation
STEPS TO PERFORM:
(i) Perform Banker's algorithm when a request for R is made.
(ii) Compute Need[i,j] = Max[i,j] - Allocation[i,j].
(iii) Update accordingly.
Once the resources are allocated, check to see if the system state is
safe. If unsafe, the process must wait and the old resource-allocated
state is restored.
OUTPUT/s:
Detection process specifies if a deadlock is present in system with
listed processes and their needs or not.
The banker’s algorithm is named so because it is used in the banking system
to check whether a loan can be sanctioned to a person or not. Suppose there
are n number of account holders in a bank and the total sum of their money
is S. If a person applies for a loan then the bank first subtracts the loan
amount from the total money that the bank has and if the remaining amount
is greater than S then only the loan is sanctioned. It is done because if all the
account holders come to withdraw their money then the bank can easily do
it.
It also helps the OS to successfully share the resources between all the pro-
cesses. It is called the banker’s algorithm because bankers need a similar al-
gorithm- they admit loans that collectively exceed the bank’s funds and then
release each borrower’s loan in installments. The banker’s algorithm uses
the notation of a safe allocation state to ensure that granting a resource re-
quest cannot lead to a deadlock either immediately or in the future.
In other words, the bank would never allocate its money in such a way that it
can no longer satisfy the needs of all its customers. The bank would try to be
in a safe state always.
The following Data structures are used to implement the Banker’s Algorithm:
Let ‘n’ be the number of processes in the system and ‘m’ be the number of
resource types.
Available
· It is a 1-d array of size ‘m’ indicating the number of available resources
of each type.
· Available[ j ] = k means there are ‘k’ instances of resource type Rj
Max
· It is a 2-d array of size ‘n*m’ that defines the maximum demand of
each process in a system.
· Max[ i, j ] = k means process Pi may request at most ‘k’ instances of
resource type Rj.
Allocation
· It is a 2-d array of size ‘n*m’ that defines the number of resources of
each type currently allocated to each process.
· Allocation[ i, j ] = k means process Pi is currently allocated ‘k’ in-
stances of resource type Rj
Need
· It is a 2-d array of size ‘n*m’ that indicates the remaining resource
need of each process.
· Need [ i, j ] = k means process Pi currently needs ‘k’ instances of re-
source type Rj
· Need [ i, j ] = Max [ i, j ] – Allocation [ i, j ]
· Allocation specifies the resources currently allocated to process Pi and
Needi specifies the additional resources that process Pi may still re-
quest to complete its task.
· Banker’s algorithm consists of a Safety algorithm and a Resource re-
quest algorithm.
Banker’s Algorithm
1. Active:= Running U Blocked;
for k=1…r
New_ request[k]:= Requested_ resources[requesting_ process, k];
2. Simulated_ allocation:= Allocated_ resources;
for k=1…..r //Compute projected allocation state
Simulated_ allocation [requesting _process, k]:= Simulated_ allocation [re-
questing _process, k] + New_ request[k];
3. feasible:= true;
for k=1….r // Check whether projected allocation state is feasible
if Total_ resources[k]< Simulated_ total_ alloc [k] then feasible:= false;
4. if feasible= true
then // Check whether projected allocation state is a safe allocation state
while set Active contains a process P1 such that
For all k, Total _resources[k] – Simulated_ total_ alloc[k]>= Max_ need [l ,k]-
Simulated_ allocation[l, k]
Delete Pl from Active;
for k=1…..r
Simulated_ total_ alloc[k]:= Simulated_ total_ alloc[k]- Simulated_
allocation[l, k];
5. If set Active is empty
then // Projected allocation state is a safe allocation state
for k=1….r // Delete the request from pending requests
Requested_ resources[requesting_ process, k]:=0;
for k=1….r // Grant the request
Allocated_ resources[requesting_ process, k]:= Allocated_ resources[request-
ing_ process, k] + New_ request[k];
Total_ alloc[k]:= Total_ alloc[k] + New_ request[k];
PROGRAM
// Banker's Algorithm
#include <stdio.h>
#include <stdbool.h>
#define MAX_PROCESSES 10
#define MAX_RESOURCES 10
int available[MAX_RESOURCES];
int max[MAX_PROCESSES][MAX_RESOURCES];
int allocation[MAX_PROCESSES][MAX_RESOURCES];
int need[MAX_PROCESSES][MAX_RESOURCES];
bool finished[MAX_PROCESSES];
// Function to check if the requested resources can be granted to a process
bool isSafeState(int process_count, int resource_count) {
int work[MAX_RESOURCES];
bool finish[process_count];
// Initialize work array with available resources
for (int i = 0; i < resource_count; ++i) {
work[i] = available[i];
// Initialize finish array
for (int i = 0; i < process_count; ++i) {
finish[i] = false;
int count = 0;
while (count < process_count) {
bool found = false;
for (int i = 0; i < process_count; ++i) {
if (!finish[i]) {
bool need_less_than_work = true;
for (int j = 0; j < resource_count; ++j) {
if (need[i][j] > work[j]) {
need_less_than_work = false;
break;
}
if (need_less_than_work) {
// Process i can be executed
for (int j = 0; j < resource_count; ++j) {
work[j] += allocation[i][j];
finish[i] = true;
count++;
found = true;
if (!found) {
// System is in an unsafe state
return false;
return true;
// Function to perform resource allocation
void resourceRequest(int process_count, int resource_count, int
process_num, int request[]) {
for (int i = 0; i < resource_count; ++i) {
// Check if requested resources exceed the need
if (request[i] > need[process_num][i]) {
printf("Error: Requested resources exceed the need.\n");
return;
// Check if requested resources exceed the available
if (request[i] > available[i]) {
printf("Error: Requested resources exceed the available resources.\
n");
return;
// Try allocating resources
for (int i = 0; i < resource_count; ++i) {
available[i] -= request[i];
allocation[process_num][i] += request[i];
need[process_num][i] -= request[i];
// Check if the system is in a safe state after resource allocation
if (isSafeState(process_count, resource_count)) {
printf("Request is granted.\n");
} else {
// Roll back resource allocation
for (int i = 0; i < resource_count; ++i) {
available[i] += request[i];
allocation[process_num][i] -= request[i];
need[process_num][i] += request[i];
printf("Request cannot be granted. System is in an unsafe state.\n");
int main() {
int process_count, resource_count;
// Input the number of processes and resources
printf("Enter the number of processes: ");
scanf("%d", &process_count);
printf("Enter the number of resources: ");
scanf("%d", &resource_count);
// Input the available resources
printf("Enter the available resources: ");
for (int i = 0; i < resource_count; ++i) {
scanf("%d", &available[i]);
// Input the maximum resources for each process
printf("Enter the maximum resources for each process:\n");
for (int i = 0; i < process_count; ++i) {
printf("For Process %d: ", i);
for (int j = 0; j < resource_count; ++j) {
scanf("%d", &max[i][j]);
// Input the allocated resources for each process
printf("Enter the allocated resources for each process:\n");
for (int i = 0; i < process_count; ++i) {
printf("For Process %d: ", i);
for (int j = 0; j < resource_count; ++j) {
scanf("%d", &allocation[i][j]);
// Calculate need = max - allocation
need[i][j] = max[i][j] - allocation[i][j];
// Perform resource request
int process_num;
int request[MAX_RESOURCES];
printf("Enter the process number making the request: ");
scanf("%d", &process_num);
printf("Enter the requested resources: ");
for (int i = 0; i < resource_count; ++i) {
scanf("%d", &request[i]);
// Check if the request can be granted
resourceRequest(process_count, resource_count, process_num, request);
return 0;
}
Experiment 6:
Inter process Communication – Semaphore
Implement the solution for Bounded Buffer (Producer-Consumer)
problem using inter process communication technique –
Semaphores.
II. Implement the solution for Readers-Writers problem using inter
process communication technique – Semaphores.
III. Implement the solution for Dining-Philosopher problem using in-
ter process communication technique – Semaphores.
IMPLEMENTATION DETAILS:
(i) For programming this problem, we use JAVA (multi-threading
concept) for implementing the synchronization problem using sema-
phores.
(ii) Our main focus is to obtain three conditions of
(a) mutual exclusion
(b) progress
(c) bounded wait.
(iii) Implement semaphore concept considering above mentioned
problem.
OUTPUT/s:
Synchronization of the problem satisfying conditions of mutual ex-
clusion, progress and bounded wait.
A semaphore S is an integer variable that can be accessed only through two
standard operations: wait(). and signal(). The wait() operation reduces the
value of semaphore by 1 and the signal() operation increases its value by
1.
wait(S){
while(S<=0); / busy waiting
S-;
signal(S){
S++;
Semaphores are of two types:
Binary Semaphore - This is similar to mutex lock but not the same thing. It
can have only two values - 0 and 1. Its value is initialized to 1. It is used to
implement the solution of critical section problem with multiple processes.
Counting Semaphore - Its value can range over an unrestricted domain. It is
used to control access to a resource that has multiple instances.
1. Bounded Buffer (Producer-Consumer) Problem:
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#define BUFFER_SIZE 5
int buffer[BUFFER_SIZE];
int in = 0, out = 0;
sem_t empty, full, mutex;
void* producer(void* arg) {
int item = 1;
while (1) {
sem_wait(&empty);
sem_wait(&mutex);
buffer[in] = item;
printf("Produced: %d\n", item);
in = (in + 1) % BUFFER_SIZE;
item++;
sem_post(&mutex);
sem_post(&full);
}
}
void* consumer(void* arg) {
while (1) {
sem_wait(&full);
sem_wait(&mutex);
int item = buffer[out];
printf("Consumed: %d\n", item);
out = (out + 1) % BUFFER_SIZE;
sem_post(&mutex);
sem_post(&empty);
}
}
int main() {
sem_init(&empty, 0, BUFFER_SIZE);
sem_init(&full, 0, 0);
sem_init(&mutex, 0, 1);
pthread_t prod_tid, cons_tid;
pthread_create(&prod_tid, NULL, producer, NULL);
pthread_create(&cons_tid, NULL, consumer, NULL);
pthread_join(prod_tid, NULL);
pthread_join(cons_tid, NULL);
sem_destroy(&empty);
sem_destroy(&full);
sem_destroy(&mutex);
return 0;
}
2. Readers-Writers Problem:
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#define READERS_COUNT 3
#define WRITERS_COUNT 2
int readers_count = 0;
sem_t mutex, rw_mutex;
void* reader(void* arg) {
while (1) {
sem_wait(&mutex);
readers_count++;
if (readers_count == 1) {
sem_wait(&rw_mutex);
sem_post(&mutex);
// Read from shared resource
sem_wait(&mutex);
readers_count--;
if (readers_count == 0) {
sem_post(&rw_mutex);
}
sem_post(&mutex);
// Continue reading
void* writer(void* arg) {
while (1) {
sem_wait(&rw_mutex);
// Write to shared resource
sem_post(&rw_mutex);
// Continue writing
int main() {
sem_init(&mutex, 0, 1);
sem_init(&rw_mutex, 0, 1);
pthread_t readers_tid[READERS_COUNT], writers_tid[WRITERS_COUNT];
int i;
for (i = 0; i < READERS_COUNT; i++) {
pthread_create(&readers_tid[i], NULL, reader, NULL);
}
for (i = 0; i < WRITERS_COUNT; i++) {
pthread_create(&writers_tid[i], NULL, writer, NULL);
for (i = 0; i < READERS_COUNT; i++) {
pthread_join(readers_tid[i], NULL);
for (i = 0; i < WRITERS_COUNT; i++) {
pthread_join(writers_tid[i], NULL);
sem_destroy(&mutex);
sem_destroy(&rw_mutex);
return 0;
}
3.
Dining Philosophers Problem:
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#define NUM_PHILOSOPHERS 5
#define THINKING 0
#define HUNGRY 1
#define EATING 2
#define LEFT (philosopher_num + NUM_PHILOSOPHERS - 1) %
NUM_PHILOSOPHERS
#define RIGHT (philosopher_num + 1) % NUM_PHILOSOPHERS
int state[NUM_PHILOSOPHERS];
sem_t mutex, forks[NUM_PHILOSOPHERS];
void test(int philosopher_num) {
if (state[philosopher_num] == HUNGRY && state[LEFT] != EATING &&
state[RIGHT] != EATING) {
state[philosopher_num] = EATING;
printf("Philosopher %d is Eating\n", philosopher_num + 1);
sem_post(&forks[philosopher_num]);
void take_forks(int philosopher_num) {
sem_wait(&mutex);
state[philosopher_num] = HUNGRY;
printf("Philosopher %d is Hungry\n", philosopher_num + 1);
test(philosopher_num);
sem_post(&mutex);
sem_wait(&forks[philosopher_num]);
void put_forks(int philosopher_num) {
sem_wait(&mutex);
state[philosopher_num] = THINKING;
printf("Philosopher %d is Thinking\n", philosopher_num + 1);
test(LEFT);
test(RIGHT);
sem_post(&mutex);
void* philosopher(void* arg) {
int philosopher_num = *((int*)arg);
while (1) {
// Thinking
printf("Philosopher %d is Thinking\n", philosopher_num + 1);