Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
0% found this document useful (0 votes)
3 views

OS Lab (1)

The document outlines a series of experiments related to UNIX operating system commands and programming, including basic command usage, system calls, and simulations of common UNIX commands. Each experiment includes aims, explanations, and example code demonstrating various functionalities such as process management, file operations, and directory handling. The document serves as a practical guide for learning and implementing UNIX commands and concepts.

Uploaded by

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

OS Lab (1)

The document outlines a series of experiments related to UNIX operating system commands and programming, including basic command usage, system calls, and simulations of common UNIX commands. Each experiment includes aims, explanations, and example code demonstrating various functionalities such as process management, file operations, and directory handling. The document serves as a practical guide for learning and implementing UNIX commands and concepts.

Uploaded by

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

S.

No Page Experiment Date of Remarks Signature


No Experiment
1 Practicing of Basic UNIX
Commands

2 Write programs using the


following UNIX operating system
calls fork, exec, getpid, exit, wait,
close, stat, opendir and readdir
3 Simulate UNIX commands like cp,
ls, grep, etc.,

4 Simulate the following CPU


scheduling algorithms a) FCFS
b) SJF c) Priority d) Round Robin
5 Control the number of ports
opened by the operating system
with a) Semaphore b) Monitors.
6 Write a program to illustrate
concurrent execution of threads
using pthreads library
7 Write a program to solve
producer-consumer problem
using Semaphores.
8 Implement the following
memory allocation methods for
fixed partition a) First fit b) Worst
fit c) Best fit
9 Simulate the following page
replacement algorithms a) FIFO
b) LRU c) LFU
10 Simulate Paging Technique of
memory management.

11 Implement Bankers Algorithm for


Dead Lock avoidance and
prevention
12 Simulate the following file
allocation strategies
a) Sequential b) Indexed c)
Linked
13 Download and install nachos
operating system and
experiment with it
Experiment -1 Date:
Practicing of Basic UNIX Commands.
Aim: Practicing of Basic UNIX Commands.
Introduction to Unix
Unix is an OS that provides both CLI and GUI-based interaction. It was
developed by Dennis Ritchie in the C language. Unix operating system is
multitasking, which also gives an opportunity for two or more users to use its
benefits. In other words, it is a multi-user OS. Ubuntu OS is a Unix version that
enables us to do every work that Unix is supposed to do.

Hence, it is recommended by professionals who operate with servers; it is also


recommended to learn how the command-line-based operating system works.
Many large and complex applications that utilize Unix to execute because of its
aspect to handle the processes easily. It is a bit faster and provides a nice user
experience when compared with the Windows operating system.

What is Unix Command?


The below article gives an overview of Unix commands. An OS providing both
command line interface (CLI) and graphical user interface (GUI) based
interaction integrated by Dennis Ritchie, Douglas Mcllroy, Joe Ossanna Brian
Kernighan, and Ken Thompson at Bell laboratory in 1970 called a multi-tasking
operating system permitting two or more users to simultaneously operate on the
operating system and offers commands for the users for interacting with the
application from command line interface, such as sudo command, chmod
command, su command, mv command, rm command, vi command, cat
command, rmdir command, mkdir command, clear command, and ls command,
which can be used to implement complex tasks.

Several testing activities, such as performance and installation testing,


depending on OS knowledge. Almost every web server is Unix based
nowadays. So, knowledge of Unix is essential for testers. If we are unfamiliar
with Unix, then learning Unix commands can be a great start. One of the best
ways to understand these commands is to practice and read them simultaneously
on Unix OS.

1| P a g e
Study of Unix basic command list: man, who, cat, cd, cp, ps, ls, mv, rm,
mkdir, rmdir, echo, more, date, time, kill, history, chmod, chown, finger,
pwd, cal, logout, shutdown.
1. man - Manual pages
• Description: Displays the manual (help) page for a command.
• Example: $man ls
This will display the manual for the ls command.
2. who - Who is logged in
• Description: Displays information about the users currently logged into
the system.
• Example: $who
• Output:
user1 tty1 2024-12-25 10:00
user2 pts/1 2024-12-25 10:30
3. cat - Concatenate and display file contents
• Description: Displays the content of a file.
• Example: $cat file.txt
The output of the content of file.txt contains “Hello, World!”
• Output: Hello, World!
4. cd - Change directory
• Description: Changes the current working directory.
• Example: $cd /home/user
• Other uses:
o To go back to the previous directory: cd -
o To go to the home directory: cd ~

2| P a g e
5. cp - Copy files or directories
• Description: Copies files or directories.
• Example: cp file1.txt /home/user/backup/
• To copy directories:
cp -r directory_name /home/user/backup/
6. ps - Report process status
• Description: Displays information about active processes.
• Example: ps aux
This lists all processes currently running on the system.
7. ls - List directory contents
• Description: Lists files and directories in the current directory.
• Example: ls -l
This lists files with detailed information like permissions, size, and
modification time.
8. mv - Move or rename files or directories
• Description: Moves or renames files or directories.
• Example: mv file1.txt file2.txt
• To move a file:
mv file1.txt /home/user/backup/
9. rm - Remove files or directories
• Description: Removes files or directories.
• Example: $rm file1.txt
• To remove a directory and its contents:
$rm -r directory_name
10. mkdir - Make directories
• Description: Creates a new directory.
• Example: $ mkdir new_directory

3| P a g e
11. rmdir - Remove empty directories
• Description: Deletes an empty directory.
• Example: $rmdir new_directory
12. echo - Display a line of text or variables
• Description: Prints a line of text to the terminal.
• Example: $echo "Hello, World!"
This will print Hello, World! to the terminal as the output.
13. more - View file contents one page at a time
• Description: Allows you to view the content of a file page by page.
• Example: $more largefile.txt
This will display the contents of largefile.txt page by page.
14. date - Display or set system date and time
• Description: Displays the current system date and time.
• Example: $date
• Output:
Tue Dec 25 12:00:00 UTC 2024
15. time - Measure command execution time
• Description: Measures how long a command takes to execute.
• Example: time ls
This will output the time it took to run the ls command.
16. kill - Terminate a process
• Description: Sends a signal to terminate a process.
• Example: $kill 1234
This will terminate the process with PID 1234.

4| P a g e
17. history - Display the command history
• Description: Displays a list of previously entered commands.
• Example: $history
• It prints the server logged in username as history.
18. chmod - Change file permissions
• Description: Changes the permissions of files or directories.
• Example: $chmod 755 file1.txt
• Explanation:
o 7 = read, write, and execute for the owner.
o 5 = read and execute for the group.
o 5 = read and execute for others.
19. chown - Change file ownership
• Description: Changes the owner and group of a file or directory.
• Example: $chown user:group file1.txt
20. finger - User information
• Description: Displays information about a user on the system.
• Example: $finger user1
21. pwd - Print working directory
• Description: Displays the current directory path.
• Example: $pwd
• Output: /home/user/Documents
22. cal - Display a calendar
• Description: Displays the current month’s calendar.
• Example: $cal

5| P a g e
• Output: January 2025
Su Mo Tu We Th Fr Sa
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
23. logout - Logout from the current session
• Description: Logs out of the current user session.
• Example: $logout
24. shutdown - Shut down or restart the system
• Description: Shuts down or restarts the system.
• Example: $shutdown -h now
This shuts down the system immediately.
• To restart: $shutdown -r now

6| P a g e
Experiment- 2 Date:
programs using the following UNIX operating system calls fork, exec,
getpid, exit, wait, close, stat, opendir and readdir.

Aim: Write programs using the following UNIX operating system calls
fork, exec, getpid, exit, wait, close, stat, opendir and readdir.

Explanation:
• fork: Creates a new process. The parent process receives the child process
ID (PID), and the child receives 0.
• exec: Replaces the current process with a new program (in the example,
execlp runs the ls command).
• getpid: Retrieves the process ID of the current process.
• exit: Terminates the current process.
• wait: Makes the parent process wait until the child process terminates.
• close: Closes a file descriptor after opening or writing to a file.
• stat: Retrieves information about a file (such as its size and permissions).
• opendir and readdir: Open a directory and read its contents, one entry at
a time.

7| P a g e
Output:

Parent process : waiting for child

Child process : executing ‘ls’ command

Total 16

-rwxrwxr-x .1 cse524 cse524 8804 Jan 9 10:54 a.out

-rw-rw-r--.1 cse524 cse524 404 Jan 9 10:54 fork.c

Parent process : child finished

8| P a g e
1. fork and exec

This demonstrates the use of fork to create a child process, and exec to
replace the child's memory with a new program.

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
int main ()
{
Pid_t pid = fork (); // Create a child process
if (pid < 0)
{
// Error occurred perror("fork");
exit(1);
}
if (pid == 0)
{
// Child process
printf("Child process: executing 'ls' command\n");
execlp("ls", "ls", "-l", NULL); // Replace the child process with 'ls'
perror("execlp failed"); // If exec fails
exit(1);
}
Else
{ // Parent process
printf("Parent process: waiting for child\n");
wait(NULL); // Wait for the child process to finish
printf("Parent process: child finished\n");
}
return 0;
}

9| P a g e
Output:

Process ID: 8918

10| P a g e
2. getpid and exit

This demonstrates how to get the process ID (getpid) and exit a


program using exit.

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main ()
{
pid_t pid = getpid (); // Get the process ID
printf("Process ID: %d\n", pid); // Program exits here
exit (0);
}

11| P a g e
Output:

Child process : sleeping for 2 seconds

Child process : exiting

Parent process : child finished with status

12| P a g e
3. wait

This demonstrates the use of wait to wait for a child process to


terminate.

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
int main ()
{
int pid = fork (); // Create a child process
if (pid < 0)
{
perror("fork");
exit (1);
}
if (pid == 0)
{ // Child process
printf("Child process: sleeping for 2 seconds\n");
sleep (2);
printf("Child process: exiting\n");
exit (0);
}
else
{ // Parent process int status;
wait (NULL); // Wait for child to finish
printf("Parent process: child finished with status\n");
}
return 0;
}

13| P a g e
Output:

File closed.

14| P a g e
4. close (close a file descriptor)

This demonstrates how to open, write to, and then close a file.

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
int main()
{
int fd = open("example.txt", O_CREAT | O_WRONLY, 0644); // Open a file
if (fd == -1)
{
perror("open");
return 1;
}
write(fd, "Hello, World!\n", 14); // Write to the file
close(fd); // Close the file descriptor
printf("File closed.\n");
return 0;
}

15| P a g e
Output:

File size: 14 bytes

Permissions: 420

16| P a g e
5. stat

This demonstrates how to use the stat system call to get information
about a file.

#include <stdio.h>
#include <sys/stat.h>
#include <unistd.h>
int main()
{
struct stat file_stat; const char *filename = "example.txt";
if (stat(filename, &file_stat) == -1)
{
perror("stat"); return 1;
}
printf("File size: %ld bytes\n", file_stat.st_size);
printf("Permissions: %o\n", file_stat.st_mode & 0777);
return 0;
}

17| P a g e
Output:

getpid.c

a.out

.bash_logout

example.txt

opendir.c

.bash_profile

.ccache

close.c

stat.c

.dileep.c.swp

wait.c

.bashrc

.bash_history

..

fork.c

.local

.mozzila

18| P a g e
6. opendir and readdir

This demonstrates how to open a directory, read its contents, and close it
using opendir, readdir, and closedir.

#include <stdio.h>
#include <dirent.h>
#include <stdlib.h>
int main()
{
DIR *dir = opendir("."); // Open the current directory
if (dir == NULL)
{
perror("opendir");
return 1;
}
struct dirent *entry;
printf("Contents of current directory:\n");
while ((entry = readdir(dir)) != NULL)
{
printf("%s\n", entry->d_name); // Print each directory entry
}
closedir(dir); // Close the directory
return 0;
}

19| P a g e
Experiment-3 Date:
Simulate UNIX commands like cp, ls, grep, etc.,
Aim: Simulate UNIX commands like cp, ls, grep, etc.,
Explanation:
• open() is used to open the file for reading.
• read() reads chunks of the file into a buffer.
• Each line is null-terminated and checked for the search pattern using
strstr().
• Matching lines are printed to the terminal.
Additional Features & Customization
These basic implementations can be extended to match additional functionality
of the actual ls, cp, and grep commands:
1. ls options like -l (long format) can be added using stat to show file sizes,
permissions, etc.
2. cp options like -r (recursive) can be added to copy directories.
3. grep options like -i (ignore case) or -v (invert match) can be implemented
by modifying the string search logic.

1. Simulate ls
The ls command lists the contents of a directory. We will implement a basic
version that lists files in the current directory.
ls Command Simulation
Explanation:
• opendir() opens the directory.
• readdir() reads each file entry in the directory.
• closedir() closes the directory.

20| P a g e
Output:

getpid.c

a.out

.bash_logout

example.txt

ls.c

opendir.c

.bash_profile

wat.c

.ccache

close.c

stat.c

.dileep.c.swp

wait.c

.bashrc

.bash_history

..

fork.c

.local

.mozzila

21| P a g e
Source Code:

#include <stdio.h>
#include <dirent.h>
#include <stdlib.h>
void simulate_ls(const char *path)
{
DIR *dir = opendir(path); // Open the directory
if (dir == NULL)
{
perror("opendir");
exit(1);
}
struct dirent *entry;
while ((entry = readdir(dir)) != NULL)
{
printf("%s\n", entry->d_name); // Print the name of each file
}
closedir(dir); // Close the directory
}
int main(int argc, char *argv[])
{
if (argc == 1)
{
simulate_ls("."); // If no argument, list current directory
}
else
{

22| P a g e
simulate_ls(argv[1]); // List the directory specified as argument
}
return 0;
}

23| P a g e
2. Simulate cp
The cp command copies files from one location to another. Here is how we can
simulate it using open, read, write, and close.
Explanation:
• open() is used to open both the source and destination files.
• read() reads data from the source file, and write() writes it to the
destination file.
• The files are closed with close() after the operation.

24| P a g e
Output:
$ vi cp.c
$ gcc cp.c
$ ./a.out example.txt c33.txt
File copied from example.txt to c33.txt

25| P a g e
Source code:
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
void simulate_cp(const char *src, const char *dest)
{
int src_fd = open(src, O_RDONLY); // Open the source file for reading
if (src_fd == -1)
{
perror("open source");
exit(1);
}
int dest_fd = open(dest, O_WRONLY | O_CREAT | O_TRUNC, 0644); // Open
the destination file for writing
if (dest_fd == -1)
{
perror("open destination");
close(src_fd);
exit (1);
}
char buffer[1024];
ssize_t bytes_read;
while ((bytes_read = read(src_fd, buffer, sizeof(buffer))) > 0)
{
write(dest_fd, buffer, bytes_read); // Write data to the destination file
}
if (bytes_read == -1) { perror("read");
26| P a g e
}
close(src_fd); // Close the source file close(dest_fd); // Close the destination file
}
int main (int argc, char *argv[])
{
if (argc != 3)
{
fprintf(stderr, "Usage: %s <source> <destination>\n", argv[0]);
return 1;
}
simulate_cp(argv[1], argv[2]); // Simulate the copy operation
printf("File copied from %s to %s\n", argv[1], argv[2]);
return 0;
}

27| P a g e
3. Simulate grep
The grep command searches for patterns in a file. Here's how we can simulate
the basic grep functionality using open, read, and string matching.
Explanation:
• Open () is used to open the file for reading.
• read() reads chunks of the file into a buffer.
• Each line is null-terminated and checked for the search pattern using
strstr ().
• Matching lines are printed to the terminal.
Additional Features & Customization
These basic implementations can be extended to match additional functionality
of the actual ls, cp, and grep commands:
1. ls options like -l (long format) can be added using stat to show file sizes,
permissions, etc.
2. cp options like -r (recursive) can be added to copy directories.
3. grep options like -i (ignore case) or -v (invert match) can be implemented
by modifying the string search logic.

28| P a g e
Output:
$ vi g33.txt
$ gcc g33.txt
$ ./a.out am s33.txt
i am student
name is KCB
name is KCB
ame is KCB
^
C

29| P a g e
Source Code:
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#define BUFFER_SIZE 1024
void simulate_grep(const char *pattern, const char *filename)
{
int fd = open(filename, O_RDONLY); // Open the file for reading
if (fd == -1)
{
perror("open");
exit(1);
}
char buffer[BUFFER_SIZE];
ssize_t bytes_read;
while ((bytes_read = read(fd, buffer, sizeof(buffer))) > 0)
{
for (ssize_t i = 0; i < bytes_read; i++)
{
if (buffer[i] == '\n') { buffer[i] = '\0'; // Null-terminate each line
if (strstr(buffer, pattern) != NULL)
{ // Search for pattern in line
printf("%s\n", buffer); // Print matching line
}
memmove(buffer, buffer + i + 1, bytes_read - i - 1); // Move remainder to start
of buffer
30| P a g e
i = -1; // Reset index for new line
}
}
}
if (bytes_read == -1)
{
perror("read");
}
close(fd); // Close the file
}
int main(int argc, char *argv[])
{
if (argc != 3)
{
fprintf(stderr, "Usage: %s <pattern> <file>\n", argv[0]); return 1;
}
simulate_grep(argv[1], argv[2]); // Simulate the grep operation
return 0;
}

