0% found this document useful (0 votes)
19 views19 pages

Smu CS7343 Midterm

Uploaded by

unnathi.vithlani
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
19 views19 pages

Smu CS7343 Midterm

Uploaded by

unnathi.vithlani
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 19

1. a.

The Gantt chart:

b. P1: 20-0 - 20, P2: 80-25 = 55, P3: 90 - 30 = 60, P4: 75-60 = 15, P5:
120-100 = 20, P6: 115-105 = 10
c. P1: 0, p2: 40, P3: 35, P4: 0, P5: 10, P6: 0
d. 105/120 = 87.5 percent.

2. Analysis of Each Algorithm


In this project, you will have a CPU simulator for implementing and evaluating four different
CPU scheduling algorithms.
First Come First Serve (FCFS)
First-Come, First-Served is the simplest CPU scheduling algorithm possible. The process that
reached the head of the queue first is supposed to run until it voluntarily relinquishes the
CPU, either because of I/O demands or for processing completion. In other words, FCFS is a
non-pre-emptive CPU scheduling algorithm.
Shortest Job First (SJF)(Non-pre-emptive)
Shortest Job First is a priority-based scheduling algorithm that associates with each process
the length of the process's next CPU burst. Whenever the CPU becomes available, it is
allocated to the process currently having the shortest next CPU burst.
Shortest Remaining Time Next (SRTN)(Pre-emptive)
Shortest Remaining Time Next (SRTN) is a preemptive version of the Shortest Job First
scheduling algorithm. With this algorithm, the process with the shortest estimated remaining
execution time is selected to run. If a new process gets into the ready queue with a time
requirement that is less than the remaining time for the currently running process, it will
preemptively interrupt that process and execute the new one instead.
Roundrobin (RR)
Round-Robin Scheduling is somewhat similar to First-Come, First-Served scheduling, except
that preemption occurs, ensuring that a process does not monopolize the CPU. To give a fair
chance to every process, the CPU scheduler processes the processes stored in the ready queue
in a round-robin fashion. In other words, it gives each process the CPU for a maximum of 1
time quantum.
Different types to be separate within the class according to their scheduling preference
are distinct and depend on exactly what is required from the system:
1. Interactive systems:
O They need Round Robin because of its excellent response time and fairness.
O Time quantum should be adjusted according to the common characteristics of the
processes.
2. Batch processing:
O SJF could be optimal if burst times are known.
O FCFS could be a good alternative if simplicity is preferred.
3. Real-time systems:
O Priority scheduling based on appropriate allocations of priority.
O Aging mechanisms should be taken into account to prevent starvation.
4. General-purpose systems:
O Multi-level feedback queue (a combination of RR and priority).
O Adaptive quantum on the basis of load on the system.
Detailed Analysis of Results

Full Code in Python:


from dataclasses import dataclass
from typing import List, Dict
from collections import deque
import heapq
@dataclass
class Process:
pid: int
arrival_time: int
burst_time: int
priority: int = 0

# For tracking metrics


waiting_time: int = 0
turnaround_time: int = 0
response_time: int = -1
remaining_time: int = 0

def __lt__(self, other):


return self.burst_time < other.burst_time

class CPUScheduler:
def __init__(self):
self.processes: List[Process] = []

def add_process(self, pid: int, arrival_time: int, burst_time: int, priority: int = 0):
self.processes.append(Process(pid, arrival_time, burst_time, priority))

def reset_processes(self):
for process in self.processes:
process.waiting_time = 0
process.turnaround_time = 0
process.response_time = -1
process.remaining_time = process.burst_time

def fcfs(self) -> Dict[str, float]:


self.reset_processes()
processes = sorted(self.processes, key=lambda x: (x.arrival_time, x.pid))
current_time = 0
for process in processes:
if current_time < process.arrival_time:
current_time = process.arrival_time

process.response_time = current_time - process.arrival_time


process.waiting_time = current_time - process.arrival_time
current_time += process.burst_time
process.turnaround_time = current_time - process.arrival_time

return self._calculate_metrics()

def sjf(self) -> Dict[str, float]:


self.reset_processes()
ready_queue = []
current_time = 0
remaining_processes = sorted(self.processes, key=lambda x: x.arrival_time)
completed_processes = []

while remaining_processes or ready_queue:


while remaining_processes and remaining_processes[0].arrival_time <= current_time:
heapq.heappush(ready_queue, remaining_processes.pop(0))

