5.
Control the number of ports opened by the operating system with
a)
Semaphore b) Monitors.
A. Using
Semaphore
- Suppose
only 2 ports are available.
- If more than 2
students request, others must wait.
Program:
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#include <unistd.h>
sem_t port; // Semaphore variable
void* openPort(void* arg)
{
int id = *(int*)arg;
sem_wait(&port); // Request
a port
printf("Student %d is using a port\n", id);
sleep(2); // Using port
printf("Student %d released the port\n", id);
sem_post(&port); // Release
port
return NULL;
}
int main()
{
pthread_t t1, t2, t3;
int id1 = 1, id2 = 2, id3 = 3;
sem_init(&port, 0, 2); //
Only 2 ports available
pthread_create(&t1, NULL, openPort, &id1);
pthread_create(&t2, NULL, openPort, &id2);
pthread_create(&t3, NULL, openPort, &id3);
pthread_join(t1, NULL);
pthread_join(t2, NULL);
pthread_join(t3, NULL);
sem_destroy(&port);
return 0;
}
OUTPUT:
Student 1 is using a port
Student 2 is using a port
Student 1 released the port
Student 2 released the port
Student 3 is using a port
Student 3 released the port
B. Monitor
Concept in C
Suppose
only 2 ports are available.
Program:
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#define MAX_PORTS 2
int available_ports = MAX_PORTS;
pthread_mutex_t mutex;
pthread_cond_t condition;
void* use_port(void* arg)
{
int id = *(int*)arg;
pthread_mutex_lock(&mutex);
// Enter monitor
while (available_ports == 0)
{
printf("Student %d waiting for
port...\n", id);
pthread_cond_wait(&condition,
&mutex);
}
available_ports--;
printf("Student %d opened a port\n", id);
pthread_mutex_unlock(&mutex);
// Exit monitor
sleep(2); // Using port
pthread_mutex_lock(&mutex);
available_ports++;
printf("Student %d closed the port\n", id);
pthread_cond_signal(&condition);
// Wake one waiting thread
pthread_mutex_unlock(&mutex);
return NULL;
}
int main()
{
pthread_t t1, t2, t3;
int id1 = 1, id2 = 2, id3 = 3;
pthread_mutex_init(&mutex, NULL);
pthread_cond_init(&condition, NULL);
pthread_create(&t1, NULL, use_port, &id1);
pthread_create(&t2, NULL, use_port, &id2);
pthread_create(&t3, NULL, use_port, &id3);
pthread_join(t1, NULL);
pthread_join(t2, NULL);
pthread_join(t3, NULL);
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&condition);
return 0;
}
OUTPUT:
Student 1 opened a port
Student 3 opened a port
Student 2 waiting for port...
Student 1 closed the port
Student 3 closed the port
Student 2 opened a port
Student 2 closed the port
6. Write a program to illustrate concurrent
execution of threads using pthreads library.
Threads
Using Pthreads Library
What
is a Thread?
A thread
is the smallest unit of execution inside a process.
A process can contain multiple threads that run concurrently.
Threads
share:
- Same memory
- Same global
variables
- Same resources of
the process
But each
thread has:
- Its own stack
- Its own program
counter
- Its own registers
What
is Pthreads?
Pthreads
(POSIX Threads)
is a standard C library used for creating and managing threads in Unix/Linux
systems.
It
allows:
- Creating threads
- Synchronizing
threads
- Controlling
execution of threads
Header
file used:
#include
<pthread.h>
Important
Pthread Functions
|
Function |
Purpose |
|
pthread_create() |
Creates
a new thread |
|
pthread_exit() |
Terminates
a thread |
|
pthread_join() |
Waits
for a thread to finish |
|
pthread_mutex_lock() |
Locks a
mutex |
|
pthread_mutex_unlock() |
Unlocks
a mutex |
Syntax
of Creating a Thread
pthread_create(&thread_id,
NULL, function_name, argument);
- thread_id → Thread
variable
- function_name →
Function executed by thread
- argument → Data
passed to thread
Life
Cycle of a Thread
- Thread Creation
- Thread Execution
- Thread Termination
Advantages of Threads
·
Faster
execution
·
Better
CPU utilization
·
Resource
sharing
·
Efficient
communication
Example Structure:
#include <stdio.h>
#include <pthread.h>
void* myThread(void* arg)
{
printf("Thread is running\n");
return NULL;
}
int main()
{
pthread_t t;
pthread_create(&t, NULL, myThread, NULL);
pthread_join(t, NULL);
return 0;
}
OUTPUT:
Thread is running
Program:
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
// Function executed by Thread 1
void* task1(void* arg)
{
for(int i = 1; i <= 5; i++)
{
printf("Thread 1: %d\n", i);
sleep(1); // Delay to show concurrency
}
return NULL;
}
// Function executed by Thread 2
void* task2(void* arg)
{
for(int i = 1; i <= 5; i++)
{
printf("Thread 2: %d\n", i);
sleep(1);
}
return NULL;
}
int main()
{
pthread_t t1, t2;
// Create threads
pthread_create(&t1, NULL, task1, NULL);
pthread_create(&t2, NULL, task2, NULL);
// Wait for threads to finish
pthread_join(t1, NULL);
pthread_join(t2, NULL);
printf("Both threads finished execution.\n");
return 0;
}
OUTPUT:
Thread 1: 1
Thread 2: 1
Thread 1: 2
Thread 2: 2
Thread 1: 3
Thread 2: 3
Thread 1: 4
Thread 2: 4
Thread 1: 5
Thread 2: 5
Both threads finished execution.
7. Write
a program to solve producer-consumer problem using Semaphores.
Program:
👉 Buffer size = 3
👉
Produce & consume only 5 items
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#include <unistd.h>
#define SIZE 3
int buffer[SIZE];
int in = 0, out = 0;
sem_t empty, full;
pthread_mutex_t mutex;
// Producer
void* producer(void* arg)
{
for(int i = 1; i <= 5; i++)
{
sem_wait(&empty); // Check empty space
pthread_mutex_lock(&mutex); // Lock buffer
buffer[in] = i;
printf("Produced: %d\n", i);
in = (in + 1) % SIZE;
pthread_mutex_unlock(&mutex); // Unlock buffer
sem_post(&full); // Increase full count
sleep(1);
}
return NULL;
}
// Consumer
void* consumer(void* arg)
{
for(int i = 1; i <= 5; i++)
{
sem_wait(&full); // Check if item exists
pthread_mutex_lock(&mutex); // Lock buffer
int item = buffer[out];
printf("Consumed: %d\n",
item);
out = (out + 1) % SIZE;
pthread_mutex_unlock(&mutex); // Unlock buffer
sem_post(&empty); // Increase empty count
sleep(2);
}
return NULL;
}
int main()
{
pthread_t p, c;
sem_init(&empty, 0, SIZE); //
All slots empty
sem_init(&full, 0, 0); //
No items initially
pthread_mutex_init(&mutex, NULL);
pthread_create(&p, NULL, producer, NULL);
pthread_create(&c, NULL, consumer, NULL);
pthread_join(p, NULL);
pthread_join(c, NULL);
return 0;
}
OUTPUT:
Produced: 1
Consumed: 1
Produced: 2
Consumed: 2
Produced: 3
Produced: 4
Consumed: 3
Produced: 5
Consumed: 4
Consumed: 5
8.
Implement the following memory allocation methods for fixed partition First fit
b) Worst fit c) Best fit
Memory
Allocation Methods (Fixed Partition)
In fixed
partition memory management:
- Memory is divided
into fixed size blocks.
- Processes request
memory.
- OS allocates
blocks using different strategies.
Given in
programs:
Memory
Blocks:
Block1 =
100
Block2 =
500
Block3 =
200
Block4 =
300
Block5 =
600
Processes:
P1 = 212
P2 = 417
P3 = 112
P4 = 426
First
Fit – Explanation
Rule:
Allocate
the first block that is large enough.
Step-by-Step
Execution:
➤
P1 = 212
- Block1 (100) ❌
- Block2 (500) ✅ Allocate
- Remaining Block2 =
500 − 212 = 288
➤
P2 = 417
- Block1 (100) ❌
- Block2 (288) ❌
- Block3 (200) ❌
- Block4 (300) ❌
- Block5 (600) ✅ Allocate
- Remaining Block5 =
600 − 417 = 183
➤
P3 = 112
- Block1 (100) ❌
- Block2 (288) ✅ Allocate
- Remaining Block2 =
288 − 112 = 176
➤
P4 = 426
- No block large
enough ❌ Not Allocated
First
Fit Result:
|
Process |
Block |
|
P1 |
Block2 |
|
P2 |
Block5 |
|
P3 |
Block2 |
|
P4 |
Not
Allocated |
Best
Fit – Explanation
Rule:
Allocate
the smallest block that fits.
➤
P1 = 212
Suitable
blocks:
- 500
- 300
- 600
Smallest
= 300 → Allocate
Remaining Block4 = 300 − 212 = 88
➤
P2 = 417
Suitable
blocks:
- 500
- 600
Smallest
= 500 → Allocate
Remaining Block2 = 83
➤
P3 = 112
Suitable
blocks:
- 200
- 600
Smallest
= 200 → Allocate
Remaining Block3 = 88
➤
P4 = 426
Suitable
block:
- 600 → Allocate
Remaining Block5 = 174
Best Fit Result:
|
Process |
Block |
|
P1 |
Block4 |
|
P2 |
Block2 |
|
P3 |
Block3 |
|
P4 |
Block5 |
Worst
Fit – Explanation
Rule:
Allocate
the largest available block.
➤
P1 = 212
Largest
block = 600 → Allocate
Remaining = 388
➤
P2 = 417
Largest
block = 500 → Allocate
Remaining = 83
➤
P3 = 112
Largest
block = 388 → Allocate
Remaining = 276
➤
P4 = 426
No block
large enough ❌ Not Allocated
Worst Fit Result:
|
Process |
Block |
|
P1 |
Block5 |
|
P2 |
Block2 |
|
P3 |
Block5 |
|
P4 |
Not
Allocated |
Comparison
|
Method |
Advantage |
Disadvantage |
|
First
Fit |
Fast |
More
fragmentation |
|
Best
Fit |
Less
wasted space |
Slower |
|
Worst
Fit |
Avoids
small fragments |
May
waste large blocks |
Program
for First Fit:
#include <stdio.h>
int main()
{
int blocks[5] = {100, 500, 200, 300, 600};
int process[4] = {212, 417, 112, 426};
int i, j;
printf("First Fit Allocation:\n");
for(i = 0; i < 4; i++)
{
for(j = 0; j < 5; j++)
{
if(blocks[j] >= process[i])
{
printf("Process %d
allocated to Block %d\n", i+1, j+1);
blocks[j] -= process[i];
break;
}
}
if(j == 5)
printf("Process %d not
allocated\n", i+1);
}
return 0;
}
OUTPUT:
First Fit Allocation:
Process 1 allocated to Block 2
Process 2 allocated to Block 5
Process 3 allocated to Block 2
Process 4 not allocated
Program for Best Fit:
#include <stdio.h>
int main()
{
int blocks[5] = {100, 500, 200, 300, 600};
int process[4] = {212, 417, 112, 426};
int i, j, best;
printf("Best Fit Allocation:\n");
for(i = 0; i < 4; i++)
{
best = -1;
for(j = 0; j < 5; j++)
{
if(blocks[j] >= process[i])
{
if(best == -1 || blocks[j] <
blocks[best])
best = j;
}
}
if(best != -1)
{
printf("Process %d allocated
to Block %d\n", i+1, best+1);
blocks[best] -= process[i];
}
else
printf("Process %d not
allocated\n", i+1);
}
return 0;
}
OUTPUT:
Best Fit Allocation:
Process 1 allocated to Block 4
Process 2 allocated to Block 2
Process 3 allocated to Block 3
Process 4 allocated to Block 5
Program for Worst Fit:
#include <stdio.h>
int main()
{
int blocks[5] = {100, 500, 200, 300, 600};
int process[4] = {212, 417, 112, 426};
int i, j, worst;
printf("Worst Fit Allocation:\n");
for(i = 0; i < 4; i++)
{
worst = -1;
for(j = 0; j < 5; j++)
{
if(blocks[j] >= process[i])
{
if(worst == -1 || blocks[j]
> blocks[worst])
worst = j;
}
}
if(worst != -1)
{
printf("Process %d allocated
to Block %d\n", i+1, worst+1);
blocks[worst] -= process[i];
}
else
printf("Process %d not
allocated\n", i+1);
}
return 0;
}
OUTPUT:
Worst Fit Allocation:
Process 1 allocated to Block 5
Process 2 allocated to Block 2
Process 3 allocated to Block 5
Process 4 not allocated
9.
Simulate the following page replacement algorithms FIFO b) LRU c) LFU
Explanation:
Reference
string:
1 2 3 4 1 2 5 1 2 3 4 5
A. FIFO
(First In First Out)
Idea:
- Oldest page is
removed first.
- Like a queue.
#include <stdio.h>
int main()
{
int pages[] = {1,2,3,4,1,2,5,1,2,3,4,5};
int frames[3];
int i, j, k = 0, flag, faults = 0;
for(i = 0; i < 3; i++)
frames[i] = -1;
for(i = 0; i < 12; i++)
{
flag = 0;
for(j = 0; j < 3; j++)
{
if(frames[j] == pages[i])
{
flag = 1;
break;
}
}
if(flag == 0)
{
frames[k] = pages[i];
k = (k + 1) % 3;
faults++;
}
}
printf("Total Page Faults (FIFO) = %d", faults);
return 0;
}
OUTPUT:
Total Page Faults (FIFO) = 9
Rule:
Remove
the page that entered first (oldest).
|
Page |
Frames |
Fault? |
|
1 |
1
- - |
Yes |
|
2 |
1 2 - |
Yes |
|
3 |
1
2 3 |
Yes |
|
4 |
4 2 3 |
Yes (1
removed) |
|
1 |
4
1 3 |
Yes
(2 removed) |
|
2 |
4 1 2 |
Yes (3
removed) |
|
5 |
5
1 2 |
Yes
(4 removed) |
|
1 |
5 1 2 |
No |
|
2 |
5
1 2 |
No |
|
3 |
5 3 2 |
Yes (1
removed) |
|
4 |
5
3 4 |
Yes
(2 removed) |
|
5 |
5 3 4 |
No |
Total
Page Faults = 9
👉 FIFO keeps replacing oldest page, even if it
is frequently used
B. LRU
(Least Recently Used)
Idea:
- Remove the page
that was not used for the longest time.
#include <stdio.h>
int main()
{
int pages[] = {1,2,3,4,1,2,5,1,2,3,4,5};
int frames[3], time[3];
int i, j, faults = 0, counter = 0, pos, min;
for(i = 0; i < 3; i++)
frames[i] = -1;
for(i = 0; i < 12; i++)
{
int flag = 0;
for(j = 0; j < 3; j++)
{
if(frames[j] == pages[i])
{
counter++;
time[j] = counter;
flag = 1;
break;
}
}
if(flag == 0)
{
min = 0;
for(j = 1; j < 3; j++)
if(time[j] < time[min])
min = j;
frames[min] = pages[i];
counter++;
time[min] = counter;
faults++;
}
}
printf("Total Page Faults (LRU) = %d", faults);
return 0;
}
OUTPUT:
Total Page Faults (LRU) = 8
Rule:
Remove
the page that was least recently used.
|
Page |
Frames |
Fault? |
|
1 |
1
- - |
Yes |
|
2 |
1 2 - |
Yes |
|
3 |
1
2 3 |
Yes |
|
4 |
4 2 3 |
Yes (1
removed) |
|
1 |
4
1 3 |
Yes
(2 removed) |
|
2 |
4 1 2 |
Yes (3
removed) |
|
5 |
5
1 2 |
Yes
(4 removed) |
|
1 |
5 1 2 |
No |
|
2 |
5
1 2 |
No |
|
3 |
3 1 2 |
Yes (5
removed) |
|
4 |
3
4 2 |
Yes
(1 removed) |
|
5 |
3 4 5 |
Yes (2
removed) |
Total
Page Faults = 8
👉 LRU performs better because it removes pages
that are not used recently.
C. LFU
(Least Frequently Used)
Idea:
- Remove the page
with lowest frequency count.
#include <stdio.h>
int main()
{
int pages[] = {1,2,3,4,1,2,5,1,2,3,4,5};
int frames[3], freq[3];
int i, j, faults = 0, pos;
for(i = 0; i < 3; i++)
{
frames[i] = -1;
freq[i] = 0;
}
for(i = 0; i < 12; i++)
{
int flag = 0;
for(j = 0; j < 3; j++)
{
if(frames[j] == pages[i])
{
freq[j]++;
flag = 1;
break;
}
}
if(flag == 0)
{
pos = 0;
for(j = 1; j < 3; j++)
if(freq[j] < freq[pos])
pos = j;
frames[pos] = pages[i];
freq[pos] = 1;
faults++;
}
}
printf("Total Page Faults (LFU) = %d", faults);
return 0;
}
OUTPUT:
Total Page Faults (LFU) = 9
Rule:
Remove the page that was least recently used.
|
Page |
Frames |
Fault? |
|
1 |
1
- - |
Yes |
|
2 |
1 2 - |
Yes |
|
3 |
1
2 3 |
Yes |
|
4 |
4 2 3 |
Yes (1
removed) |
|
1 |
4
1 3 |
Yes
(2 removed) |
|
2 |
4 1 2 |
Yes (3
removed) |
|
5 |
5
1 2 |
Yes
(4 removed) |
|
1 |
5 1 2 |
No |
|
2 |
5
1 2 |
No |
|
3 |
3 1 2 |
Yes (5
removed) |
|
4 |
3
4 2 |
Yes
(1 removed) |
|
5 |
3 4 5 |
Yes (2
removed) |
Total
Page Faults = 8
👉 LRU performs better because it removes pages
that are not used recently.
10. Simulate Paging Technique of
memory management.
Concept
of Paging
What
is Paging?
Paging is
a memory management technique in which:
- Physical memory is
divided into fixed size blocks called Frames.
- Logical memory
(process) is divided into same size blocks called Pages.
- A Page Table
is used to map pages to frames.
Why
Paging?
✔
Avoids external fragmentation
✔ Allows non-contiguous memory allocation
✔ Efficient memory utilization
Address
Translation in Paging
Logical
Address =
Page
Number (p) + Offset (d)
Physical
Address =
Frame
Number + Offset
Formula:
Physical
Address = Frame_Number × Frame_Size + Offset
Example
Suppose:
- Page size = 100
bytes
- Page table:
|
Page |
Frame |
|
0 |
2 |
|
1 |
0 |
|
2 |
1 |
|
3 |
3 |
Logical
Address = 250
Step 1:
Page number = 250 / 100 = 2
Offset = 250 % 100 = 50
Step 2:
From page table → Page 2 → Frame 1
Step 3:
Physical Address = 1 × 100 + 50 = 150
Program
for Paging
👉 This program converts logical address to
physical address.
#include <stdio.h>
int main()
{
int page_size, n, i;
int page_table[10];
int logical_address, page_number, offset, physical_address;
printf("Enter number of pages: ");
scanf("%d", &n);
printf("Enter page size: ");
scanf("%d", &page_size);
printf("Enter frame number for each page:\n");
for(i = 0; i < n; i++)
{
printf("Page %d -> Frame:
", i);
scanf("%d", &page_table[i]);
}
printf("Enter logical address: ");
scanf("%d", &logical_address);
page_number = logical_address / page_size;
offset = logical_address % page_size;
if(page_number >= n)
{
printf("Invalid Page
Number!\n");
}
else
{
physical_address =
page_table[page_number] * page_size + offset;
printf("Page Number = %d\n",
page_number);
printf("Offset = %d\n",
offset);
printf("Physical Address =
%d\n", physical_address);
}
return 0;
}
OUTPUT:
Enter number of pages: 4
Enter page size: 100
Enter frame number for each page:
Page 0 -> Frame: 2
Page 1 -> Frame: 0
Page 2 -> Frame: 1
Page 3 -> Frame: 3
Enter logical address: 250
Page Number = 2
Offset = 50
Physical Address = 150
How
Program Works
- User enters:
- Number of pages
- Page size
- Page table
mapping
- Enter logical
address.
- Program
calculates:
- Page number
- Offset
- Finds frame from
page table.
- Calculates
physical address.
11.
Implement Bankers Algorithm for Dead Lock avoidance and prevention
12.
Simulate the following file allocation strategies Sequential b) Indexed c)
Linked