 All right, quiet rainy day. So I have some good news. Had a meeting with Ashvin yesterday, so Lab three, or sorry, yeah, Lab three is pushed back another week, so you have three weeks for it. Lab six is deleted, so there would be no Lab six. And then what we're gonna do is I'm creating a new Lab four that is going to be on pipes and making processes and all that fun stuff. So this will be my creation of a Lab, so hopefully it will be fun and doable. So I guess we'll see, so you'll have a flavor of what I do for Labs and then for the last Lab, the web server one, we're not gonna make them depend on each other anymore, so we'll give you a threaded server with maybe a bug in it and then you'll have three weeks to do the last Lab, which is like a combined Lab four and five that's currently on the website. So that will hopefully make things more doable, it stops there being a Lab due when there's a quiz. So also the other class is kind of behind, so the scheduling stuff is not going to be on the quiz, so the quiz is going to be processes, threads, locks, and all the fun questions you can ask for that. So next week we have kind of free. So early in the week I'll do like a Lab four primer where we'll have an example, we'll use a pipe and we'll kind of see how to use some of the tools you'll need to use for Lab four and then other than that for next week we can do review or just let me know on Discord what you wanna see. I'll go through Ashwin's stuff again to make sure we're not missing anything, but if we want more practice on threads, processes, locks, we can do that next week. But while we're at it, let's just wrap up scheduling. So after the next week, when we have our quiz, we can go into virtual memory and all that fun stuff and actually learn what an MMU is. So we talked about scheduling before, it was fairly basic, we ended off doing round robin and today we're going to, one of the comments last time was, well, what about if we added something like priorities? So we can add priorities, that seems like a good idea, but it's also going to complicate our life. So if we add priorities, that means we want to favor some processes over other processes. So if you sign each processes priority, then you have to decide what you want to do with that. Do I always want to run processes at a higher priority or do maybe I just want to run them for longer or what does that even mean? So if you do decide to just run higher priority processes first, you could just round robin all the processes of equal priority and then after those are all done or sleeping or blocked, then you can move down to the next lowest priority and round robin all those and go through that and that is one valid thing you could do. So when you add priorities, you have to give them a number, the easiest thing to do is just assign a number for a priority, but then you have to pick, does a higher number mean a higher priority or does a lower number mean a higher priority? So the way Linux decided to pick it is they decided that the lower number means it is a higher priority. So for normal processes in Linux, if you see a minus 20, that is the highest priority for normal processes and a 19 is the lowest priority. So when you have priorities that also might lead to starvation that we touched on last time. So if you always schedule processes with a higher priority, then you may starve lower priority processes. If there's enough higher priority processes that take up the entire CPU time, then you might starve lower priority processes because they just might never run, which would not be fair and you can just starve them out. So if you want to avoid that problem, if you're the OS, one thing you could do is you could dynamically change the priority. So if a low priority process hasn't been executed in a very long time, you could temporarily bump up its priority until it finally gets to execute and then after it executes, you can reset it back to its original priority or you might lower the priority of a process that's been hogging the CPU. Either way is a solution. Yep, just because. I don't know why they picked this range. It's a range of 40. We'll see it gets weirder. We'll see it like a normal range. We'll see it. Weird historical reasons. I don't know why exactly it was 40. They figured 40 was enough. So now that we have priorities, another new issue we have is something called priority inversion. So you can accidentally kind of change the priority of a process. So you can accidentally change a low priority process to kind of behave as a high priority process and that's caused by dependencies. So if a high priority process depends on a low priority process, well, if it just yields its CPU time away and you always prioritize the highest priority process, well, it'll just get woken up again and again and it's waiting on a low priority process. The only way to actually break that dependency is if you schedule the lower priority process as a high dependency process because a high priority process is depending on it. So that's one solution and it's called priority inheritance. So if you detect that one process depends on another one, then you would want to make both processes have whatever one has the highest priority. So you just inherit the highest priority number between the two processes. And the kernel is going to be able to detect this because if there's a dependency between two processes, they must be communicating together and we know that all communication has to go through the kernel. So the kernel can actually detect a dependency. So if there's two processes that are communicating together, then it would make them the highest priority of the two. And then of course it would also have to do this recursively to chain across multiple dependencies if they're needed. So if A depends on B and B depends on C and C depends on D and A is a super high priority process, then the kernel would have to make all the processes temporarily a high priority. And then whenever that dependency is broken, it could revert them back to their original priorities. So that is how we would deal with priority inversion. So another thing you might do when you do scheduling is well, you might want to separate the processes into two categories. A common historical way is to change them into foreground processes and background processes where a foreground process is connected to a terminal which is something that you type on and you see the results for. So any foreground processes are ones you are literally typing into and you would want them to be more responsive. While background processes are something you don't interact with which is probably gonna do computation. So maybe you don't care about response time for those and you actually just want them to be completed as quick as possible. So maybe you want to reduce the average waiting time. So you separate your processes, one cares about response time, one cares about average waiting time. So you could use a different scheduling algorithm for both of these types of processes just because you have different needs for both of them. So that's one thing you could do. And then if you do that, you essentially have multiple cues for each processes and you schedule them differently. So for all your foreground processes you could just use round robin because you want good response time and for all your background processes you might just use first come first serve just to make the scheduler as easy as possible to implement and just make decisions as quickly as possible and then reduce the number of context which you have to do because it's just some long rank computation you don't actually care when it finishes. But now you only have, you'll still only have like one processor on your machine and now you have to schedule between cues. So maybe you round robin between foreground and background process or the foreground and background cues and or maybe you want to do all the foreground processes as a higher priority than all the background processes. And so your foreground cue is a higher priority than your background cue. There's all types of options you can do once you try and optimize things in scheduling because there's no clear answer and every process has different demands in what they care about. So there's no right answer, only trade-offs unfortunately that is our lives as engineers. And we haven't even talked about scheduling for a system with multiple cores on it because it gets even worse. So we'll assume symmetric multi-processing so that's SMP which is what the kernel says so it's SMP preempt. So what SMP is, it assumes that all CPUs are connected to the same physical memory so they all access the same physical memory and that they have their own private cache at least at the lowest level. So at least they have their own L1 and L2 cache. There are systems where CPU cores are not all connected to the same memory and programming them is very, very fun, not really. Oops. So we'll just talk about a normal system that assumes all CPUs are connected to main memory because that just makes our lives infinitely easier. So if you have multiple CPUs now, well I could just use the same scheduling algorithms we talked about and just tweak it slightly. So I just maintain only one scheduler, it could be first come, first serve, it could be round robin. And instead of just picking one process to run on one CPU, I will keep picking processes off using the normal round robin and giving them to the next available CPU until I utilize all my cores. So that's one easy solution, yeah. So the question is, is there a kernel that just takes over a single core and just stays there? Yeah, so you could I guess have the kernel just only execute on one CPU core forever? Would you use that system? Would you install an operating system that if you have like a four core machine, you can only use three now? Yeah, the goal of the kernel is just stay out of the way and let you run your applications because really that's all anyone actually cares about, right? The whole, yeah, the kernel's job is just to be transparent and let you run your stuff and essentially get out of the way. But that's something you could do but I don't think anyone would use that kernel, unfortunately. So if you do this and just do round robin or something, just chuck it on CPUs as they're available. Well, you get good CPU utilization because you're just feeding all the CPUs any running processes they can and it's pretty fair to all processes. Like you could just use round robin between it. But the disadvantages of this is it's not going to be scalable because we only have one scheduler. So there's essentially one global lock around that scheduler that is divvying up, putting the processes on all the CPUs so there's only one thing making the decision. And the other problem is it has poor cache locality because when you context switch processes they might swap what CPU core they're actually executing on which would invalidate all their caches and it would have to repopulate them again which is going to be slow. But this scheduler is the first real schedule we'll see. It was used in Linux in 2.4 which if you look at the timeline that's probably like, how old is it at this point? It's probably like 40 years old at this point but this is one scheduling algorithm that was actually used. So we've seen our first real scheduling. But, oops, better solution would be that hey, if having one scheduler is bad and that creates a global lock and if I have a bunch of CPUs maybe I want a scheduler just runs independently on each CPU so then I don't have to put a lock or a mutex along it. I know only one CPU has one scheduler and it doesn't have to fight their completely independent. So you could do that. So each CPU just has a round robin scheduler that it keeps track of and then when there's a new process the scheduler's only job is to just assign it to a CPU. So if you have eight or four cores and eight processes then the global scheduler would just put two processes on each core and then they would stay on each core for as long as they're running. So this is easy to implement. It's just, you just do the same scheduling you just did but just once per core and you just have to decide what core process goes to. So also scalable because everything's independent it's got good casual quality because all the processes stay on their CPU. So if you had like a super long running process that's doing some computation and it gets context switched out and it switches in something like LS that doesn't do that much. Most of your cache would still be intact so when you context switch back in that process it's doing this big computation. A lot of your cache would still be valid and it could just keep on running and doesn't have to repopulate the cache so it would be actually really, really fast. Disadvantages of this is you might have some ridiculous load and balance over time as processes come and go. So if you just have one CPU core that's happened to get all the very short running processes on it it might be that it finishes all those processes and all the long running processes are on all the other cores and if all the processes to stay on their core there'll just be one core that's idle now that's not used. So it's not gonna be able to use all your CPU resources as they kind of come and go. But you can make a compromise. That sounds like it has a fairly easy solution. You can detect if there's an idle CPU so you'd have a global scheduler that essentially rebalances the per-CPU cores so if one of the CPUs are idle it can just take a process from another CPU which is called work stealing and just give it to the idle CPU so it has something to do. But if this happens you might actually want some say as into what processes can actually switch cores. So you might want some control. If you have a process that you know is really, really sensitive to its caches then maybe you don't want that process to switch cores and you would prefer that if it's stay if it executes on a core it stays there and maybe you have some other process that you don't care about if it switches. So the configurable option for that is called processor affinity which if people are using Windows at some point if you go into task manager that's one of the options you can see if you right click on a process you can set its affinity and basically that means the preference of that process just to stay on a single core which will essentially get you some better performance. So if you have, I don't know like a game or something you really, really care about performance you could just essentially set it to only run on one core and not move and then it would maintain its cache better so you're sure it doesn't move. So this is another scheduling technique that is actually used in the Linux curl. This is a kind of simplified version of the 01 scheduler and that was used in Linux like 2.6 which now we're like only 30 years old at this point which is actually pretty good. Oh, okay and now an aside if you get into like super high performance computing there's another strategy called gang scheduling which is another unfortunate term. So you might be in the situation with high performance computing where multiple processes you have to schedule them all simultaneously. So the scheduler on each CPU can't be independent because I have eight processes I want to run all at once and all together. So gang scheduling is also called co-scheduling and you need to schedule all the processes simultaneously so they essentially just act as one big unit. So it just requires a big global context which across all CPUs to change them all more or less simultaneously and that is something you'll only really see when you get to like high performance computing. So another thing, another difficulty is something called real time scheduling and that's yet another thing yet another problem and real time just means that there are some time constraints either a deadline or some rate you need to maintain. For example, like audio there is a set frequency that you need to maintain in order to fill up some audio buffer on like your sound card. And if you don't maintain that your audio will just sound like garbage. It will be a little garbled and you just won't run fast enough to get good audio output. And so that's a really common one. Another one is like any auto-piloting software like on a plane, if you need, if you know you need to make a decision within a few microseconds or a few nanoseconds that's something you actually care about for a plane. If you miss the deadline and the plane crashes you can't just say, oops, sorry, I missed the deadline my software ran a bit slow. So you actually need to provide some guarantee that you can actually hit your deadlines. So a hard real time system does provide a guarantee and you have to guarantee a task completes within a certain amount of time. Typically this will only be done for like little embedded chips where you can actually count the clock cycles for everything and it's doing something really, really simple. So you actually have to count the clock cycles and it would be a simplified processor where you can understand what happens with the cache. So you'd have to assume like all the caches are cold. I count the number of cycles that it could take and hey, it runs within 10 milliseconds or something. So I know I can hit this deadline. That is really hard and kind of annoying. So what people typically do and you can imagine counting cycles especially with something running on the Linux kernel or running on a modern CPU that has multiple levels of cache, associativity for the cache, different pipelines and all sorts of stuff going on. You can't actually provide any guarantees because it is going to be way too complicated. So for anything, essentially anything running on like the Linux kernel, it's something called soft real time where it's just always hit in practice. So you just give critical processes a high priority and you just figure out, hey, the deadlines always met in practice. So when you have a system that's essentially overkill so you have like a five gigahertz chip on your machine and you only have to fill in a few hundred bytes every few microseconds to your audio chip, well, it's going to be fast enough that you don't really have to argue about it. It's always met in practice. And so Linux does let you do soft real time and we'll see kind of examples of that because there are some threads on your system that are running as this real time, this real time priority. So Linux, in addition to the scheduling we've already talked about, it actually implements first come first serve and round robin and they're actually useful scheduling algorithms and are in use. So if you are sadistic enough like me and you want to search the Linux kernel source tree, you can look up sketch FIFO, that's first come first serve and then round robin is sketch RR and you can see exactly how they implement it or you can even change your processes to use this type of scheduling if you want. So what they do is just they use a multi-level queue if it's like a soft real time scheduling for all the processes of the same priority and it lets the kernel itself dynamically adjust the priority to be a bit more fair. But the way there's going to be on Linux, there's going to be two types of processes. There's going to be soft real time processes and those are always scheduled first. So they always schedule them at the highest priority and they'll use something simple that you can argue about like first come first serve or round robin and then normal processes are going to kind of essentially adjust their priority based on aging and use at least back in the day the two strategies we just discussed. So this is where it gets weird if you, oh yeah. So you get the choice. On Linux your process can be soft real time or it can just be normal scheduling. So first come first serve and round robin are choices for scheduling soft real time. Yeah, you can say what it uses. So I think by default uses round robin but you can change it if you want and you can set priority as a process and we'll see what some processes are doing. So the real, the soft real time processes are always going to be prioritized and this is where it gets weird if you want to read the priority numbers on Linux. So soft real time the scheduling policy will again either be first come first serve or round robin and historically there are 100 static priority levels zero to 99 with zero being the lowest and 99 being the highest. And then annoyingly the normal scheduling that applies to processes so they'll be under the name sketch normal. The default priority is a zero and the priority ranges for those processes are negative 20 to 19 where negative 20 is the highest and 19 is the lowest which is kind of bad. And then because of this if you're a process you can also change your own priorities with system calls so you can use the nice system call to say how nice you are and we'll see what that means and the schedule set scheduler to figure out if you want first come first serve around Robin. So let's go ahead and pop over to our VM because we should actually be able to read this now. So at least the first few columns. So the first column there is our process ID can everyone read this by the way? We're good. Okay so the first column is the process ID again it's just a number we know number one is fairly important and then we can read the second column that's just the user that the process is running in and then the third column PRI is the priority which is doesn't correspond to any priority I've shown you so far annoyingly. It's like kind of a unified Linux priority and then the last one or the next column is the niceness which is NI which only applies to normal processes. So this is where it gets kind of weird to explain this stupid Linux specific thing. So how it works. So at the bottom is Linux priorities and the Linux priorities are always the lowest number is the highest priority. So and then it divides them up into two regions that kind of have a unified priority. So if you have a normal process it essentially shifts the negative 20 to 19 to be like a positive number on the number scale. So it will shift it up in this number line. So if you're a normal process you will be between zero and 39. So if I look here quickly at the third column there the PRI if that zero or above it just means it's a normal process it's not a soft real-time process. So if that's the case then I can actually use the niceness number and the niceness number is gonna be the priority number just minus 20. So the niceness is just gonna be so a niceness of minus 20 corresponds to a unified Linux priority number of zero. So does this make sense? Cause it's kind of weird and this is really only so you can read output of top. So that way annoyingly well so that so what they do is any real soft real-time process is then represented as a negative number. So they took the old priority which had zero as the lowest and 99 the highest and they just take the minus of that and then subtract one to it to put on this kind of unified Linux priority. So an old priority of zero would correspond to a negative one which is here and then an old priority of 99 which was super high corresponds to the next priority of negative 100. So negative 100 is the highest priority and you know from reading the priority number if it's negative it's soft real-time if it's negative 100 it's as real soft real-time as it gets and if it's positive it's a normal process and then I can read its niceness number from negative 20 to 19. Why it's like that I have no idea but does that kind of make sense? Again there will never be like a question like this this is just really so you can read the output of top and it makes sense. So let's go through it and see what we can glean from all of our processes. So everything in white there on the right side column that's a process and any green text means it's a thread. Yeah, yes. Yeah so PRI is priority which corresponds to this Linux priority at the bottom the unified one. So if it's negative it's soft real-time if it's zero or above it's a normal process and then you can read its niceness which would be negative 20 to 19 but it's gonna be the same thing just shifted by 20. So the default niceness is zero which corresponds to the priority of 20 so all the processes I see here and all the kernel threads are all just a default priority which is fairly boring. So let's go ahead and look through them to see if we have something interesting. So here this RT kit demon has a slightly different priority it's 21 so it is a lower priority so whoever made this program it doesn't seem to be that important except that one of its kernel threads so it is two additional kernel threads that are here in the green and one of them its priority is RT and that I don't know why that corresponds to negative 100. Yeah so RT is real-time so negative 100 is as real-time as it gets and then at that point I'm pretty sure the niceness number is meaningless if the priority is negative. So the process apparently is not that important but one of its threads are so important it should schedule it before literally anything else. So that's kind of interesting. Let's see what else we got. So GNOME shell is like kind of your desktop display so one of the processes aren't that important it's 39 it's as low as it gets so it essentially doesn't care so I have no idea what that thread does but apparently they deemed it not important. So we can go through, there's another one in Alacrity which is the terminal I'm using right now so apparently that's not that important either. See if we can find anything more interesting. There is a lot of garbage here. All right so TrackerMiner apparently not that interesting. VS Code using 44 gigs of RAM. Hell yeah. So the question is well how is it using 44 gigs of RAM? Well it's virtual and we'll figure that out later. So it has 44 gigs of valid virtual memory space it can use obviously it's not using that because my computer is not, that doesn't have that much RAM but it could access it if it wants to. Why it wants to access 44 gigs of RAM? I have no earthly idea but hey it does. Let's see, yeah they all want to use 44 gigs, who knows? But they're all kernel threads, right? But yeah, so who wants to program, so everyone thought threads were bad and kind of a pain with all the data races and stuff. Who wants to code VS Code that has like, how many threads, like 20 threads all fighting over stuff? I don't know, VS Code likes to do stuff. I don't know. So that's a good question, why does it do that? Cause there's a lot, holy crap, VS Code please stop. Now, so now we get to more interesting ones. So here priority of zero, negative 20, so that's the highest priority but still a normal process. But oddly this one is process ID two and it is not its parent, which is kind of weird. So this is an internal kernel thread that only exists in the kernel. So it schedules it like anything else but this one is always in kernel mode and it is the kernel. So it has a few high priority ones here. I don't know what they do. So negative 20 and I, so it's zero in priority which means it's a normal process and negative 20 is the highest priority for a normal process. Yeah, no this is just the tree thing. Here is parent-child relationship. So like this, if I scrolled all the way up it's parents and knit. Yeah, kthread is like an odd one because it's actually only lives in the kernel. So it's parent is technically process ID zero which does not exist. So it's kind of a weird exception and it's just a normal kernel thread but that's the actual kernel. And it's a user process. Yeah, and we know what a knit does, right? It forks and waits and it's just a user process but kthread is internal to the kernel. But we can see here, so there's a few good ones. So there's this which is real time which is negative 100 and it's a thread that does migration. So this actually I believe does the actual context switch because there's one of these for each of the CPUs. So I have four CPUs on my machine. This one's real time priority to do the context switching for CPU zero, then here, CPU one, then CPU two, then CPU three and I only have three CPUs on this. So by going through that we can kind of see how, you can deem how important each thing is. Yeah, so the migration ones are all real time. So the priority there. Yeah, yeah, yeah. Yeah, so htop, so I'm using htop right now and it's a process like anything else. I don't know where it is because this list is gigantic but htop would be here. So this is the current process I'm using. So it's other columns here, this s column is the state it's in, r means it's running. So it's running because I can display myself sleeping. Yeah, but because it's being context switch in and out but because it's displaying itself, whenever it's running it would read what it's doing which it's running, right? So yeah, htop would all, if htop doesn't report itself as running that's something really, something really weird's going on because your process that's displaying itself isn't running doesn't really make sense but everything else is sleeping because all my cores aren't really doing anything. They're at like 1% usage so something's waking up and doing something quick and going back to sleep. All right, any questions about that? We can now kind of read this now and understand most of the columns. So we'll understand vert column later maybe why, well we'll never understand why VS Code wants 44 gigs of RAM, that's weird but we'll kind of understand more what it means, yeah. Which is zero. Yeah, which basically means it just never ran. Yeah, so you can, yeah, I mean you can create threads that do nothing and wait on something that never happens. Okay, so let's wrap up so any questions about, we can now read top and see the priorities so that's kind of neat. So to go back to the Linux scheduler, so we already saw the ON global queue and the O1 scheduler that has like a per CPU core. So now we'll talk about the actual algorithm that they use. So it's something called the completely fair scheduler and it's fair which allows for pretty good interactivity. So, oops, so the kind of O1 scheduler has really bad issues with modern processes. It wasn't that bad when it was easy to detect what foreground processes were and what background processes were because you just had a dumb terminal so you knew if it was connected to the terminal or not. But if you have GUI applications, you have no idea what is actually in the foreground because maybe you think oh whatever window is focused is in the foreground but that doesn't mean the user can't see it and it just kind of gets really, really hard. So the kernel could try and detect which applications are kind of user applications through some heuristics. Maybe you think a process that sleeps a lot may be more interactive, like it does something with keyboard input and then goes back to sleep after it updates its display but it's kind of ad hoc which just means you're just guessing in the dark and it could result in kind of unfair scheduling. So to introduce fairness for different priority processes, what you could do and what they do is use different size time slices based off the priority. So the higher the priority, the larger time slice you get and if you are a lower priority task you get a smaller time slice. So there's also situations where this could be unfair but in practice it seems to work quite well. So if you have ideal fair fill scheduling where you assume that you have an infinitely small time slice, well then that's simple. If you have end processes you just give them all one divided by N of the CPU time. So if you have one process, one CPU, it gets it. If you just have one processor and three processors or in three processes then they all get a third of the CPU time. So it's just evenly divided. That's as fair as you can get. So here's an example of that where four processes come in at the same time at T equals zero and they have some alternate burst times or complete time they need on the CPU. So two processes take eight units to complete, one takes four and process three takes 16. So here if we draw that out in kind of a Gantt chart where we're assuming a fair scheduler we'll just make every column here represent four time units just so it's easy to divide everything. So in the first four time units since we have four processes active to be fair we just give them all one fourth of the total CPU time which would be one time unit. So they each execute for one time unit here. And then for the next eight time units they still all exist, they still want to use the CPU. So we all give them one time unit again and we do that a third time and then we do that a fourth time. Then at this point process two and process four are done executing because they only need to execute for four time units. So now if we're being completely fair with our four time units of CPU time we would give each process two time units. So process one and process three would both execute for two time units because there's only two processes now. So they'd go from four to six time units of execution and then you do it again. So now process one's execute for eight and same with process three but now process one is done. So we have four time units of work only one process left. So it would take them all execute from eight to 12 and then go from 12 to 16 and then we'd be done. So this is what you would do if you had four processes with varying times and you want to be completely fair. So any questions about how that works? Yeah. So at that time it's the only process left executing. So each of the columns is four time units so it's just taking them all. It's taking four units of time. So this is all a single CPU. So it's just, I just divide it up just so that's easier to show it being divided. So yeah. So the first four time units I'm just assuming it can switch like infinitely fast. So after four time units they've all executed for one because I'm being completely fair. So then after that, when there's only P3 left it just executes for all four because it's the only one left. This is ideal fair scheduling where I just want to be completely fair to everything. So that's the ideal fairness but it's also terribly impractical because yeah. So every process gets an equal amount of CPU time. It would boost interactivity and has ideal response time since you're assuming that you can switch infinitely fast but in practice this would require way too many context switches. And you also have to constantly scan all the processes which is order N which takes a long time especially if you just want kernel to do something quick and get out of the way. So this is, but it's a basis for the completely fair scheduler. So this is what the kernel actually uses now. So for each process on your machine it assigns it a virtual runtime and then at each scheduling point when where the process runs for some like time slice T you increase the virtual runtime by the amount of time it ran for and the weight which would be based off the priority. So this is why they use a lower number to represent a higher priority. So if you are a very high priority process you would only increase your virtual runtime by a little bit and if you are low priority process you would increase your virtual runtime by a large amount. So the property of the virtual runtime is it monotonically increases which is just a fancy math word of saying it never decreases it always only grows over time so it never gets any smaller. So what the Skeldruler does as soon as it keeps track of this number to schedule a process it just keeps, it just selects the process with the lowest virtual runtime and that's it. And then it computes its dynamic time slice based off how many processes are running on your machine and what you would do in the case where it was, you made the ideal fair decision. So in this case it allows the process to run and then it would run for whatever time slice it decides and then when that time slice ends it would have increased its virtual runtime by some amount and then the Skeldruler would just pick the process with the next lowest virtual runtime. And to do that we, I think you just learned this in another course, red block tree. So here's where it's actually used. So you get a practical application already, right? Great planning. So completely fair scheduling is implemented with a red black tree. So it's a red black tree and it's keyed by that virtual runtime. So whenever a process ends or you create it or it terminates and you need to delete it, that's a log logarithmic operation which is quite fast. And whenever you want to schedule a new process you just pick the one with the lowest virtual runtime. If it's in a red black tree, it's 01, right? It's constant time so it's nice and fast. So in this red black tree, all the virtual runtimes at a nanosecond granularity which just means that's the smallest time unit it keeps track of, it doesn't keep track of like pico seconds or half nanoseconds, just goes down to the nanoseconds. And using this approach it doesn't actually need to guess the interactivity of a process anymore. It just kind of falls out of what happens. So if you have an interactive process that typically does a lot of IO and completely fair scheduling favors those by default. So if you're waiting on a bunch of input most of the time you're not running, you're blocked and then the kernel would wake you up and you do just a short operation to use that data. So you would voluntarily put yourself back to sleep. You wouldn't use much of your virtual runtime at all so it would only increase by a small amount. And then that way whenever you have new data you're likely to be picked again whenever you're eligible to run because you still probably have the lowest virtual runtime. And then because of that and your process exists for a lot longer it would also gradually over time if you're trying to model the ideal fair situation you'd get a larger and larger time slice and then you would catch up to the ideal so if you need to do a lot of computation after you get all your data you would already kind of have built up your time. So this is actually used. Any questions about this? Yeah, but eventually if they never get to it and they never get to that virtual runtime they'll be put to sleep and then you'll probably have no other option but to run the 22nd one. Yeah, but you also kind of base the time slices based off the fair policy and then you might just fudge it a bit with increasing their virtual runtime by a lot. So there's some fudging details to get around but this is actually what they implement and you can see the details if you want to poke around in it. All right, any other questions? Because that's, yeah, like the issues just pointed out the actual implementation is going to be a bit trickier than that but at the core it just uses a virtual runtime picks off the lowest one and that seems to work quite well. Okay, so we saw today scheduling got even more complex. We introduced priority but also introduced priority inversion. Then we figured out A, processes probably fall into two categories where some need good interactivity others we only care about how fast they finish and then what you could do separate queues for both and then you have to schedule between queues. Then we saw what you do for multi-processor systems that may require poorer CPU cores which is kind of what links used back in the day. We also saw kind of real time that requires some predictability and has some timing constraints. Then we finally saw the completely fair schedule that tries to model ideal fairness and that's the actual scheduling algorithm that your kernel uses today. So just remember, I'm pulling for you. We're all in this together.