if not ready_queue:
current_time = remaining_processes[0].arrival_time
continue

process = heapq.heappop(ready_queue)
process.response_time = current_time - process.arrival_time
process.waiting_time = current_time - process.arrival_time
current_time += process.burst_time
process.turnaround_time = current_time - process.arrival_time
completed_processes.append(process)

self.processes = completed_processes
return self._calculate_metrics()

def round_robin(self, time_quantum: int) -> Dict[str, float]:


self.reset_processes()
ready_queue = deque()
current_time = 0
remaining_processes = sorted(self.processes, key=lambda x: x.arrival_time)

while remaining_processes or ready_queue:


while remaining_processes and remaining_processes[0].arrival_time <= current_time:
process = remaining_processes.pop(0)
process.remaining_time = process.burst_time
ready_queue.append(process)

if not ready_queue:
current_time = remaining_processes[0].arrival_time
continue

process = ready_queue.popleft()
if process.response_time == -1:
process.response_time = current_time - process.arrival_time

execution_time = min(time_quantum, process.remaining_time)


process.remaining_time -= execution_time
current_time += execution_time

while remaining_processes and remaining_processes[0].arrival_time <= current_time:


ready_queue.append(remaining_processes.pop(0))
if process.remaining_time > 0:
ready_queue.append(process)
else:
process.turnaround_time = current_time - process.arrival_time
process.waiting_time = process.turnaround_time - process.burst_time

return self._calculate_metrics()

def priority_scheduling(self) -> Dict[str, float]:


self.reset_processes()
ready_queue = []
current_time = 0
remaining_processes = sorted(self.processes, key=lambda x: x.arrival_time)
completed_processes = []

while remaining_processes or ready_queue:


while remaining_processes and remaining_processes[0].arrival_time <= current_time:
process = remaining_processes.pop(0)
heapq.heappush(ready_queue, (process.priority, process))

if not ready_queue:
current_time = remaining_processes[0].arrival_time
continue

_, process = heapq.heappop(ready_queue)
process.response_time = current_time - process.arrival_time
process.waiting_time = current_time - process.arrival_time
current_time += process.burst_time
process.turnaround_time = current_time - process.arrival_time
completed_processes.append(process)
self.processes = completed_processes
return self._calculate_metrics()

def _calculate_metrics(self) -> Dict[str, float]:


avg_turnaround = sum(p.turnaround_time for p in self.processes) / len(self.processes)
avg_waiting = sum(p.waiting_time for p in self.processes) / len(self.processes)
avg_response = sum(p.response_time for p in self.processes) / len(self.processes)

return {
"avg_turnaround_time": round(avg_turnaround, 2),
"avg_waiting_time": round(avg_waiting, 2),
"avg_response_time": round(avg_response, 2)
}

# Test the implementation


def run_simulation():
scheduler = CPUScheduler()

# Mixed workload scenario


test_processes = [
(1, 0, 6, 2), # (pid, arrival_time, burst_time, priority)
(2, 1, 4, 4),
(3, 2, 8, 1),
(4, 3, 3, 3),
(5, 4, 2, 5)
]

for pid, arrival, burst, priority in test_processes:


scheduler.add_process(pid, arrival, burst, priority)
# Run all algorithms
fcfs_metrics = scheduler.fcfs()
sjf_metrics = scheduler.sjf()
rr_metrics = scheduler.round_robin(time_quantum=2)
priority_metrics = scheduler.priority_scheduling()