31| P a g e
Experiment – 4 Date:
Simulate the following CPU scheduling algorithms with implements C
programming
a) FCFS b) SJF c) Priority d) Round Robin
Aim: Simulate the following CPU scheduling algorithms with implements
C programming
a) FCFS b) SJF c) Priority d) Round Robin

A. FCFS (First-Come, First-Served)


This is the simplest CPU scheduling algorithm. The process that arrives first is
executed first.
First-Come, First-Served (FCFS) Scheduling Algorithm
FCFS (First-Come, First-Served) is one of the simplest and most intuitive CPU
scheduling algorithms. In FCFS, the process that arrives first is the one that is
executed first.
Key Points:
• It is a non-preemptive scheduling algorithm.
• The CPU is allocated to the process that arrives first.
• Processes are executed in the order of their arrival.
• FCFS is easy to implement but may not be the most efficient in terms of
average waiting time, especially when long processes arrive before
shorter ones.
Steps to Simulate FCFS:
1. Input the processes: You are given a set of processes with their arrival
time and burst time.
2. Sort the processes by arrival time: This is necessary because the
algorithm is based on the arrival time of processes.
3. Calculate the waiting time for each process: The waiting time of a process
is the total time it has to wait before it gets executed.
o For the first process, the waiting time is always 0.

32| P a g e
o For each subsequent process, the waiting time is calculated as the
sum of the burst times of all previous processes.
4. Calculate the turnaround time: The turnaround time is the total time taken
for a process to complete from its arrival.
o Turnaround Time = Waiting Time + Burst Time.
5. Calculate average waiting time and average turnaround time.
Example:
Consider the following processes:

Process Arrival Time Burst Time

P1 0 4

P2 1 3

P3 2 1

P4 3 2

Step-by-Step Execution of FCFS:


1. Sort processes by Arrival Time (though they are already sorted in
this case):
o P1 arrives at 0, Burst Time = 4
o P2 arrives at 1, Burst Time = 3
o P3 arrives at 2, Burst Time = 1
o P4 arrives at 3, Burst Time = 2
2. Calculate the waiting times (WT):
o WT(P1) = 0 (since it starts at time 0)
o WT(P2) = Completion Time of P1 - Arrival Time of P2 = 4 - 1 = 3
o WT(P3) = Completion Time of P2 - Arrival Time of P3 = 7 - 2 = 5
o WT(P4) = Completion Time of P3 - Arrival Time of P4 = 8 - 3 = 5
3. Calculate the turnaround times (TAT):
o TAT(P1) = WT(P1) + Burst Time(P1) = 0 + 4 = 4

33| P a g e
o TAT(P2) = WT(P2) + Burst Time(P2) = 3 + 3 = 6
o TAT(P3) = WT(P3) + Burst Time(P3) = 5 + 1 = 6
o TAT(P4) = WT(P4) + Burst Time(P4) = 5 + 2 = 7
4. Calculate Average Waiting Time and Average Turnaround Time:
o Average Waiting Time = (WT(P1) + WT(P2) + WT(P3) + WT(P4))
/ 4 = (0 + 3 + 5 + 5) / 4 = 13 / 4 = 3.25
o Average Turnaround Time = (TAT(P1) + TAT(P2) + TAT(P3) +
TAT(P4)) / 4 = (4 + 6 + 6 + 7) / 4 = 23 / 4 = 5.75
Summary of Results:

Process Arrival Time Burst Time Waiting Time Turnaround Time

P1 0 4 0 4

P2 1 3 3 6

P3 2 1 5 6

P4 3 2 5 7

• Average Waiting Time = 3.25


• Average Turnaround Time = 5.75
Explanation of the Code:
• Find Waiting Time: This function calculates the waiting time for each
process. The waiting time for the first process is always 0, and for each
subsequent process, it is the sum of burst times of all previous processes.
• Find Turnaround Time: This function calculates the turnaround time for
each process, which is the sum of burst time and waiting time.
• Find Avg Time: This function computes the average waiting time and
turnaround time after all processes have been processed.

34| P a g e
Output
Process Burst Time Waiting Time Turnaround Time
1 4 0 4
2 3 3 6
3 1 5 6
4 2 5 7
Average waiting time: 3.25
Average turnaround time: 5.75

35| P a g e
Source code:
#include <stdio.h>
void findWaitingTime(int processes[], int n, int bt[], int wt[])
{
wt[0] = 0; // The waiting time for the first process is always 0
for (int i = 1; i < n; i++) {
wt[i] = bt[i - 1] + wt[i - 1];
}
}
void findTurnaroundTime(int processes[], int n, int bt[], int wt[], int tat[])
{
for (int i = 0; i < n; i++) {
tat[i] = bt[i] + wt[i];
}
}
void findAvgTime(int processes[], int n, int bt[]) {
int wt[n], tat[n];
findWaitingTime(processes, n, bt, wt);
findTurnaroundTime(processes, n, bt, wt, tat);
float total_wt = 0, total_tat = 0;
printf("Process\tBurst Time\tWaiting Time\tTurnaround Time\n");
for (int i = 0; i < n; i++) {
total_wt += wt[i];
total_tat += tat[i];
printf("%d\t\t%d\t\t%d\t\t%d\n", processes[i], bt[i], wt[i], tat[i]);
}
printf("Average waiting time: %.2f\n", total_wt / n);

36| P a g e
printf("Average turnaround time: %.2f\n", total_tat / n);
}
int main()
{
int processes[] = {1, 2, 3, 4}; // Process IDs
int burst_time[] = {4, 3, 1, 2}; // Burst time of processes
int n = sizeof(processes) / sizeof(processes [0]);
findAvgTime(processes, n, burst_time);
return 0;
}

37| P a g e
B) Shortest Job First (SJF) Scheduling Algorithm
The Shortest Job First (SJF) scheduling algorithm is one of the most efficient
non-preemptive CPU scheduling algorithms. It selects the process with the
shortest burst time (i.e., the process that requires the least CPU time) to
execute first. This approach minimizes the average waiting time for a set of
processes.
Key Characteristics of SJF:
• Non-preemptive: Once a process starts execution, it cannot be
interrupted until it finishes.
• Optimal for minimizing average waiting time: If all jobs arrive at the
same time, SJF will minimize the waiting time.
• Problem with SJF: It suffers from the "starvation" problem, where
processes with longer burst times may never get executed if shorter
processes keep arriving.
Steps to Simulate the SJF Algorithm:
1. Input the processes: You are given a set of processes, each with an
arrival time and a burst time.
2. Sort the processes by burst time: The process with the shortest burst
time is executed first. If multiple processes have the same burst time, then
they are scheduled based on their arrival time.
3. Calculate waiting time (WT) for each process: Waiting time is the time
a process spends in the ready queue before it starts executing.
o WT for the first process is 0.
o For each subsequent process, WT is the sum of burst times of all
previously executed processes.
4. Calculate turnaround time (TAT): Turnaround time is the total time
taken from the arrival of the process to its completion.
o TAT = Waiting Time + Burst Time

38| P a g e
5. Calculate average waiting time and average turnaround time.
Example:
Consider the following set of processes:

Process Arrival Time Burst Time

P1 0 6

P2 1 8

P3 2 7

P4 3 3

Step-by-Step Execution of SJF:


1. Sort the processes by burst time:
o P1: Arrival Time = 0, Burst Time = 6
o P2: Arrival Time = 1, Burst Time = 8
o P3: Arrival Time = 2, Burst Time = 7
o P4: Arrival Time = 3, Burst Time = 3
Sort by burst time: P4 (3) → P1 (6) → P3 (7) → P2 (8)
2. Calculate the waiting times (WT):
o WT(P4) = 0 (P4 starts immediately)
o WT(P1) = Completion Time of P4 - Arrival Time of P1 = 3 - 0 = 3
o WT(P3) = Completion Time of P1 - Arrival Time of P3 = 9 - 2 = 7
o WT(P2) = Completion Time of P3 - Arrival Time of P2 = 16 - 1 =
15
3. Calculate the turnaround times (TAT):
o TAT(P4) = WT(P4) + Burst Time(P4) = 0 + 3 = 3
o TAT(P1) = WT(P1) + Burst Time(P1) = 3 + 6 = 9
o TAT(P3) = WT(P3) + Burst Time(P3) = 7 + 7 = 14
o TAT(P2) = WT(P2) + Burst Time(P2) = 15 + 8 = 23

39| P a g e
4. Calculate the average waiting time and average turnaround time:
o Average Waiting Time = (WT(P4) + WT(P1) + WT(P3) +
WT(P2)) / 4 = (0 + 3 + 7 + 15) / 4 = 25 / 4 = 6.25
o Average Turnaround Time = (TAT(P4) + TAT(P1) + TAT(P3) +
TAT(P2)) / 4 = (3 + 9 + 14 + 23) / 4 = 49 / 4 = 12.25
Summary of Results:

Process Arrival Time Burst Time Waiting Time Turnaround Time

P4 3 3 0 3

P1 0 6 3 9

P3 2 7 7 14

P2 1 8 15 23

• Average Waiting Time = 6.25


• Average Turnaround Time = 12.25
SJF Algorithm in C
the C implementation for simulating the Shortest Job First (SJF) algorithm:
Explanation of the C Code:
1. findWaitingTime: This function calculates the waiting time for each
process. It follows the principle that a process's waiting time is the sum of
the burst times of all previously executed processes.
2. findTurnaroundTime: This function calculates the turnaround time for
each process, which is the sum of the waiting time and the burst time of
the process.
3. Sorting processes by burst time: The processes are sorted in ascending
order of burst time before calculating the waiting times and turnaround
times.
4. findAvgTime: This function computes the average waiting time and
turnaround time after all processes have been processed.

40| P a g e
Output:

Process Burst Time Waiting Time Turnaround Time


4 3 0 3
1 6 3 9
3 7 7 14
2 8 15 23

Average waiting time: 6.25


Average turnaround time: 12.25

41| P a g e
Source code:
#include <stdio.h>
void findWaitingTime(int processes[], int n, int bt[], int wt[])
{
wt[0] = 0; // Waiting time for the first process is 0
for (int i = 1; i < n; i++) {
wt[i] = bt[i - 1] + wt[i - 1];
}
}
void findTurnaroundTime(int processes[], int n, int bt[], int wt[], int tat[])
{
for (int i = 0; i < n; i++)
{
tat[i] = bt[i] + wt[i];
}
}
void findAvgTime(int processes[], int n, int bt[])
{
int wt[n], tat[n];

// Sort processes based on burst time (SJF)


for (int i = 0; i < n-1; i++)
{
for (int j = i+1; j < n; j++)
{
if (bt[i] > bt[j]) {
// Swap burst times

42| P a g e
int temp = bt[i];
bt[i] = bt[j];
bt[j] = temp;
// Swap corresponding processes
temp = processes[i];
processes[i] = processes[j];
processes[j] = temp;
}
}
}
findWaitingTime(processes, n, bt, wt);
findTurnaroundTime(processes, n, bt, wt, tat);
float total_wt = 0, total_tat = 0;
printf("Process\tBurst Time\tWaiting Time\tTurnaround Time\n");
for (int i = 0; i < n; i++)
{
total_wt += wt[i];
total_tat += tat[i];
printf("%d\t\t%d\t\t%d\t\t%d\n", processes[i], bt[i], wt[i], tat[i]);
}

printf("Average waiting time: %.2f\n", total_wt / n);


printf("Average turnaround time: %.2f\n", total_tat / n);
}

43| P a g e
int main()
{
int processes[] = {1, 2, 3, 4}; // Process IDs
int burst_time[] = {6, 8, 7, 3}; // Burst times of processes
int n = sizeof(processes) / sizeof(processes[0]);

findAvgTime(processes, n, burst_time);

return 0;
}

44| P a g e
C) Priority Scheduling Algorithm
Priority Scheduling is a non-preemptive scheduling algorithm where each
process is assigned a priority. The process with the highest priority is executed
first. If two processes have the same priority, they are scheduled according to
their arrival time or any other criterion.
Key Characteristics:
• Non-preemptive: Once a process starts executing, it runs to completion.
• Priority Value: Each process is assigned a priority value. Lower priority
values represent higher priority.
• Problem: It suffers from starvation, where lower priority processes may
never get executed if higher priority processes keep arriving.
• Preemptive variant: There is a preemptive version of priority scheduling
called Preemptive Priority Scheduling, where the CPU can switch from
one process to another if a process with higher priority arrives.
Steps to Simulate the Priority Scheduling Algorithm:
1. Input the processes: Each process has an arrival time, burst time, and a
priority value.
2. Sort the processes by priority: The process with the highest priority
(lowest priority number) is selected first.
3. Execute the processes based on priority: The CPU executes the
processes in order of their priority.
4. Calculate the waiting time (WT): The waiting time for each process is
calculated as the total time spent in the ready queue.
5. Calculate turnaround time (TAT): The turnaround time is the time from
process arrival to completion.
6. Calculate average waiting time and average turnaround time.
Example:
Consider the following processes with their arrival time, burst time, and priority
(lower number indicates higher priority):

45| P a g e
Process Arrival Time Burst Time Priority

P1 0 4 2

P2 1 3 1

P3 2 1 4

P4 3 2 3

Step-by-Step Execution of Priority Scheduling:


1. Sort the processes by priority:
o P2: Priority = 1 (highest priority)
o P1: Priority = 2
o P4: Priority = 3
o P3: Priority = 4 (lowest priority)
2. Execution order: The processes are executed in the order of their
priority:
o P2 → P1 → P4 → P3
3. Calculate waiting times (WT):
o WT(P2) = 0 (since it starts immediately)
o WT(P1) = Completion Time of P2 - Arrival Time of P1 = 4 - 0 = 4
o WT(P4) = Completion Time of P1 - Arrival Time of P4 = 8 - 3 = 5
o WT(P3) = Completion Time of P4 - Arrival Time of P3 = 10 - 2 =
8
4. Calculate turnaround times (TAT):
o TAT(P2) = WT(P2) + Burst Time(P2) = 0 + 3 = 3
o TAT(P1) = WT(P1) + Burst Time(P1) = 4 + 4 = 8
o TAT(P4) = WT(P4) + Burst Time(P4) = 5 + 2 = 7
o TAT(P3) = WT(P3) + Burst Time(P3) = 8 + 1 = 9

46| P a g e
5. Calculate the average waiting time and average turnaround time:
o Average Waiting Time = (WT(P2) + WT(P1) + WT(P4) +
WT(P3)) / 4 = (0 + 4 + 5 + 8) / 4 = 17 / 4 = 4.25
o Average Turnaround Time = (TAT(P2) + TAT(P1) + TAT(P4) +
TAT(P3)) / 4 = (3 + 8 + 7 + 9) / 4 = 27 / 4 = 6.75
Summary of Results:

Arrival Burst Waiting Turnaround


Process Priority
Time Time Time Time

P2 1 3 1 0 3

P1 0 4 2 4 8

P4 3 2 3 5 7

P3 2 1 4 8 9

• Average Waiting Time = 4.25


• Average Turnaround Time = 6.75
C implementation for simulating Priority Scheduling:
Explanation of the C Code:
1. Sorting based on priority: The processes are sorted in ascending order
of their priority values (lower numbers indicate higher priority).
2. findWaitingTime: This function calculates the waiting time for each
process.
3. findTurnaroundTime: This function calculates the turnaround time for
each process.
4. findAvgTime: This function computes the average waiting time and
turnaround time after all processes have been processed.

47| P a g e
OUT PUT

Process Burst Time Priority Waiting Time Turnaround Time


2 3 1 0 3
1 4 2 4 8
4 2 3 5 7
3 1 4 8 9

Average waiting time: 4.25


Average turnaround time: 6.75

48| P a g e
Source code:
#include <stdio.h>
void findWaitingTime(int processes[], int n, int bt[], int wt[], int priority[])
{
wt[0] = 0; // The waiting time for the first process is always 0
for (int i = 1; i < n; i++) {
wt[i] = bt[i - 1] + wt[i - 1];
}
}
void findTurnaroundTime(int processes[], int n, int bt[], int wt[], int tat[])
{
for (int i = 0; i < n; i++) {
tat[i] = bt[i] + wt[i];
}
}
void findAvgTime(int processes[], int n, int bt[], int priority[])
{
int wt[n], tat[n];

// Sort processes based on priority


for (int i = 0; i < n - 1; i++)
{
for (int j = i + 1; j < n; j++)
{
if (priority[i] > priority[j])
{
int temp = bt[i]; // Swap burst times

49| P a g e
bt[i] = bt[j];
bt[j] = temp;
// Swap priorities
temp = priority[i];
priority[i] = priority[j];
priority[j] = temp;
// Swap processes
temp = processes[i];
processes[i] = processes[j];
processes[j] = temp;
}
}
}
findWaitingTime(processes, n, bt, wt, priority);
findTurnaroundTime(processes, n, bt, wt, tat);
float total_wt = 0, total_tat = 0;
printf("Process\tBurst Time\tPriority\tWaiting Time\tTurnaround Time\n");
for (int i = 0; i < n; i++)
{
total_wt += wt[i];
total_tat += tat[i];
printf("%d\t\t%d\t\t%d\t\t%d\t\t%d\n", processes[i], bt[i], priority[i], wt[i],
tat[i]);
}
printf("Average waiting time: %.2f\n", total_wt / n);
printf("Average turnaround time: %.2f\n", total_tat / n);
}

