← All work

Systems simulation · ASELSAN internship · 2026

CPU Scheduler Simulator

Round Robin, MLFQ, CFS and aging priority on the same workload, measured by one accountant that has never heard of any of them.

  • C++20
  • Catch2
  • Python
  • Manim

Why I built it

This task wasn’t actually going to be mine. I kept begging my mentor for a C++ project, and in the end the team talked it over and told me to build a scheduler. I was really excited when I heard it: it was a chance to learn how the algorithms from our OS course are actually implemented, and how algorithms far more complex than the ones in that course work.

What it is

A CPU scheduling comparison harness I built during my internship on ASELSAN’s embedded systems team. It has one simulation engine, interchangeable scheduling policies, and a single set of metrics computed the same way for all of them, so the comparison is fair.

The founding rule is that no algorithm may see the future. A policy is told only what has already happened: who arrived, who has been waiting, how much CPU each job has used. That’s the standard a real scheduler is held to, and it’s why Shortest Job First isn’t here while MLFQ and CFS are.

The pieces

  1. Driver Owns the loop and decides how time moves.
  2. advance
  3. Kernel Wakes only when something can change: charges elapsed time, notices what finished, records every event.
  4. who runs next?
  5. Policy Has no clock and no power to act. It only answers.
  1. Event trace One CSV row per event, in order: seq, time, event, job, cpu.
  2. read afterwards
  3. Accountant Turns the trace into metrics. It has never heard of any algorithm.
  4. checked against
  5. Animation Manim draws the same trace and refuses to draw a chart that disagrees with the numbers.
During a run, the kernel asks a policy it can’t name for decisions. Afterwards, everything is measured from the trace alone.

Round Robin, MLFQ, CFS and aging priority each live in one file and register themselves by name, so a run picks its algorithm with a string. Because the accountant only reads the trace, adding a new algorithm never needs a new metric, and adding a metric never touches an algorithm.

Checking the numbers

Between arriving and finishing, a job is always in exactly one state: waiting, running, blocked on I/O, or being switched in. So for every finished job, turnaround = waiting + CPU + I/O + switch overhead must hold exactly. It’s a conservation law rather than an estimate, and the strongest check on the whole pipeline: if it fails, blocked time is being counted as waiting time somewhere. All numbers are integer ticks until the last division, so results are exact and reproducible. The project has 84 Catch2 tests.

One workload, five runs

RunAvg responseAvg waitingAvg turnaroundCPU useContext switches
RR, quantum 23.1722.5035.0098%28
RR, quantum 88.8321.8334.3396%16
Aging priority4.1714.8327.3392%24
MLFQ2.8321.5034.00100%28
CFS1.5018.1730.6792%30

Averages in ticks over one six-job workload, mixing CPU-heavy and I/O-heavy jobs. RR is Round Robin. Best value per column in bold. With one workload this shows trade-offs, not a winner.

Even this small workload shows the trade-offs. CFS gets every new job onto the CPU fastest. Aging priority finishes jobs soonest on average. Raising Round Robin’s quantum from 2 to 8 cuts context switches from 28 to 16, but nearly triples the average response time.

The animation

The video above is rendered with Manim (Python) straight from the simulator’s output. Nothing is invented at render time: each scene reads the trace files, and the loader cross-checks every trace against its recorded metrics before drawing. It walks through the six jobs, why they can’t all be satisfied at once, each algorithm in turn, and a final scoreboard.