add: project 2

This commit is contained in:
Kiana Sheibani 2025-10-28 22:15:17 -04:00
parent 2b3c28d249
commit 4e8e564b44
Signed by: toki
GPG key ID: 6CB106C25E86A9F7
25 changed files with 921 additions and 3 deletions

2
.gitignore vendored
View file

@ -35,6 +35,7 @@
*.i*86
*.x86_64
*.hex
build/
# Debug files
*.dSYM/
@ -56,6 +57,7 @@ dkms.conf
# LaTeX
.auctex-auto/
_minted/
*.fls
*.log
*.synctex.gz

View file

@ -12,6 +12,7 @@
"assignment1"
"assignment2"
"project1"
"project2"
];
eachSystem = nixpkgs.lib.genAttrs (import systems);

View file

@ -1,8 +1,6 @@
#include <errno.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>

31
project2/Makefile Normal file
View file

@ -0,0 +1,31 @@
SRC_DIR := ./src
BUILD_DIR := ./build
EXEC ?= cpu-schedulers
INSTALL ?= $(PREFIX)/bin/
# Find all the C files we want to compile
SRCS := $(shell find $(SRC_DIR) -name '*.c')
OBJS := $(SRCS:$(SRC_DIR)/%.c=$(BUILD_DIR)/%.o)
.PHONY: build install clean
build: $(BUILD_DIR)/$(EXEC)
install: build
mkdir -p $(INSTALL)
cp $(BUILD_DIR)/$(EXEC) $(INSTALL)
# Link
$(BUILD_DIR)/$(EXEC): $(OBJS)
$(CC) $^ -o $@ $(LDFLAGS)
# Compile
$(BUILD_DIR)/%.o: $(SRC_DIR)/%.c
mkdir -p $(dir $@)
$(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@
clean:
rm -r $(BUILD_DIR)

36
project2/README.md Normal file
View file

@ -0,0 +1,36 @@
# CS3502 Project 2: CPU Scheduling Simulation
This directory contains a simulator for various CPU scheduling algorithms.
## Compilation
If you have [Nix](https://nixos.org/) installed:
```sh
nix run .#cpu-schedulers
```
Otherwise, the code may be compiled using GNU Make:
``` sh
make
./build/cpu-schedulers
```
## Usage
This program supports seven scheduling algorithms:
- `sjf` - Shortest Job First
- `fcfs` - First Come First Serve
- `roundrobin` - Round Robin
- `priority` - Priority Queue
- `priorityaging` - Priority Queue with Aging
- `lottery` - Lottery
- `srtf` - Shortest Remaining Time First
The `compare` option can also be entered to run all schedulers at once.
Test data must be entered by the user; all time values must be given in
integers. Flags include `-v` for verbose debug output, and `-m` for
machine-readable output (prints data in CSV format).

8
project2/default.nix Normal file
View file

@ -0,0 +1,8 @@
rec {
project2 = cpu-schedulers;
cpu-schedulers = { stdenv }: stdenv.mkDerivation {
name = "cpu-schedulers";
src = ./.;
installFlags = [ "PREFIX=$(out)" "EXEC=$(name)" ];
};
}

View file

@ -0,0 +1,91 @@
\documentclass{article}
\usepackage{xcolor}
\usepackage{minted}
\usepackage{hyperref}
\title{CS3502 Project 2: CPU Scheduling Simulator}
\author{Kiana Sheibani}
\definecolor{lgray}{gray}{0.9}
\begin{document}
\maketitle
\newpage
\section{Introduction}
CPU scheduling, the allotment of processor execution time to running processes, is an integral part of any modern computer. There are many different strategies for doing so, designed to optimize different metrics:
\begin{enumerate}
\item \emph{Waiting time}, the amount of time between a process being ready and the CPU beginning to execute it;
\item \emph{Turnaround time}, the amount of time between a process being ready and finishing;
\item \emph{CPU utilization}, the percentage of CPU cycles spent executing a process;
\item \emph{Throughput}, the rate at which processes are finished executing.
\end{enumerate}
CPU scheduling strategies include FCFS (First Come First Serve), Round Robin, priority scheduling, and more. To determine which of these methods is most effective, it is useful to construct test models of CPUs in order to assess them in practice.
\section{Methodology}
To run these tests, I wrote a program in C to simulate different scheduling strategies based on process statistics input by the user. The code for this program is stored in my repository for this course under the \texttt{project2} subdirectory: \url{https://git.tokinanpa.dev/toki/CS3502}.
The simulator supports seven different strategies:
\begin{itemize}
\item \texttt{sjf} (Shortest Job First) --- Processes are run in ascending order of time to execute (\emph{burst time}). This is theoretically ideal, but impossible in practice, as it is impossible to know how long a program will take to execute.
\item \texttt{fcfs} (First Come First Serve) --- Processes are run in the order that they arrived in the ready queue. The simplest and least sophisticated strategy.
\item \texttt{roundrobin} (Round Robin) --- The currently running processes are cycled between at a fixed interval.
\item \texttt{priority} (Priority Queue) --- Each process is assigned a priority, where higher priority processes are run first over lower priority ones.
\item \texttt{priorityaging} (Priority Queue with Aging) --- Similar to \texttt{priority}, but each running process has its priority increased over time to prevent starvation, an issue where a low priority process is never run due to more important processes running first.
\item \texttt{lottery} (Lottery) --- The next process to run is chosen at random from the ready queue.
\item \texttt{srtf} (Shortest Remaining Time First) --- The most complex algorithm supported. This strategy is similar to \texttt{sjf}, but the burst time of each process is approximated based on the burst times of the previous processes, and the currently running process is always that which takes the least remaining time to complete.
\end{itemize}
The simulator also supports a mode to compare the performance of each strategy on the same dataset.
\subsection{Technical Details}
The processes to be scheduled are passed to the scheduler as an array of structs, where each struct contains the process's properties: a process ID, its arrival time, its burst length, and a priority value if the scheduler requires one. The struct also contains fields for the scheduler's output, specifically the intervals of time in which the CPU is running the process.
\begin{listing}[!ht]
\inputminted[
firstline=11, lastline=28,
fontsize=\footnotesize,
linenos,
frame=lines,
framesep=2mm,
baselinestretch=1.2,
]{c}{../src/main.h}
\vspace*{-\baselineskip}
\caption{\texttt{main.h} --- Definition of \texttt{process}}
\end{listing}
The \texttt{cpu\_time\_t} type is an alias defined to be an integer type, specifically \texttt{long}. This type is used as the time unit for specifying both timestamps and durations.
In order to better facilitate the execution of the scheduling algorithms, the list of processes must be sorted by arrival time. This is enforced by the program when the arrival times are input.
\section{Performance}
Unfortunately, due to various bugs and other issues with the program that I could not resolve in time, I was not able to obtain as much testing data as I hoped on the performance of each scheduler. From what data I was able to obtain, however, the \texttt{srtf} scheduler performed consistently better in both waiting time and turnaround time than all other scheduler strategies (discounting the impossible \texttt{sjf} scheduler).
\begin{table}[!ht]
\centering
\begin{tabular}{|c|c|c|c|c|}
Scheduler & Waiting Time & Turnaround Time & CPU \% & Throughput \\
\hline
\texttt{sjf} & 1112.2 & 1580.7 & 100\% & 0.002134 \\
\hline
\texttt{fcfs} & 2570.8 & 3039.3 & 100\% & 0.002134 \\
\texttt{roundrobin} & 2219.6 & 3015.7 & 100\% & 0.002134 \\
\texttt{priority} & 3097.8 & 3566.3 & 100\% & 0.002134 \\
\texttt{priorityaging} & 3097.8 & 3566.3 & 100\% & 0.002134 \\
\texttt{lottery} & 2590 & 3058.5 & 100\% & 0.002134 \\
\texttt{srtf} & 1541 & 2435.7 & 100\% & 0.002134 \\
\end{tabular}
\caption{Example output from a randomized test suite. The CPU utilization and throughput are at maximum for all schedulers because this was a high-process-density test.}
\end{table}
This suggests that the SRTF scheduling strategy is the optimal method out of these options, and that this strategy should be chosen for ideal scheduling. There are still more possible strategies for implementing a scheduler, however, and other considerations to account for. For example, adding a priority system on top of SRTF may allow it to become more flexible and to perform better in all circumstances.
\end{document}

24
project2/src/fcfs.c Normal file
View file

@ -0,0 +1,24 @@
#include "fcfs.h"
#include "main.h"
#include <stdlib.h>
void fcfs_run(process processes[], int num_processes) {
// Assign each to next free time
cpu_time_t next_free = 0;
for (int i = 0; i < num_processes; i++) {
cpu_time_t arrival = processes[i].arrival;
cpu_time_t burst = processes[i].burst;
processes[i].num_schedules = 1;
processes[i].starts = malloc(sizeof(cpu_time_t));
processes[i].starts[0] = arrival >= next_free ? arrival : next_free;
processes[i].durations = malloc(sizeof(cpu_time_t));
processes[i].durations[0] = burst;
next_free = processes[i].starts[0] + burst;
}
}
scheduler fcfs = (scheduler){.name = "fcfs",
.description = "First Come First Serve",
.run = fcfs_run,
.uses_priority = 0};

7
project2/src/fcfs.h Normal file
View file

@ -0,0 +1,7 @@
#ifndef FCFS_H_
#define FCFS_H_
#include "main.h"
extern scheduler fcfs;
#endif // FCFS_H_

52
project2/src/lottery.c Normal file
View file

@ -0,0 +1,52 @@
#include "lottery.h"
#include "main.h"
#include <stdlib.h>
#include <time.h>
void lottery_run(process processes[], int num_processes) {
unsigned int seed = time(NULL);
process *ready_queue[num_processes];
int num_ready = 0;
int num_processed = 0;
int num_run = 0;
cpu_time_t next_free = 0;
while (num_run < num_processes) {
// Add new arrivals to ready queue
for (int i = num_processed;
i < num_processes && processes[i].arrival <= next_free; i++) {
ready_queue[num_ready] = &processes[i];
num_ready++;
num_processed++;
}
// Idle if ready queue is empty
if (num_ready == 0) {
next_free = processes[num_processed].arrival;
continue;
}
// Remove random process
int next_i = rand_r(&seed) % num_ready;
process *next = ready_queue[next_i];
num_ready--;
for (int i = next_i; i < num_ready; i++) {
ready_queue[i] = ready_queue[i + 1];
}
// Run process
next->num_schedules = 1;
next->starts = malloc(sizeof(cpu_time_t));
next->starts[0] = next_free;
next->durations = malloc(sizeof(cpu_time_t));
next->durations[0] = next->burst;
num_run++;
next_free = next->starts[0] + next->burst;
}
}
scheduler lottery = (scheduler){.name = "lottery",
.description = NULL,
.run = lottery_run,
.uses_priority = 0};

7
project2/src/lottery.h Normal file
View file

@ -0,0 +1,7 @@
#ifndef LOTTERY_H_
#define LOTTERY_H_
#include "main.h"
extern scheduler lottery;
#endif // LOTTERY_H_

263
project2/src/main.c Normal file
View file

@ -0,0 +1,263 @@
#include "main.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "fcfs.h"
#include "lottery.h"
#include "priority.h"
#include "round_robin.h"
#include "sjf.h"
#include "srtf.h"
// See main.h
struct process;
struct scheduler;
scheduler *schedulers[] = {
&sjf, &fcfs, &round_robin, &priority, &priority_aging, &lottery, &srtf};
int num_schedulers = sizeof(schedulers) / sizeof(*schedulers);
int read_comma_sep(process list[], int num, size_t offset, int is_long) {
char *buffer = malloc(num * 10);
fgets(buffer, num * 10, stdin);
int processed = 0;
char *value_str = strtok(buffer, ",");
while (value_str != NULL) {
if (is_long)
sscanf(value_str, "%ld", (long *)((void *)&list[processed] + offset));
else
sscanf(value_str, "%d", (int *)((void *)&list[processed] + offset));
processed++;
value_str = strtok(NULL, ",");
}
free(buffer);
if (processed != num) {
return 0;
}
return 1;
}
void run_scheduler(scheduler *scheduler, process processes[], int num_processes,
int verbose, int machine_readable) {
if (!machine_readable) {
if (scheduler->description != NULL)
printf("\nScheduler %s (%s):\n", scheduler->name, scheduler->description);
else
printf("\nScheduler %s:\n", scheduler->name);
}
scheduler->run(processes, num_processes);
cpu_time_t total_time = 0;
cpu_time_t used_time = 0;
cpu_time_t total_waiting = 0;
cpu_time_t total_turnaround = 0;
if (!machine_readable && verbose)
printf("Schedule times: ");
for (int i = 0; i < num_processes; i++) {
used_time += processes[i].burst;
if (!machine_readable && verbose) {
for (int j = 0; j < processes[i].num_schedules; j++) {
printf("%ld-%ld%s", processes[i].starts[j],
processes[i].starts[j] + processes[i].durations[j],
i == num_processes - 1 && j == processes[i].num_schedules - 1
? "\n"
: j == processes[i].num_schedules - 1 ? ", "
: " ");
}
}
}
if (!machine_readable && verbose)
printf("Waiting times: ");
for (int i = 0; i < num_processes; i++) {
cpu_time_t first_start = processes[i].starts[0];
for (int j = 1; j < processes[i].num_schedules; j++) {
if (processes[i].starts[j] < first_start)
first_start = processes[i].starts[j];
}
processes[i].waiting = first_start - processes[i].arrival;
total_waiting += processes[i].waiting;
if (!machine_readable && verbose)
printf("%ld%s", processes[i].waiting,
i == num_processes - 1 ? "\n" : ", ");
}
if (!machine_readable && verbose)
printf("Turnaround times: ");
for (int i = 0; i < num_processes; i++) {
cpu_time_t process_end = processes[i].starts[0] + processes[i].durations[0];
for (int j = 0; j < processes[i].num_schedules; j++) {
int end = processes[i].starts[j] + processes[i].durations[j];
if (process_end < end)
process_end = end;
if (total_time < end)
total_time = end;
}
processes[i].turnaround = process_end - processes[i].arrival;
total_turnaround += processes[i].turnaround;
if (!machine_readable && verbose)
printf("%ld%s", processes[i].turnaround,
i == num_processes - 1 ? "\n" : ", ");
}
double avg_waiting = (double)total_waiting / (double)num_processes;
double avg_turnaround = (double)total_turnaround / (double)num_processes;
double cpu_util = (double)used_time / (double)total_time;
double throughput = (double)num_processes / (double)total_time;
if (machine_readable) {
printf("%lf,%lf,%lf,%lf\n", avg_waiting, avg_turnaround, cpu_util,
throughput);
} else {
printf("Avg. Waiting Time: %.3lf\n", avg_waiting);
printf("Avg. Turnaround Time: %.3lf\n", avg_turnaround);
printf("CPU Utilization: %.2lf%%\n", cpu_util * 100);
printf("Throughput: %.6lf proc/t\n", throughput);
}
// Free memory
for (int i = 0; i < num_processes; i++) {
free(processes[i].starts);
free(processes[i].durations);
}
}
int main(int argc, char *argv[]) {
// Parse argument (-v verbose, for debug)
char opt;
int verbose = 0;
int machine_readable = 0;
while ((opt = getopt(argc, argv, "vm")) != -1) {
switch (opt) {
case 'v':
verbose = 1;
break;
case 'm':
machine_readable = 1;
break;
default:
printf("Usage: %s [-v] [-m]\n", argv[0]);
return 1;
}
}
if (!machine_readable) {
printf("CPU Scheduling Simulator\n");
printf("========================\n\n");
}
// Input scheduler to run
char scheduler_name[32];
if (!machine_readable) {
printf("Scheduler to run (");
for (int i = 0; i < num_schedulers; i++) {
printf("%s, ", schedulers[i]->name);
}
printf("compare): ");
}
fgets(scheduler_name, sizeof(scheduler_name), stdin);
if (scheduler_name[strlen(scheduler_name) - 1] != '\n') {
printf("Too many characters\n");
return 1;
}
scheduler_name[strlen(scheduler_name) - 1] = '\0';
scheduler *scheduler = NULL;
if (strcmp(scheduler_name, "compare") != 0) {
for (int i = 0; i < num_schedulers; i++) {
if (strcmp(scheduler_name, schedulers[i]->name) == 0) {
scheduler = schedulers[i];
break;
}
}
if (scheduler == NULL) {
printf("Invalid scheduler name\n");
return 1;
}
}
// Number of processes
int num_processes = 0;
if (!machine_readable)
printf("Number of processes: ");
char num_processes_str[10];
fgets(num_processes_str, sizeof(num_processes_str), stdin);
sscanf(num_processes_str, "%d", &num_processes);
if (num_processes < 1) {
printf("Invalid number of processes\n");
return 1;
}
process processes[num_processes];
for (int i = 0; i < num_processes; i++) {
processes[i] = (process){.pid = i};
}
// Arrival times
if (!machine_readable)
printf("Process arrival times (ordered, comma-separated): ");
if (!read_comma_sep(processes, num_processes,
(void *)&(processes->arrival) - (void *)processes, 1)) {
printf("Invalid arrival times\n");
return 1;
}
// Check ordering
for (int i = 1; i < num_processes; i++) {
if (processes[i].arrival < processes[i - 1].arrival) {
printf("Arrival times not ordered\n");
return 1;
}
}
// Burst times
if (!machine_readable)
printf("Process burst times (comma-separated): ");
if (!read_comma_sep(processes, num_processes,
(void *)&(processes->burst) - (void *)processes, 1)) {
printf("Invalid burst times\n");
return 1;
}
// Priorities
if (scheduler == NULL || scheduler->uses_priority) {
if (!machine_readable)
printf("Process priorities (comma-separated): ");
if (!read_comma_sep(processes, num_processes,
(void *)&(processes->priority) - (void *)processes,
0)) {
printf("Invalid priorities\n");
return 1;
}
}
// Run scheduler(s)
if (scheduler != NULL) {
run_scheduler(scheduler, processes, num_processes, verbose,
machine_readable);
} else {
for (int i = 0; i < num_schedulers; i++) {
// Reset process fields
for (int i = 0; i < num_processes; i++) {
processes[i].num_schedules = 0;
processes[i].executed = 0;
}
run_scheduler(schedulers[i], processes, num_processes, verbose,
machine_readable);
}
}
}

37
project2/src/main.h Normal file
View file

@ -0,0 +1,37 @@
#ifndef MAIN_H_
#define MAIN_H_
#include <stddef.h>
typedef long cpu_time_t;
// Average CPU burst length
// This is used for Round Robin and SRTF scheduling
#define AVG_BURST 1000
typedef struct process {
// Input values
int pid;
cpu_time_t arrival;
cpu_time_t burst;
int priority;
// Used internally by some schedulers
cpu_time_t executed;
// Scheduler output value
size_t num_schedules;
cpu_time_t *starts; // Dynamically allocated arrays; free after read
cpu_time_t *durations;
// Derived output values
cpu_time_t waiting;
cpu_time_t turnaround;
} process;
typedef struct scheduler {
char *name;
char *description;
void (*run)(process *processes, int num_processes);
int uses_priority;
} scheduler;
#endif // MAIN_H_

75
project2/src/priority.c Normal file
View file

@ -0,0 +1,75 @@
#include "priority.h"
#include "main.h"
#include <stdlib.h>
void priority_run(process processes[], int num_processes, int aging) {
process *ready_queue[num_processes];
int num_ready = 0;
int num_processed = 0;
int num_run = 0;
cpu_time_t next_free = 0;
while (num_run < num_processes) {
// Add new arrivals to ready queue
for (int i = num_processed;
i < num_processes && processes[i].arrival <= next_free; i++) {
ready_queue[num_ready] = &processes[i];
num_ready++;
num_processed++;
}
// Idle if ready queue is empty
if (num_ready == 0) {
next_free = processes[num_processed].arrival;
continue;
}
// Remove process with highest priority (FCFS if same priority)
int next_i = 0;
for (int i = 0; i < num_ready; i++) {
if (ready_queue[i]->priority > ready_queue[next_i]->priority ||
ready_queue[i]->priority == ready_queue[next_i]->priority) {
next_i = i;
}
}
process *next = ready_queue[next_i];
num_ready--;
for (int i = next_i; i < num_ready; i++) {
ready_queue[i] = ready_queue[i + 1];
}
// Age processes (increase priority by 1)
if (aging) {
for (int i = 0; i < num_ready; i++) {
ready_queue[i]->priority++;
}
}
// Run process
next->num_schedules = 1;
next->starts = malloc(sizeof(cpu_time_t));
next->starts[0] = next_free;
next->durations = malloc(sizeof(cpu_time_t));
next->durations[0] = next->burst;
num_run++;
next_free = next->starts[0] + next->burst;
}
}
void priority_run_no_aging(process processes[], int num_processes) {
priority_run(processes, num_processes, 0);
}
void priority_run_aging(process processes[], int num_processes) {
priority_run(processes, num_processes, 1);
}
scheduler priority = (scheduler){.name = "priority",
.description = NULL,
.run = priority_run_no_aging,
.uses_priority = 1};
scheduler priority_aging = (scheduler){.name = "priorityaging",
.description = "Priority - With Aging",
.run = priority_run_aging,
.uses_priority = 1};

8
project2/src/priority.h Normal file
View file

@ -0,0 +1,8 @@
#ifndef PRIORITY_H_
#define PRIORITY_H_
#include "main.h"
extern scheduler priority;
extern scheduler priority_aging;
#endif // PRIORITY_H_

View file

@ -0,0 +1,66 @@
#include "round_robin.h"
#include "main.h"
#include <stdlib.h>
// Exact quantum does not matter much for sandbox testing;
// arbitrarily set to 100 time units
cpu_time_t quantum = AVG_BURST;
void round_robin_run(process processes[], int num_processes) {
// Initialize memory
for (int i = 0; i < num_processes; i++) {
size_t memory_heuristic = processes[i].burst / quantum + 1;
processes[i].starts = malloc(memory_heuristic * sizeof(cpu_time_t));
processes[i].durations = malloc(memory_heuristic * sizeof(cpu_time_t));
}
process *ready_queue[num_processes];
int num_ready = 0;
int num_processed = 0;
int num_run = 0;
cpu_time_t next_free = 0;
while (num_run < num_processes) {
// Add new arrivals to ready queue
for (int i = num_processed;
i < num_processes && processes[i].arrival <= next_free; i++) {
ready_queue[num_ready] = &processes[i];
num_ready++;
num_processed++;
}
// Idle if ready queue is empty
if (num_ready == 0) {
next_free = processes[num_processed].arrival;
continue;
}
// Run next process (potentially partially)
process *next = ready_queue[0];
num_ready--;
for (int i = 0; i < num_ready; i++) {
ready_queue[i] = ready_queue[i + 1];
}
size_t num_sch = next->num_schedules;
next->starts[num_sch] = next_free;
if (next->executed + quantum < next->burst) {
// Partial execution
next->durations[num_sch] = quantum;
next->executed += quantum;
// Place back into execution
ready_queue[num_ready] = next;
num_ready++;
} else {
next->durations[num_sch] = next->burst - next->executed;
num_run++;
}
next_free = next->starts[num_sch] + next->durations[num_sch];
next->num_schedules++;
}
}
scheduler round_robin = (scheduler){.name = "roundrobin",
.description = NULL,
.run = round_robin_run,
.uses_priority = 0};

View file

@ -0,0 +1,7 @@
#ifndef ROUND_ROBIN_H_
#define ROUND_ROBIN_H_
#include "main.h"
extern scheduler round_robin;
#endif // ROUND_ROBIN_H_

54
project2/src/sjf.c Normal file
View file

@ -0,0 +1,54 @@
#include "sjf.h"
#include "main.h"
#include <stdlib.h>
void sjf_run(process processes[], int num_processes) {
process *ready_queue[num_processes];
int num_ready = 0;
int num_processed = 0;
int num_run = 0;
cpu_time_t next_free = 0;
while (num_run < num_processes) {
// Add new arrivals to ready queue
for (int i = num_processed;
i < num_processes && processes[i].arrival <= next_free; i++) {
ready_queue[num_ready] = &processes[i];
num_ready++;
num_processed++;
}
// Idle if ready queue is empty
if (num_ready == 0) {
next_free = processes[num_processed].arrival;
continue;
}
// Remove process with shortest burst
int shortest = 0;
for (int i = 0; i < num_ready; i++) {
if (ready_queue[i]->burst < ready_queue[shortest]->burst) {
shortest = i;
}
}
process *next = ready_queue[shortest];
num_ready--;
for (int i = shortest; i < num_ready; i++) {
ready_queue[i] = ready_queue[i + 1];
}
// Run process
next->num_schedules = 1;
next->starts = malloc(sizeof(cpu_time_t));
next->starts[0] = next_free;
next->durations = malloc(sizeof(cpu_time_t));
next->durations[0] = next->burst;
num_run++;
next_free = next->starts[0] + next->burst;
}
}
scheduler sjf = (scheduler){.name = "sjf",
.description = "Shortest Job First",
.run = sjf_run,
.uses_priority = 0};

7
project2/src/sjf.h Normal file
View file

@ -0,0 +1,7 @@
#ifndef SJF_H_
#define SJF_H_
#include "main.h"
extern scheduler sjf;
#endif // SJF_H_

94
project2/src/srtf.c Normal file
View file

@ -0,0 +1,94 @@
#include "srtf.h"
#include "main.h"
#include <stdio.h>
#include <stdlib.h>
double alpha = 0.5;
void srtf_run(process processes[], int num_processes) {
// Compute predicted burst times based on exponential averaging
cpu_time_t predicted_bursts[num_processes];
predicted_bursts[0] = AVG_BURST;
for (int i = 1; i < num_processes; i++) {
predicted_bursts[i] = (cpu_time_t)(alpha * processes[i - 1].burst +
(1 - alpha) * predicted_bursts[i - 1]);
}
// Allocate memory
for (int i = 0; i < num_processes; i++) {
processes[i].starts = malloc(5 * sizeof(cpu_time_t));
processes[i].durations = malloc(5 * sizeof(cpu_time_t));
}
process *ready_queue[num_processes];
int num_ready = 0;
int num_processed = 0;
int num_run = 0;
cpu_time_t next_free = 0;
while (num_run < num_processes) {
// Add new arrivals to ready queue
for (int i = num_processed;
i < num_processes && processes[i].arrival <= next_free; i++) {
ready_queue[num_ready] = &processes[i];
num_ready++;
num_processed++;
}
// Idle if ready queue is empty
if (num_ready == 0) {
next_free = processes[num_processed].arrival;
continue;
}
// Remove process with shortest predicted burst remaining
int next_i = 0;
for (int i = 0; i < num_ready; i++) {
int next_remaining = predicted_bursts[ready_queue[next_i]->pid] -
ready_queue[next_i]->executed;
int i_remaining =
predicted_bursts[ready_queue[i]->pid] - ready_queue[i]->executed;
if (i_remaining < next_remaining) {
next_i = i;
}
}
process *next = ready_queue[next_i];
num_ready--;
for (int i = next_i; i < num_ready; i++) {
ready_queue[i] = ready_queue[i + 1];
}
// Grow memory if not big enough
if (next->num_schedules % 5 == 0) {
if (!realloc(next->starts, next->num_schedules + 5) ||
!realloc(next->durations, next->num_schedules + 5)) {
exit(1);
}
}
// Run process preemptively (only until next one arrives)
size_t num_sch = next->num_schedules;
next->starts[num_sch] = next_free;
if (num_processed != num_processes &&
next_free + next->burst - next->executed >
processes[num_processed].arrival) {
// Partial execution
next->durations[num_sch] = processes[num_processed].arrival - next_free;
next->executed += next->durations[num_sch];
// Place back into execution
ready_queue[num_ready] = next;
num_ready++;
} else {
next->durations[num_sch] = next->burst - next->executed;
num_run++;
}
next->num_schedules++;
next_free = next->starts[num_sch] + next->durations[num_sch];
}
}
scheduler srtf = (scheduler){.name = "srtf",
.description = "Shortest Remaining Time First",
.run = srtf_run,
.uses_priority = 0};

7
project2/src/srtf.h Normal file
View file

@ -0,0 +1,7 @@
#ifndef SRTF_H_
#define SRTF_H_
#include "main.h"
extern scheduler srtf;
#endif // SRTF_H_

1
project2/test.nix Normal file
View file

@ -0,0 +1 @@
{}

5
project2/test/input.txt Normal file
View file

@ -0,0 +1,5 @@
compare
10
0,10,73,78,78,80,90,137,211,237
853,1,1470,654,108,6,1074,508,1,10
7,6,15,12,8,1,14,1,4,6

34
project2/test/random Executable file
View file

@ -0,0 +1,34 @@
#!/usr/bin/env bash
echo "Building executable..."
cd "${FLAKE:-$(dirname $0)}"
if command -v nix; then
cpu_schedulers="$(nix build --no-link --print-out-paths .#cpu-schedulers)/bin/cpu-schedulers"
else
make
cpu_schedulers="build/cpu-schedulers"
fi
num_processes=10
printf "compare\n$num_processes\n" >input.txt
proc_arrival=0
for i in $(seq 2 "$num_processes"); do
echo -n "$proc_arrival," >>input.txt
proc_arrival=$(($RANDOM % 100 + $proc_arrival))
done
echo "$proc_arrival" >>input.txt
for i in $(seq 2 "$num_processes"); do
echo -n $((($RANDOM % 1250) ** 4 / 1000000000 + 1))"," >>input.txt
done
echo $((($RANDOM % 1250) ** 4 / 1000000000 + 1)) >>input.txt
for i in $(seq 2 "$num_processes"); do
echo -n $(($RANDOM % 20))"," >>input.txt
done
echo $(($RANDOM % 20)) >>input.txt
"$cpu_schedulers" -m <input.txt

View file

@ -1,4 +1,7 @@
{ mkShell, llvmPackages_21 }:
{ mkShell,
llvmPackages_21,
dotnet-sdk
}:
mkShell {
packages = [