50| P a g e
int main()
{
int processes[] = {1, 2, 3, 4}; // Process IDs
int burst_time[] = {4, 3, 1, 2}; // Burst times of processes
int priority[] = {2, 1, 4, 3}; // Priority of processes (lower is higher priority)
int n = sizeof(processes) / sizeof(processes[0]);
findAvgTime(processes, n, burst_time, priority);
return 0;
}

51| P a g e
D) Round Robin (RR) Scheduling Algorithm
Round Robin (RR) is one of the simplest and most widely used preemptive
CPU scheduling algorithms. It works by assigning a fixed time quantum (or
time slice) to each process in the ready queue. Each process is given a chance to
execute for this fixed time period, and if it does not finish during this time, it is
placed at the end of the queue to wait for the next turn. This cycle continues
until all processes are completed.
Key Characteristics of Round Robin Scheduling:
• Preemptive: If a process does not finish within its allocated time slice, it
is interrupted and moved to the end of the queue.
• Fair: Every process gets an equal share of CPU time.
• Time Quantum: The fixed time slice (time quantum) can be a few
milliseconds and is determined by the system's scheduler.
Steps to Simulate the Round Robin (RR) Algorithm:
1. Input the processes: Each process has a burst time, and a time quantum
is assigned to the scheduler.
2. Initialize the ready queue: Processes are executed in a cyclic order with
each process receiving a time slice (time quantum).
3. Execute the processes:
o For each process, if the remaining burst time is less than or equal to
the time quantum, it will finish its execution.
o If the remaining burst time is greater than the time quantum, it will
be preempted, and its remaining burst time will be updated.
4. Calculate the waiting time (WT): Waiting time for each process is the
total time spent in the ready queue waiting to be executed.
5. Calculate turnaround time (TAT): Turnaround time is the total time
taken from the arrival of the process to its completion.

52| P a g e
6. Calculate average waiting time and average turnaround time.
Example:
Consider the following set of processes:

Process Arrival Time Burst Time

P1 0 24

P2 0 3

P3 0 3

Let us assume the time quantum (TQ) is 4 units.


Step-by-Step Execution of Round Robin:
1. Queue Initialization: Processes are initially arranged in the ready queue:
P1 → P2 → P3.
2. First Cycle:
o P1 executes for 4 units (remaining burst time = 24 - 4 = 20).
o P2 executes for 3 units (remaining burst time = 3 - 3 = 0, process
completes).
o P3 executes for 3 units (remaining burst time = 3 - 3 = 0, process
completes).
The queue now contains: P1 (remaining burst time = 20).
3. Second Cycle:
o P1 executes for 4 units (remaining burst time = 20 - 4 = 16).
o P1 continues execution in the next cycle.
The queue now contains: P1 (remaining burst time = 16).
4. Third Cycle:
o P1 executes for 4 units (remaining burst time = 16 - 4 = 12).
The queue now contains: P1 (remaining burst time = 12).
5. This continues until P1 completes after 6 cycles.

53| P a g e
Calculating Waiting Time and Turnaround Time:
• Waiting Time: The time spent by each process in the ready queue
waiting for its next turn.
• Turnaround Time: The total time from arrival to completion (including
waiting time).
Calculations Breakdown:
1. Waiting Time (WT):
o P1: Waiting time = 18 units.
o P2: Waiting time = 0 units (P2 executes without any interruption).
o P3: Waiting time = 3 units.
2. Turnaround Time (TAT):
o P1: Turnaround time = Waiting time + Burst time = 18 + 24 = 42.
o P2: Turnaround time = Waiting time + Burst time = 0 + 3 = 3.
o P3: Turnaround time = Waiting time + Burst time = 3 + 3 = 6.
C implementation of the Round Robin (RR) algorithm:
Explanation of the C Code:
1. findWaitingTime: This function calculates the waiting time for each
process. It simulates the Round Robin scheduling and reduces the
remaining burst time as processes execute in cycles of the given time
quantum.
2. findTurnaroundTime: This function calculates the turnaround time for
each process, which is the sum of the waiting time and burst time for each
process.
3. findAvgTime: This function computes the average waiting time and
turnaround time after all processes have been processed.

54| P a g e
OUTPUT:
Assume the input processes are:
• P1 with burst time 24
• P2 with burst time 3
• P3 with burst time 3
The time quantum is 4 units.
The output will be something like this:
Process Burst Time Waiting Time Turnaround Time
1 24 18 42
2 3 0 3
3 3 3 6

Average waiting time: 7.00


Average turnaround time: 17.00

55| P a g e
Source code:
#include <stdio.h>
void findWaitingTime(int processes[], int n, int bt[], int wt[], int tq)
{
int rem_bt[n];
for (int i = 0; i < n; i++)
{
rem_bt[i] = bt[i];
}
int time = 0; // current time
while (1)
{
int done = 1;
for (int i = 0; i < n; i++)
{
if (rem_bt[i] > 0)
{
done = 0;
if (rem_bt[i] > tq)
{
time += tq;
rem_bt[i] -= tq;
} else {
time += rem_bt[i];
wt[i] = time - bt[i]; // Calculate waiting time
rem_bt[i] = 0;
}

56| P a g e
}
}
if (done) break; // All processes are finished
}
}

void findTurnaroundTime(int processes[], int n, int bt[], int wt[], int tat[])
{
for (int i = 0; i < n; i++)
{
tat[i] = bt[i] + wt[i]; // Turnaround time = Burst time + Waiting time
}
}

void findAvgTime(int processes[], int n, int bt[], int tq)


{
int wt[n], tat[n];
findWaitingTime(processes, n, bt, wt, tq);
findTurnaroundTime(processes, n, bt, wt, tat);
float total_wt = 0, total_tat = 0;
printf("Process\tBurst Time\tWaiting Time\tTurnaround Time\n");
for (int i = 0; i < n; i++)
{
total_wt += wt[i];
total_tat += tat[i];
printf("%d\t\t%d\t\t%d\t\t%d\n", processes[i], bt[i], wt[i], tat[i]);
}

57| P a g e
printf("Average waiting time: %.2f\n", total_wt / n);
printf("Average turnaround time: %.2f\n", total_tat / n);
}

int main ()
{
int processes [] = {1, 2, 3} ; // Process IDs
int burst_time [] = {24, 3, 3}; // Burst times of processes
int n = sizeof(processes) / sizeof(processes [0]);
int time_quantum = 4; // Time quantum
findAvgTime(processes, n, burst_time, time_quantum);
return 0;
}

58| P a g e
Experiment -5 Date:
Control the number of ports opened by the operating system with
a) Semaphore b) Monitors.
Aim: Control the number of ports opened by the operating system with
a) Semaphore b) Monitors.
A) Semaphore
Semaphore Algorithm is used to Control the Number of Ports Opened by the
Operating System
A semaphore is a synchronization primitive used in operating systems to control
access to shared resources. It is commonly used to manage concurrency in
systems with limited resources, such as controlling the number of ports that can
be opened by applications.
Problem Definition:
In this case, the operating system has a limited number of ports (e.g., 5 ports).
We want to control access to these ports such that only a specified number of
processes can open a port at the same time. If all ports are occupied, any process
attempting to open a port should be blocked until a port is available.
Solution Using Semaphores
We can use a binary semaphore or a counting semaphore to control the number
of ports. The semaphore will be initialized with the maximum number of
available ports. Each time a process attempts to open a port, it will decrement
the semaphore. If the semaphore value is zero (i.e., all ports are in use), the
process will be blocked until a port is released.
Steps in the Semaphore Algorithm:
1. Initialization:
o Initialize a semaphore sem with the value equal to the number of
available ports. For example, if there are 5 available ports, initialize
sem = 5.
o A binary semaphore (mutex) will be used to ensure mutual
exclusion when a process is opening or closing a port.

59| P a g e
2. Process Request (Open Port):
o A process requests a port.
o The process will attempt to wait (decrement) the semaphore. If the
semaphore is greater than 0, the process can open a port, and the
semaphore is decremented by 1.
o If the semaphore is 0 (i.e., all ports are occupied), the process will
block (i.e., wait) until a port is available.
3. Release Port (Close Port):
o When a process finishes using a port, it will signal (increment) the
semaphore to release the port and make it available for other
processes.
o If there are processes waiting for a port, one of them will be
unblocked and allowed to open the port.
Example Scenario:
Consider the following:
• There are 5 available ports.
• 3 processes attempt to open ports at the same time.
Initially, the semaphore is set to 5, representing 5 available ports.

Process Action Semaphore Value

P1 Opens a port 4

P2 Opens a port 3

P3 Opens a port 2

P4 Tries to open a port 2 (Blocked)

P1 Closes a port 3

P4 Opens the released port 2

60| P a g e
Semaphore Algorithm in Pseudocode:
Initialize semaphore sem = Number of available ports

Process Request (Open Port):


1. Wait (sem) // Decrement the semaphore value by 1
2. If sem > 0, the process can open a port
3. Else, the process waits (blocked) until sem > 0

Release Port (Close Port):


1. Signal (sem) // Increment the semaphore value by 1
2. If there are any blocked processes, unblock one of them
Key Points:
• The semaphore is used to control access to the limited resources (ports)
and ensure that no more than the maximum number of processes can open
a port at the same time.
• Blocking occurs when the semaphore is 0, meaning all ports are
occupied.
• Mutual exclusion is ensured because the semaphore guarantees that only
a limited number of processes can access the resource (ports)
concurrently.
Semaphore Algorithm in C
C implementation that uses a counting semaphore to control the number of
ports opened by processes.
Explanation of the Code:
1. Semaphore Initialization:
o The semaphore is initialized with the value MAX_PORTS (which
is 5 in this case) using sem_init. This indicates the maximum
number of ports that can be opened simultaneously.

61| P a g e
2. Open Port:
o Each thread (representing a process) tries to open a port. Before
accessing a port, it calls sem_wait(&semaphore) which decrements
the semaphore. If the semaphore value is 0, the thread will block
until a port is released.
3. Close Port:
o After the process finishes using a port (simulated by sleep(2)), it
calls sem_post(&semaphore), which increments the semaphore,
effectively releasing the port and allowing another process to
access it.
4. Thread Creation:
o 10 threads (representing 10 processes) are created. Each thread
tries to open a port using the semaphore. Some threads will be
blocked if there are no available ports.
5. Semaphore Destruction:
o After all threads finish execution, the semaphore is destroyed using
sem_destroy.

62| P a g e
Output:

Process 1 trying to open a port...


Process 1 opened a port
Process 2 trying to open a port...
Process 2 opened a port
Process 3 trying to open a port...
Process 3 opened a port
Process 4 trying to open a port...
Process 5 trying to open a port...
Process 4 opened a port
Process 5 opened a port
Process 1 closed the port
Process 6 trying to open a port...
Process 6 opened a port
Process 7 trying to open a port...
Process 7 opened a port
...

63| P a g e
Source Code:
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#define MAX_PORTS 5 // Maximum number of ports
sem_t semaphore; // Semaphore to control access to ports

void* open_port(void* process_id)


{
int id = *((int*)process_id); // Process ID
printf("Process %d trying to open a port...\n", id);
// Wait on the semaphore (decrement the semaphore value)
sem_wait(&semaphore);
// Once the semaphore is decremented, a port is available
printf("Process %d opened a port\n", id);
// Simulate some work with the port
sleep(2); // Process is using the port for 2 seconds
// Signal the semaphore (increment the semaphore value)
sem_post(&semaphore);
printf("Process %d closed the port\n", id);
return NULL;
}

64| P a g e
int main()
{
pthread_t threads[10]; // Array to hold threads
int process_ids[10]; // Process IDs

// Initialize the semaphore with MAX_PORTS value


sem_init(&semaphore, 0, MAX_PORTS);

// Create processes (threads) trying to open a port


for (int i = 0; i < 10; i++) {
process_ids[i] = i + 1; // Assign process IDs (1, 2, 3, ...)
pthread_create(&threads[i], NULL, open_port, (void*)&process_ids[i]);
}

// Wait for all processes to finish


for (int i = 0; i < 10; i++)
{
pthread_join(threads[i], NULL);
}

// Destroy the semaphore


sem_destroy(&semaphore);

return 0;
}

65| P a g e
B) Monitors
Monitors Algorithm to Control the Number of Ports Opened by the
Operating System
A monitor is a high-level synchronization construct that provides a way to
control access to shared resources in concurrent programming. A monitor
encapsulates shared variables and operations on those variables into a single
abstract data type, ensuring that only one process can execute a monitor
procedure at a time.
In this case, the monitor will be used to control the number of available ports
that can be opened by processes in the operating system. The idea is to ensure
that no more than a specified number of processes can open ports
simultaneously, and processes will be blocked when the maximum number of
ports is reached.
Key Concepts of Monitors:
• Mutex (Mutual Exclusion): Ensures that only one process can execute a
monitor procedure at any given time.
• Condition Variables: Allows processes to wait and be notified when a
condition is met (e.g., when a port becomes available).
Problem Definition:
• The operating system has a limited number of ports available (e.g., 5
ports).
• Multiple processes may attempt to open a port concurrently.
• A process should be allowed to open a port if there is one available.
• If all ports are occupied, processes will wait until a port is released.
Solution Using Monitors
We will define a monitor that manages the number of available ports. This
monitor will have:
1. A shared variable to represent the number of available ports.
2. A condition variable to allow processes to wait until a port becomes
available.
3. Procedures to open and close ports.

66| P a g e
Steps in the Monitor Algorithm:
1. Initialization:
o Initialize a shared variable available_ports to the maximum number
of ports (e.g., 5).
o Use a condition variable to allow processes to wait when all ports
are occupied.
2. Open Port:
o If there is an available port (available_ports > 0), decrement the
available_ports counter and allow the process to open the port.
o If no ports are available (available_ports == 0), the process will
wait on the condition variable until a port is released.
3. Close Port:
o When a process finishes using a port, it will increment the
available_ports counter and signal the condition variable to notify
waiting processes that a port is available.

➢ Monitor Algorithm in Pseudocode:


Monitor PortsManager:
Shared Variables:
available_ports = 5 // Total number of available ports
condition_variable cv
Procedure OpenPort:
if available_ports > 0:
available_ports -= 1 // Allocate a port
else:
wait(cv) // Wait for a port to be released
Procedure ClosePort:
available_ports += 1 // Release the port
signal(cv) // Notify waiting processes that a port is available

67| P a g e
➢ Implementation Using Monitors in Pseudocode:
Monitor PortsManager:
Shared Variable:
available_ports = 5
cv // condition variable to wait for available ports
Procedure OpenPort:
if available_ports > 0:
available_ports -= 1
print("Port opened. Ports remaining:", available_ports)
else:
print("No available ports. Process waiting...")
wait(cv) // Wait for a port to become available
Procedure ClosePort:
available_ports += 1
print("Port closed. Ports remaining:", available_ports)
signal(cv) // Signal waiting processes that a port is available
C-like Implementation of Monitors (Pseudocode)
In C-like pseudocode, we might use threads, mutexes, and condition variables to
simulate the monitor:
Explanation of the Code:
1. Mutex: The mutex is used to ensure that only one process (thread) can
execute the monitor's procedures (open_port and close_port) at a time.
This guarantees mutual exclusion.
2. Condition Variable: The cv is used to block a process if no ports are
available. When a process is waiting for a port, it calls
pthread_cond_wait, which blocks the thread and releases the mutex.
When a port is released, pthread_cond_signal is called to wake up one
waiting process.

68| P a g e
3. Opening a Port:
o The open_port procedure first locks the mutex to ensure that no
other process can modify the shared available_ports variable at the
same time.
o If a port is available (available_ports > 0), the process can open the
port and the available_ports counter is decremented.
o If no ports are available (available_ports == 0), the process waits
on the condition variable (pthread_cond_wait).
4. Closing a Port:
o The close_port procedure increments the available_ports counter
and signals one waiting process (if any) that a port has become
available by calling pthread_cond_signal.
5. Thread Simulation:
o Each process (represented by a thread) calls open_port to try to
open a port, simulates some work using sleep(2), and then calls
close_port to release the port.
6. Thread Creation and Synchronization:
o The main function creates 10 threads to simulate 10 processes
trying to open and close ports.
o The threads are synchronized to ensure that no more than 5 threads
can open a port at any given time.

69| P a g e
Output:
Port opened. Ports remaining: 4
Port opened. Ports remaining: 3
Port opened. Ports remaining: 2
Port opened. Ports remaining: 1
Port opened. Ports remaining: 0
No available ports. Process waiting...
Port closed. Ports remaining: 1
Port opened. Ports remaining: 0
No available ports. Process waiting...
...

70| P a g e
Source code:
#include <stdio.h>
#include <pthread.h>
// Monitor: PortsManager
#define MAX_PORTS 5 // Number of available ports
pthread_mutex_t mutex; // Mutex for mutual exclusion
pthread_cond_t cv; // Condition variable to block/wake up threads
int available_ports = MAX_PORTS; // Shared variable for available ports
// Procedure to open a port

void open_port()
{
pthread_mutex_lock(&mutex); // Enter critical section
if (available_ports > 0)
{
available_ports--;
printf("Port opened. Ports remaining: %d\n", available_ports);
}
else
{
printf("No available ports. Process waiting...\n");
pthread_cond_wait(&cv, &mutex); // Wait for a port to be released
}
pthread_mutex_unlock(&mutex); // Exit critical section
}

