 Welcome back to 353, thanks for joining me today. So today we get to talk about more advanced scheduling. So where we left off last lecture, we made it up to round Robin and we started talking about maybe some processes are more important than other ones. What should we do in that case? So one thing we could do, which basically what airlines love to do is we can just add priorities. So if we favor some processes over another one, maybe we assign each one a priority. And easiest thing to do is just to assign them a number and then we can give meaning to that priority. So maybe a lower number is higher priority. Maybe a higher number is higher priority. Kind of depends. It's up to us as long as we're consistent. So if we just add priorities, one of the first ideas we might have is just always run the higher priority processes first, round Robin, everything that's equal priority and then only switch to other processes as long as there's no other higher priority processes that needs to run. This can be done preemptive. It could be done non-preemptive. Most on most real operating systems, it's going to be done preemptively, meaning we still have control over what processes run when and which ones. So like I said, priorities can just be assigned an integer. So we can pick, like I said, lower is higher or higher is higher. Well, in Linux, what it does is it picks a ish. Lower number means higher priority. So for regular processes, a negative 20, that's the highest priority, and then 19 is the lowest. So if we just implement priority and just round Robin equal priority and then prioritize them ahead of anything else, then while we might lead to starvation, and that means there's some possibility that a process will never run. So if we just have a low priority process with a lot of higher priority processes that we always run ahead of it, while we might create a bunch of high priority processes over time and that low priority process will never run, in which case has the potential to lead to starvation. So we could fix this, we could just band aid it. So with scheduling, there's no one true solution. It's just a bunch of trade-offs and heuristics, which are just a fancy word for an educated guess or some crap I do that seems to work kind of well. So one solution, have the operating system or kernels specifically dynamically change the priority. So it could see that, hey, if this process hasn't run in a very long time, I'll temporarily increase its priority until it actually runs. And then as soon as it runs, I'll reset it back to what it was before. So I could try and work around that starvation issue if I really wanted to. Another fun issue if you just implement priorities is something called priority inversion. So I can accidentally change the priority of a low priority process to essentially de facto be a high priority one. So this could be caused by dependencies. So a high priority process depends on a low priority process. So can imagine we've already seen some dependency between processes. So like when we had a pipe and then we forked and then one was reading and one was reading, well, the process that was reading was waiting for that other process to write before it could continue. So there was some type of dependency between the processes. So if the process that was doing the read and waiting was a high priority process, well, then if we want to actually treat it as a high priority process, the process it's depending on should also be high priority because we want it to actually finish. So we can do our high priority things. We're much more important. So you can think of that as like, hey, if a high priority process is waiting on some low priority peasant that doesn't really matter. Well, they get an express pass to help out the important process. And then after they've helped out the important process, it can go ahead and they can get their priority reset. So that solution is called priority inheritance. So you inherit the highest priority of waiting processes. So if you're waiting on another process and you are higher priority than it, well, it temporarily gets your priority. And then if there's a chain, so if like A depends on B, B depends on C, C depends on D, while the offering system is going to have to detect that priority, which it will, because well, it knows all the system calls, all the processes are making. So it would have to go ahead and increase the priority of everything on that chain until that dependency is resolved. And then it can go ahead and reset it back to the original dependency. So that's one solution to work around this problem of just simply adding priorities. We also might divide processes into something called foreground processes and background processes. Difference between them is foreground processes are something you as a user are typically interacting with. So you want to have good response time for that. So if you type a key or you move your mouse, you should probably see it get updated pretty quickly or else you'll probably say, hey, this piece of crap is lagging and I'll either need to get a new operating system or get a new computer or something like that. Background processes, typically you don't interact with them. They're doing something maybe important in the background. You don't see their output until they're done. Maybe you might not care about their response time. Maybe you'd rather prioritize them just completing as fast as possible. So back in the day when we had like a physical terminal and it was really easy to figure out what's a background and a foreground process because a foreground process would be the current process that's like directly connected to the terminal and accepting user input. So back in the day when we just had a dumb terminal, really easy to figure it out. Not so easy to figure out anymore because while we have a whole user interface, just because you don't have the window focused, it might be visible to you. You might actually care about response time, things like that. So you don't need to know the specific definition for like what a terminal foreground processes because no one really uses real terminals anymore. They're fake ones. So, but the idea is still the same. You might want to separate out processes that users interact with. So foreground ones, they're interacting with it. We want to prioritize response time and background processes don't really need good response time, just throughput, try and finish them as quickly as possible. So we might try to schedule these differently depending on their needs because well, we might want to do round robin for foreground processes and maybe just do first come first serve for background, something like that. So we can just keep adding things and see how well they work. So we could have multiple queues if we want to separate out foreground and background processes. So like I said, we could just have a round robin between all of the foreground processes and then all the ones that we detect are just in the background. They can just do first come first serve. But now I also have to figure out when should I run a foreground process versus when should I run a background process? And then, well, what you might do is you might just round robin between foreground and background too. So you just add a round robin on top of a round robin and because this is scheduling, there's no right answer. Maybe you want to round robin and have foreground processes take up more time than background. Maybe you want the opposite. Maybe you always want to do foreground instead of background. Again, there is no single answer here. And that's why scheduling can get complicated because there's no right answer. There is only a series of trade-offs and we haven't talked about having multiple cores on your machine yet, which gets even more complicated and it's essentially where most of the complexity in this course is from. So for sake of argument, we will assume like multiple cores or something called a symmetric multi-processing system, which basically just means the architecture of the machine is all CPUs are, and in this case would be all CPU cores are connected to the same physical memory. So they share memory addresses and everything like that. And all the CPUs have their own private cache at least at the lowest levels like the L1 cache is private per CPU core. Maybe L2 is shared too, who knows, but that's what model of computer we'll think of in this course. If you get into real high-performance computing courses, that may not be true or like some enterprise-y machines that have lots of CPU cores and some will be connected to this memory and others will be connected to another bank of memory and then you have to care about which CPU core you're on because well, maybe it can't access some memory and then it gets even more complicated than we want to get into in this course if you can believe it. So we'll assume symmetric multi-processing, just everything's connected to the same memory. Life is relatively simple. So that's what the term SMP is. If you've ever seen that, you might see that when you run your name as well. So one approach I could do if I have a bunch of CPUs or CPU cores, I'll use those terms interchangeably is still only have one scheduler and just keep adding processes to CPU cores as long as the CPU cores are idle. So advantage to this is I don't really have to change anything. I just essentially put a loop on it. So I would have good CPU utilization. I'd still be fair to all processes assuming I'm doing something like round Robin, but the disadvantages of this system is it's not scalable. So you have to coordinate between all the CPU cores to make sure that each CPU core is running a different process because if you had two CPU cores trying to run the same process at the same time, while that would be a whole disaster. So you shouldn't do that. And you kind of have to make a global decision. You have to make sure all your CPU cores agree, which is kind of a little bit of a prelude to when we talk about threads and multiple things trying to contend with the same memory and making our life very, very difficult. Turns out in order to solve that problem, you essentially just have to make it serial. So you just have to make sure that only one thing happens at a time, which is going to be slow. So even if I have 16 CPU cores, I can't do anything 16 times as fast, they all have to wait for the same operation. And typically that's something we want to avoid. The other disadvantages is it has poor cache locality, which might be a new term with those two words together. Basically what that means is when your CPU is executing instructions, it might cache some memory values. So it speeds up like reading and writing to memory addresses. And typically whenever you're using some cache, well, it's going to be valid for a while. It's probably going to stick around for a bit. And if your process can go back on the same CPU core, likely some of your caches are still valid. So you wouldn't have to retrieve all that data from memory again, which is really, really slow. So if you can get scheduled right back on the CPU core, so like you get scheduled off and then back onto the same CPU core, likely some of your caches are still valid and still around. So things are going to be a lot faster than if you just went to a completely new CPU and then nothing's cached, you have to get everything from memory again and slow. So that's cache locality. Typically if you use something, you'll use it again in the recent future. So, oh, was that close? So yeah, poor cache locality is you're not guaranteed that process will always be on the same CPU. So you're not guaranteed that your caches are going to be stick around, yep. So what I mean by everything blocks on the global scheduler is that one scheduler, that's the only thing making the decision. So it has to essentially make the decision for each CPU core. So they all have to wait on that to make a decision instead of each making their own independent decision. And yeah, so this scheduler, so just essentially assigning different processes to whatever CPU core happens to be available at the time, that was the approach in Linux 2.4 which was released way before any of you were born and way before it was even remotely common to have a machine with more than one CPU and certainly not more than one CPU core, that concept just didn't exist yet. So this was like in the 90s or even before then in ye olden times. So first extension they made when it was more common to have multiple cores on your machine is to create like a per CPU scheduler. So instead of just having one thing making the decision and saying what process runs on which core, well, if there's a new process that comes in, we just assign it to a CPU and that's kind of our big global decision. And then after that, each CPU independently manages its own scheduling. So when I'm initially, when new process is initially created, I might just assign it to the CPU with the lowest number of processes currently running. So advantages of this, easy to implement and it's scalable, so there's no blocking on the resource. So each CPU core is scheduling itself independently. It doesn't have to wait on any one thing in order to run. And we have good cash locality in this case because well, as soon as a process gets scheduled on a CPU, it will stay on that CPU. So it won't move between CPU cores. So it's cash is likely to hopefully still be valid whenever it gets context switched out and then context switch back in. So that way we maximize our chances to have cash hits. Disadvantages of this system, oh, yep. Yeah, so that was a good question. So the question was what if we're waiting for, we have a high priority process that's waiting for another high priority process on a different CPU core or a low CPU core or a low priority process on a different CPU core, then same thing, we'd probably wanna do priority inheritance and make it high priority. And hopefully it runs at a higher priority than anything else on that CPU core or otherwise we're in trouble and you can see how scheduling quickly gets out of control with that, yep. Yeah, so the question is, how does this avoid the global scheduler? Don't we still need one? So in this case, in the case we had before, like assume we had round robin, we just have one round robin algorithm that just keeps on assigning processes to CPU cores as long as there's CPU cores available. In this case, our only global decision would be to what, when a new process comes in, what CPU to assign it to. So say we have four CPU cores, each of those CPU cores would be doing its own round robin between all the processes it has. So that's how we avoid that. So we do have a global decision here, but our global decision is what CPU do we assign this process to? And then after that, the actual algorithm runs independently on the CPU core. All right, yeah. So the disadvantage to this is well, if each CPU gets its own independent set of processes, over time I could get really unlucky and there could be some type of imbalance. So say I just have like two CPU cores and I have a whole bunch of processes come in. So I have like, I don't know, 10 new processes come in. So I wanna do five and five, but I might get unlucky where I assign five processes to one CPU core that all finish in like a millisecond or something like that. And then all the other five processes that I assign to the other CPU core, maybe they take 10 seconds each. So if that happens, well, very shortly that first CPU core that just got the very, very quick test, it's gonna finish all five and then it's gonna sit there idle and not do anything. And then that one CPU core is just gonna be juggling all of those other five processes that are all running for quite a long time. So I have the, it might be a case that I can have this type of load and bounce with this system because well, I can't move a process between CPU cores. So yep. So in this case, the scheduler would not keep track of what process gets assigned to what CPU, but it would know it's assigned processes. So the kernel would just say, hey, let's say process two comes in. It'll say, okay, process two, you're on the CPU core. And then that CPU core knows, oh, okay, I have to schedule process two with everything else that I'm scheduling. And then I'll keep doing that. Yeah, yeah. In this case, each CPU works in parallel. Each CPU would have its own, in the case of round robin, each CPU would have its own round robin scheduler. And then it would just, each CPU core would know what processes it has to schedule because it would just be assigned that. So in this case, to fix that load and balance problem where one CPU might become idle and it's done all the processes that it is tasked with. Well, I can make a bit of a compromise between like a global and a pure CPU scheduler. So I can have a global scheduler now that can rebalance CPU cores. So it can go ahead and check, hey, is a CPU idle? Oh, okay, well, if a CPU is idle, I should probably just take a process from another CPU and assign it to the idle one. And this is called cleverly work stealing. So maybe you want more control over which processes can switch because like I said before, switching a process between CPU cores, well, that will essentially wipe out your entire cache and you might have performance issues with that if it's something you really, really care about. So maybe you want some more control over what processes can switch cores. Some may be more sensitive to caches and you care more about performance than others. So there is a term to control this in the scheduler and it's called processor affinity. And what that means is just the preference of a process to stay scheduled on the same core. And this exists in lots of operating systems. So even if you're using Windows, this is an option. So if you had a process you really, really cared about, like I don't know, like a game or something like that, well, you can open up Task Manager, you can right click that process and then in one of the menu options, it'll be set affinity and then you could say set affinity high. And what that means is that process will likely always stick around on the same core. It's not gonna have any problems where it has its caches invalidated or anything like that. And hopefully you get better performance out of it. So that option is pretty much on every single operating system and that's what the term affinity means. So this scheduler, whoops, is kind of a simplified version of the 01 scheduler and that was used in Linux like 2.6 which is getting slightly more recent but not quite there. All right, another term that adds to our fun terminology in this course is called gang scheduling. So especially with high performance computing, you might want to schedule multiple processes as a group altogether. I don't know why they just can call it group scheduling. Don't ask me, I just work here. So here the scheduler on each CPU cannot be completely independent because I want to schedule them all at once because maybe they all depend on each other, maybe they all communicate with each other. So it only makes sense that if I run them all at the same time. So sometimes it's also called co-scheduling. There's a slight difference there. We won't get into what the difference is in this course but basically allows you to schedule a set of processes at a time acting as a unit and that would require context switching all of the CPU cores to this set of processes all at the same time. Mostly this is for high performance computing. Yep, yeah the question was this kind of like grouping the CPUs together, making them like a huge CPU. It's a bit more like as soon as we get into threading and running things in parallel, we'll talk more about that. But this is making sure that like all these processes can run at the same time in parallel because maybe they're all communicating with each other. So likely I don't want to just like have a process depend on another one that's not running. Things like that. Run at the same time meaning like they are running at the exact same time like you've say you have four processes and four CPU cores. We want each process to run on a separate CPU core at the same time. Yep, yeah they could communicate with each other that's IPC, inter-process communication. Yep, so the global context which in this case means like I can't have each CPU core be independent and make its own decision because I need to context everything at once like across all the CPU cores. So that's what global means in this case. And in terms of cache locality in this case, well, depends. Hopefully they're all scheduled on the same CPU core. So there'll be some private cache between the processes but then all the processes can use the same memory because they're all connected to the same memory. And then the kernel, if we want to communicate between them we have to ask the kernel to do that stuff for us. Yep, how many schedulers do you need for this? As many as you want. We can just keep on adding them as much as we want. So in this case, like we can combine like round robin different priorities. Do use different schedulers for different priorities we can do whatever we want. But this technique could be used with any scheduling algorithm we decide to use. So right now we haven't really talked about aside from like round robin and adding priorities other algorithms we could do. So we'll get into what Linux does in short time but there's even more complications in here which makes things even more fun. So there's something called real time scheduling. That's another problem. So real time means there are like some timing constraints or like a deadline. So it's been a while since I took like a signals course but like for audio especially, while there's like a sample rate and things like that and usually for audio devices there's a buffer. So you want to be able to fill it up in time and make sure that the sample rate happens or you'll get weird distortions. You might hear crackling and other things if your audio can't keep up maybe like autopilot that should probably react within a certain time before we like steer into a real child not like a process child or maybe we're directing a missile or something like that. Well, we probably want to react within a certain amount of time otherwise we could hit something. There's like a huge bug with some autopilot thing that was yeah, some weird stuff. So there are two types of real time. There are hard real time. So that means you have to guarantee that a task completes within a certain amount of time and that means you have to essentially count every single CPU cycle that you're in this case process or application or program could make and you have to make actual guarantees about the timing. So likely this will be like for very, very simple things that are running on an embedded processor where we can go ahead and actually argue about the timing. We'll have like a set frequency that our CPU runs at then we count the instructions, how long they take and then we've set limits on everything so that we can guarantee that things complete within a certain amount of time. That's pretty much only for simple systems that are embedded. What Linux uses is something called soft real time. So all it does is critical processes. They just get a higher priority than literally everything else and then the deadlines always met in practice otherwise you complain. And why is it soft real time? Well, it's because the Linux kernel and especially the hardware we have now is insanely complicated and for a general purpose OS there are just some things you can't control. So if you have, you might hit your deadline if you have a single process running or like a normal set of processes but you can't guarantee that the user doesn't launch like a thousand programs that are doing infinite loop things or they're not fork bombing themselves for fun or anything else like that. Like you have no control over the actual system. You have no control really over the hardware. So like even if the temperature changes, right? Your CPU is gonna throttle, maybe it goes slow. Maybe you're not connected to power. Maybe, I don't know, it just decides to do whatever. Yep. Yeah, so the question is do you have control over your priorities, essentially? So stick with me for a second. We'll get into that because that will help us read what Linux is doing. So we can see all the processes priorities and we can change them. But we'll get into that in a second. Yeah, so in this case, Linux too complicated, software time, it just prioritizes those processes ahead of anything else and typically the deadlines met in practice. So like audio streaming, you don't have frame drops or anything like that. If you get frame drops, well, you just get mad. So Linux, those scheduling algorithms like First Come, First Serve and Round Robin, they're actually implemented in the kernel and they are two scheduling algorithms that are actually used. So you can search the kernel sources if you want. If you see schedule FIFO, that refers to First Come, First Serve. If you see schedule RR, that's Round Robin. So it has, at least for soft real-time processes, it has like a multi-level queue scheduler for processes with the same priority and then the OS can dynamically adjust the priority like we discussed before. So for the soft real-time processes, it will always schedule the highest priority process first and the kind of gentleman's agreement between that is, well, the soft real-time processes should just do something that's time critical and then give up the CPU. And then the algorithm it uses for normal processes will get into in a bit. Hand wavy, it kind of adjusts the priority based on aging and how much CPU time it has. So we can almost talk about what the priorities are in Linux. Yep. So aging is just how long that process has been around for or how much CPU time it's got. So on Linux specifically, real-time processes, so that soft real-time, they're always prioritized. So this is where the numbers get weird whenever we want to look into the Linux kernel and see what the hell is actually going on. So these soft real-time processes, they're scheduling algorithm they use or scheduling policy. It'll either be first come, first serve or round robin. Idea behind that is, well, if they're soft real-time, we want a scheduling algorithm that's fairly predictable and simple. So first come, first serve, round robin, good ones to do. So for soft real-time processes, there is technically a hundred different static priority levels, zero to 99. And then the normal scheduling algorithm that runs, it applies to other processes and it's just called schedule normal and we'll get into what the scheduling algorithm is for normal processes in a bit. But by default, annoyingly for the normal processes, the default priority is zero and then the priority ranges from negative 20 to 19. And in this case, a lower number is higher priority and then in this case, a higher number is higher priority, which makes sense, right? So you can actually change your priority of a process with system calls. So this here, these priorities for normal processes, instead of calling them priorities, the latest kernel calls them niceness. So the idea here is that the lower the number, the less nice you are, the more of the CPU you will hog. So if you want to be really nice, that means you will give up the CPU and other processes to go ahead and run. So you can adjust your priority through the nice system call, nice. And basically what that means is adjusting your own priority and there's also a schedule set scheduler, also a system call, so you can go ahead and adjust your priority for different processes. So we can almost read the H-top screen now. So there are those two weird priority systems, like one was higher numbers, higher priority, the other was lower is lower priority. Well, the latest kernel developers were like, that's silly. It's like that XKCD comic, like there are 15 competing standards. This is stupid. Let's make something that makes sense and now there are 16 competing standards. Yeah, this is kind of like that. So there were two systems before, so software time priority, which was zero to 99 where higher was a higher priority and then for normal processes they had niceness, which lower was a higher priority. So this normal processes went from negative 20 all the way up to 19. So the Linux priority tries to unify these both on kind of the same number line. So for Linux priority, it always means that a lower number is lower priority. So what they did to scale the number is if it's a normal process, they just add 20 to it. So if it's a negative 20 niceness, a Linux priority, it's zero and then it goes all the way up to 19, which is 39, so they just added 20 to it. So in order to take the soft real time and make it lower is a higher priority, they just kind of inverted it. So they took that zero and converted that zero to a negative one and then converted a 99 to a negative 100 and kind of just inverted it so that the lower number means higher priority. And using this Linux priority, we can know if it's zero or above, it's a normal process. If it's negative, it's soft real time. So now we can finally read some more columns of htop. So if I run htop now, okay, so we know this first column, this PID. So that's the process ID of the process. And then the command that we used to run it, hey, that looks like lab one. User that ran it and then this PRI, that is the Linux priority. So if it's zero or above, that means it's a normal process. And then this NI is the niceness. So for normal processes, it's just going to be the Linux priority minus 20. So if I look through these, well, and it has the default priority, niceness of zero. Now we can go ahead and browse and see if anything is fun. So here's a very nice process. So its priority is 39 or its niceness is 19. So it is the absolute lowest priority process. It's called Tracker Miner FS3. No idea what the hell that is, but it's not important. Doesn't seem that important because, well, they opted in to be very, very nice. Likely if I kill it, probably nothing bad will happen, but I'll leave that as an exercise to the reader. All right. So we have this. So this one, the priority is nine. So it's positive. So it's still a normal process, but niceness is negative 11. So it is a higher priority process, and it's called wire plumber. What the hell is wire plumber? Hmm. Turns out that wire plumber is the process that is like managing your audio for your desktop. So that should have a higher priority than other processes because typically audio is more important and it doesn't take up that much amount of time. So whoever the wire plumber developers were, they decided they were important and no one really complained. So it seems like they're important. All right. Other fun ones. All right. There's another pipe wire. Pipe wire pulse. All right. You can see pipe wire thinks it's very, very important. So we got this, which is running at a negative two. So if it's negative, it means it's a soft real time. It keeps on jumping around. And it also means it's niceness. It doesn't matter for soft real time. It's just always zero. So there's this process, low memory manager, which might be trying to look around for processes that are using too much memory and killing them. For other ones, there's some other high priority so everything in green here, they're actually kernel threads, which we'll get into threads later. And then there were some important ones like migration that says real time. If it says RT here, that's negative 100. So it's the absolute highest priority of anything. And this migration, that's basically the dispatcher. That's basically what's doing the context which is on your CPU cores. So that's what's represented. Seems like context switching, very, very important. So that's fun. So you can go ahead, you can explore, you can see what things do and look up what they do and why they think they are important. So we can finally talk about the end of this evolution. So what we're using right now for scheduling normal processes on Linux is something called the completely fair scheduler. So the idea behind that is, well, it's fair and it has good interactivity. So if we're using, you know, typical desktops and everything like that, well, we want good interactivity, we want good response times. So with modern processes that 01 that we saw before that had like a per CPU scheduler problem, it has problems with modern processes. Like I said, not easy to figure out which is foreground, which is background. Maybe the kernel can detect it using some heuristics. So like maybe if they sleep a lot, they might be interactive if they're waiting for the keyboard. Maybe that means they're interactive, but that's like ad hoc, they just made up some rules. Could be unfair, you could be wrong. So the idea behind this is we want to have some fairness for different priority processes. Maybe we want to use different time slices, like different size time slices. Maybe the higher priority processes get a larger time slice. Maybe we want to be fair and kind of even them out. Maybe we want higher priority, larger time slice. Maybe there's also situations where that is unfair. So what can we do? So if we could context switch essentially instantly, we could do something called the ideal fair scheduling. So we can assume like we have an infinitely small time slice. And if we're being completely fair, like how I'm sure you with siblings, like shared screen time or whatever, that's definitely completely fair. You tried to say, hey, if you've end processes, then each of them gets like an nth of the CPU time. So if I have one process running, I assume I just have a single core, then it gets the entire run time, it gets essentially exclusive access to it. And then if I have three processes all running, well, they all get a third of the time. And I want to keep my CPU usage divided equally among all the processes at all times. So if I had something like this, so I had four processes that all arrived at time zero and the burst time was like eight seconds or eight time units, four, 16, four, I could assume that each vertical slice here, just to show what contribution of the CPU core it gets. Each of these vertical slices are four time units and I'm going to not show the context switches because I'm assuming I can context switch infinitely fast. So if I have four processes all at once and I have four time units, well, each of them gets a fourth of the time. So each of them is going to run for one time unit. So after my first four time units, I'm being completely fair. Each of them runs for one time unit. And then while for the next four time units, I'm still being completely fair. So they each run for a total of two time units now. And then similarly until we get to 16, where all of them have run for four time units, now process two and process four are done. So only have two processes left. So if I only have two processes left, if I'm being completely fair, well, you get half and then the other gets the other half. So I would now jump up by two. So I'm taking those four time units and I'm splitting it up half and half. So now process one, that would execute for two time units. Process three would execute for two time units. And then once we get to time 24, then process one is done because it's executed for its eight time units. So it's now done. And then the only process that's left is process three. So it would get all four time units. So it would run from eight to 12. And then 12 to 16, and then that would be done. So that's what we could do if we were being completely fair and we could just have an infinitely small time slice. And because we're also assuming an infinitely small time slice, then our response time is also pretty much zero and we're living in a perfect world. So yep. Yep, the six. So where the six came from is each of these vertical bars is like four time units of CPU time. So if I only have two processes left, then each of them will run for two time units if I'm being fair. And then so the numbers in the boxes are like, how long at that point has that process been executing for? So it was at four, it like ran for four time units. And then since it gets a share of two time units in the next four blocks, it goes up from four to six. Isn't which one? So process one wants to execute for eight time units here. Yeah, so it's not done yet. Process two and process four are done. All right, so that's the first policy. Impractical. Every process gets the same amount of CPU time. Not really going to work that well and you also have to constantly scan the processes, which is like an ON thing, which is slow. So this is the scheduling algorithm that Linux actually uses finally. So called the completely fair scheduler and how it works is for each runable process, it assigns it a virtual run time. So it essentially keeps track of how long each process has run on the CPU for and it keeps track of that in terms of nanoseconds. So fairly granular or not granular, fine granularity because a nanosecond, well, if you haven't seen this before, a nanosecond in terms of like how much distance light can travel in a nanosecond, it's like this. It's like about 30 centimeters, which is really, really fast because hey, the speed of light pretty damn fast and nanosecond not very long. So it can go about 30 centimeters. So how this works is at each scheduling point, so where some process runs for some amount of nanoseconds t, it will increase the virtual run time or how much it considers that process to have run by the time it actually ran, multiplied by some scaling factor, which is a weight based on the priority. So that's why we have lower means a higher priority. So if it's a higher priority, which is a lower number, the weight's going to be lower and if it actually ran for like, let's say it actually ran for two seconds, well, maybe the weight is like 0.5, so it only gets one second counted against it. So it would scale this based off the priority and the virtual run time would monotonically increase what the hell does that stupid math word mean? It means it only increases. So just a fancy word is it only gets bigger, it never gets smaller, just always increases. So the scheduler will just, in order to select which process to run, it's going to select the process that has the lowest virtual run time that's actually had the less, least amount of time on the CPU and then it's going to compute that time slice and dynamically scale it based off the ideal fare scheduling. So the idea behind it is, well, if this CPU or if this process is lagging behind all the other processes and hasn't had its fair share yet, I will give it a bigger time slice in order for it to catch up. Of course, practically it would have minimums and maximums for this, but that's the idea. And then we let the process runs, when the time slice ends, we repeat the process. So this is implemented with a red black tree because hey, keep by virtual run time. What I do? Oh, red black trees. Yeah, so those dumb things you learned. Hey, here's where it comes up. So you don't have to implement a red black tree in this course, but they're actually used. Look, they have a practical purpose. Yay. So why they do that? Yeah. Yeah, so each process has its own virtual run time, which is just keeping track of how long it's run for. Yeah, so it'll fudge with the virtual run time based off its priority. So maybe higher priority ones, we don't increase its virtual run time that much and that's more likely to run again. Kind of. It's a linear scaling. Yeah, you can switch your priority whenever it would just change that scaling factor whenever it computes the virtual run time. Yeah. Yeah, so the question wouldn't this mean I incentivize new programs over old programs and the answer to that is yeah, if you just run a program, you want it to like interact really quick and get some good response time. So yeah, yep. Yeah, so you might get to the situation where hey, if something's really, really old and has a very large virtual run time, maybe we don't run it anymore. It's less likely. So they'll have scaling factors that for like minimums and maximums and stuff like that. But that's yeah, that's something they would have to consider as well. All right. But yeah, so this tends to favor IO bound processes by default, which means they just do something really quick and then give up the CPU like they want good interactivity. So small CPU burst means we're only going to run it for a little bit so it'll contribute less to its virtual run time so it's more likely to run again and yeah, we went over a bit. So just remember, pulling for you. We're all in this together.