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 0000000..96f8202 Binary files /dev/null and b/process-scheduling/main.pdf differ 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