71| P a g e
// Procedure to close a port
void close_port()
{
pthread_mutex_lock(&mutex); // Enter critical section
available_ports++;
printf("Port closed. Ports remaining: %d\n", available_ports);
pthread_cond_signal(&cv); // Signal one waiting process that a port is
available
pthread_mutex_unlock(&mutex); // Exit critical section
}
// Thread function simulating the process of opening and closing ports
void* process(void* arg)
{
open_port(); // Try to open a port
sleep(2); // Simulate some work with the port
close_port(); // Close the port
return NULL;
}
int main()
{
pthread_t threads[10]; // Array to hold threads
pthread_mutex_init(&mutex, NULL); // Initialize the mutex
pthread_cond_init(&cv, NULL); // Initialize the condition variable
// Create 10 processes (threads)
for (int i = 0; i < 10; i++)
{
pthread_create(&threads[i], NULL, process, NULL);
}
72| P a g e
// Wait for all threads to finish
for (int i = 0; i < 10; i++)
{
pthread_join(threads[i], NULL);
}
// Clean up
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&cv);
return 0;
}

73| P a g e
Experiment -6 Date:

Write a program to illustrate concurrent execution of threads using


pthreads library.

Aim:
Write a program to illustrate concurrent execution of threads using
pthreads library.

Program to Illustrate Concurrent Execution of Threads Using Pthreads


Library
In this example, we will write a program that demonstrates the concurrent
execution of threads using the pthreads library in C. The program will create
multiple threads, and each thread will perform a simple task (such as printing a
message or performing a calculation).
We will also use the pthread_create function to create threads, and pthread_join
to ensure the main thread waits for the completion of all created threads.
Steps:
1. Include necessary headers: We need to include pthread.h for thread
management and stdio.h for printing messages.
2. Create a function for thread execution: This function will be executed
by each thread.
3. Create threads using pthread_create: The main function will create
multiple threads.
4. Join threads using pthread_join: The main function will wait for all
threads to finish before it exits.
Explanation of the Code:
1. Include Headers:
o pthread.h: Required to work with threads using the pthreads library.
o stdio.h: Used for printing messages.
o unistd.h: Used for the sleep function to simulate some work.

74| P a g e
2. Thread Function (print_hello):
o Each thread executes the print_hello function, which prints a
message with the thread ID.
o The sleep(1) simulates a small delay to demonstrate concurrent
execution. After the sleep, the thread prints a completion message.
3. Thread Creation:
o In the main function, we create 5 threads using a loop. The thread
IDs are passed to the threads so each can print a unique message.
o pthread_create(&threads[i], NULL, print_hello,
(void*)&thread_ids[i]) is used to create a new thread. The fourth
parameter is the argument passed to the thread function
(print_hello). Here, we pass the address of the thread_ids[i]
variable.
4. Thread Joining:
o The main thread waits for each of the 5 threads to finish using
pthread_join. This ensures that the main program doesn't terminate
before the threads complete.
o pthread_join(threads[i], NULL) blocks until the thread identified
by threads[i] finishes execution.
5. Error Handling:
o We check if pthread_create or pthread_join fails. If either function
fails, we print an error message and return a non-zero exit code.
6. Final Output:
o The main thread prints a message once all threads have completed.
Key Points:
• Concurrency: All threads are executing concurrently, which can be
observed by the interleaved output.
• Thread Synchronization: The pthread_join function ensures that the
main thread waits for all the other threads to finish before it terminates.
• Thread Execution: Each thread is executed independently, allowing
them to run concurrently, and the sleep (1) simulates some processing
time for each thread.

75| P a g e
Output:

Hello from thread 1


Hello from thread 2
Hello from thread 3
Hello from thread 4
Hello from thread 5
Thread 1 finished execution
Thread 2 finished execution
Thread 3 finished execution
Thread 4 finished execution
Thread 5 finished execution
Main thread completed execution

76| P a g e
Source code:
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
// Function to be executed by each thread
void* print_hello(void* arg)
{
int thread_id = *((int*)arg); // Thread ID passed as argument
printf("Hello from thread %d\n", thread_id);
sleep(1); // Simulate some work
printf("Thread %d finished execution\n", thread_id);
return NULL;
}
int main()
{
pthread_t threads[5]; // Array to hold thread IDs
int thread_ids[5]; // Array to pass unique IDs to each thread
// Create 5 threads
for (int i = 0; i < 5; i++)
{
thread_ids[i] = i + 1; // Assigning unique IDs to each thread
if (pthread_create(&threads[i], NULL, print_hello, (void*)&thread_ids[i]) != 0)
{
perror("pthread_create failed");
return 1; // Return 1 on error
}
}

77| P a g e
// Wait for all threads to finish
for (int i = 0; i < 5; i++)
{
if (pthread_join(threads[i], NULL) != 0)
{
perror("pthread_join failed");
return 1; // Return 1 on error
}
}
printf("Main thread completed execution\n");
return 0;
}

78| P a g e
Experiment -7 Date:

Write a program to solve producer-consumer problem using


Semaphores

Aim: Write a program to solve producer-consumer problem using


Semaphores
Producer-Consumer Problem Using Semaphores
The Producer-Consumer problem is a classic synchronization problem in
computer science where:
• Producers generate data and put it in a shared buffer.
• Consumers take data from the shared buffer.
• There is a finite size buffer, so producers must wait if the buffer is full,
and consumers must wait if the buffer is empty.
We can use semaphores to solve this problem by:
• Using a mutex (binary semaphore) to ensure mutual exclusion when
accessing the shared buffer.
• Using counting semaphores to track the number of items in the buffer and
the available space in the buffer.
Semaphores:
1. Empty (initial value = buffer size): Tracks the number of empty slots in
the buffer (space for new items).
2. Full (initial value = 0): Tracks the number of filled slots in the buffer
(items available for consumption).
3. Mutex (initial value = 1): Ensures that only one process (either producer
or consumer) can access the buffer at a time.
Steps to Solve the Problem:
1. Producer:
o The producer will wait until there is space in the buffer (i.e., empty
> 0).
o It will then acquire the mutex to safely insert an item into the
buffer.

79| P a g e
o The producer will signal that an item has been added to the buffer
by incrementing the full semaphore.
2. Consumer:
o The consumer will wait until there is an item to consume (i.e., full
> 0).
o It will then acquire the mutex to safely remove an item from the
buffer.
o The consumer will signal that space has been freed in the buffer by
incrementing the empty semaphore.
Explanation of the Code:
1. Shared Resources:
o buffer [BUFFER_SIZE]: The shared buffer where produced items
are stored.
o in: The index where the next item will be inserted (produced item).
o out: The index where the next item will be consumed (consumed
item).
2. Semaphores:
o empty: Semaphore initialized to the size of the buffer
(BUFFER_SIZE). It tracks how many empty slots are available for
the producer.
o full: Semaphore initialized to 0. It tracks how many items are
available for the consumer.
o mutex: A binary semaphore initialized to 1. It ensures that only one
thread (either producer or consumer) can access the buffer at a
time.
3. Producer Function:
o The producer waits for an empty slot (sem_wait(&empty)), then
enters the critical section (protected by the mutex semaphore).
o It inserts an item into the buffer, updates the in index, and signals
that the item has been produced (sem_post(&full)).
o The producer sleeps for 1 second to simulate time taken to produce
an item.

80| P a g e
4. Consumer Function:
o The consumer waits for a full slot (sem_wait(&full)), then enters
the critical section.
o It consumes an item from the buffer, updates the out index, and
signals that an empty slot is available (sem_post(&empty)).
o The consumer sleeps for 2 seconds to simulate time taken to
consume an item.
5. Main Function:
o The sem_init function initializes the semaphores.
o The pthread_create function creates two threads (producer and
consumer).
o The pthread_join function ensures the main thread waits for both
the producer and consumer threads to complete their execution.
o The semaphores are destroyed at the end using sem_destroy.
Key Points:
• Producer-Consumer Synchronization: This solution uses semaphores
(empty, full, mutex) to synchronize the producer and consumer.
• Critical Section Protection: The mutex ensures mutual exclusion when
accessing the shared buffer, preventing race conditions.
• Blocking and Waiting: The producer and consumer are blocked using
semaphores when necessary (e.g., when the buffer is full or empty).
• Concurrency: The producer and consumer run concurrently in different
threads, demonstrating the concurrent nature of the problem.

81| P a g e
OUTPUT:
Produced: 1
Produced: 2
Produced: 3
Produced: 4
Produced: 5
Produced: 6
Produced: 7
Produced: 8
Produced: 9
Produced: 10
Consumed: 1
Consumed: 2
Consumed: 3
Consumed: 4
Consumed: 5
Consumed: 6
Consumed: 7
Consumed: 8
Consumed: 9
Consumed: 10

82| P a g e
Source code:
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#include <unistd.h>
#define BUFFER_SIZE 5 // Size of the buffer
// Shared buffer and variables
int buffer[BUFFER_SIZE];
int in = 0; // Points to the next empty slot for the producer
int out = 0; // Points to the next filled slot for the consumer
// Semaphores
sem_t empty; // Semaphore to count empty slots in the buffer
sem_t full; // Semaphore to count full slots in the buffer
sem_t mutex; // Mutex to protect access to the buffer
// Producer thread function
void* producer(void* arg)
{
for (int i = 0; i < 10; i++)
{
// Produce an item
int item = i + 1; // Just an example of the item being produced
sem_wait(&empty); // Wait for an empty slot
sem_wait(&mutex); // Enter critical section
// Add item to the buffer
buffer[in] = item;
in = (in + 1) % BUFFER_SIZE; // Update the "in" index cyclically
printf("Produced: %d\n", item);

83| P a g e
sem_post(&mutex); // Exit critical section
sem_post(&full); // Signal that the buffer has one more item
sleep(1); // Simulate time taken to produce an item
}
return NULL;
}

// Consumer thread function


void* consumer(void* arg)
{
for (int i = 0; i < 10; i++)
{
sem_wait(&full); // Wait for a full slot
sem_wait(&mutex); // Enter critical section
// Consume an item from the buffer
int item = buffer[out];
out = (out + 1) % BUFFER_SIZE; // Update the "out" index cyclically
printf("Consumed: %d\n", item);
sem_post(&mutex); // Exit critical section
sem_post(&empty); // Signal that there is one more empty slot
sleep (2); // Simulate time taken to consume an item
}
return NULL;
}

84| P a g e
int main ()
{
// Initialize semaphores
sem_init(&empty, 0, BUFFER_SIZE); // Buffer has BUFFER_SIZE empty
slots initially
sem_init(&full, 0, 0); // No full slots initially
sem_init(&mutex, 0, 1); // Mutex initialized to 1 (binary semaphore)
pthread_t prod, cons;
// Create producer and consumer threads
pthread_create(&prod, NULL, producer, NULL);
pthread_create(&cons, NULL, consumer, NULL);
// Wait for both threads to finish
pthread_join(prod, NULL);
pthread_join(cons, NULL);
// Destroy semaphores
sem_destroy(&empty);
sem_destroy(&full);
sem_destroy(&mutex);
return 0;
}

85| P a g e
Experiment – 8 Date:

Write a program to solve producer-consumer problem using


Semaphores.

Aim: Implement the following memory allocation methods for fixed


partition
a) First fit b) Worst fit c) Best fit

a) First fit
First-Fit Memory Allocation Method (Fixed Partition)
The First-Fit memory allocation strategy is one of the simplest memory
management techniques. In this method, memory is divided into fixed-size
partitions, and when a process arrives, it is placed into the first partition that is
large enough to accommodate it.
Steps:
1. Fixed Partitions: The memory is divided into a fixed number of partitions
of predetermined sizes.
2. Process Arrival: When a process arrives, it is assigned to the first
available partition that is large enough to accommodate the process.
3. Process Allocation: The process is allocated to the partition, and the
partition is marked as occupied.
4. Unsuccessful Allocation: If no partition is large enough for the process, it
will be considered a failed allocation.
Explanation of the Code:
1. Structures:
o struct Partition: Represents a memory partition with size (size of
the partition) and is_allocated (status of the partition, whether it's
allocated or not).
o struct Process: Represents a process with id (unique ID for the
process) and size (size of the process).
o

86| P a g e
2. Function first_fit:
o Takes the list of partitions and processes as input.
o For each process, it tries to find the first partition that is large
enough and not already allocated.
o If a suitable partition is found, the process is allocated to that
partition, and the partition is marked as allocated.
o If no suitable partition is found, the process is marked as
unallocated.
3. Input:
o The program first asks the user to input the number of partitions
and the sizes of those partitions.
o Then, it asks for the number of processes and their sizes.
4. Output:
o The program prints the allocation status of each process. If a
process is successfully allocated to a partition, it prints which
partition the process was allocated to.
o If a process cannot be allocated to any partition, it prints that the
process could not be allocated.
Key Points:
• First-Fit Allocation: The process is allocated to the first partition that is
large enough to hold it.
• Partition Status: Partitions are marked as allocated or free using the
is_allocated field.
• Simple Allocation: This approach is simple but can lead to fragmentation
if the process sizes vary significantly or if many small processes are
allocated to larger partitions.

87| P a g e
Output:
Enter the number of memory partitions: 3
Enter the size of each partition:
Partition 1 size: 300
Partition 2 size: 500
Partition 3 size: 200
Enter the number of processes: 4
Enter the size of each process:
Process 1 size: 120
Process 2 size: 450
Process 3 size: 300
Process 4 size: 600

88| P a g e
Source Code:
#include <stdio.h>
#include <stdlib.h>
// Structure to represent a memory partition
struct Partition
{
int size; // Size of the partition
int is_allocated; // 0 if unallocated, 1 if allocated
};
// Structure to represent a process
struct Process
{
int id; // Process ID
int size; // Size of the process
};
// First-Fit Memory Allocation function
void first_fit(struct Partition partitions[], int partition_count, struct Process
processes[], int process_count)
{
for (int i = 0; i < process_count; i++)
{
int allocated = 0;
// Try to allocate the current process to the first suitable partition
for (int j = 0; j < partition_count; j++)
{
if (partitions[j].size >= processes[i].size && partitions[j].is_allocated == 0)
{

89| P a g e
// Allocate the process to this partition
partitions[j].is_allocated = 1;
printf("Process %d (size %d) allocated to Partition %d (size %d)\n",
processes[i].id, processes[i].size, j+1, partitions[j].size);
allocated = 1;
break;
}
}
if (!allocated)
{
printf("Process %d (size %d) could not be allocated.\n", processes[i].id,
processes[i].size);
}
}
}
int main ()
{
int partition_count, process_count;
// Input number of partitions
printf("Enter the number of memory partitions: ");
scanf("%d", &partition_count);
struct Partition partitions[partition_count];
// Input size for each partition
printf("Enter the size of each partition:\n");
for (int i = 0; i < partition_count; i++)
{
printf("Partition %d size: ", i+1);
scanf("%d", &partitions[i].size);
90| P a g e
partitions[i].is_allocated = 0; // Initially all partitions are unallocated
}
// Input number of processes
printf("Enter the number of processes: ");
scanf("%d", &process_count);
struct Process processes[process_count];
// Input size for each process
printf("Enter the size of each process:\n");
for (int i = 0; i < process_count; i++)
{
printf("Process %d size: ", i+1);
scanf("%d", &processes[i].size);
processes[i].id = i + 1; // Assign unique ID to each process
}
// Call the First-Fit function to allocate processes
first_fit(partitions, partition_count, processes, process_count);
return 0;
}

91| P a g e
B) Worst-Fit
Worst-Fit Memory Allocation Method (Fixed Partition)
The Worst-Fit memory allocation strategy is a memory management technique
in which the process is allocated to the largest available partition. The idea
behind this method is to leave the largest remaining partitions for future
processes, avoiding small unusable fragments that could arise from smaller
partitions being filled first.
Steps for Worst-Fit Memory Allocation:
1. Fixed Partitions: The memory is divided into fixed-size partitions, and
processes can be allocated to these partitions.
2. Process Arrival: When a process arrives, it is placed into the partition
that has the largest available space that is still large enough to
accommodate the process.
3. Allocation: The process is allocated to the largest partition that is large
enough.
4. Failed Allocation: If no partition can accommodate the process, it cannot
be allocated.
Explanation of the Code:
1. Structures:
o struct Partition: Represents a partition in memory with size (the
size of the partition) and is_allocated (a flag that indicates if the
partition is allocated or not).
o struct Process: Represents a process with id (unique identifier for
the process) and size (size of the process).
2. Function worst_fit:
o This function tries to allocate each process to the largest available
partition that is large enough to accommodate the process.
o It loops through the partitions to find the one with the largest size
that can still accommodate the process.
o If a suitable partition is found, the process is allocated to that
partition, and the partition is marked as allocated (is_allocated = 1).

92| P a g e
o If no partition can accommodate the process, the process cannot be
allocated.
3. Input:
o The program first asks for the number of partitions and the size of
each partition.
o Then, it asks for the number of processes and the size of each
process.
4. Output:
o The program prints the allocation status of each process. If a
process is successfully allocated to a partition, it prints which
partition the process was allocated to.
o If a process cannot be allocated to any partition, it prints that the
process could not be allocated.
Key Points:
• Worst-Fit Allocation: The process is allocated to the largest available
partition that is large enough to accommodate it.
• Partition Status: Partitions are marked as allocated or free using the
is_allocated field.
• Efficiency: The worst-fit method tends to leave larger gaps between
allocated memory blocks, which may be inefficient in the long term,
leading to fragmentation.

