What I have done this week
The threads now remember how much of their time slice is still remaining from the previous runs. A thread is preempted as soon as the remaining part of its time slice expires. This is different from the previous implementation, where the thread was always assigned a new full time quantum when it was preempted to.
The virtual deadline mechanism now applies both to the time sharing and idle threads.
What I will do next week
When a thread wakes up, it is simply put on the global run queue. However, the BFS firstly looks if there is any suitable idle CPU, or a CPU that is running a lower priority thread than the thread that just woke up. If such a CPU exists, an inter-processor interrupt is sent to the CPU which causes the CPU to reschedule.
If there are more idle CPUs, the BFS does not choose one by random, but according to the CPU cache locality. When the BFS is initialized, it builds a topology map of the CPUs present in the system. The most suitable idle CPU is chosen according to this CPU topology map. I will also implement this part into the FBFS scheduler, but probably not the next week.