From 32473b67bc79fb67dd0565129c149a8b304d0676 Mon Sep 17 00:00:00 2001 From: LordBaryhobal Date: Mon, 2 Dec 2024 22:56:33 +0100 Subject: [PATCH] added process scheduling algorithms --- process-scheduling/algorithms.typ | 4 + process-scheduling/algorithms/fcfs.typ | 20 +++ process-scheduling/algorithms/pr.typ | 149 +++++++++++++++++++++++ process-scheduling/algorithms/rr.typ | 81 +++++++++++++ process-scheduling/algorithms/srtf.typ | 155 ++++++++++++++++++++++++ process-scheduling/algorithms/utils.typ | 30 +++++ process-scheduling/main.pdf | Bin 0 -> 41056 bytes process-scheduling/main.typ | 40 ++++++ process-scheduling/utils.typ | 155 ++++++++++++++++++++++++ 9 files changed, 634 insertions(+) create mode 100644 process-scheduling/algorithms.typ create mode 100644 process-scheduling/algorithms/fcfs.typ create mode 100644 process-scheduling/algorithms/pr.typ create mode 100644 process-scheduling/algorithms/rr.typ create mode 100644 process-scheduling/algorithms/srtf.typ create mode 100644 process-scheduling/algorithms/utils.typ create mode 100644 process-scheduling/main.pdf create mode 100644 process-scheduling/main.typ create mode 100644 process-scheduling/utils.typ diff --git a/process-scheduling/algorithms.typ b/process-scheduling/algorithms.typ new file mode 100644 index 0000000..2db3ef0 --- /dev/null +++ b/process-scheduling/algorithms.typ @@ -0,0 +1,4 @@ +#import "algorithms/fcfs.typ": FCFS +#import "algorithms/pr.typ": Pr +#import "algorithms/srtf.typ": SRTF +#import "algorithms/rr.typ": RR \ No newline at end of file diff --git a/process-scheduling/algorithms/fcfs.typ b/process-scheduling/algorithms/fcfs.typ new file mode 100644 index 0000000..8dc0ecb --- /dev/null +++ b/process-scheduling/algorithms/fcfs.typ @@ -0,0 +1,20 @@ +#import "utils.typ": * + +#let FCFS(tasks) = { + let (processes, tasks) = _prepare-tasks(tasks) + + let cur-time = 0 + for task in tasks { + let proc = processes.at(task.id) + if task.arrival < cur-time { + proc.events.push((READY, task.arrival)) + } + let wait-time = calc.max(0, cur-time - task.arrival) + proc.events.push((RUNNING, task.arrival + wait-time)) + cur-time = task.arrival + task.duration + wait-time + proc.events.push((TERMINATED, cur-time)) + processes.at(task.id) = proc + } + + return _prepare-output(processes) +} \ No newline at end of file diff --git a/process-scheduling/algorithms/pr.typ b/process-scheduling/algorithms/pr.typ new file mode 100644 index 0000000..2b4597c --- /dev/null +++ b/process-scheduling/algorithms/pr.typ @@ -0,0 +1,149 @@ +#import "utils.typ": * + +#let prio-add(list, task) = { + let left-i = list.rev() + .position(t => t.priority > task.priority) + + if left-i == none { + left-i = list.rev() + .position(t => t.arrival > task.arrival) + } + + let i = if left-i == none { + 0 + } else { + list.len() - left-i + } + if task.pid == 7 { + let dbg = (list, task, i) + } + list.insert(i, task) + return list +} + +#let Pr(tasks, ctx-switch: 1) = { + let (processes, tasks) = _prepare-tasks(tasks) + tasks = tasks.rev() + + let n-tasks = tasks.len() + let cur-time = 0 + let current = none + let finished = 0 + let prio-list = () + let logs = () + while finished != n-tasks { + let log = "Current time: " + str(cur-time) + " / " + if (current != none) { + log += "Current: " + str(current.pid) + " / " + } else { + log += "Current: none / " + } + if (tasks.len() != 0) { + log += "Next: " + str(tasks.last().pid) + } else { + log += "Next: none" + } + logs.push(log) + + if current == none { + current = tasks.pop() + logs.push("No running process: running " + str(current.pid)) + current.state = RUNNING + processes.at(current.id).events.push((RUNNING, current.arrival)) + + } else if tasks.len() != 0 { + logs.push(log-list(prio-list)) + logs.push("Processing next process (" + str(tasks.last().pid) + ")") + tasks.last().state = READY + + while current != none and cur-time + current.remaining <= tasks.last().arrival { + logs.push(" Process " + str(current.pid) + " finished before next") + if current.state != RUNNING { + processes.at(current.id).events.push((RUNNING, cur-time)) + } + current.state = TERMINATED + cur-time += current.remaining + current.remaining = 0 + processes.at(current.id).events.push((TERMINATED, cur-time)) + finished += 1 + if prio-list.len() != 0 { + current = prio-list.pop() + } else { + current = none + } + } + + if current != none { + logs.push("Removing time from current process") + if current.state != RUNNING and tasks.last().arrival > cur-time { + processes.at(current.id).events.push((RUNNING, cur-time)) + current.state = RUNNING + //processes.at(current.id).events.push((READY, tasks.last().arrival)) + } + current.remaining -= tasks.last().arrival - cur-time + } + if tasks.last().arrival > cur-time { + cur-time = tasks.last().arrival + } + tasks.last().state = READY + + if current == none { + current = tasks.pop() + logs.push("Queue is empty, running next process " + str(current.pid)) + current.state = RUNNING + processes.at(current.id).events.push((RUNNING, cur-time)) + } else if tasks.last().priority < current.priority { + logs.push("Next process (" + str(tasks.last().pid) + ") has higher priority") + if current.state != READY { + processes.at(current.id).events.push((READY, cur-time)) + } + if current.state == RUNNING { + logs.push(" Preempting current process (" + str(current.pid) +")") + processes.at(tasks.last().id).events.push((READY, cur-time)) + cur-time += ctx-switch + } + if current.state == RUNNING { + current.state = READY + prio-list = prio-add(prio-list, current) + } else { + prio-list.push(current) + } + + current = tasks.pop() + + current.state = RUNNING + processes.at(current.id).events.push((RUNNING, cur-time)) + } else { + logs.push("Adding next process (" + str(tasks.last().pid) + ") to list") + processes.at(tasks.last().id).events.push((READY, cur-time)) + prio-list = prio-add(prio-list, tasks.pop()) + } + if current != none { + if current.state != RUNNING { + processes.at(current.id).events.push((RUNNING, cur-time)) + } + current.state = RUNNING + } + } else { + logs.push("No new processes, emptying queue") + logs.push(log-list(prio-list)) + while current != none { + logs.push("Completing process " + str(current.pid)) + current.state = TERMINATED + cur-time += current.remaining + current.remaining = 0 + processes.at(current.id).events.push((TERMINATED, cur-time)) + finished += 1 + if prio-list.len() != 0 { + current = prio-list.pop() + current.state = RUNNING + processes.at(current.id).events.push((RUNNING, cur-time)) + } else { + current = none + } + } + } + } + + return _prepare-output(processes) +} \ No newline at end of file diff --git a/process-scheduling/algorithms/rr.typ b/process-scheduling/algorithms/rr.typ new file mode 100644 index 0000000..c9feb1d --- /dev/null +++ b/process-scheduling/algorithms/rr.typ @@ -0,0 +1,81 @@ +#import "utils.typ": * + +#let RR(tasks, quantum: 2, ctx-switch: 1) = { + let (processes, tasks) = _prepare-tasks(tasks) + + let n-tasks = tasks.len() + let cur-time = 0 + let current = none + let finished = 0 + let running = 0 + let last-running = none + let logs = () + let failsafe = 0 + + while (failsafe < 160 and finished < n-tasks) { + failsafe += 1 + for (i, p) in tasks.enumerate() { + if p.state == TERMINATED { + continue + } + logs.push("Current time: " + str(cur-time) + ", pid: " + str(p.pid)) + + if p.state == UNBORN { + if p.arrival <= cur-time { + p.state = READY + if running == 0 { + processes.at(p.id).events.push((RUNNING, p.arrival)) + } else { + processes.at(p.id).events.push((READY, p.arrival)) + } + running += 1 + if last-running != none { + logs.push("Preempting last running process (current time: " + str(cur-time) + ", pid: " + str(last-running.pid) + ")") + processes.at(last-running.id).events.push((READY, cur-time)) + cur-time += ctx-switch + } + logs.push("Process " + str(p.pid) + " is now ready") + logs.push("Running processes: " + str(running)) + } + } + + if p.state == READY { + //if running > 1 { + if last-running == none or last-running.pid != p.pid { + processes.at(p.id).events.push((RUNNING, cur-time)) + } + p.remaining -= quantum + cur-time += quantum + last-running = p + logs.push("Executing quantum for process " + str(p.pid) + ", remaining time " + str(p.remaining)) + + if p.remaining <= 0 { + logs.push(" Process has finished") + processes.at(p.id).events.push((TERMINATED, cur-time + p.remaining)) + p.remaining = 0 + p.state = TERMINATED + last-running = none + finished += 1 + running -= 1 + + } else if running > 1 { + logs.push(" Preempting process (current time: " + str(cur-time) + ", pid: " + str(p.pid) + ")") + last-running = none + processes.at(p.id).events.push((READY, cur-time)) + cur-time += ctx-switch + } + } + + tasks.at(i) = p + } + if finished < n-tasks and running == 0 { + cur-time += 1 + } + } + + let f = failsafe + + //let logs2 = logs.slice(-60) + + return _prepare-output(processes) +} \ No newline at end of file diff --git a/process-scheduling/algorithms/srtf.typ b/process-scheduling/algorithms/srtf.typ new file mode 100644 index 0000000..69c75d2 --- /dev/null +++ b/process-scheduling/algorithms/srtf.typ @@ -0,0 +1,155 @@ +#import "utils.typ": * + +#let remtime-add(list, task) = { + let left-i = list.rev() + .position(t => t.remaining > task.remaining) + + if left-i == none { + left-i = list.rev() + .position(t => t.arrival > task.arrival) + } + + let i = if left-i == none { + 0 + } else { + list.len() - left-i + } + if task.pid == 7 { + let dbg = (list, task, i) + } + list.insert(i, task) + return list +} + +#let SRTF(tasks, ctx-switch: 1) = { + let (processes, tasks) = _prepare-tasks(tasks) + tasks = tasks.rev() + + let n-tasks = tasks.len() + let cur-time = 0 + let current = none + let finished = 0 + let remtime-list = () + let logs = () + + while finished != n-tasks { + let log = "Current time: " + str(cur-time) + " / " + if (current != none) { + log += "Current: " + str(current.pid) + " / " + } else { + log += "Current: none / " + } + if (tasks.len() != 0) { + log += "Next: " + str(tasks.last().pid) + } else { + log += "Next: none" + } + logs.push(log) + + if current == none { + current = tasks.pop() + logs.push("No running process: running " + str(current.pid)) + current.state = RUNNING + processes.at(current.id).events.push((RUNNING, current.arrival)) + + } else if tasks.len() != 0 { + logs.push(log-list(remtime-list)) + logs.push("Processing next process (" + str(tasks.last().pid) + ")") + tasks.last().state = READY + let delta = tasks.last().arrival - cur-time + + while current != none and current.remaining <= delta { + logs.push(" Process " + str(current.pid) + " finished before next") + if current.state != RUNNING { + processes.at(current.id).events.push((RUNNING, cur-time)) + } + current.state = TERMINATED + cur-time += current.remaining + delta = tasks.last().arrival - cur-time + current.remaining = 0 + processes.at(current.id).events.push((TERMINATED, cur-time)) + finished += 1 + if remtime-list.len() != 0 { + current = remtime-list.pop() + } else { + current = none + } + } + + if current != none { + logs.push("Removing time from current process") + if current.state != RUNNING and tasks.last().arrival > cur-time { + processes.at(current.id).events.push((RUNNING, cur-time)) + current.state = RUNNING + //processes.at(current.id).events.push((READY, tasks.last().arrival)) + } + current.remaining -= delta + logs.push(" New remaining time " + str(current.remaining)) + } + if tasks.last().arrival > cur-time { + cur-time = tasks.last().arrival + } + tasks.last().state = READY + + if current == none { + current = tasks.pop() + logs.push("Queue is empty, running next process " + str(current.pid)) + current.state = RUNNING + processes.at(current.id).events.push((RUNNING, cur-time)) + + } else if tasks.last().remaining < current.remaining { + logs.push("Next process (" + str(tasks.last().pid) + ") has shorter remaining time") + if current.state != READY { + processes.at(current.id).events.push((READY, cur-time)) + } + if current.state == RUNNING { + logs.push(" Preempting current process (" + str(current.pid) +")") + current.remaining -= tasks.last().arrival - cur-time + processes.at(tasks.last().id).events.push((READY, cur-time)) + cur-time += ctx-switch + } + if current.state == RUNNING { + current.state = READY + remtime-list = remtime-add(remtime-list, current) + } else { + remtime-list.push(current) + } + + current = tasks.pop() + + current.state = RUNNING + processes.at(current.id).events.push((RUNNING, cur-time)) + } else { + logs.push("Adding next process (" + str(tasks.last().pid) + ") to list") + processes.at(tasks.last().id).events.push((READY, cur-time)) + remtime-list = remtime-add(remtime-list, tasks.pop()) + } + if current != none { + if current.state != RUNNING { + processes.at(current.id).events.push((RUNNING, cur-time)) + } + current.state = RUNNING + } + } else { + logs.push("No new processes, emptying queue") + logs.push(log-list(remtime-list)) + while current != none { + logs.push("Completing process " + str(current.pid)) + current.state = TERMINATED + cur-time += current.remaining + current.remaining = 0 + processes.at(current.id).events.push((TERMINATED, cur-time)) + finished += 1 + if remtime-list.len() != 0 { + current = remtime-list.pop() + current.state = RUNNING + processes.at(current.id).events.push((RUNNING, cur-time)) + } else { + current = none + } + } + } + } + + return _prepare-output(processes) +} \ No newline at end of file diff --git a/process-scheduling/algorithms/utils.typ b/process-scheduling/algorithms/utils.typ new file mode 100644 index 0000000..d1054ce --- /dev/null +++ b/process-scheduling/algorithms/utils.typ @@ -0,0 +1,30 @@ +#import "../utils.typ": UNBORN, READY, RUNNING, TERMINATED + +#let _prepare-tasks(tasks) = { + let processes = (:) + for (i, task) in tasks.enumerate() { + let id = str(i) + task.insert("id", id) + task.insert("state", UNBORN) + processes.insert(id, (task: task, events: ())) + tasks.at(i) = task + } + return (processes, tasks) +} + +#let _prepare-output(processes) = { + return processes.values() + .map(p => ( + pid: p.task.pid, + priority: p.task.priority, + events: p.events + )) + .sorted(key: t => t.pid) +} + +#let log-list(list) = { + if list == none or list.len() == 0 { + return "queue:" + } + return "queue: " + list.map(t => str(t.pid)).join(", ") +} \ No newline at end of file diff --git a/process-scheduling/main.pdf b/process-scheduling/main.pdf new file mode 100644 index 0000000000000000000000000000000000000000..96f8202fa02ea629e10542ee72a1eb0c30ccda4d GIT binary patch literal 41056 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 Pt60IK&h_i>yu1G|YaJ7^ literal 0 HcmV?d00001 diff --git a/process-scheduling/main.typ b/process-scheduling/main.typ new file mode 100644 index 0000000..01db75b --- /dev/null +++ b/process-scheduling/main.typ @@ -0,0 +1,40 @@ +#import "utils.typ": task, display-processes, RUNNING +#import "algorithms.typ": FCFS, Pr, SRTF, RR + +#set document(title: "Process scheduling algorithms") +#set page(height: auto) +#set text(font: "Source Sans 3") + +#let tasks = { + task(1, 0, 10, 3) + task(2, 20, 40, 2) + task(3, 30, 20, 2) + task(4, 60, 10, 3) + task(5, 80, 30, 1) + task(6, 90, 20, 1) + task(7, 100, 50, 3) + task(8, 130, 30, 2) + task(9, 180, 10, 3) + task(10, 200, 60, 1) +} + +#text(size: 1.2em)[*Process scheduling algorithms*] +#figure( + display-processes(..FCFS(tasks)), + caption: [Process scheduling: FCFS] +) +#pagebreak() +#figure( + display-processes(..Pr(tasks), display-priorities: true), + caption: [Process scheduling: Pr] +) +#pagebreak() +#figure( + display-processes(..SRTF(tasks), display-durations: (RUNNING,)), + caption: [Process scheduling: SRTF] +) +#pagebreak() +#figure( + display-processes(..RR(tasks)), + caption: [Process scheduling: RR] +) \ No newline at end of file diff --git a/process-scheduling/utils.typ b/process-scheduling/utils.typ new file mode 100644 index 0000000..3482354 --- /dev/null +++ b/process-scheduling/utils.typ @@ -0,0 +1,155 @@ +#import "@preview/cetz:0.3.1": canvas, draw + + +#let UNBORN = 0 +#let READY = 1 +#let RUNNING = 2 +#let TERMINATED = 3 + +#let colors = ( + none, + gray, + green, + blue.lighten(50%) +) + +#let task(pid, arrival, duration, priority) = { + return (( + pid: pid, + arrival: arrival, + duration: duration, + remaining: duration, + priority: priority + ),) +} + +#let display-processes(width: auto, display-durations: none, display-priorities: false, ..args) = layout(size => { + let processes = args.pos() + processes = processes.map(p => if type(p) == dictionary {p} else {( + pid: p.first(), + priority: if display-priorities {p.at(1)} else {0}, + events: p.slice(if display-priorities {2} else {1}) + )}) + let max-t = calc.max( + ..processes.map( + p => calc.max( + ..p.events.map( + e => e.last() + ) + ) + ) + ) + + let display-durations = if display-durations == none { + () + } else if type(display-durations) == array { + display-durations + } else { + (display-durations,) + } + + let shapes = () + let width = width + if (width == auto) { + width = size.width + } + let y = 0 + for proc in processes { + proc.events.push((TERMINATED, max-t)) + shapes += draw.content( + (0, y - .25), + str(proc.pid), + anchor: "east", + padding: 5pt + ) + + for i in range(proc.events.len() - 1) { + let (state1, t1) = proc.events.at(i) + let (state2, t2) = proc.events.at(i+1) + let x1 = t1 / max-t * width + let x2 = t2 / max-t * width + let y1 = y + let y2 = y - .5 + if (state1 == TERMINATED) { + y1 = y - .2 + y2 = y - .3 + } + + shapes += draw.rect( + (x1, y1), + (x2, y2), + fill: colors.at(state1) + ) + + if i == 0 and display-priorities { + shapes += draw.content( + ((x1 + x2) / 2, (y1 + y2) / 2), + strong(str(proc.priority)), + anchor: "center" + ) + } + if state1 in display-durations { + shapes += draw.content( + ((x1 + x2) / 2, (y1 + y2) / 2), + strong(str(t2 - t1)), + anchor: "center" + ) + } + + if state1 == RUNNING and state2 == READY { + shapes += draw.on-layer(1, draw.circle( + (x2, (y1 + y2)/2), + radius: .1, + fill: red + )) + } + } + y -= 0.5 + } + + let step = calc.pow( + 10, + calc.max( + 0, + calc.floor( + calc.log( + base: 10, + max-t + ) - 1 + ) + ) + ) + let n-steps = calc.floor(max-t / step) + 1 + for i in range(n-steps) { + let t = i * step + let x = t / max-t * width + shapes += draw.line( + (x, 0), + (x, if calc.rem(i, 2) == 0 {.5} else {.3}), + stroke: black + ) + shapes += draw.on-layer(-1, draw.line( + (x, 0), + (x, -processes.len() * .5), + stroke: ( + dash: (6pt, 4pt), + thickness: 1pt, + paint: gray + ) + )) + if calc.rem(i, 2) == 0 { + shapes += draw.content( + (x, .5), + str(t), + anchor: "south", + padding: 5pt + ) + } + } + shapes += draw.rect( + (0, 0), + (width, -processes.len() * .5) + ) + + canvas(shapes) +}) \ No newline at end of file