93| P a g e
Output:
Enter the number of memory partitions: 3
Enter the size of each partition:
Partition 1 size: 300
Partition 2 size: 500
Partition 3 size: 200
Enter the number of processes: 4
Enter the size of each process:
Process 1 size: 120
Process 2 size: 450
Process 3 size: 300
Process 4 size: 600
(Or)
Process 1 (size 120) allocated to Partition 2 (size 500)
Process 2 (size 450) allocated to Partition 1 (size 300)
Process 3 (size 300) allocated to Partition 3 (size 200)
Process 4 (size 600) could not be allocated.

94| P a g e
Source Code:
#include <stdio.h>
#include <stdlib.h>
// Structure to represent a memory partition
struct Partition
{
int size; // Size of the partition
int is_allocated; // 0 if unallocated, 1 if allocated
};
// Structure to represent a process
struct Process
{
int id; // Process ID
int size; // Size of the process
};
// Worst-Fit Memory Allocation function
void worst_fit(struct Partition partitions[], int partition_count, struct Process
processes[], int process_count)
{
for (int i = 0; i < process_count; i++)
{
int max_index = -1;
int max_size = -1;
// Find the largest available partition that is large enough for the process
for (int j = 0; j < partition_count; j++)
{
if (partitions[j].size >= processes[i].size && partitions[j].is_allocated == 0)
{

95| P a g e
if (partitions[j].size > max_size)
{
max_size = partitions[j].size;
max_index = j;
}
}
}
// If a suitable partition is found, allocate the process
if (max_index != -1)
{
partitions[max_index].is_allocated = 1;
printf("Process %d (size %d) allocated to Partition %d (size %d)\n",
processes[i].id, processes[i].size, max_index+1, partitions[max_index].size);
}
else
{
printf("Process %d (size %d) could not be allocated.\n", processes[i].id,
processes[i].size);
}
}
}
int main()
{
int partition_count, process_count;
// Input number of partitions
printf("Enter the number of memory partitions: ");
scanf("%d", &partition_count);
struct Partition partitions[partition_count];

96| P a g e
// Input size for each partition
printf("Enter the size of each partition:\n");
for (int i = 0; i < partition_count; i++)
{
printf("Partition %d size: ", i+1);
scanf("%d", &partitions[i].size);
partitions[i].is_allocated = 0; // Initially all partitions are unallocated
}
// Input number of processes
printf("Enter the number of processes: ");
scanf("%d", &process_count);
struct Process processes[process_count];
// Input size for each process
printf("Enter the size of each process:\n");
for (int i = 0; i < process_count; i++)
{
printf("Process %d size: ", i+1);
scanf("%d", &processes[i].size);
processes[i].id = i + 1; // Assign unique ID to each process
}
// Call the Worst-Fit function to allocate processes
worst_fit(partitions, partition_count, processes, process_count);
return 0;
}

97| P a g e
c) Best-Fit
Best-Fit Memory Allocation Method (Fixed Partition)
The Best-Fit memory allocation method is another approach in memory
management where a process is allocated to the smallest available partition that
is large enough to accommodate it. The goal of this method is to minimize
waste by using the smallest partition that can accommodate the process, leaving
larger partitions available for future allocations.
Steps for Best-Fit Memory Allocation:
1. Fixed Partitions: The memory is divided into fixed-size partitions.
2. Process Arrival: When a process arrives, it is placed in the partition that
has the smallest space remaining after allocation, but still large enough to
accommodate the process.
3. Allocation: The process is allocated to the partition that leaves the
smallest leftover space.
4. Failed Allocation: If no partition is large enough, the process is not
allocated
Explanation of the Code:
1. Structures:
o struct Partition: Represents a memory partition, containing:
▪ size: The size of the partition.
▪ is_allocated: A flag indicating whether the partition is
allocated (1 if allocated, 0 if free).
o struct Process: Represents a process, containing:
▪ id: A unique identifier for the process.
▪ size: The size of the process.
2. Function best_fit:
o This function tries to allocate each process to the smallest
available partition that can accommodate it.
o The process is allocated to the partition that leaves the least
unused space. This is done by checking the difference between the
partition size and the process size.

98| P a g e
o If no suitable partition is found (i.e., no partition large enough), the
process is marked as unallocated.
3. Input:
o The program first asks for the number of partitions and the size of
each partition.
o Then, it asks for the number of processes and their respective sizes.
4. Output:
o The program prints the allocation status of each process. If a
process is successfully allocated to a partition, it prints which
partition the process was allocated to.
o If a process cannot be allocated to any partition, it prints that the
process could not be allocated.
Key Points:
• Best-Fit Allocation: The process is allocated to the partition that leaves
the smallest leftover space. This minimizes unused memory in the
partition.
• Partition Status: Partitions are marked as allocated (1) or free (0) using
the is_allocated flag.
• Efficiency: Best-Fit allocation reduces wasted space, but it can lead to
fragmentation in the long term if many small gaps accumulate.

99| P a g e
Output:
Enter the number of memory partitions: 3
Enter the size of each partition:
Partition 1 size: 300
Partition 2 size: 500
Partition 3 size: 200
Enter the number of processes: 4
Enter the size of each process:
Process 1 size: 120
Process 2 size: 450
Process 3 size: 300
Process 4 size: 600
(Or)
Process 1 (size 120) allocated to Partition 1 (size 300)
Process 2 (size 450) allocated to Partition 2 (size 500)
Process 3 (size 300) allocated to Partition 3 (size 200)
Process 4 (size 600) could not be allocated.

100| P a g e
Source Code:
#include <stdio.h>
#include <stdlib.h>
// Structure to represent a memory partition
struct Partition
{
int size; // Size of the partition
int is_allocated; // 0 if unallocated, 1 if allocated
};
// Structure to represent a process
struct Process
{
int id; // Process ID
int size; // Size of the process
};
// Best-Fit Memory Allocation function
void best_fit(struct Partition partitions[], int partition_count, struct Process
processes[], int process_count)
{
for (int i = 0; i < process_count; i++)
{
int best_index = -1;
int best_size = 9999999; // A large number to compare with
// Find the smallest partition that is large enough to fit the process
for (int j = 0; j < partition_count; j++)
{
if (partitions[j].size >= processes[i].size && partitions[j].is_allocated == 0)
{
101| P a g e
// If the partition is large enough and better than previous ones
if (partitions[j].size - processes[i].size < best_size)
{
best_size = partitions[j].size - processes[i].size;
best_index = j;
}
}
}
// If a suitable partition is found, allocate the process
if (best_index != -1)
{
partitions[best_index].is_allocated = 1;
printf("Process %d (size %d) allocated to Partition %d (size %d)\n",
processes[i].id, processes[i].size, best_index+1, partitions[best_index].size);
}
else
{
printf("Process %d (size %d) could not be allocated.\n", processes[i].id,
processes[i].size);
}
}
}
int main()
{
int partition_count, process_count;
// Input number of partitions
printf("Enter the number of memory partitions: ");
scanf("%d", &partition_count);
102| P a g e
struct Partition partitions[partition_count];
// Input size for each partition
printf("Enter the size of each partition:\n");
for (int i = 0; i < partition_count; i++)
{
printf("Partition %d size: ", i+1);
scanf("%d", &partitions[i].size);
partitions[i].is_allocated = 0; // Initially all partitions are unallocated
}
// Input number of processes
printf("Enter the number of processes: ");
scanf("%d", &process_count);
struct Process processes[process_count];
// Input size for each process
printf("Enter the size of each process:\n");
for (int i = 0; i < process_count; i++)
{
printf("Process %d size: ", i+1);
scanf("%d", &processes[i].size);
processes[i].id = i + 1; // Assign unique ID to each process
}
// Call the Best-Fit function to allocate processes
best_fit(partitions, partition_count, processes, process_count);
return 0;
}

103| P a g e
Experiment -9 Date:

program to solve producer-consumer problem using Semaphores

Aim: Simulate the following page replacement algorithms


a) FIFO b) LRU c) LFU

A) FIFO
To simulate the FIFO (First-In, First-Out) page replacement algorithm, let’s first
understand how it works.
FIFO Algorithm:
• FIFO replaces the page that has been in memory the longest.
• It maintains a queue of pages, where the page that arrives first will be
replaced first when memory is full.
• The algorithm works as follows:
1. When a page needs to be accessed, check if it's already in memory.
2. If the page is not in memory (a page fault occurs):
▪ If there is space in memory, simply add the page.
▪ If memory is full, remove the page that has been in memory
the longest (the page at the front of the queue), and add the
new page.
3. Continue this process for all the page accesses.
Simulation Example:
Let’s say we have the following parameters:
• Page references: 7, 0, 1, 2, 3, 0, 4, 2, 3, 0, 3.
• Number of frames: 3 (i.e., the number of pages the memory can hold).
Let’s simulate the FIFO page replacement with this example. We will trace the
steps and show the contents of the page frames after each page reference, as
well as the number of page faults.

104| P a g e
Explanation:
1. Memory Initialization: The memory [] array holds the pages currently in
memory. It's initialized to -1 to represent empty slots.
2. Page Fault Detection: For each page in the reference string, we check if it
is already present in memory. If not, a page fault occurs.
3. FIFO Logic: When a page fault occurs and memory is full, the oldest
page (the one that has been in memory the longest) is replaced.
4. Output: The program prints the state of memory after each page reference
and the total number of page faults at the end.

105| P a g e
Input:

For the page reference string {7, 0, 1, 2, 3, 0, 4, 2, 3, 0, 3}


with 3 frames, the output would look like:

OUTPUT:

Page reference string: 7 0 1 2 3 0 4 2 3 0 3


Page fault! Reference: 7
Memory: 7 - -
Page fault! Reference: 0
Memory: 7 0 -
Page fault! Reference: 1
Memory: 7 0 1
Page fault! Reference: 2
Memory: 0 1 2
Page fault! Reference: 3
Memory: 1 2 3
Page fault! Reference: 0
Memory: 2 3 0
Page fault! Reference: 4
Memory: 3 0 4
Page fault! Reference: 2
Memory: 0 4 2
Page fault! Reference: 3
Memory: 4 2 3
Page fault! Reference: 0
Memory: 2 3 0
Page fault! Reference: 3
Memory: 2 0 3

Total page faults: 10

106| P a g e
Source Code:
#include <stdio.h>
void fifoPageReplacement(int pageReferences[], int numPages, int numFrames)
{
int memory[numFrames]; // To store pages in memory
int pageFaults = 0; // To count the number of page faults
int index = 0; // To keep track of the index of the page to be replaced
// Initialize memory with -1 (indicating empty slots)
for (int i = 0; i < numFrames; i++)
{
memory[i] = -1;
}
printf("Page reference string: ");
for (int i = 0; i < numPages; i++)
{
printf("%d ", pageReferences[i]);
}
printf("\n");
// Simulate the FIFO page replacement
for (int i = 0; i < numPages; i++)
{
int page = pageReferences[i];
int found = 0;
// Check if the page is already in memory
for (int j = 0; j < numFrames; j++)
{
if (memory[j] == page)
{
found = 1;
break;
}
}
// If page not found, it's a page fault
if (!found) {
pageFaults++;
printf("Page fault! Reference: %d\n", page);
// If memory is not full, insert the page
if (index < numFrames)
{
memory[index] = page;
107| P a g e
index++;
}
else
{
// If memory is full, replace the page at the front (FIFO)
for (int j = 0; j < numFrames - 1; j++)
{
memory[j] = memory[j + 1];
}
memory[numFrames - 1] = page;
}
}

// Print the current state of the memory


printf("Memory: ");
for (int j = 0; j < numFrames; j++)
{
if (memory[j] != -1)
printf("%d ", memory[j]);
else
printf(" - ");
}
printf("\n");
}

printf("\nTotal page faults: %d\n", pageFaults);


}

int main()
{
// Example: page references and number of frames
int pageReferences[] = {7, 0, 1, 2, 3, 0, 4, 2, 3, 0, 3};
int numPages = sizeof(pageReferences) / sizeof(pageReferences[0]);
int numFrames = 3;

fifoPageReplacement(pageReferences, numPages, numFrames);

return 0;
}

108| P a g e
B) LRU
To simulate the LRU (Least Recently Used) page replacement algorithm, the
steps involved and how it works:
LRU Page Replacement Algorithm:
• Goal: When a page fault occurs and memory is full, replace the page that
has not been used for the longest time.
• Mechanism: It keeps track of the pages in memory and the last time they
were accessed. When memory is full and a new page needs to be loaded,
it replaces the page with the least recent usage.
Steps:
1. Check if a page is already in memory:
o If the page is found in memory, update the "last used time" to the
current time or step.
o If the page is not found in memory, it's a page fault.
2. If memory is not full, simply load the page.
3. If memory is full, identify the page with the oldest "last used time" and
replace it with the new page.
4. Track page accesses and update the "last used time" for pages in
memory.
Example:
Given the page reference string and memory size (number of frames), let's
simulate the LRU page replacement algorithm. Here's the logic we will
implement in a simulation:
Example Input:
• Page references: [7, 0, 1, 2, 3, 0, 4, 2, 3, 0, 3]
• Number of frames: 3 (i.e., memory can hold 3 pages)
Pseudocode:
We can proceed with the following steps in pseudocode to simulate LRU:
1. Initialize memory: Set up an empty memory of size numFrames.
2. Track last used time: We can store the last used time of each page.
3. Process each page:
o If the page is already in memory, update its last usage time.
o If the page is not in memory:
▪ If there’s space in memory, add the page.
▪ If memory is full, remove the least recently used page and
add the new page

109| P a g e
Explanation:
1. Memory Initialization: The memory[] array holds the pages currently in
memory. Initially, all slots are set to -1 to indicate empty.
2. Last Used Time: The lastUsed[] array keeps track of the last time each
page was used. When a page is accessed, its "last used time" is updated.
3. Page Fault Detection: If a page is not found in memory, it causes a page
fault. The program then finds the least recently used page by comparing
the last used times and replaces it with the new page.
4. State of Memory: After processing each page reference, the program
prints the current state of the memory and updates it if needed.
5. Output: The program also counts the total number of page faults and
prints it at the end.
How it works:
• Initially, the memory is empty.
• Each time a page reference is made:
o If the page is already in memory, the last used time is updated.
o If it’s a page fault, the least recently used page is replaced.

110| P a g e
Output:
For the page reference string {7, 0, 1, 2, 3, 0, 4, 2, 3, 0, 3} with 3 frames, the
output would be:
Page reference string: 7 0 1 2 3 0 4 2 3 0 3
Page fault! Reference: 7
Memory: 7 - -
Page fault! Reference: 0
Memory: 7 0 -
Page fault! Reference: 1
Memory: 7 0 1
Page fault! Reference: 2
Memory: 0 1 2
Page fault! Reference: 3
Memory: 1 2 3
Page fault! Reference: 0
Memory: 2 3 0
Page fault! Reference: 4
Memory: 3 0 4
Page fault! Reference: 2
Memory: 0 4 2
Page fault! Reference: 3
Memory: 4 2 3
Page fault! Reference: 0
Memory: 2 3 0
Page fault! Reference: 3
Memory: 2 0 3

Total page faults: 10

111| P a g e
Source Code:
#include <stdio.h>
void lruPageReplacement(int pageReferences[], int numPages, int numFrames)
{
int memory[numFrames]; // To store pages in memory
int lastUsed[numFrames]; // To store the last used time of pages
int pageFaults = 0; // To count the number of page faults
// Initialize memory with -1 (indicating empty slots)
for (int i = 0; i < numFrames; i++)
{
memory[i] = -1;
lastUsed[i] = -1; // Initially no pages have been used
}
printf("Page reference string: ");
for (int i = 0; i < numPages; i++)
{
printf("%d ", pageReferences[i]);
}
printf("\n");
// Simulate the LRU page replacement
for (int i = 0; i < numPages; i++)
{
int page = pageReferences[i];
int found = 0;
// Check if the page is already in memory
for (int j = 0; j < numFrames; j++)
{
if (memory[j] == page)
{
found = 1;
lastUsed[j] = i; // Update the last used time
break;
}
}
// If page not found, it's a page fault
if (!found)
{
pageFaults++;
printf("Page fault! Reference: %d\n", page);

112| P a g e
// If memory is not full, insert the page
int minIndex = 0;
if (lastUsed[minIndex] != -1)
{ // Find the least recently used page
for (int j = 1; j < numFrames; j++)
{
if (lastUsed[j] < lastUsed[minIndex])
{
minIndex = j;
}
}
}
// If memory is full, replace the least recently used page
memory[minIndex] = page;
lastUsed[minIndex] = i; // Update the last used time
}
// Print the current state of the memory
printf("Memory: ");
for (int j = 0; j < numFrames; j++)
{
if (memory[j] != -1)
printf("%d ", memory[j]);
else
printf(" - ");
}
printf("\n");
}
printf("\nTotal page faults: %d\n", pageFaults);
}
int main()
{
// Example: page references and number of frames
int pageReferences[] = {7, 0, 1, 2, 3, 0, 4, 2, 3, 0, 3};
int numPages = sizeof(pageReferences) / sizeof(pageReferences[0]);
int numFrames = 3;
lruPageReplacement(pageReferences, numPages, numFrames);
return 0;
}

