Multi-Level Feedback Queue algorithm was designed to address problems associated with existing scheduling algorithms i.e. providing a good response and turnaround time resulting in a balanced scheduling algorithm which can cater to a general purpose CPU requirements.

It can do so without needing to have any prior information about the executing processes. It develops its knowledge about them on the fly and hence modifying its approach on how to share the CPU time between them.

Quality Metrics

Round Robin is the algorithm that is able to provide better response time usually, which is a very good choice if you have several interactive processes running that each needs a piece of CPU time on a regular basis. It’s able to do so based on a good selection of time slice length providing all applications an equal share of CPU on a regular interval leading to good interactivity.

Shortest Job First/Shorted Remaining Time First (preemptive version) is able to complete the jobs at a very rapid pace due to greedy selection of which task to process given the information about how long the task will run for. Hence, resulting in superior average turn around time.

---
displayMode: compact
---
gantt
	title Round Robin Scheduling
	dateFormat ss
	axisFormat %S
	section Process 1
	P1 : 00, 2s
	P1 : 06, 2s
	section Process 2
	P2 : 02, 2s
	P2 : 08, 1s
	section Process 3
	P3 : 04, 2s
	P3 : 09, 1s
gantt
	title Shortest Job First Scheduling
	dateFormat ss
	axisFormat %S
	P2 :a1, 00, 3s
	P3 :a2, after a1, 3s
	P1 :a3, after a2, 4s

The average response time is

  • Round Robin: (0 + 2 + 4)/3 = 2s
  • Shortest Job First: (0 + 3 + 6)/3 = 3s

The average turnaround time is:

  • Round Robin: (8 + 9 + 10)/3 = 9s
  • Shorted Job First: (3 + 6 + 10)/3 = 6.33s

SJF can be a good option but there is a very unrealistic assumption we make for it, “we are aware of the time a process will take to complete”, which is actually in-deterministic.

So let’s start with understanding how does MLFQ combines these two algorithms.

Multi Level Queues

As the name suggests instead of a single process queue we use multiple distinct queues with different priority levels. Q3 is given the highest priority and Q1 the least.

graph LR
	q3[Q3] --> A((A)) --> B((B))
	q2[Q2] --> C((C))
	q1[Q1] --> D((D))

The rule for any CPU to select a process to run is as follows:

  • Priority(A) > Priority(B): A runs B doesn’t, atleast until A’s completion.
  • Priority(A) = Priority(B): A & B run in round robin manner.

Now a snapshot of the queues at any particular instant doesn’t give a good idea about the dynamic nature of MLFQ which learns from previous execution of process and updates its state.

Priority Feedback

We can assume generally that an interactive/short job which give up its CPU time share to do some I/O or reaches completion early needs to have a good response time hence has to be prioritised, whereas a long running CPU bound process needs good turn around time but not a quick response.

So MLFQ with no details about the total execution time

Starts with assumption that process is either a short running or an interactive job and hence is placed in the highest priority queue.

Now to decrease priority over time we use the below rules:

  • If the process consumes its complete time slice we reduce its priority.
  • If the process gives up its time slice before its up, it stays at the same priority.
---
displayMode: compact
---
gantt
	title Priority Decrease (2s)
	dateFormat ss.SSS
	axisFormat %S

	section Queue 1
	P1 :a1, 00.000, 2s
	P2 :02.100, 1.4s
	P2 :05.500, 1s

	section Queue 2
	P1 :03.500, 2s
	P1 :06.500, 2.5s

Now there are two issues with our current system let’s try to remedy them

Starvation

In presence of a lot of I/O bound or short processes a long running CPU bound job sitting in the lowest priority queue will suffer starvation. Also it might be the case that a process started as CPU bound but now became I/O bound.

gantt
	dateFormat ss
	axisFormat %S
	tickInterval 2s
	section Queue 1
	P1 :crit, a1, 00, 3s
	P2 :a2, after a1, 1s
	P3 :a3, after a2, 2s
	P4 :a4, after a3, 3s
	P5 :a5, after a4, 2s
	section Queue 2
	P1 :crit, after a5, 4s

In both the above scenario we can use a new rule for priority boosting.

  • After some time period S, all the jobs are moved to highest priority queue.
gantt
	dateFormat ss
	axisFormat %S
	tickInterval 2s
	section Queue 1
	P1 :crit, a1, 00, 3s
	P2 :a2, after a1, 1s
	P3 :a3, after a2, 2s
	P4 :a4, after a3, 3s
	Priority Boost :milestone, 09
	P1 :crit, a5, after a4, 3s
	P5 :a6, after a5, 2s
	section Queue 2
	P1 :crit, after a6, 1s

S here is what we call a voo-doo constant that needs to be configured properly by a senior level engineer based on his understanding about the requirements of a system.

Game the Scheduler

Now an expert programmer who has read through the draft of our open source MLFQ standard can try to game our scheduler for his application to consume all the CPU time.

By manually inserting unnecessary I/O calls into the process just before end of time slice and then restarting after a short span, it can make the scheduler think that since process is giving up its time slice it can stay in highest priority queue.

---
displayMode: compact
---
gantt
	dateFormat ss.SSS
	axisFormat %S
	section Queue 1
	P1 :active, 02.000, 1.9s
	P1 :active, 04.000, 1.9s
	P1 :active, 06.000, 2s
	section Queue 2
	P2 :00.000, 2s
	P2 :08.000, 1.9s

To remedy this our algorithm can use better accounting here, in form of a new rule:

  • Once a job a uses up its time allotment in a particular queue we reduce its priority irrespective of how many times it gave up CPU time.
gantt
	dateFormat ss.SSS
	axisFormat %S
	section Queue 1
	P1 :active, 02.000, 1.9s
	P1 :active, 04.000, 0.1s
	Time Up: milestone, 04.100
	section Queue 2
	P2 :00.000, 2s
	P2 :04.100, 1.9s
	P1 :active, 06.000, 1.9s

Tuning MLFQ

Generally, we keep short time slice for high priority queue consisting of interactive processes and it keeps on increasing as the priority decreases and we reach CPU bound processes in low priority queues.

In some implementations of MLFQ scheduler the highest priority queue is reserved for operating system processes so user created ones can never enter there. In some cases user can issue command (nice) to increase or decrease the priority of a process.

Now, there are multiple parameters when it comes to configuring the MLFQ so “how do we set them for optimal setup?” is a question that arises in mind.

  • How many queues should be there?
  • How often should priority be boosted?

As a senior software engineer would say it depends

After my unsuccessful attempt at learning about operating systems through my bachelor’s degree program I have started reading the book Operating System Three Easy Pieces to better my understanding of it. OS is the key for understanding how our high level applications will function.

good bye

Thanks for reading through my blog, as always I look for your feedback which you can provide by reaching out to me @1108king.