 All right. Good morning. So we'll start off with some good news Lab six is gone So yay Yeah Book to Ashton last night. So lab three got extended to it's extended a week. So you have three weeks for it So that's pretty sweet Then there will be a new lab four that I'm making so you'll get a taste of my labs It shouldn't be terribly bad. Yeah Sorry Yeah, so Lab four will be new and then we're essentially going to combine old lab Old lab four and five Which was like first you made like a concurrent web server and then you added caching to it in lab five So we'll give you a concurrent web server that probably has like a little mistake in it You need to fix that should be like the weak part of it and you'll have three weeks for that combined lab So it'll yeah, so it'll be a new lab four then we're gonna combine lab old lab four and five Give you some code and make that three weeks as well So one last lab. That's all good and you'll get a taste of my lab. So hopefully my lab will be slightly less well Slightly less time-consuming. Let's say so Yeah, and that way Lab three due date doesn't conflict with next week's quiz and all that fun stuff also the other classes behind so Scheduling will not be on the quiz. So this is just bonus for next quiz and then next week I think I'll start off the week with like a lab for primer So lab four will just pretty much be combining a whole bunch of system calls We saw earlier. We need practice about it anyway So I'll do a lab for primer and then if you let me know on discord anything else you want to do Because we essentially have two free lectures. We can turn into quiz review or just do whatever do more locking examples Or something like that So let's get on with today's lecture and then we can yeah, we can chill out for today's lecture so We'll be talking about more scheduling and what actually happened. So what One of the things the student mentioned last time is well if we have scheduling and a bunch of processes We might want to add priorities some processes may be more important than other ones So you could assign each process of priority, which is just a number which essentially this says how important they are so you can Make the priority number whatever you want Usually it makes sense to say the higher priority processes run first and sometimes that corresponds to a higher or lower number You just need to be consistent and then so we always run higher priority processes first And then you might round robin processes if they are equal priority and this can apply to both preemptive and non-preemptive processes So you can assign processes a priority. That's just an integer You can pick a lower or higher number to mean high priority again That's kind of up to you annoyingly Linux due to historical reasons has both So in Linux for normal processes The lower number the higher the priority so if it's negative 20 It's the highest priority and it is 18 or sorry 19. It's the lowest priority What also may happen if you have priority and you always prioritize some processes over another is it may lead to another case of Starvation when there's a lot of higher priority processes. So that means that you would Prioritize the higher priority process and that means a Lower priority process would never be able to run because you know, there's too many high priority processes So that is another case of starvation which you have to be Like it's like week five like geez So you might starve processes if there's a lot of higher priority processes and you always run them instead of lower priority processes So one solution you can do to combat This starvation problem is to have the operating system dynamically change the priority So if you're a high priority task and you've run for a long time Well, maybe we bump your priority down to try and be more fair to the lower priority classes or Processes and then whenever they have a chance to run we can bump up her priority again So if it's an old process that hasn't been executed a long time You could go the other way too and just increase its priorities. So eventually it gets a chance to run So with priority is another problem So we still have that old starvation issue that we discussed before but now we have priority inversion Which is a new issue. So you can accidentally change the priority of a low priority process Essentially to a high one caused by dependencies So if a high priority process depends on a low priority process Well, it's not going to be able to make process It would just be scheduled over and over again, and it's essentially blocked on a lower priority process So if it's dependent on that low priority process to actually make progress on your machine that low priority process Should at least temporarily be treated as a high priority process because they're a high priority process depends on it So one solution you could do to this is something called priority inheritance So if one thread is or sorry if one process is doing something dependent on another You inherit the highest priority of the two processes And then of course you could recursively do this to chain or to chain priorities together So that everything that if you have a dependency chain They all have the highest dependency and that way you can hopefully resolve that dependency And then whenever that solve everything can reset to its original priority so there's also kind of different tasks are kind of at the at odds with each other and They are given names. So there's some people Divide processes into what they call foreground processes and then background processes and foreground processes are generally Applications of users direct or interacting with directly and background processes. You don't really care They're just doing some computation in the background. You might not even care about the response time as long as they finish quickly So in unix you can't you can also background processes yourself When the process group ID differs from its terminal group ID But that's like something you will never see if you look an H top you might be able to see those numbers But that's just like something that's good to know essentially just means it's not connected to a terminal So you do not need to know the specific definition But the idea again is just to separate processes that users interact with so foreground processes You can prioritize having good Interactivity and low response time while background processes. You might just care about you know lowering the average waiting time Yeah Yeah, so if a high priority process is essentially waiting on a low priority process That low priority process should be treated as a high priority process just to resolve that Otherwise, it's the scheduler would just say hey, I want to run the high priority process over and over again It's not going to make any progress, right? Yeah, so temporarily if there's a dependency the low priority process would inherit the high priority processes Priority and then that way when it yields They could actually schedule the one that's dependent on and then it would you know do its computation then resolve that Dependency and then it can go back down to its original priority After it's done, whatever the high priority process is waiting for it to do Yeah, so if their processes right if there's a dependency between them They have to do some inter process communication and that way the kernel knows about it So the kernel know that there's communication between those two processes And then be able to switch the priority temporarily Yep, cool So Also, you might be able to use multiple different cues So if you have foreground processes and background processes You could treat them differently and use two different scheduling algorithms So you could just round robin the foreground processes because you care about Interactivity and being fair and having a low response time So you could round robin all the foreground processes and then for all the background processes You could just use first come first serve if you just want to kind of complete them as fast as possible And you can't predict Whatever the shortest job is running first but Now you because you have multiple cues So you have a cue for foreground and a cue for background processes You also have to schedule between the cues. So when do I cue a foreground? Something from the foreground verse. When do I cue something from the background? so you could round robin between the cues or you could treat the Foreground cue is having a higher priority than the background cue and kind of trade-off between them and this is where scheduling gets complicated where there's just no right answers. It's just a Continuous series of trade-offs that just kind of feel better So like I said, no right answer only trade-offs and we haven't even talked about how you schedule for a multiprocessor system So if you assume symmetric multiprocessing, so remember I showed you name before that said SMP So that's what SMP stands for It basically assumes that all CPUs are connected to the same physical memory and that they all have their own private cash At least at like the lowest cash levels of like L1 L2 cash So that's what symmetric processing means. It assumes all the CPUs are the same. They all use the same memory and They're connected or they all have their own private cash So one approach if you want to schedule multiple cores on your machine is just hey I'll just use the same scheduling algorithm. I've always used so I just have only one scheduler But I just keep adding processes while there's available CPU. So if I have, you know, nine processes I need to schedule and Ten CPUs. I'll just keep giving those processes while there's still available CPUs And then as more process comes on I fill up all the cores then I have to worry about switching between them But while there's still some CPU cores, I can just go ahead and give a process directly to a core So some advantages of this is it's got good CPU utilization I'm trying to feed all the cores as fast as possible It's pretty fair to all the processes But it's also not super scalable since everything blocks on the global scheduler since we know about locking now This queue would have to be managed by the kernel and because all CPUs are using it You can just assume that the queue would have to be protected by a mutex So it essentially makes it only single threaded. Yep So this is just So for just a single core system, you can just do round Robin or something like that and this isn't a problem So this is problem-specific if you have multiple cores and what you have to do So if you do have multiple cores, right, you can just give out you can just keep your same queue Your same scheduling algorithm, but just keep on giving processes as you have more cores So you don't actually have to change that much But it's not going to be terribly scalable because now You have only a single scheduler and it would have to be protected by a mutex makings essentially single threaded and It's also got fairly poor Cache locality because the see the processes whenever they context switch in and out might change between CPUs so if you Context switch a process, you know in and out on the CPU it still might have some valid cache entries So it would actually be faster if it stays on the same CPU Well, if you just give it to any old random CPU you essentially would trash its cache every single time And it would have to refill its cache So that's another disadvantage But this scheduler is actually one of the first ones used if you go way back and figure out What year Linux 2.4 was out? This is the exact scheduler that's in it So let's go to the future where we might think of a better idea So we could create a scheduler for each individual CPU so Now since each CPU has its own scheduler There's no global fight over it They wouldn't have to have a mutex because each CPU essentially has its own queue or its own scheduler So now when there's a new process comes in your goal is just hey I need to assign you to a CPU and then you'll stay on that CPU and be bound by its scheduler So the advantages of this is it's pretty easy to implement You just take what you implemented before with a single scheduler and just do it as many times as you have CPUs which isn't too bad and It's also going to be pretty scalable because there's no fight over any global data structures You don't have to have a mutex or anything like that just one core gets its own scheduler and that's it And it's also has good low cache locality because a process is not changing between Different CPU cores, so if it gets context switched in and out it might still have some valid entries in the cache and That's good because if you don't you use your cache to speed things up Disadvantages of this of course is that you might have some load and balance as you use your system So that may be fairly fair if all system or if all processes Run for the same amount of time and last for the same amount of time But you could imagine getting fairly unlucky where you schedule All your short running processes on a single core and all your long running processes on all the other cores And then suddenly that core finishes all the processes and there's no new ones and then suddenly that CPUs just Cores just sitting there idle and not doing anything. So you can have some load and balance just from However, you happen to assign your processes and however the processes happen to run Yeah So everything so in this approach at least what we're discussing right now As soon as you assign a process to CPU core it stays on that core Yeah, so if it gets context switched out it would assign, you know pick another process I got scheduled to that core But then whenever that process gets scheduled out it could switch back to the original process Right, so if you have something like I don't know what's some run So you have some long running computation process that's running on that single core It's running running running and then it gets context switched out to run like LS or something So it would get context switched out LS would run Do its thing probably not touch that much of cash because it's not that big of a program and then it would context switch back in that big computation and Because it context switches back on the same CPU core it would still have a lot of valid cache entries Because it's the same CPU core. So under this scheme as soon as we assign a process to a CPU. It will always run on that CPU if it gets chosen to So it'll stay on that CPU and won't move between CPU cores All right, any other questions Cool All right. Well That you can imagine something better that you can compromise between this global This global data structure and the per CPU scheduler So you could keep a global scheduler, but just reduce the amount of work It needs to do so everything is not dependent on it So you could have a global scheduler that basically just checks if a CPU is idle And if a CPU is idle it can do something it can just steal a process from another CPU core and Move it to the idle CPU Just pretty much to put it back to work, right? so if under this if this does happen and you Allow the scheduler to essentially steal processes and shift them between cores Well, you might want something you might want to control what actually happened so there might be a process you really really care about that you don't want to be moved between cores and They might be really sensitive. So you want some form of control where you can keep it on the CPU core If you absolutely need to and that is where processor affinity comes in so Every process has a affinity and basically that just assigns a value to it that says what this Processes preferences to be scheduled on the same core. So if it has a high affinity it would Prefer to stay on the same core and not switch between cores. So that's what that means You might see this too in you know Windows task manager and stuff like that. You can actually set core affinities and Try and when you try and increase your performance You just you know if you have a game or something you might want your game to only run on a certain core and stay there So it never gets context switched out and then we can you know, you can have lower latency and own people So this is This is again another scheduler the kernel actually used So this is like the simplified version of a it was called the 01 Scheduler that was in Unix one point or sorry in Linux 2.6, which is you know, probably like 30 years old or 20 years old. No like 30 years old So we're getting closer to modern times so another strategy in Terms of you know bad Kind of unfortunate naming conventions. It's called gang strategy. So this is more for high performance computing So you might be in the case where you have multiple processes And you might want to schedule them all as a single unit and schedule them all simultaneously So the scheduler on each CPU can't be completely independent if I want to run all the processes at the exact same time So you might use something called gang scheduling It's also might be referred to as co-scheduling that allows you to run the set of processes Simultaneously and essentially they are all scheduled as one big unit But of course, this would require a big global context switch across all CPUs because they all need the context which Essentially simultaneously to schedule all the processes, but you will only really find this if you ever go into like high performance computing So there's also a another type of scheduling called real-time scheduling and it's yet another problem So real-time means that there are like certain very very hard time constraints So either for a deadline or a rate so for audio is an example of a real-time system So you have a certain bit rate. You need to keep up with that bit rate Otherwise your audio will sound garbled and it will just be completely trash So you have to maintain that weight that rate otherwise You know, you won't hear clear audio and it will essentially be useless Another thing that might be very important is something like an autopilot So it needs to react within a certain time period or otherwise your plane crashes and that's no good So hard real-time systems There's kind of two times of real-time systems hard real-time systems are like they are Absolutely required to guarantee a task completes within a certain amount of time and that is a very very difficult thing to do and typically it's meant for like Microcontrollers and things where you can actually count the number of clock cycles that an operation might take to guarantee it happens You know within ten microseconds or something like that So if you do get into like embedded systems in real-time development, you'll pretty much be using You'll pretty much be using, you know your own little chip and you will not probably not even have a kernel or anything on it You will program directly on it and you'll be able to essentially count the clock cycles and Figure out that it meets some type of timing constraint and It essentially for any like big kernel like the Linux kernel You essentially can't do hard real-time because there's it's too complicated too complicated and there's too many things going on So you would have to argue about, you know the scheduler when things can be made You'd have to you'd have to you argue about its runtime in the worst-case scenarios Where all the caches don't seem to line up and you'd have to understand everything about a complicated CPU and all that and it's pretty much impossible but the alternative to a hard real-time system is you use what's called a soft real-time system where Critical processes just have a higher priority process and your CPU is so fast that in practice the deadlines always Always met and you just don't really worry about it So audio might be hard for like a very small embedded microcontroller that runs at like a hundred megahertz But for your big, you know your big desktop system that runs at like five gigahertz I mean any audio thing is going to be done so fast that it's going to keep up with the rate. No problem so Linux is also an example of soft real-time and you are It's something you can It's something you can specify when you create processes and when you change their priority so in terms of these Real-time tasks generally you want the scheduler to be super predictable So the scheduling algorithms like first-come-first-serve and round robin that we saw yesterday are actually Implemented in the Linux kernel and actually are in use and are perfectly valid scheduling algorithms So you can actually search if you're you know crazy enough you can go search the Linux kernel source tree and look up them So anywhere you see schedule FIFO that's first-come-first-serve and schedule round robin is round robin And you can see exactly how they implemented and see Exactly what's going on. So it's as you would expect And What they do is use kind of a multi-level Queue scheduler for processes with the same priority if they're soft real-time tasks So it will always do tasks of a higher priority before doing a lower priority task And it would use you know first-come-first-serve or round robin as you see fit And then that would also do what we said before where you know to avoid starving processes The operating system can kind of dynamically adjust the priority if it's taking away too much time So on Linux there's going to be two types of processes. There is soft real-time processes Where it always schedules the highest priority process first And then there's normal processes that adjust priority based off aging and do some other things We'll see the actual scheduling algorithm But this is enough to see To kind of understand what the priorities are and understand what top is telling us So on Linux the soft real-time processes are always prioritized And they'll either be scheduled with first-come-first-serve or fifo or round robin And there's a hundred priority levels and due to historical reasons They're given the priority zero to 99 and this is where things get weird due to historical reasons So with these 100 static priority levels Zero is lowest priority and 99 is the highest priority And then so those are soft real-time processes And then the Linux kernel also separates that from normal processes where the normal scheduler runs Which if it's an older version of Linux it runs that 01 scheduler So other processes would just be under the sketch normal flag And by default their priority is a zero And the priority ranges from negative 20 to 19 Where unfortunately negative 20 is the highest priority and 19 is the lowest priority So processes can also change their own priorities with system calls Nice and sketch set scheduler And let's go ahead and see Some processes in our VM. So now we can actually read this kind of So in here, can everyone read that kind of All of it All right, so Let's follow my cursor. So here there is these are all the processes and threads running on my machine Or on my VM And we see the first process here is pid one. So that is a knit and we can see it's system d And then so that's at the first column what the process id is and then there's some others There's user. So the user of the first priority or the user of the first pid one is root And then pri is its priority Which is on kind of a big global scale and this is where things get kind of Weird So its priority is a 20 on the global scale and then there's this ni Which is its niceness And they're separate So The niceness follows under that normal scheduling So a niceness number will be between negative 20 and 19 and Yeah, so a niceness will be between negative 20 and 19 and by default it will be zero So it's niceness of all the processes I see here are all zero So they're all the default priority. They're all the default priority and they're all normal processes So if I look through them, so I'll look just look at this pri column and this Nice column or the ni column if I look through all my processes and here all the If you look at the command on the right all of the text in white means it's a process And if it's in green it means it's a kernel thread So for example, this process gdm here. This process has two extra kernel threads in it So let's go through and see if there's anything with interesting priorities So here's this process So this process its niceness is a one. So it's a slightly lower priority Then so it's a slightly lower priority But then if we look down two more We see that this RT kit demon Its priority is RT, which is real time, which is the absolute highest priority So the kernel would schedule that before anything else if it wants to run But for the most part it's just sitting there sleeping and it's blocked So Annoyingly, I'll just illustrate this So the priority column that pri column is essentially this line on the bottom here So the priority line on the bottom is essentially a way that the Way to for the kernel to unify normal processes and soft real-time processes So it essentially takes all the priorities from normal processes and Soft real-time processes and places them on the same number line Where the lower number the higher the priority So everything falls on the same number line and then all the normal processes which are over here So all the normal processes which are over here on the right essentially map To positive values on this global priority line So if you're a normal process that is between negative 20 and 19 It essentially just shifts it up so that on the global priority line If it's zero or above, it's a normal process and if it's negative, it's a lower priority Don't ask me why it's kind of weird and then for the soft real-time tasks that should have a priority of zero to 99 where zero is lowest and 99 is highest to map it back onto this to make it make sense it essentially Makes it negative and then subtracts one from it So if it's a 99 and like the soft real-time process on the kind of unified Line it would correspond to a negative 100 and if it is zero soft real-time So lowest priority it would correspond to a negative one So so if we go back here if we see any negative numbers in the pri column that means it's a soft real-time one and Unfortunately, if it just says rt there, it means it's a negative 100 And in that case the niceness doesn't really matter All right any questions about that to read this? Because it's kind of weird that they do that So for the most part the lower number the higher the priority except if it's you know The historic soft real-time things where the higher number is Higher priority and then they just make it negative Why I don't know All right any questions on that we can explore Okay, so cool. There's a real-time task here. Let's go through and see if there's anything else interesting So here we can pick off Yeah, so here we can pick off This is a normal process because it's global priority is 39 and its niceness is 19. So it's the highest number. So it's the lowest priority So essentially Gnomeshell has a thread that it doesn't really care about Yeah, so also Yeah, so also going back to this So essentially the niceness number only applies if it's a normal process So if the priority is zero or above Niceness applies otherwise the niceness number if it's a soft real-time doesn't really apply and you can kind of ignore it So for this real-time one its priority is negative of 100 So it's soft real-time and we can kind of ignore the niceness So hopefully that answers the chat question So let's go explore. So there's one thread in Gnomeshell that apparently It shouldn't care about because it has a niceness of 19 So we go through here. There's another thread here In my shell that doesn't seem to matter And let's see if we can find anything else Yeah, so when it's a soft real-time so the priority column is negative the niceness essentially doesn't mean anything So here we go. We have some more things VS code wants apparently 44 gigs of memory. That's cool That's holy crap. There's a lot of processes running So here we can see So here Process ID two and it doesn't have a parent. So this is an internal kernel thread So we can actually look at some kernel threads. So there's a internal kernel thread here That has a normal priority and it has some higher priority Things that are just kind of scheduled as a process So Which you can read what they do It's not super entertaining and then there are some high real-time Kernel threads here. So there's this migration which essentially is doing the context switches So its priority is real-time. So if it needs to context switch, it's a very high priority So there's going to be one of these per cpu. So this is you know the migration for Core zero then there's one for core one core two and core three So they're all scheduled as real-time because they are very very important And I think that's pretty much it. Oh, finally here this watchdog one. So its priority is negative 51 So it's like we Half care about it. It's a soft real-time, but it's not like the most important real-time But we kind of care about it So you can just kind of go through and see, you know, we can actually read these numbers now And understand kind of what the kernel is doing All right. Any questions about this fun adventure through htop? Cool Yeah priority above 30 Here 39 So it's just so the higher the number the lower the priority in this So Yeah Yeah, so the priority number in the Like third column there Corresponds to this big global priority number So this is like the global priority for the Linux kernel where if it's negative It's soft real-time and if it's zero or above. It's a normal process And then this annoyingly is the soft real-time priority, which was between zero and 99 where Higher meant a higher number, but for all the linux to kind of Unify everything they just use a big global priority number Where lower is higher priority in what yeah, we saw it so when it was we saw a negative 51 And when it says rt that means negative 100 Yeah Yeah, yeah, so niceness corresponds to this negative 20 to 19 And then it would just be shifted on the global priorities scale So they'll always be off by the same amount. So If it's if the priority is, you know, a zero or above You just subtract 20 from it and that's its niceness Well, yeah, so for historical reasons, they just did it like that Yeah, so you just ignore nice priority when it's negative. It's a real time niceness doesn't mean anything then Okay, so this is going to be the linux scheduler evolution. So we saw that On that global queue it was simple but had poor performance on multi processors and with many processes Then in 2.6, there's that per cpu scheduler that was called o1 that was fairly complex to get right and had Interactivity also had issues and it had no guarantee of fairness. So that was that one that could steal processes between cores and move them around then Since 2.6 point 23 Which is the current scheduler They opted to create something called the completely fair scheduler or cfs And it is fair allows for good interactivity and it's been used for like the last long time so the o1 scheduler has issues with modern processes so It would have been nice if you could actually separate out foreground and background processes Which was easy with a terminal So you could tell what processes are connected with the terminal But nowadays we have GUI applications and you can't always tell if they're connected You don't know if the user has them focused So on and so forth There's a lot you don't know that the kernel just can't know about the processes so it can't help with scheduling them So if you're the kernel you could try and detect them So you could try and make some heuristics that try and determine whether or not a user is interacting with a process or not For example, you might assume that processes that sleep a lot might be more interactive. So they like Interact to some keyboard input do something really quick and then go back to sleep So if you detect a process behaves like that, you might be like, uh, I guess it's an interactive process Maybe I'll prioritize this in my scheduling a bit more But it's kind of ad hoc and could be fairly unfair and you could also guess wrong So if you want to introduce fairness for different priority processes What you could do While still being slightly fair is to use different size time slices So we essentially will give higher priority processes A proportional more cpu time But still have low priority processes get at least some cpu time to be fair So this is better, but is also kind of ad hoc and hand wavy. You're just kind of guessing ad hoc So ad hoc just means I'm just guessing like it's just a complete shot in the dark I'm just making something up. It's just a fancy way of saying I'm making something up so When you make something up, you might want to consider the ideal situation So with like ideal fair scheduling You could assume that you have an infinitely small time slice and then if you have end processes You would just give them each one divide by n of the total cpu So if you have one process you would just give it the entire cpu time Then if you have three processes you just divide it up equally between them Give them all a third of the cpu time and then everything's completely fair So it would be evenly divided along every processes. You can't get any more fair than that So here is some scheduling. So if you have three or sorry four processes come in at the same time t equals zero And they all have different burst times thankfully a multiple of four So process one takes eight time units process two and four take four time units Then process three takes 16 time units So just to make this easier each vertical slice here is going to be four time units and each box represents, you know, one of those time units so So at time zero if I want to be completely fair and divide up my four units of cpu time I would give each process a single unit of cpu time So each of my processes would execute for one time unit So process one two three and four each execute for one time unit then for the next four Next four time units. I let them all execute one more because they're not done yet So they all have executed for two time units now Then I do this again for three and do it again for four Then after four there's only going to be two processes left So I can give them each Divide up that four and give them each two time units of cpu time So if I do that after t equals 16 Why can't I highlight it? After t equals 16 process One and three both get two time units So they have executed for a total of six time units now and then if I'm being completely fair There's still two processes. So I give them each two units of cpu time So now p1 is done after its time units and now Since I only have one process left it can get all four time units. So it would go from Eight to 12 because it's using all the cpu cores at this time Or all the resources at this time And then it would do 16 and it's done All right, does that make sense to everyone? I'm being completely fair here So it's fair, but it's also insanely impractical Um that each processes gets the same amount of cpu time It would boost interactivity because it has the ideal response time It just you know assumes infinitely small time slices and because goes immediately But in practice, you know, you can't do things instantly And context switches take time So this just wouldn't happen because there'd be too way too many context switches And you also have to constantly scan all the processes which is on so Every single time I have to scan all the processes give them all Equal share the cpu time and then switch between them and it's just Not feasible at all So this is where the completely fair scheduler comes in so for each Runnable process you assign it a virtual runtime um And then at scheduling point So at each scheduling point where a process runs for Some time slice you increase the virtual time by however long it actually ran for Then times of weight based off its priority So if it's lower it would if it's a higher priority task which corresponds to a lower number It would increase that virtual runtime by a smaller amount than a low priority task and then Because of this the virtual runtime monotonically increases, which is just a fancy Mathward monotonic functions. It just means they never decrease So it will only ever increase or stay the same. So the virtual runtime never goes down. It only increases So then what the scheduler does is all it does is select the process with the lowest virtual runtime So and then it computes how long it needs to run for How like how long its time slices kind of based on what it would be What would be ideal based off the number of processes currently running on the machine So then it allows the process to run for whatever time slice it determines And then when the time slice ends it just repeats the process So after its time slice it would have increased its virtual runtime And then the scheduler would run again and just pick whatever process has the lowest virtual runtime So your completely fair scheduler would be implemented with a fun red black tree So those are actually used. So here's you probably learned that in did we learn that before? All right, we learned that yesterday. So now we see where it's actually used So they use a red black tree for the completely scarce Completely fair schedule. It's we all know it's you know a self-balancing binary search tree So it's keyed by virtual runtime, which only ever increases So if you insert a process when it's done its time or need to delete it when it's dead or update it That's you know logarithmic time And if you need to find the minimum which is what the scheduler does right Whatever it needs to make a scheduling decision just picks the lowest virtual runtime So that's just oh one that's super super fast. So that's why the scheduler would use it So the virtual runtime it keeps track of everything on like nanosecond level granularity And that way it doesn't need to guess the interactivity of the process. It just kind of nicely fits in this So it will tend to favor iobound processes by default because they just Essentially have little short bursts of cpu Time which would translate to like a low virtual runtime. So they only run for like, you know, maybe a few micro seconds And then give up their time slice voluntarily So they wouldn't have increased their virtual runtime that much So whenever they get new input again and are ready to run they would be They would have a very low virtual runtime and would probably be at the top of the tree And then would be scheduled to run immediately. So it gives you a nice in it essentially prioritizes those tasks to let you have really good interactivity with them And it just kind of falls out of how the scheduler is implemented All right. So does that make sense? Cool. So that good timing with red black trees So now scheduling gets even more complex. We got more solutions and more issues So we introduced priority and that was Good and bad because it also introduced priority inversion, which is not a good thing And it's just a more complicated thing to deal with Then we saw that some processes might need good interactivity others You might only care about whenever they finish and you might want to distinguish between them Then we saw what happens if you need to scheduler across multiple cpu cores and one solution is just to make a scheduler You know Require port per cpu cool cues or schedulers for it Then we saw a real-time briefly and it requires being predictable And then finally we saw the completely fair scheduler Which is actually used by the Linux kernel and essentially everything else and that tries to model the ideal fairness And that's it. So just remember I'm pulling for you. We're on this