113| P a g e
C) LFU
The LFU (Least Frequently Used) page replacement algorithm replaces the page
that is used the least frequently. In the case of multiple pages having the same
frequency of use, it typically selects the one that was accessed the least recently.
Steps to Simulate LFU:
1. Frequency Count: Each page has an associated frequency counter that
tracks how many times the page has been accessed.
2. Replacement:
o When a page fault occurs and there is room in memory, the page is
added.
o When memory is full and a new page must be added, replace the
page with the lowest frequency.
o If multiple pages have the same frequency, choose the one that was
used the least recently.
3. Tracking: We need to track both the frequency of accesses and the most
recent access time to decide which page to replace.
Explanation:
1. Memory Initialization: The memory[] array stores the pages currently in
memory. Each page is represented by a Page struct containing:
o page: The page number.
o frequency: How many times the page has been used.
o lastUsed: The last time the page was accessed.
2. Page Fault Detection: For each page in the reference string, the program
checks if it is already in memory. If it is, the frequency is incremented,
and the lastUsed time is updated. If it is not in memory, a page fault
occurs, and the least frequently used page (or the one with the lowest
lastUsed time if multiple pages have the same frequency) is replaced.
3. LFU Replacement: When the memory is full, the page with the lowest
frequency is replaced. If there are multiple pages with the same
frequency, the one that was accessed least recently is replaced.
4. Output: The program prints the state of memory after each page reference
and the total number of page faults at the end.

114| P a g e
How it works:
• Each page reference is processed in order.
• If the page is already in memory, the frequency count is incremented.
• If the page is not in memory, a page fault occurs. The page is then added
to memory, replacing the least frequently used page (or the least recently
used one if the frequencies are the same).

115| P a g e
OUTPUT:
For the page reference string {7, 0, 1, 2, 3, 0, 4, 2, 3, 0, 3} with 3 frames, the
output might look like:
Page reference string: 7 0 1 2 3 0 4 2 3 0 3
Page fault! Reference: 7
Memory: 7 - -
Page fault! Reference: 0
Memory: 7 0 -
Page fault! Reference: 1
Memory: 7 0 1
Page fault! Reference: 2
Memory: 7 0 2
Page fault! Reference: 3
Memory: 0 2 3
Page fault! Reference: 0
Memory: 2 3 0
Page fault! Reference: 4
Memory: 3 0 4
Page fault! Reference: 2
Memory: 0 4 2
Page fault! Reference: 3
Memory: 4 2 3
Page fault! Reference: 0
Memory: 2 3 0
Page fault! Reference: 3
Memory: 2 0 3

Total page faults: 10


116| P a g e
Source Code:
#include <stdio.h>
#define MAX_FRAMES 10
// Structure to represent a page in memory
typedef struct
{
int page; // The page number
int frequency; // How many times the page has been used
int lastUsed; // The last access time
} Page;
// Function to find the least frequently used page
int findLFUPage(Page memory[], int numFrames)
{
int minFreq = 99999; // Initialize with a high value
int minIndex = -1;
for (int i = 0; i < numFrames; i++)
{
if (memory[i].frequency < minFreq || (memory[i].frequency == minFreq
&& memory[i].lastUsed < memory[minIndex].lastUsed))
{
minFreq = memory[i].frequency;
minIndex = i;
}
}
return minIndex;
}
void lfuPageReplacement(int pageReferences[], int numPages, int numFrames)
{
117| P a g e
Page memory[numFrames]; // To store pages in memory
int pageFaults = 0; // To count the number of page faults
// Initialize memory with -1 (indicating empty slots)
for (int i = 0; i < numFrames; i++)
{
memory[i].page = -1;
memory[i].frequency = 0;
memory[i].lastUsed = -1; // No page accessed yet
}
printf("Page reference string: ");
for (int i = 0; i < numPages; i++)
{
printf("%d ", pageReferences[i]);
}
printf("\n");
// Simulate the LFU page replacement
for (int i = 0; i < numPages; i++)
{
int page = pageReferences[i];
int found = 0;
// Check if the page is already in memory
for (int j = 0; j < numFrames; j++)
{
if (memory[j].page == page)
{
found = 1;
memory[j].frequency++; // Increment the frequency

118| P a g e
memory[j].lastUsed = i; // Update the last used time
break;
}
}
// If page not found, it's a page fault
if (!found)
{
pageFaults++;
printf("Page fault! Reference: %d\n", page);
// If memory is not full, insert the page
int minIndex = -1;
for (int j = 0; j < numFrames; j++)
{
if (memory[j].page == -1)
{
minIndex = j;
break;
}
}
// If memory is full, replace the least frequently used page
if (minIndex == -1) {
minIndex = findLFUPage(memory, numFrames);
}
// Replace the page
memory[minIndex].page = page;
memory[minIndex].frequency = 1; // The new page is used once
memory[minIndex].lastUsed = i; // Update the last used time

119| P a g e
}
// Print the current state of the memory
printf("Memory: ");
for (int j = 0; j < numFrames; j++)
{
if (memory[j].page != -1)
printf("%d ", memory[j].page);
else
printf(" - ");
}
printf("\n");
}
printf("\nTotal page faults: %d\n", pageFaults);
}
int main()
{
// Example: page references and number of frames
int pageReferences[] = {7, 0, 1, 2, 3, 0, 4, 2, 3, 0, 3};
int numPages = sizeof(pageReferences) / sizeof(pageReferences[0]);
int numFrames = 3;
lfuPageReplacement(pageReferences, numPages, numFrames);
return 0;
}

120| P a g e
Experiment: 10 Date:
Simulate Paging Technique of memory management
Aim: Simulate Paging Technique of memory management

In paging memory management, the physical memory is divided into small


fixed-size blocks called frames, and the logical memory is divided into blocks
of the same size called pages. The process is allocated one or more pages, and
the operating system maintains a page table to map the pages to the physical
frames in memory.
The basic idea is:
• Page Table: It stores the mapping between the logical pages and physical
frames.
• Page Fault: Occurs when a page referenced by a process is not in
memory, causing the operating system to load the page from disk into
memory.
Simulating Paging Technique:
We can simulate paging by:
1. Dividing the logical memory into pages and the physical memory into
frames.
2. Maintaining a page table that maps virtual pages to physical frames.
3. Simulating the loading of pages into frames and handling page faults.
Steps:
1. Initialize the page table and memory.
2. For each page reference:
o If the page is already in memory, no page fault occurs.
o If the page is not in memory, a page fault occurs, and the page
must be loaded into memory.
3. Keep track of page faults and print the current state of the memory and
the page table after each page reference.

121| P a g e
Example:
Assume:
• Logical memory: A sequence of page references.
• Physical memory: A fixed number of frames (let's say 3 frames).
• Page table: Keeps track of which page is mapped to which frame.
Explanation:
1. Page Table Initialization:
o The pageTable[] array is used to simulate the mapping of pages to
frames in memory. Initially, it is filled with -1 to indicate that no
pages are loaded into memory.
2. Simulating Paging:
o For each page reference, we first check if the page is already in
memory (i.e., it exists in the pageTable[] array).
o If the page is found, it means no page fault occurs.
o If the page is not found, it is a page fault, and we need to load the
page into memory:
▪ If there is an empty slot in the page table (a slot containing -
1), the page is loaded into that slot.
▪ If there is no empty slot (i.e., memory is full), we replace the
first page in the page table using the FIFO replacement
policy (for simplicity, we remove the first page in memory
and shift the remaining pages forward).
3. Output:
o The program prints the current state of the page table after each
reference, showing which pages are currently loaded into memory.
o It also prints the total number of page faults at the end.

122| P a g e
Explanation:
1. Page Table Initialization:
o The pageTable[] array is used to simulate the mapping of pages to
frames in memory. Initially, it is filled with -1 to indicate that no
pages are loaded into memory.
2. Simulating Paging:
o For each page reference, we first check if the page is already in
memory (i.e., it exists in the pageTable[] array).
o If the page is found, it means no page fault occurs.
o If the page is not found, it is a page fault, and we need to load the
page into memory:
▪ If there is an empty slot in the page table (a slot containing -
1), the page is loaded into that slot.
▪ If there is no empty slot (i.e., memory is full), we replace the
first page in the page table using the FIFO replacement
policy (for simplicity, we remove the first page in memory
and shift the remaining pages forward).
3. Output:
o The program prints the current state of the page table after each
reference, showing which pages are currently loaded into memory.
o It also prints the total number of page faults at the end.

123| P a g e
Output:
For the page reference string {7, 0, 1, 2, 3, 0, 4, 2, 3, 0, 3} with 3 frames, the
output would be:
Page reference string: 7 0 1 2 3 0 4 2 3 0 3
Page fault! Reference: 7
Memory (Page Table): 7 - -
Page fault! Reference: 0
Memory (Page Table): 7 0 -
Page fault! Reference: 1
Memory (Page Table): 7 0 1
Page fault! Reference: 2
Memory (Page Table): 0 1 2
Page fault! Reference: 3
Memory (Page Table): 1 2 3
Page fault! Reference: 0
Memory (Page Table): 2 3 0
Page fault! Reference: 4
Memory (Page Table): 3 0 4
Page fault! Reference: 2
Memory (Page Table): 0 4 2
Page fault! Reference: 3
Memory (Page Table): 4 2 3
Page fault! Reference: 0
Memory (Page Table): 2 3 0
Page fault! Reference: 3
Memory (Page Table): 3 0 3

Total page faults: 10


124| P a g e
Source Code:
#include <stdio.h>
#define MAX_FRAMES 3 // Physical memory frames
// Function to simulate paging technique (page replacement)
void simulatePaging(int pageReferences[], int numPages, int numFrames)
{
int pageTable[numFrames]; // Page table to store mapping of pages to frames
int pageFaults = 0; // To count the number of page faults
// Initialize the page table with -1 (indicating no pages are loaded)
for (int i = 0; i < numFrames; i++)
{
pageTable[i] = -1;
}
printf("Page reference string: ");
for (int i = 0; i < numPages; i++)
{
printf("%d ", pageReferences[i]);
}
printf("\n");
// Simulate the paging process
for (int i = 0; i < numPages; i++)
{
int page = pageReferences[i];
int found = 0;
// Check if the page is already in memory (page table)
for (int j = 0; j < numFrames; j++)
{

125| P a g e
if (pageTable[j] == page)
{
found = 1;
break;
}
}
// If the page is not found in memory, it's a page fault
if (!found) {
pageFaults++;
printf("Page fault! Reference: %d\n", page);
// Find an empty slot in the page table
int emptySlot = -1;
for (int j = 0; j < numFrames; j++)
{
if (pageTable[j] == -1)
{
emptySlot = j;
break;
}
}
// If memory is full, replace the first page (FIFO replacement)
if (emptySlot == -1)
{
for (int j = 0; j < numFrames - 1; j++)
{
pageTable[j] = pageTable[j + 1];
}

126| P a g e
pageTable[numFrames - 1] = page;
}
else
{
// If there's an empty slot, insert the page there
pageTable[emptySlot] = page;
}
}
// Print the current state of the page table (memory)
printf("Memory (Page Table): ");
for (int j = 0; j < numFrames; j++)
{
if (pageTable[j] != -1)
printf("%d ", pageTable[j]);
else
printf(" - ");
}
printf("\n");
}
// Print the total number of page faults
printf("\nTotal page faults: %d\n", pageFaults);
}

127| P a g e
int main ()
{
// Example: page reference string and number of frames in memory
int pageReferences[] = {7, 0, 1, 2, 3, 0, 4, 2, 3, 0, 3};
int numPages = sizeof(pageReferences) / sizeof(pageReferences[0]);
int numFrames = 3; // Physical memory has 3 frames
simulatePaging(pageReferences, numPages, numFrames);
return 0;
}

128| P a g e
Experiment – 11 Date:
Implement Bankers Algorithm for Dead Lock avoidance and prevention
Aim: Implement Bankers Algorithm for Dead Lock avoidance and
prevention

A) Implement Bankers Algorithm for Dead Lock avoidance


The Banker's Algorithm is a classic algorithm used to ensure deadlock
avoidance in a system by dynamically analyzing resource allocation and
requests to check whether a system is in a safe state. The idea is that the system
should only grant resource requests that will keep it in a safe state, meaning that
all processes can eventually complete without causing a deadlock.
Key Terms:
• Safe State: A state where there is a sequence of processes that can
execute without causing a deadlock.
• Unsafe State: A state where no such sequence exists, meaning a deadlock
may occur.
• Request: A process asks for resources. The Banker's Algorithm checks if
granting the request will leave the system in a safe state.
• Allocation: Resources that have been currently allocated to the process.
• Maximum: The maximum number of resources that a process may
require.
• Available: The number of available resources in the system.
Key Steps:
1. Check for Safe State: Before allocating resources to a process, the
Banker's algorithm checks if the system will remain in a safe state after
granting the request.
2. Safety Algorithm: The system tries to find a sequence of processes that
can execute without running out of resources (i.e., without causing
deadlock).

129| P a g e
Banker's Algorithm Structure:
The algorithm uses four matrices:
• Available[]: A vector of the available resources in the system.
• Max[][]: A matrix where Max[i][j] represents the maximum number of
resources of type j process i may need.
• Allocation[][]: A matrix where Allocation[i][j] represents the number of
resources of type j allocated to process i.
• Need[][]: A matrix where Need[i][j] represents the remaining resources
needed by process i to finish its execution (Need[i][j] = Max[i][j] -
Allocation[i][j]).
Safety Check Algorithm:
1. Initialize a Work[] vector and a Finish[] vector.
o Work[]: Contains the available resources, initially set to
Available[].
o Finish[]: Boolean array, initially set to false for all processes,
which indicates whether a process has finished.
2. Find a process i such that Finish[i] == false and Need[i] <= Work[]. If
found:
o Add Allocation[i] to Work[] (simulate the process finishing and
releasing its resources).
o Set Finish[i] = true (mark the process as finished).
3. If all processes are finished (i.e., Finish[] is all true), the system is in a
safe state. If not, the system is in an unsafe state, and a deadlock might
occur.
Explanation of Code:
1. isSafe function:
o It checks whether the system is in a safe state by trying to find a
safe sequence of processes.
o It uses a Need matrix to track the remaining resources each process
needs to complete and checks if it can be satisfied by the available
resources.

130| P a g e
o If a safe sequence is found, it prints the sequence; otherwise, it
returns false, indicating that the system is in an unsafe state.
2. requestResources function:
o It checks if a resource request from a process is less than or equal
to both its maximum claim and the available resources.
o If the request is valid, it temporarily allocates the resources and
checks if the system is still in a safe state. If not, the allocation is
rolled back.
3. Main Function:
o It initializes the system's available resources, the maximum claim
matrix, and the allocation matrix.
o It first checks if the system is in a safe state.
o Then, it simulates a resource request for process 1 and either grants
or denies the request based on the Banker's Algorithm.
How Banker's Algorithm Works:
• The system checks whether granting a resource request will leave the
system in a safe state by simulating the allocation and checking for the
availability of resources.
• If granting the request would leave the system in an unsafe state, the
request is denied, preventing deadlock.

131| P a g e
Output:
System is in a safe state.
Safe Sequence: 1 3 4 0 2
Request granted.

132| P a g e
Source Code:
// Banker's Algorithm for Deadlock Avoidance
#include <stdio.h>
#include <stdbool.h>
#define P 5 // Number of processes
#define R 3 // Number of resource types
// Function to calculate whether the system is in a safe state
bool isSafe(int processes[], int avail[], int max[][R], int allot[][R])
{
int need[P][R];
bool finish[P] = {false};
int work[R];
int safeSeq[P];
int count = 0;
// Calculate Need matrix
for (int i = 0; i < P; i++)
{
for (int j = 0; j < R; j++)
{
need[i][j] = max[i][j] - allot[i][j];
}
}
// Initialize work with available resources
for (int i = 0; i < R; i++)
{
work[i] = avail[i];
}

133| P a g e
// Try to find a safe sequence
while (count < P)
{
bool found = false;
for (int p = 0; p < P; p++)
{
if (!finish[p])
{
int j;
// Check if all resources required by process p are available
for (j = 0; j < R; j++)
{
if (need[p][j] > work[j])
break;
}
// If all needs of process p can be satisfied, simulate it finishing
if (j == R)
{
for (int k = 0; k < R; k++)
{
work[k] += allot[p][k]; // Release allocated resources
}
safeSeq[count++] = p; // Add process to safe sequence
finish[p] = true; // Mark process p as finished
found = true;
break;
}

134| P a g e
}
}
// If no process was found, the system is in an unsafe state
if (!found)
{
return false;
}
}
// If all processes are finished, system is in a safe state
printf("System is in a safe state.\nSafe Sequence: ");
for (int i = 0; i < P; i++)
{
printf("%d ", safeSeq[i]);
}
printf("\n");
return true;
}
// Function to request resources
bool requestResources(int processes[], int avail[], int max[][R], int allot[][R],
int request[], int process)
{
// Check if request is less than or equal to need
for (int i = 0; i < R; i++)
{
if (request[i] > max[process][i] - allot[process][i])
{
printf("Error: Process has exceeded maximum claim.\n");
return false;
135| P a g e
}
}
// Check if request is less than or equal to available resources
for (int i = 0; i < R; i++)
{
if (request[i] > avail[i])
{
printf("Error: Resources not available.\n");
return false;
}
}
// Tentatively allocate requested resources
for (int i = 0; i < R; i++)
{
avail[i] -= request[i];
allot[process][i] += request[i];
}
// Check if the system is in a safe state after allocation
if (isSafe(processes, avail, max, allot))
{
return true;
}
else
{
// Rollback if not in safe state
for (int i = 0; i < R; i++)
{

136| P a g e
avail[i] += request[i];
allot[process][i] -= request[i];
}
printf("Error: System is in an unsafe state after request.\n");
return false;
}
}
int main()
{
int processes[] = {0, 1, 2, 3, 4}; // Process IDs
int avail[] = {3, 3, 2}; // Available resources
// Maximum resources needed by processes
int max[][R] = {
{7, 5, 3},
{3, 2, 2},
{9, 0, 2},
{2, 2, 2},
{4, 3, 3}
};
// Resources currently allocated to processes
int allot[][R] = {
{0, 1, 0},
{2, 0, 0},
{3, 0, 2},
{2, 1, 1},
{0, 0, 2}
};

137| P a g e
// Check if system is in a safe state
isSafe(processes, avail, max, allot);
// Request resources for process 1
int request[] = {1, 0, 2}; // Process 1 requests 1 instance of R0, 0 of R1, and
2 of R2
if (requestResources(processes, avail, max, allot, request, 1))
{
printf("Request granted.\n");
}
else
{
printf("Request denied.\n");
}
return 0;
}