# Print results
print("\nPerformance Metrics Comparison:")
print("-" * 70)
print(f"{'Algorithm':<15} {'Avg Turnaround Time':<20} {'Avg Waiting Time':<20} {'Avg
Response Time'}")
print("-" * 70)
print(f"{'FCFS':<15} {fcfs_metrics['avg_turnaround_time']:<20.2f}
{fcfs_metrics['avg_waiting_time']:<20.2f} {fcfs_metrics['avg_response_time']:.2f}")
print(f"{'SJF':<15} {sjf_metrics['avg_turnaround_time']:<20.2f}
{sjf_metrics['avg_waiting_time']:<20.2f} {sjf_metrics['avg_response_time']:.2f}")
print(f"{'Round Robin':<15} {rr_metrics['avg_turnaround_time']:<20.2f}
{rr_metrics['avg_waiting_time']:<20.2f} {rr_metrics['avg_response_time']:.2f}")
print(f"{'Priority':<15} {priority_metrics['avg_turnaround_time']:<20.2f}
{priority_metrics['avg_waiting_time']:<20.2f} {priority_metrics['avg_response_time']:.2f}")

if __name__ == "__main__":
run_simulation()

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


o Average Turnaround Time: 13.60 units
o Average Waiting Time: 9.00 units
o Average Response Time: 9.00 units
o Performance: Third best performer in this workload
o Shows typical FCFS behavior with high waiting times due to no consideration
of burst time
2. Shortest Job First (SJF)
o Average Turnaround Time: 10.60 units
o Average Waiting Time: 6.00 units
o Average Response Time: 6.00 units
o Performance: Best overall metrics
o Demonstrates SJF's optimal nature for minimizing average times
3. Round Robin (RR)
o Average Turnaround Time: 15.20 units
o Average Waiting Time: 10.60 units
o Average Response Time: 2.80 units
o Performance: Best response time but worst turnaround and waiting times
o Shows classic RR trade-off between response time and overall efficiency
4. Priority Scheduling
o Average Turnaround Time: 14.20 units
o Average Waiting Time: 9.60 units
o Average Response Time: 9.60 units
o Performance: Fourth in overall metrics
o Indicates priority assignments might not be optimal for this workload
Key Observations
1. Algorithm Efficiency Ranking (based on Average Turnaround Time):
1. SJF (10.60)
2. FCFS (13.60)
3. Priority (14.20)
4. Round Robin (15.20)
2. Response Time Analysis:
o Round Robin significantly outperforms others (2.80)
o Other algorithms show much higher response times (6.00-9.60)
o Demonstrates RR's effectiveness for interactive systems
3. Waiting Time Comparison:
o SJF shows best waiting time (6.00)
o Round Robin shows worst waiting time (10.60)
o FCFS (9.00) performs better than Priority (9.60)
Important Findings Based on These Results
1. SJF Performance:
o Consistently best performer in turnaround and waiting times
o 33% better turnaround time than RR
o Confirms theoretical optimality for minimizing average waiting time
2. Round Robin Characteristics:
o Excellent response time (2.80) - 71% better than SJF
o Poor turnaround time (15.20) - 43% worse than SJF
o Shows classic RR trade-off between responsiveness and efficiency.
3. FCFS vs Priority:
o. In an unexpected turn of events, FCFS has been found to be superior to Priority
scheduling.
o. This suggests the priority assignments might need to be optimized.
o Both exhibit similar response time characteristics.
4. Overall System Behavior:
o A remarkable diversity of performance, with turnaround time spreading widely
(4.60 units).
o Significant variation in response time (6.80 units spread).
o Waiting time is seen to consistently correlate with turnaround time.
Recommendations Based on These Results
1. For Batch Processing:
o SJF will offer the best overall efficiency.
o Avoid the use of RR due to high turnaround times.
2. For Interactive Systems:
o Use RR when response time is critical
o However, it might be worthwhile looking into SJF when waiting time becomes
more essential than response time.
3. Priority Scheduling:
o Current implementation needs optimization
o Consider reviewing priority assignments
o Might benefit from preemptive implementation
4. FCFS Consideration:
o Better than expected performance
o Could be viable for simple systems
o Good alternative when implementation simplicity is important
These results show more pronounced differences between algorithms compared to theoretical
expectations, particularly in the Round Robin performance. This provides valuable insights
for real-world scheduling decisions based on specific system requirements and workload
characteristics.

3. Addressing each point of the system design:

a) Workload Distribution Strategy:


• Data Parallelism:
o Partition incoming images into tiles/chunks for parallel processing
o Each node processes different image segments independently
o Use consistent hashing for image distribution to maintain data locality
• Task Parallelism:
o Pipeline stages (decompression → noise reduction → feature extraction, etc.)
o Different nodes can specialize in specific pipeline stages
o Implement a task scheduler that optimizes pipeline flow
b) Multi-processing and Multi-threading Implementation:
• Process-level parallelism:
o One master process per node for coordination
o Multiple worker processes (1 per 4-8 cores) for CPU-intensive tasks
o Dedicated processes for I/O operations
• Thread-level parallelism:
o Thread pool within each worker process
o CPU-bound operations use thread count matching core count
o I/O-bound operations use higher thread counts for latency hiding
python
# Example node process management
def configure_node_processes(total_cores=64):
worker_processes = total_cores // 6 # Allocate cores among workers
threads_per_worker = total_cores // worker_processes
return {
'master_process': 1,
'worker_processes': worker_processes,
'threads_per_worker': threads_per_worker,
'io_processes': 2
}
c) Bottleneck Mitigation:
1. Network I/O:
o Implement data compression for inter-node communication
o Use local caching to reduce network transfers
o Employ batch processing for small images
2. Storage I/O:
o Implement hierarchical storage (RAM → SSD → HDD)
o Use memory-mapped files for large images
o Implement predictive prefetching
3. Processing:
o Use GPU acceleration for compute-intensive tasks
o Implement adaptive batch sizing
o Load balance across nodes based on capacity
d) Load Balancing and Fault Tolerance:
• Load Balancing:
o Dynamic work stealing between nodes
o Resource usage-based task distribution
o Predictive load balancing based on historical patterns
• Fault Tolerance:

o Heartbeat mechanism for node health monitoring


o Checkpoint-restore for long-running tasks
o Automatic task redistribution on node failure
o Redundant task execution for critical workloads
e) Communication Model Trade-offs: Shared Memory:
• Advantages:

o Lower latency for local communication


o Simpler programming model
o Efficient for large data structures
• Disadvantages:

o Limited to single node


o Synchronization overhead
o Memory consistency challenges
Message Passing:
• Advantages:

o Natural fit for distributed systems


o Better fault isolation
o Scalability across nodes
• Disadvantages:

o Higher latency
o Serialization overhead
o More complex programming model
Recommendation: Hybrid approach
• Use shared memory within nodes
• Use message passing between nodes
• Implement smart buffering and batching
f) Real-time Constraints Strategy:
1. Priority-based scheduling:
python
class TaskPriority:
CRITICAL = 0 # Real-time requirements
HIGH = 1 # Near real-time
NORMAL = 2 # Standard processing
BATCH = 3 # Background processing
2. Deadline-aware scheduling:
o Track processing time for each pipeline stage
o Use estimated completion time for scheduling
o Implement early deadline first (EDF) scheduling
3. Resource reservation:
o Reserve capacity for critical tasks
o Implement admission control
o Dynamic resource allocation
g) Dynamic Scaling Strategy:
1. Metrics-based scaling:
o Monitor CPU, memory, queue length, and processing latency
o Define scaling thresholds based on SLAs
o Implement predictive scaling based on historical patterns
2. Cost optimization:
o Use spot instances for non-critical processing
o Implement workload-aware node shutdown
o Batch processing for non-real-time tasks
3. Implementation:
python
def scale_decision(metrics):
return {
'scale_up': metrics.queue_length > QUEUE_THRESHOLD
and metrics.processing_latency > LATENCY_THRESHOLD,
'scale_down': metrics.node_utilization < UTILIZATION_THRESHOLD
and metrics.queue_length < MIN_QUEUE_LENGTH,
'recommended_nodes': calculate_needed_nodes(metrics)
}
The architecture provides an excellent basis to manage the large-scale image process while
allowing for the room needed for further scaling and modifications. One key point, however,
is the exact ratio between performance and reliability against the utilization of the needed
resources obtained through careful system design and application of the right algorithms and
policies.

4. Creating a simple kernel Module:


First of all, make sure to installed most, if not all, of the required packages to allow you to
develop kernel modules.
Open a terminal in your Kali Linux virtual machine and execute the following
commands:
1. Create the Module Directory

2. Create the Module Source File: Here, you need to create a C source file known as
my_module.c:

3. Create the Makefile: In the same directory, you will create a file called Makefile:
Compile the Kernel Module
1. From your ~/my_kernel_module directory, open a terminal and run:

2. This command compiles your kernel module and creates an output file called
my_module.ko.

Setting Up QEMU
1. Create a Virtual Disk Image:
2. Boot QEMU:

Loading and Testing the Module

1. The Module Load Once you are inside the QEMU terminal, you can load your module
with:
2. The kernel message can be checked by printing:
3. The Module Unload: After you finish testing the module, you can remove it from memory
with:
4. Check Again: Make sure to check the kernel message once again to verify that it has been
unloaded:
Clean Up

Once you finish with testing, you clean up the module:

Conclusion

This guide presents an elementary framework for kernel module creation, based on process
management and synchronisation. The form of the module can be extended into far more
complicated process management features or synchronization mechanisms, like spinlocks,
mutexes, and others.

You might also like