138| P a g e
B) Implement Bankers Algorithm for Dead Lock prevention
The Banker's Algorithm for Deadlock Prevention works by ensuring that the
system does not enter an unsafe state when resources are requested. In contrast
to deadlock avoidance, where the system dynamically checks the state before
granting requests, prevention ensures that the system never enters an unsafe
state by preventing dangerous resource allocations from happening in the first
place.
Key Idea of Deadlock Prevention:
To prevent deadlock, we must ensure that at least one of the following
conditions is not satisfied:
1. Mutual Exclusion: At least one resource must be held in a non-shareable
mode (this is inherently true in most systems).
2. Hold and Wait: A process holding one resource is waiting for additional
resources held by other processes.
3. No Preemption: Resources cannot be preempted (this can be avoided by
allowing preemption in certain cases).
4. Circular Wait: A set of processes is in a circular chain, where each
process is waiting for a resource held by the next process in the chain.
Key Prevention Strategy:
We can implement Deadlock Prevention by ensuring that one of these
conditions is avoided:
• Hold and Wait: Ensure that a process requests all of its required
resources at once (if it cannot, it must wait until all are available).
• No Preemption: If a process is holding some resources and requesting
others, we could preempt resources to break the circular wait.
• Circular Wait: We can prevent this by enforcing a linear ordering of
resources (i.e., processes must request resources in a predefined order).
Banker's Algorithm for Deadlock Prevention (C Implementation)
In this implementation, we'll focus on the Hold and Wait prevention strategy,
where processes must request all the resources they need before they can
execute. This means that if a process is already holding some resources, it
cannot request new ones until it finishes and releases the resources.

139| P a g e
Approach:
1. Maximum Resources Requested: A process must request all of its
maximum resources at once. If a process has already requested resources,
it cannot request more until it finishes.
2. Safe State Checking: We will use the same safe state checking
mechanism as in the classic Banker's Algorithm, but we will also prevent
requests that would violate the prevention rules.
Explanation of Code:
1. isSafe function:
o This function checks whether the system is in a safe state by
attempting to find a safe sequence of processes that can complete
without causing a deadlock. It works by simulating resource
allocation and checking if all processes can eventually finish.
2. requestResources function:
o This function handles resource requests from a process. It first
checks whether the process is violating the Hold and Wait
prevention rule (i.e., it must request all resources at once). If the
process has already allocated some resources, it cannot request
more.
o It also ensures that the request does not exceed the available
resources and that granting the request does not lead to an unsafe
state.
3. Main Function:
o Initializes the available resources, maximum resource demands,
and current allocations.
o First, it checks if the system is in a safe state.
o Then, it simulates a resource request from process 1, ensuring that
the Hold and Wait condition is prevented.

140| P a g e
OUTPUT:

System is in a safe state.


Safe Sequence: 1 3 4 0 2
Error: Process cannot request new resources while holding others (Hold and
Wait condition prevented).
Request denied.

141| P a g e
Source Code:
#include <stdio.h>
#include <stdbool.h>
#define P 5 // Number of processes
#define R 3 // Number of resource types
// Function to check whether the system is in a safe state
bool isSafe(int processes[], int avail[], int max[][R], int allot[][R])
{
int need[P][R];
bool finish[P] = {false};
int work[R];
int safeSeq[P];
int count = 0;
// Calculate Need matrix
for (int i = 0; i < P; i++)
{
for (int j = 0; j < R; j++)
{
need[i][j] = max[i][j] - allot[i][j];
}
}
// Initialize work with available resources
for (int i = 0; i < R; i++)
{
work[i] = avail[i];
}

142| P a g e
// Try to find a safe sequence
while (count < P) {
bool found = false;
for (int p = 0; p < P; p++)
{
if (!finish[p]) {
int j;
// Check if all resources required by process p are available
for (j = 0; j < R; j++)
{
if (need[p][j] > work[j])
break;
}
// If all needs of process p can be satisfied, simulate it finishing
if (j == R)
{
for (int k = 0; k < R; k++)
{
work[k] += allot[p][k]; // Release allocated resources
}
safeSeq[count++] = p; // Add process to safe sequence
finish[p] = true; // Mark process p as finished
found = true;
break;
}
}
}

143| P a g e
// If no process was found, the system is in an unsafe state
if (!found)
{
return false;
}
}
// If all processes are finished, system is in a safe state
printf("System is in a safe state.\nSafe Sequence: ");
for (int i = 0; i < P; i++) {
printf("%d ", safeSeq[i]);
}
printf("\n");
return true;
}
// Function to request resources for a process
bool requestResources(int processes[], int avail[], int max[][R], int allot[][R],
int request[], int process)
{
// Check if process has requested more resources than needed
for (int i = 0; i < R; i++)
{
if (request[i] > max[process][i] - allot[process][i])
{
printf("Error: Process has exceeded maximum claim.\n");
return false;
}
}

144| P a g e
// Check if process is requesting resources while holding some resources
for (int i = 0; i < R; i++)
{
if (allot[process][i] > 0 && request[i] > 0)
{
printf("Error: Process cannot request new resources while holding others
(Hold and Wait condition prevented).\n");
return false;
}
}
// Check if request is less than or equal to available resources
for (int i = 0; i < R; i++)
{
if (request[i] > avail[i])
{
printf("Error: Resources not available.\n");
return false;
}
}
// Tentatively allocate requested resources
for (int i = 0; i < R; i++)
{
avail[i] -= request[i];
allot[process][i] += request[i];
}
// Check if the system is in a safe state after allocation
if (isSafe(processes, avail, max, allot))
{
145| P a g e
return true;
}
else
{
// Rollback if not in safe state
for (int i = 0; i < R; i++)
{
avail[i] += request[i];
allot[process][i] -= request[i];
}
printf("Error: System is in an unsafe state after request.\n");
return false;
}
}

int main()
{
int processes[] = {0, 1, 2, 3, 4}; // Process IDs
int avail[] = {3, 3, 2}; // Available resources
// Maximum resources needed by processes
int max[][R] = {
{7, 5, 3},
{3, 2, 2},
{9, 0, 2},
{2, 2, 2},
{4, 3, 3}
};

146| P a g e
// Resources currently allocated to processes
int allot[][R] = {
{0, 1, 0},
{2, 0, 0},
{3, 0, 2},
{2, 1, 1},
{0, 0, 2}
};
// Check if system is in a safe state
isSafe(processes, avail, max, allot);
// Request resources for process 1 (should fail due to hold and wait
prevention)
int request[] = {1, 0, 2}; // Process 1 requests 1 instance of R0, 0 of R1, and
2 of R2
if (requestResources(processes, avail, max, allot, request, 1))
{
printf("Request granted.\n");
}
else
{
printf("Request denied.\n");
}
return 0;
}

147| P a g e
Experiment – 12 Date:

Simulate the following file allocation strategies


a) Sequential b) Indexed c) Linked

Aim: Simulate the following file allocation strategies


a) Sequential b) Indexed c) Linked
a) Sequential b) Indexed c) Linked
a) Sequential
The Sequential File Allocation algorithm involves storing files in contiguous
blocks on the storage device. Once a file is allocated, all of its blocks must
occupy consecutive storage locations. This method is simple to implement and
provides fast access, but it suffers from fragmentation over time as files are
created, deleted, and resized.
Below is an example simulation of sequential file allocation:
Scenario:
You have a disk with 100 blocks (numbered 0 to 99). Several files need to be
allocated on the disk sequentially.
1. File Table:
o File A: Size 10 blocks
o File B: Size 20 blocks
o File C: Size 15 blocks
o File D: Size 25 blocks
2. Disk Allocation Before:
Initially, all blocks are free.
Algorithm Implementation (Steps):
1. Start with the first free block.
2. For each file:
o Allocate the required number of contiguous blocks.
o Record the starting block and the length (size) in the File
Allocation Table (FAT).
3. Move the pointer to the next available block for the next file
148| P a g e
Simulation:
Step 1: Allocate File A
• Size: 10 blocks.
• Starting Block: 0.
• Blocks allocated: 0–9.
Step 2: Allocate File B
• Size: 20 blocks.
• Starting Block: 10.
• Blocks allocated: 10–29.
Step 3: Allocate File C
• Size: 15 blocks.
• Starting Block: 30.
• Blocks allocated: 30–44.
Step 4: Allocate File D
• Size: 25 blocks.
• Starting Block: 45.
• Blocks allocated: 45–69.
Disk Allocation After:
Blocks 0–69 are occupied. Blocks 70–99 remain free.
File Allocation Table (FAT):
File Starting Block Length (Blocks)

A 0 10

B 10 20

C 30 15

D 45 25

149| P a g e
Advantages of Sequential Allocation:
1. Simple and easy to implement.
2. Fast read/write performance for sequential access (no seek overhead).
Disadvantages:
1. Fragmentation: Over time, deleting files can create gaps, making it
difficult to allocate large files.
2. Resizing Issues: If a file grows beyond its allocated space, it may need to
be relocated entirely, which is expensive.
Sequential File Allocation implemented in C. The program assumes a disk of a
fixed size and simulates the allocation of files based on user input.
Explanation:
1. Disk Representation:
o A boolean array (disk) is used to represent the disk, where 0 means
free, and 1 means occupied.
2. File Allocation:
o The program checks for a sequence of free blocks large enough to
accommodate the file.
o If found, it allocates the blocks by marking them as 1 in the disk
array.
3. File Allocation Table:
o Each file's name, starting block, and size are stored in a File struct.
4. Disk Status:
o The program visually represents the disk status after each
allocation.
5. Input:
o The user provides file names and their sizes interactively.
6. Output:
o The program outputs the File Allocation Table and the Disk
Status after each operation.

150| P a g e
OUTPUT:
Sequential File Allocation Simulation
Disk Size: 100 blocks
Enter file name: A
Enter file size (in blocks): 10
File A allocated starting at block 0.
Disk Status:
1111111111
0000000000
0000000000
...

File Allocation Table:


File Start Block Size
A 0 10
Do you want to add another file? (y/n): y
Enter file name: B
Enter file size (in blocks): 20
File B allocated starting at block 10.
Disk Status:
1111111111
1111111111
1111111111
...

File Allocation Table:


File Start Block Size
A 0 10
B 10 20
Do you want to add another file? (y/n): n
151| P a g e
Source code:
#include <stdio.h>
#include <stdbool.h>
#define DISK_SIZE 100 // Total number of blocks on the disk
// Struct to represent a file
typedef struct
{
char name[20];
int startBlock;
int size;
} File;
void displayFileAllocation(File files[], int fileCount)
{
printf("\nFile Allocation Table:\n");
printf("File\tStart Block\tSize\n");
for (int i = 0; i < fileCount; i++)
{
printf("%s\t%d\t\t%d\n", files[i].name, files[i].startBlock, files[i].size);
}
}
void displayDiskStatus(bool disk[])
{
printf("\nDisk Status:\n");
for (int i = 0; i < DISK_SIZE; i++)
{
printf("%d", disk[i]); // 1 means occupied, 0 means free
if ((i + 1) % 10 == 0)

152| P a g e
{
printf("\n");
}
}
}
int main()
{
bool disk[DISK_SIZE] = {0}; // Initialize all blocks as free (0)
File files[10]; // Array to store file allocation info
int fileCount = 0;
printf("Sequential File Allocation Simulation\n");
printf("Disk Size: %d blocks\n", DISK_SIZE);
char choice;
do {
// Input file details
printf("\nEnter file name: ");
scanf("%s", files[fileCount].name);
printf("Enter file size (in blocks): ");
scanf("%d", &files[fileCount].size);
// Find a free space for the file
int startBlock = -1;
for (int i = 0; i < DISK_SIZE; i++)
{
bool spaceAvailable = true;
for (int j = 0; j < files[fileCount].size; j++)
{
if (i + j >= DISK_SIZE || disk[i + j]) { // Check if space is available

153| P a g e
spaceAvailable = false;
break;
}
}
if (spaceAvailable)
{
startBlock = i;
break;
}
}
if (startBlock != -1)
{
// Allocate the blocks
files[fileCount].startBlock = startBlock;
for (int i = 0; i < files[fileCount].size; i++)
{
disk[startBlock + i] = 1; // Mark blocks as occupied
}
printf("File %s allocated starting at block %d.\n", files[fileCount].name,
startBlock);
fileCount++;
}
else
{
printf("Not enough contiguous space available to allocate file %s.\n",
files[fileCount].name);
}
// Display current disk status and file allocation table

154| P a g e
displayDiskStatus(disk);
displayFileAllocation(files, fileCount);
// Ask user if they want to add another file
printf("\nDo you want to add another file? (y/n): ");
scanf(" %c", &choice);
} while (choice == 'y' || choice == 'Y');
printf("\nFinal File Allocation Table:");
displayFileAllocation(files, fileCount);
printf("\nFinal Disk Status:");
displayDiskStatus(disk);
return 0;
}

155| P a g e
b) Indexed
Simulation of the Indexed File Allocation algorithm step by step. This
example explains how the strategy works in practice:
Indexed File Allocation Overview:
• Indexed Allocation uses an index block that stores the addresses of all
the blocks allocated to a file.
• A file is assigned one index block, and all the file’s data block addresses
are stored in this index block.
• File blocks can be stored non-contiguously, reducing fragmentation.
Steps for the Simulation:
Disk Information:
• Total disk size: 12 blocks (numbered 0 to 11).
• Initial state: All blocks are free.
File Table:
File Size (in blocks)

A 3

B 4

C 2

Algorithm Steps:
1. Start with an empty disk (all blocks free).
2. For each file:
o Allocate a free block as the index block.
o Allocate the required number of free blocks for the file's data and
store their addresses in the index block.
3. Record the index block and allocated blocks for each file in the File
Allocation Table (FAT).

156| P a g e
Simulation:
Step 1: Allocate File A
• File size: 3 blocks.
• Find a free block for the index block: Block 0.
• Allocate 3 data blocks: Blocks 1, 2, and 3.
• Index block 0 stores: [1, 2, 3].
Step 2: Allocate File B
• File size: 4 blocks.
• Find a free block for the index block: Block 4.
• Allocate 4 data blocks: Blocks 5, 6, 7, and 8.
• Index block 4 stores: [5, 6, 7, 8].
Step 3: Allocate File C
• File size: 2 blocks.
• Find a free block for the index block: Block 9.
• Allocate 2 data blocks: Blocks 10 and 11.
• Index block 9 stores: [10, 11].
Final Disk Allocation:
Block Number Contents

0 Index block (File A)

1 Data block (File A)

2 Data block (File A)

3 Data block (File A)

4 Index block (File B)

5 Data block (File B)

6 Data block (File B)

7 Data block (File B)

157| P a g e
Block Number Contents

8 Data block (File B)

9 Index block (File C)

10 Data block (File C)

11 Data block (File C)

File Allocation Table (FAT):


File Index Block Allocated Blocks

A 0 1, 2, 3

B 4 5, 6, 7, 8

C 9 10, 11

Visualization of Disk Allocation:


mathematica
Copy code
Disk Blocks: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
Content: [IdxA, A, A, A, IdxB, B, B, B, B, IdxC, C, C]
Algorithm (Pseudo Code):
plaintext
Copy code
1. Initialize disk blocks as free.
2. For each file:
a. Allocate one free block as the **index block**.
b. Allocate the required number of free blocks for the file's data.
c. Store the addresses of the allocated data blocks in the index block.
3. Record the file name, index block, and allocated blocks in the File Allocation
Table (FAT).
158| P a g e
4. Repeat until all files are allocated or no space is left on the disk.
5. Output the File Allocation Table and the final disk status.
Advantages of Indexed Allocation:
1. Eliminates external fragmentation since blocks do not need to be
contiguous.
2. Efficient for random access due to direct access via the index block.
Disadvantages:
1. Requires extra storage space for the index block.
2. If the index block is lost or corrupted, the entire file is lost.
simulation of the Indexed File Allocation strategy in C. This method uses an
index block to keep track of all the disk blocks allocated to a file, which allows
non-contiguous storage.
Explanation:
1. Disk Representation:
o A boolean array (disk) is used to represent the disk, where 0 means
free, and 1 means occupied.
2. File Allocation:
o Each file is allocated an index block. This block stores the
addresses of all other blocks used by the file.
o The file's data blocks can be allocated in any order (non-
contiguous).
3. Index Block Management:
o The index block itself is stored in the disk and marked as occupied.
o The actual block numbers allocated to the file are stored in an array
(blocks[]).
4. File Allocation Table (FAT):
o Contains the file's name, its index block, size, and the list of
allocated blocks.
5. Input and Output:
o The user inputs file names and sizes interactively.

159| P a g e
OUTPUT:
Indexed File Allocation Simulation
Disk Size: 100 blocks
Enter file name: A
Enter file size (in blocks): 5
File A allocated with index block 0.
Disk Status:
1111110000
0000000000
0000000000
...

File Allocation Table:


File Index Block Size Allocated Blocks
A 0 5 12345
Do you want to add another file? (y/n): y
Enter file name: B
Enter file size (in blocks): 8
File B allocated with index block 6.
Disk Status:
1111111111
1111110000
0000000000
...

File Allocation Table:


File Index Block Size Allocated Blocks
A 0 5 12345
B 6 8 7 8 9 10 11 12 13 14
Do you want to add another file? (y/n): n
160| P a g e
Source Code:
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
#define DISK_SIZE 100 // Total number of blocks on the disk
// Struct to represent a file
typedef struct
{
char name[20];
int indexBlock;
int size;
int blocks[DISK_SIZE]; // Stores the block numbers allocated to the file
} File;
// Function to display the file allocation table
void displayFileAllocation(File files[], int fileCount)
{
printf("\nFile Allocation Table:\n");
printf("File\tIndex Block\tSize\tAllocated Blocks\n");
for (int i = 0; i < fileCount; i++)
{
printf("%s\t%d\t\t%d\t", files[i].name, files[i].indexBlock, files[i].size);
for (int j = 0; j < files[i].size; j++)
{
printf("%d ", files[i].blocks[j]);
}
printf("\n");
}

161| P a g e
}
// Function to display the status of the disk
void displayDiskStatus(bool disk[])
{
printf("\nDisk Status:\n");
for (int i = 0; i < DISK_SIZE; i++)
{
printf("%d", disk[i]); // 1 means occupied, 0 means free
if ((i + 1) % 10 == 0)
{
printf("\n");
}
}
}
int main()
{
bool disk[DISK_SIZE] = {0}; // Initialize all blocks as free (0)
File files[10]; // Array to store file allocation info
int fileCount = 0;
printf("Indexed File Allocation Simulation\n");
printf("Disk Size: %d blocks\n", DISK_SIZE);
char choice;
do
{
// Input file details
printf("\nEnter file name: ");
scanf("%s", files[fileCount].name);

162| P a g e
printf("Enter file size (in blocks): ");
scanf("%d", &files[fileCount].size);
// Allocate an index block for the file
int indexBlock = -1;
for (int i = 0; i < DISK_SIZE; i++)
{
if (!disk[i]) { // Find a free block for the index
indexBlock = i;
disk[i] = 1; // Mark index block as occupied
break;
}
}
if (indexBlock == -1) {
printf("No free blocks available for the index block of file %s.\n",
files[fileCount].name);
continue;
}
files[fileCount].indexBlock = indexBlock;
// Allocate blocks for the file
int blocksAllocated = 0;
for (int i = 0; i < DISK_SIZE && blocksAllocated < files[fileCount].size; i++)
{
if (!disk[i]) { // Find a free block
files[fileCount].blocks[blocksAllocated] = i;
disk[i] = 1; // Mark block as occupied
blocksAllocated++;
}
}
163| P a g e
if (blocksAllocated < files[fileCount].size)
{
printf("Not enough free blocks available to allocate file %s.\n",
files[fileCount].name);
// Free the index block and any allocated blocks
disk[indexBlock] = 0;
for (int i = 0; i < blocksAllocated; i++)
{
disk[files[fileCount].blocks[i]] = 0;
}
continue;
}
printf("File %s allocated with index block %d.\n", files[fileCount].name,
indexBlock);
fileCount++;
// Display current disk status and file allocation table
displayDiskStatus(disk);
displayFileAllocation(files, fileCount);
// Ask user if they want to add another file
printf("\nDo you want to add another file? (y/n): ");
scanf(" %c", &choice);
} while (choice == 'y' || choice == 'Y');
printf("\nFinal File Allocation Table:");
displayFileAllocation(files, fileCount);
printf("\nFinal Disk Status:");
displayDiskStatus(disk);
return 0;
}

164| P a g e
c) Linked
Indexed File Allocation Overview:
• Indexed Allocation uses an index block that stores the addresses of all the
blocks allocated to a file.
• A file is assigned one index block, and all the file’s data block addresses
are stored in this index block.
• File blocks can be stored non-contiguously, reducing fragmentation.
Steps for the Simulation:
Disk Information:
• Total disk size: 12 blocks (numbered 0 to 11).
• Initial state: All blocks are free.
File Table:
File Size (in blocks)

A 3

B 4

C 2

Algorithm Steps:
1. Start with an empty disk (all blocks free).
2. For each file:
o Allocate a free block as the index block.
o Allocate the required number of free blocks for the file's data and
store their addresses in the index block.
3. Record the index block and allocated blocks for each file in the File
Allocation Table (FAT).

165| P a g e
Simulation:
Step 1: Allocate File A
• File size: 3 blocks.
• Find a free block for the index block: Block 0.
• Allocate 3 data blocks: Blocks 1, 2, and 3.
• Index block 0 stores: [1, 2, 3].
Step 2: Allocate File B
• File size: 4 blocks.
• Find a free block for the index block: Block 4.
• Allocate 4 data blocks: Blocks 5, 6, 7, and 8.
• Index block 4 stores: [5, 6, 7, 8].
Step 3: Allocate File C
• File size: 2 blocks.
• Find a free block for the index block: Block 9.
• Allocate 2 data blocks: Blocks 10 and 11.
• Index block 9 stores: [10, 11].
Final Disk Allocation:
Block Number Contents

0 Index block (File A)

1 Data block (File A)

2 Data block (File A)

3 Data block (File A)

4 Index block (File B)

5 Data block (File B)

6 Data block (File B)

7 Data block (File B)

166| P a g e
Block Number Contents

8 Data block (File B)

9 Index block (File C)

10 Data block (File C)

11 Data block (File C)

File Allocation Table (FAT):


File Index Block Allocated Blocks

A 0 1, 2, 3

B 4 5, 6, 7, 8

C 9 10, 11

Visualization of Disk Allocation:


Disk Blocks: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
Content: [IdxA, A, A, A, IdxB, B, B, B, B, IdxC, C, C]
Algorithm (Pseudo Code):
1. Initialize disk blocks as free.
2. For each file:
a. Allocate one free block as the **index block**.
b. Allocate the required number of free blocks for the file's data.
c. Store the addresses of the allocated data blocks in the index block.
3. Record the file name, index block, and allocated blocks in the File Allocation
Table (FAT).
4. Repeat until all files are allocated or no space is left on the disk.
5. Output the File Allocation Table and the final disk status.

167| P a g e
Advantages of Indexed Allocation:
1. Eliminates external fragmentation since blocks do not need to be
contiguous.
2. Efficient for random access due to direct access via the index block.
Disadvantages:
1. Requires extra storage space for the index block.
2. If the index block is lost or corrupted, the entire file is lost.
Concept:
• Files are stored in non-contiguous blocks, but each block contains a
pointer to the next block in the chain.
• A file is represented as a linked list of disk blocks, where the last block
contains a NULL pointer to signify the end of the file.
Algorithm for Linked File Allocation
1. Initialize the Disk:
o All blocks on the disk are marked as free.
2. For each file to be allocated:
o Find the required number of free blocks.
o Link the blocks in a chain by storing the address of the next block
in the current block.
o The last block in the chain contains a NULL or -1 to signify the
end of the file.
3. Maintain a File Table:
o Store the file name and the starting block of the chain for each file.
4. Access a File:
o Use the starting block to traverse the chain and access all blocks of
the file.

168| P a g e
Simulation Example
Disk Information:
• Total disk size: 12 blocks (numbered 0 to 11).
• Initially, all blocks are free.
File Table:
File Size (in blocks)

A 4

B 5

C 3
Steps:
1. File A (Size: 4 blocks):
o Allocate blocks: 0 → 4 → 6 → 8.
o Block 0 points to Block 4, Block 4 points to Block 6, Block 6
points to Block 8, and Block 8 points to NULL.
2. File B (Size: 5 blocks):
o Allocate blocks: 1 → 5 → 7 → 9 → 10.
o Block 1 points to Block 5, Block 5 points to Block 7, and so on.
3. File C (Size: 3 blocks):
o Allocate blocks: 2 → 3 → 11.
o Block 2 points to Block 3, and Block 3 points to Block 11.
Final Disk Allocation Table:
File Start Block Block Chain

A 0 0 → 4 → 6 → 8 → NULL

B 1 1 → 5 → 7 → 9 → 10 → NULL

C 2 2 → 3 → 11 → NULL

169| P a g e
Explanation:
1. Disk Representation:
o disk[]: Boolean array to track whether a block is free or occupied.
o next[]: Array to store pointers to the next block in the chain.
2. File Allocation:
o Free blocks are found iteratively.
o The next pointer links each block to the next one in the chain.
o The last block in the chain points to -1 (end of chain).
3. File Allocation Table:
o Stores the file name, starting block, and block chain for each file.
4. User Interaction:
o User inputs file names and sizes.
o The program allocates the blocks and displays the file table and
disk status.

170| P a g e
OUTPUT:
Linked File Allocation Simulation
Disk Size: 12 blocks
Enter file name: A
Enter file size (in blocks): 4
File A allocated starting at block 0.
Disk Status (1 = occupied, 0 = free):
111000
100000
000000
File Allocation Table:
File Start Block Block Chain
A 0 0 → 4 → 6 → 8 → NULL
Do you want to add another file? (y/n): y
Enter file name: B
Enter file size (in blocks): 5
File B allocated starting at block 1.
Disk Status (1 = occupied, 0 = free):
111000
111110
000000
File Allocation Table:
File Start Block Block Chain
A 0 0 → 4 → 6 → 8 → NULL
B 1 1 → 5 → 7 → 9 → 10 → NULL

171| P a g e
Source Code:
#include <stdio.h>
#include <stdbool.h>
#define DISK_SIZE 12 // Total number of disk blocks
typedef struct
{
char name[20];
int size;
int startBlock;
} File;
void displayFileTable(File files[], int fileCount, int next[])
{
printf("\nFile Allocation Table:\n");
printf("File\tStart Block\tBlock Chain\n");
for (int i = 0; i < fileCount; i++)
{
printf("%s\t%d\t\t", files[i].name, files[i].startBlock);
int block = files[i].startBlock;
while (block != -1) {
printf("%d → ", block);
block = next[block];
}
printf("NULL\n");
}
}

172| P a g e
void displayDiskStatus(bool disk[])
{
printf("\nDisk Status (1 = occupied, 0 = free):\n");
for (int i = 0; i < DISK_SIZE; i++)
{
printf("%d", disk[i]);
if ((i + 1) % 6 == 0) printf("\n");
}
}
int main()
{
bool disk[DISK_SIZE] = {0}; // Disk blocks initialized to free (0)
int next[DISK_SIZE]; // Array to store pointers to the next block
for (int i = 0; i < DISK_SIZE; i++) {
next[i] = -1; // Initialize all pointers to -1 (end of chain)
}
File files[10]; // Array to store file information
int fileCount = 0;
printf("Linked File Allocation Simulation\n");
printf("Disk Size: %d blocks\n", DISK_SIZE);
char choice;
do {
// Input file details
printf("\nEnter file name: ");
scanf("%s", files[fileCount].name);
printf("Enter file size (in blocks): ");
scanf("%d", &files[fileCount].size);

173| P a g e
// Allocate blocks for the file
int allocatedBlocks = 0;
int prevBlock = -1;
int startBlock = -1;
for (int i = 0; i < DISK_SIZE && allocatedBlocks < files[fileCount].size;
i++)
{
if (!disk[i]) { // Find a free block
disk[i] = 1; // Mark block as occupied
if (startBlock == -1)
{
startBlock = i; // Set starting block of the file
}
else
{
next[prevBlock] = i; // Link previous block to the current block
}
prevBlock = i;
allocatedBlocks++;
}
}
if (allocatedBlocks < files[fileCount].size)
{
printf("Not enough free blocks available to allocate file %s.\n",
files[fileCount].name);
// Free allocated blocks
int block = startBlock;
while (block != -1)

174| P a g e
{
disk[block] = 0;
int temp = next[block];
next[block] = -1;
block = temp;
}
continue;
}
next[prevBlock] = -1; // Mark end of chain
files[fileCount].startBlock = startBlock;
fileCount++;
printf("File %s allocated starting at block %d.\n", files[fileCount - 1].name,
startBlock);
// Display current disk status and file allocation table
displayDiskStatus(disk);
displayFileTable(files, fileCount, next);
// Ask user if they want to add another file
printf("\nDo you want to add another file? (y/n): ");
scanf(" %c", &choice);
} while (choice == 'y' || choice == 'Y');
printf("\nFinal File Allocation Table:");
displayFileTable(files, fileCount, next);
printf("\nFinal Disk Status:");
displayDiskStatus(disk);
return 0;
}

175| P a g e
Experiment – 13 Date:
Download and install nachos operating system and experiment with it
Aim: Download and install nachos operating system and experiment with it
Nachos is typically an academic project used for understanding OS concepts. To
download, install, and experiment with the Nachos operating system, follow
the detailed step-by-step instructions provided below.
Step 1: Prerequisites
Make sure you have the following installed on your system:
• Linux or macOS (preferable)
• GNU Make
• C++ Compiler (g++)
• Git
For Windows users, it's recommended to use WSL (Windows Subsystem for
Linux) or Cygwin to run Nachos.
Install Necessary Packages on Linux/macOS
Run these commands in the terminal to install the necessary tools:
sudo apt update
sudo apt install build-essential git gcc g++ make
Step 2: Download Nachos Source Code
Clone the Nachos repository using Git.
git clone https://github.com/tyt2y3/nachos.git
Alternatively, you can download a Nachos tarball from a university repository if
provided.
Step 3: Setup Environment Variables
Modify your PATH to include the tools used by Nachos.
1. Open your shell configuration file (e.g., .bashrc, .zshrc, or .cshrc):
nano ~/.bashrc

176| P a g e
2. Add the following lines at the end of the file:
export PATH=$PATH:/path/to/nachos/bin:/path/to/gnu/bin
3. Reload your shell configuration:
source ~/.bashrc
Step 4: Install Nachos
Navigate to the directory where you downloaded the Nachos source code and
run the installation script.
cd nachos
./configure
make
Step 5: Build Nachos
Build the three components of Nachos:
1. coff2noff
2. Test programs
3. Nachos OS
Build coff2noff
Navigate to the coff2noff directory and run:
cd coff2noff
make
Build Test Programs
Navigate to the test directory and run:
cd ../code/test
make
Build Nachos
Navigate to the build directory and run:
cd ../build.linux
make

177| P a g e
Note: The build directory depends on your system. Use build.linux for
Linux and build.solaris for Solaris.
Step 6: Run Nachos
Once built, you can run Nachos from the build directory.
./nachos
To see the available options:
./nachos -u
Run a Simple Test Program (halt)
Run the halt test program to ensure that Nachos is working correctly.
./nachos -d ca -x ../test/halt
Step 7: Experiment with Nachos
Here are a few things you can try with Nachos:
Run Self-Tests
./nachos -K
Run Test Programs
You can run various test programs from the test directory:
./nachos -d ca -x ../test/matmult
./nachos -d ca -x ../test/sort
Modify the Source Code
Explore the Nachos codebase and experiment with modifying the following
files:
• code/threads/main.cc
• code/userprog/exception.cc
• code/filesys/filesys.cc
After making changes, rebuild Nachos:
make clean
make

178| P a g e
Step 8: Explore the Nachos Directory Structure
Familiarize yourself with the important directories:
Directory Description

coff2noff/ Code for converting COFF files to NOFF format

code/lib/ Utility libraries used by Nachos

code/machine/ Code for the simulated machine

code/threads/ Multithreading support

code/userprog/ User program handling and system calls

code/filesys/ File system implementation

Networking support for communication between Nachos


code/network/
instances

Step 9: Debugging Nachos


Use debugging options to understand the behavior of the operating system.
bash
Copy code
./nachos -d +
Additional Tips
• Explore C++ code: Read main.cc to understand how the simulation
starts.
• Test system calls: Modify exception.cc to add new system calls.
• Implement your own features: Try implementing features like process
management or memory manageme

179| P a g e

You might also like