 Hi. My turn. How's it going? We have everything's good? Okay. Anyone go to the career fair today? It doesn't look like it. You didn't go? You just dressed like that normally? I knew that already. All right. So today we're going to talk, continue talking about schedulers. We'll have some examples of very simple schedulers and then we'll talk about some slightly more complex schedulers. You probably don't care that much about operating system scheduling. How many people really care about operating system scheduling? Are you just trying to make me feel good? Anyway, so, but think of it this way. This is a algorithm design problem. Scheduling is, we've talked about these two scheduling algorithms. It's an algorithm. So this is kind of an exercise that you can use with slight variations to think about how you would approach other problems. Can I talk? Can you guys listen? Thanks. So what are the inputs you have? How does the algorithm work? Trade-offs involving the complexity of the algorithm and run time versus how much better the solutions are going to be? Stuff like that. So that's my way of trying to get you to pay attention. Okay. So I'm at two. At this point I'm hoping you guys have like polished off a couple of extra system calls. How many people have polished off a couple of system calls? A couple of the file systems. Okay. Good. Yeah. So hopefully at this point you're kind of like rolling towards the end of the file system portion of the assignment. It's been pretty quiet on the forum. I don't know why. You guys not, why don't you guys post on the forum? You guys not like the forum? Do we also need to, like, assist under score C, or... It's pretty easy. Yeah. But that's a part of it. Yeah. Wait. You didn't answer my question. Why don't people post on the forum? Have I been yelling at people on the forum too much? No, because I'm trying to be nicer this year, right? And someone the other day told me I wasn't succeeding. So I'm just trying to get some feedback. So, yeah. Okay. I won't yell at you on the forum. We might tell you to search the forum first or whatever. I'm not yelling. I never yell. I never use all caps. Save that for other people. All right. Yeah. So the forum's quiet. I guess people are in office hours, which is good. But, you know, please keep... I think what's happened is people haven't quite got to exact on some of the other system calls that are a little harder yet. That's okay. We have a couple weeks left. Any questions about this stuff? So the plan is assignment two is going to get you to spring break. By the way, if you haven't noticed, the assignment two deadline is right before spring break. So if you plan on piecing out early to go do whatever you're going to go piece out and do, you have a few fewer days on assignment two. Oops. Sorry. Anyway, so we get there, come back from break. We have a midterm exam, and then we go on with assignment three. All right. Great. So last time we talked about scheduling, and just as a brief review, scheduling is the process of choosing what thread will run, given multiple options. So in order for us to have a scheduling problem, going back to our thread states, what has to be true about the system? In order to make a scheduling decision, there's a couple of things that have to be true. There's one thing that has to be true. Yeah. Close. Yeah, I mean, I need more threads that I can run than cores that I have to run them. If I don't have more, then I just run all of them. And that's a nice problem to have. I have to do this because I'm in charge of the system. The operating system is in charge of the system. So you can think of this as like a power sharing agreement. And applications agree to let the operating system make these decisions in exchange for helping them out with other things. Obviously, that's not how it actually works. There's no real agreement. The operating system is the dictator of the machine, the autocrat. But anyway, this is sort of how it works, right? When do we have a chance to make scheduling decisions? What triggers, what are some of the things that can trigger a scheduling decision? Yeah. A system call? Yeah. What else? Timer interrupt. So the timer interrupt gives me a chance to do preemptive scheduling. I may stop the thread that's currently running. Why do a lot of system calls potentially result in a scheduling decision? I mean, not only am I running in the kernel and so I can schedule the thread without needing to fire a timer and interrupting it. But what else? What do a lot of system calls result in? Yeah. Yeah, if you think about your read and write calls that you guys are writing, those IO operations that are wrapped in there take a while. Like a long time in CPU time. And so once, remember these are synchronous read and write calls. There are systems and it's pretty common to have asynchronous read and write calls where I can ask the kernel to perform a read and then later it gives me some other indication that the read is done. But the system calls you guys are implementing are synchronous. So the thread cannot continue until the system call completes. Therefore, I have to run something else. Or nothing, if there's nothing else that's ready to run. But I can't keep running the thread that's running. And of course, I can also voluntarily give up the CPU and when a thread exits, that also creates a vacancy on the core. All right. We talked a little bit about the types of things that interactive user facing schedulers have to be good at to maintain usability. These are things that you guys notice. And these are associated with different types of behavior of your computer. And finally, you know, the tension here is between an optimal allocation of resources that allows me to do a really good job of meeting known deadlines or keeping every part of the system busy and being able to quickly respond to new things that happen, primarily generated by the user. All right. Any questions about this before we go on? All right. Cool. And this is, again, this is a good blueprint for talking about how to design algorithms. So let me tell you something exciting that you guys are really going to appreciate and that some other people are not going to appreciate. So algorithms... I'm not going to get in trouble for saying this. Turn off the camera. Algorithms matter less and less every day. Okay. I'm sorry. I'm sorry, Atree. I'm sorry, whatever other courses you took. They just do. They matter less and less every day. The reason is computers are really fast. And they've gotten so much faster. I mean, you just can't wrap your mind around how much faster computers have gotten over the past 50 years. I mean, there's nothing that compares to it. I read somewhere that if the mile per gallon in your car had kept pace with computer processor improvements, you would get like a billion miles to the gallon. Right? Okay. And I understand that don't blame the auto companies that it's a different problem. Okay. I'm not saying that they should have kept up, but that's kind of the comparable increase in performance that we've seen in computers. So complicated sort of, you know, delicate algorithms are less and less important for solving a variety of different problems today because the underlying hardware is so powerful that you can get away with doing simple things. So when you can get away with doing simple things, what should you do first whenever you're approaching a problem? Do a simple thing. If the performance of that simple thing starts to be really terrible, then you can go back and fix it later. And we'll talk about that when we talk about performance improvement in a month and a half or so. Whenever you're designing this type of algorithm, the schedulers we're going to talk about use a couple of different pieces of information. Or we're going to talk about schedulers that use a couple of different pieces of information. This is another fun thing that we can do when we design algorithms is we can design and sort of estimate the performance of hypothetical algorithms that cannot be implemented in the real world. So, oracular schedulers, sometimes these are referred to as oracle algorithms when you read papers, when you read performance comparisons, have access to information that a real system will never have access to. So in this case, what's the thing that, like if you were trying to design an ideal scheduling algorithm, what's one of the things that you would love to know about the world? You have a crystal ball. Yeah. So it would be great if I knew something about what's going to happen in the future. Yeah. Yeah, and like when the user is going to stop using them. So really the oracle schedulers, what they want to know is what's going to happen next. Are you suddenly going to start using a new application? Is a huge torrent of requests going to suddenly come to this Web API for some reason? I mean knowing the future. That's another great thing. For every task on the system, what parts of the system is it going to stress? I don't know that. I can have some ways of estimating that, but fundamentally when I'm running instructions, information from the outside world comes in and sometimes the task takes one code path and sometimes the task takes another code path and its behavior may be highly unpredictable. So these are the things that I would like to know. Now here's what I can know. What I can know is the past. What I want to know is the future. What I'm capable of knowing is the past. And a number of the resource allocation algorithms that we're going to talk about this semester do this same thing. They compute information about the past and they try to use it to make predictions about what's going to happen in the future. This is a classic operating system technique. It's referred to as use the past to predict the future. We do this all the time. I mean, you know, why you guys drive the same way to school every day, right? Because you figure, okay, I found a good route that's going to keep working for me. You don't take a random path. Does anyone take a random path every day? That would be interesting, right? It might be kind of fun, like a little bit of a brain teaser, you know, keep yourself sharp. But, you know, what you're assuming is that whatever conditions happen the day before are going to continue to happen. Or let me put it this way, you probably allocate about the same amount of time to make it to school every day. The reason is you're assuming that past is predictive of the future. Now, there could be an accident, there could be construction, whatever. But, you know, you don't, if it's only taking 15 minutes to get to school, you don't allocate like an hour to do the drive, right, unless you like to hang out. All right. And then finally, we usually want to have some way of trying to react to what the user is doing, right? Remember prioritizing interactive performance, prioritizing interactive tasks. Or, alternatively, taking input from outside of that. Now, this is not something that I think people do very much anymore. Has anybody ever adjusted the priority of an application or task on a system? Okay, there's like four hands up, right? Can you even do this on Windows? Oh, wow, okay. Power tip. I don't use Windows, I don't care. But there are inputs to, when the operating system is making decisions about what should run and how long things should wait, it's common to allow inputs to be taken in from the outside. Now, despite the fact that you didn't set the priorities of various tasks or threads, are they still being used? Does anyone know? I mean, you guys can poke around using top or whatever and see all the priorities that have been set for things. I mean, if you guys don't use priorities anymore, maybe we should just get rid of them. Right? I mean, priorities are still heavily in use, right? So they're still exogenous or external inputs that come into the scheduling system. Where do they come from, though? They don't come from you. Like, how does your system know that Chrome is more important than the spotlight background task that's built in an index? You didn't tell it that. How does it know? Yeah. I'm not even... I'm saying, like, without any additional information, your system probably still has a hierarchy. Yeah. Where'd it come from? Hmm. Let's produce it. Let's do a thought experiment. Let's say we can ask apps how important they are. Right? What's the distribution of responses that we're going to get? No, I mean, it's a fair question. Like, this is the... I will pour you the details. So never keep people for years experimented with this idea of quality of service. In the internet, some packets are important and others aren't important, so you know what we need to do? We need to get the important ones to say I'm important and the unimportant ones to say I'm not important and how well does that work? It doesn't, right? Because everyone says I'm really important, right? Like, clearly this email needs to get there right now. Anyway, so that's hard to do using that method of input, right? But we're on the right track here. Where does that information come from? Yeah. No other information. No other inputs. No, no, again, no other inputs. Without any information about how these are used. Yeah. Again, that's information about how they were used. Yeah. The people that package these systems, like Mac probably comes with a file somewhere that's like, by the way, the spotlight task is not that important, right? Because why not? I mean, like, you build that into the system and then it doesn't interfere with interactive tasks. This is common, right? So, yeah. So somebody out there who's smarter than you can make a decision once. They said, for all time, Chrome is probably more important than these other types of things, right? And you benefit from that. So priorities can be useful even if you don't control them directly. All right. So, simplest possible scheduling algorithm. Simplest possible scheduling algorithm. Who knows it? Yeah. False. Yeah. What's that? Random. Why is random simpler than FIFO? I don't have a queue. I don't need to have a queue, right? Okay, fair enough. I need to have some structure where the threads live, whatever. Yeah. I argue that random is a little bit simpler than FIFO. FIFO I need to keep track of. An order, right? Random, no order. All I need to know is, here's a basket of tasks. Okay? So here's the fantastic scheduling that we did years ago. They've aged really well, clearly. So, yeah, this is awesome. So here's what happens, right? And this is how these scheduling algorithms work. I pick a thread and I run it. Okay? I'm running thread T3. What's the dotted line on there? Yeah. Yeah, normally associated with all threads or a particular category of threads or whatever is a limit on how long I'm going to let that thread run. Before the operating system sees control of the system again and at least make another scheduling decision. Now, when T3 finishes running I may just continue to run T3. It doesn't mean I have to switch threads. But it means that's the point at which I'm going to make another scheduling decision, regardless of whether T3 blocks before it gets to that point or not. That's the farthest I'll let it run before I rethink what I'm doing. So in this case, imagine T3 hits that limit. I put it back on the ready queue. Now what do I do? How many pick the next thread to run? Every answer is correct. This is like the best question of the entire semester. All you have to do is put a T and then a number. T5, T2, T1, whatever. It doesn't matter. It's random. T5, it turns out. I chose T5. So what else can happen? It's possible the T5 won't run until it hits its quantum. Why not? What else? It waits for something. So in that case, where does it go? Actually, this is a great question. At this point, where could T5 end up? It could end up in the waiting queue if it blocked. It could also end up back on the ready queue. How? Remember this is weird little corner case. Yield. If I call yield, I end up on the ready queue. I'm voluntarily asking to be descheduled. That doesn't happen very often. So what likely happens is that T5 is not waiting and I just continue this process. T2 hits its quantum. It goes back on the ready queue. T4 goes into the waiting queue. Did I have one that doesn't? There we go. So now what happened here? T1 ran, didn't quite finish, ends up on the waiting queue. What else happened? That makes us realize that whatever T5 was waiting for, maybe it's my O finished, this is not hard to understand. Yeah, T2 yielded. I knew I built that into the slide itself. So to order and implement this algorithm, I pick a scheduling quantum if I'm going to do preemptive scheduling and then I pick threads at random, run them for up to that quantum and then when threads leave the waiting state, when I notice that what they're waiting for has happened, they're on the ready pile. A little bit of review from last week. What's the actual mechanism? What's a series of events that's going to move a thread from the waiting state to the ready state? What has to happen? Assume that I did like a disk read or something like that. So I tell the device, read this data, here's a buffer that I want you to put the data in. I move the thread onto the waiting queue. And then what happens later? How does the device tell me that it's done? Yeah, fire is an interrupt. So what would really probably happen here is that there would be an interrupt that would happen somewhere during one of these time slices. The kernel runs. Remember, when I process an interrupt, I jump into the kernel, start running code. The kernel realizes this is an interrupt from the disk. And then the disk tells it I'm done with this particular I.O. The kernel takes the thread that was waiting for that to finish, moves it back into the ready queue, and probably just exits at that point. It doesn't necessarily mean I have to make a new scheduling decision. All that's happened is the next time I reschedule threads, I find a new thread on the ready queue that is now able to run again. All right, good. Okay. And then round Robin scheduling, you know, you can think of basically this as a small variation on random where I maintain an ordered queue when, you know, as long as everything in the ready queue is the same, I'm just running things from the ready queue. If things block, I put them on a waiting queue. There's a design decision here, so let's say that something finishes waiting, what can I do with it? I could put it to the back, or I could put it... I could try to put it back in the same order. That would be kind of tough because I have fluctuating membership, so it's not clear what order means. Or I could put it in the front. I could say, oh, it's been waiting. It should run right away. So I have a little bit of a choice here about where to put things. In this case, we're going to... Well, I don't even answer this question here, but you could do both. I'm just cycling through things in order. All right. Okay. So these two algorithms have something in common, which is that they don't incorporate any state about the system. All they do is process the ready queue. To the degree that there's additional mechanisms in the operating system to move things from the waiting queue to the ready queue and onto the waiting queue, these algorithms, they don't even know anything about the waiting queue. All they know is at some point they'll get called and they pick from the ready queue. The waiting queue in those slides is designed to help you understand what's happening. But really, all that's going to happen is that here's a bunch of threads pick one or one. Here's a bunch of threads pick one or one. Here's a bunch of threads pick one or one. And the membership in that group is handled by other parts of the system. Right? So they don't use any information out to the past and they don't try to predict anything about the future. They don't accept any sort of priorities or use of input, although you can imagine ways to do that. And these are... Okay, so these algorithms are useful if you need to implement scheduling algorithms as part of an assignment, which I think we used to make you do, but I think we finally got rid of that because there was no way to test it. So that's one thing they're useful for. But there's another really, really useful thing about simple algorithms like this. Right? They don't take a lot of time. They're going to work. You're going to look at it and you're going to be like, is that really the best I can do? I took this whole course on algorithms that thick on my desk. You know, and I just like, wouldn't it be fun to spend a couple months of my life trying to implement a better algorithm? Even though I can implement a random scheduling algorithm in ten minutes. But these algorithms are extremely useful as a baseline for other things that you might try to do. Right? There is one place, and we'll talk about this on Friday, where round robins sometimes get to use and that's when the scheduling algorithm has reached a point where it has a bunch of threads that are considered equivalent. So imagine there's ten other things that I'm using to figure out which threads are important and what should run right now and how do I maintain good international performance. But at some point, I hit a dead end and I've still got three threads left that as far as my algorithm is concerned are undistinguishable. In that case, I can just use round robin or random or whatever just to make these, okay. Now, so we sort of did this before, we talked a little bit about, yeah, sorry. You could, like, the algorithms that we've presented do not, right? They don't consider anything, right? How would I... What's a way that I could use random... What's a way that I could consider priorities in random scheduling? This is an interesting question. Let's say I want to modify my random approach to respect something about the priorities between different threads. So I want there to be able to be threads and unimportant threads and maybe medium important threads. How could I do that? Yeah. Yeah. So imagine I adjust everything so the sum of all the probabilities of the threads adds up to some number. I pick a random number within that range and then I use that to figure out which thread runs. So if I want to, I can weight my random decision so that the threads that are more important have a higher chance of running. It's still random, right? At this time we'll have a chance to run but it's possible that one thread has a higher chance. Another easier way to think about this that I just blanked on for a minute is to think about a lottery, right? And there actually are lottery schedulers that people have implemented. So I give out tickets before the lottery. Important threads might get 100 tickets. An unimportant thread might get 10 tickets and then I choose a winner from the tickets that are out there. The number of tickets the thread has has an effect on its chance of winning a lottery. The things I might want to know about the thread in the future. How long is it going to run? Is it going to block or yield before it hits the end of the quantum? How long will it wait once it does that? What else? Anything else? I mean this is a very, very incomplete list. There are just some examples. What's a really useful piece of information that I might want to know about a thread beyond all this about the future? One other thing. That's a starting point for an answer. What might I be what might I want to be able to distinguish between? Interesting. No more processes. I'm not running threads that are going to fork anymore. I might want to know about the kind of resource request it's going to make. But I think it's something really, really basic, very, very basic, but very, very hard for the operating system. No. I've got 10 threads. You're using the device. What do I want to know? What are you waiting for? What is the thread that's going to cause something to happen that you are actively waiting for? You're looking at a particular part of a web page and you're waiting for the image to load. And in the meantime, the operating system is downloading updates to your apps and indexing your music collection and running broken buggy JavaScript code from another tab or whatever and you're just like I just want to see that picture. It's like I'll get there, whatever. We'll get to that JPEG decoding in a minute. That's just, you can think about, you can imagine how hard of it a question actually is. But it's a cool one. All right. I felt like it was time for a man that's been long enough. Okay. What? I said it's time for a man that's been long enough. We haven't had a man for a couple of years. What? You don't say men? I'm blaming Scott for this. Scott is the local, he's going to get mad at me. I've been saying this for a while and he hasn't corrected me. He's like the local lab authority and pronunciation. What? GIF? I live in a world with people who say GUI and there's something wrong with all of you. It's a GUI, thank you. Anyway, whatever. We can disagree about things. It sounds like I might be just totally wrong about how to say mean. So anyway, I'm going to try to adjust that. At some point in the past I was saying Enginx too and Scott had to very gently correct me. It's called Enginex. It's on our website. Enginex. What's wrong with Enginex? Anyway, memes. Know what else, schedulers. So again, look at this picture and I'm going to project something about these tasks that I might want to know. So for example, let's say I know how long they're going to run before they block. Now on some tasks the answer might be forever. That's not that common. Usually, something runs, it uses the CPU and then at some point it wants to communicate the result. So it has to draw to the screen. It has to write to disk. It has to send a network packet, whatever. To some degree, a thread that's just computing forever is like that tree that falls in the forest that no one's ever going to hear. It takes a lot of time to cut down that tree. So usually I expect the threads are going to block at some point. Now if I know this I can arrange these threads in a way that optimizes their usage of the system of resources. And the intuition here is simple. I want to give threads that are going to very quickly cause something else in the system to happen. I want to give them the ability to run first. So this is an algorithm called shortest job first and on some level it's a variant of that algorithm because what I'm really doing here is I'm trying to run the task that has the shortest time to IO first. However if I was doing a batch scheduling system remember Carl talked about those last week then if I run shortest job first it optimizes something else. Right? What is that thing? Let's say I have a bunch of different tasks and I'm running in my data center and I know how long each one is going to take. Why do I want to run shortest job first? Nope. Here's the thing. Those jobs are going to take exactly the same amount of time no matter how I schedule them. I know how long they're going to run. But I still want to put the shortest ones first. Why? Yeah. No. Now this minimizes something important. Yes. Thank you. Minimizes the average waiting time. So here's the thought experiment. Imagine I have one job that takes 100 years to finish and then I have a bunch of tasks that take 5 minutes. So there's 100 people in the room. 99 of them are waiting for the jobs that take 5 minutes. The other one is waiting for the job that takes 100 years. So I can either have everybody wait for 100 years. Right? Or I can let everybody go in 5 minutes or 10 minutes or 15 minutes or whatever. Not that long. And then one person is stuck here for 100 years. That's sort of like how people take exams. Right? No matter how many exams I give there's always one person who is determined to get stuck into the exam. Anyway, so yes. Shortest job first. Minimizes average waiting time. And this is important when you're running even when you're doing batch schedule instances because it allows other people to get on with their work. Right? The reason we now when we apply this and think about tasks because again these tasks aren't done. They're getting to the point where they're going to ask the CPU to do something else. The reason we might still prefer shortest job first when all I'm doing is thinking about the time it takes to get to the next I.O. is because as soon as the thread starts some other part of the system working. Like it sends a network packet or writes a dozen disk I.O. That can now be done in parallel with the additional use of the CPU by other threads. And so if I think about how do I keep all the different parts of the system busy getting threads that just want to do a little bit of work and then wait for something else to happen. Getting them done first means that over time I'm going to make the best use of the resources on the system. So here's a this is probably of all the diagrams the terrible SVG diagrams I produced for this class. One of my favorites. So bear with me. So here's an example where essentially what I'm looking at is here's how long the thread is going to run on the CPU and then it did something else that caused it to wait for a certain period of time. And just imagine for the sake of argument that all these things can be done in parallel. Usually that's not true because at some point they're all using the network interface or they're all using the GPU or something like that. If I line them up this way. So this is longest job first. Right. You can look at the area under this curve. So this is all the the length here. Right. This is the amount of time it takes you to finish. And what's happened here is it's very you know the amount of time for example this guy's running. There's nothing else happening on the system. Right. Whereas if I put these in the other order. You can see that I'm what I'm doing and normally normally these weights will be much longer. Right. But what's happening is all this computation was done while this was running and there was a little bit of this other task that was also running. And so if I start doing things that are going to yield first. Then I can get lots of parts of the system busy. And then while those parts of the system are off doing their work. If there's some person who wants to compute Pi I can run that task at that point. Right. And I can run it until something else happens. Something else is done and somebody else has a chance to run. Alright. Questions about SJF. This is a nice example of an oracle scheduling algorithm. Am I ready to stop it? Alright. Let me pull up slides for tomorrow. Because I must have been moving really slow last year. I should have loaded these in earlier. Oh. So the problem with an algorithm like sort of job first is that normally I can't predict the future. Right. I mean I can't really I mean you can predict certain things about the future with high confidence. Right. Like the sun is going to rise in the morning right. But even then these days I'm not so sure. So you know I can't really predict anything about the future. I can make statements with high confidence. But when I'm talking about thread scheduling it's very hard to predict anything about the future. And the reason for this is you know again I run the same task because of external inputs one time it does x the other time it does y users are constantly creating noise in the system and randomness that I'm trying to respond to. So instead the canonical approach here is to use the past to predict the future. So if there are design principles that you come away with from this class that is probably one of my favorites. Right. And this is something you can think about as you design many other types of systems. Right. Use the past to predict the future. Find some way to take some inputs based on what has happened or what has just happened. Sometimes you can use a large amount of history to do this and apply them to having things work a little bit better the next time. Right. If you imagine if I can just improve certain systems a little bit on every iteration over time I can actually improve the system quite a bit. Right. So here's how we do that. I mean one thing I can say is if a thread was recently doing a certain thing then it's probably going to continue to do that. Um the manifestation of this that we're going to talk about is the final complex scheduling algorithm. So a real scheduling algorithm on Friday is something called multi-level feedback cues. And you know real scheduling algorithms implemented on real systems combine a lot of these ideas but this is kind of a classic example of something that we talk about when we start to think about how to use different inputs to adjust scheduling priorities. Alright. So here's the basics of MLFQ which I always have a hard time saying a lot of dry consonants in a row. So as usual I pick a scheduling quantum. This is the limit on how long I'm going to let anything run before I make another scheduling decision. Then I establish some number of cues 2, 5, 18, whatever. Each cue is associated with the priority. And each represents one of the levels in the system. I always pick the thread from the highest cue first. So the algorithm here for making a decision is very easy. I look at my cues and I say if the top cue is empty I go to the next cue. If that cue is empty I go to the next cue. And I keep doing that until I find a thread that's runnable and I run it. Within the cues themselves the cues sort of represent ties about how important threads are to the system. So we use what I just described where I just use like I could do random, I could use a round robin approach or something like that. So when I'm running things from a particular cue I pick the first thing and when it's done I might put it on the back of the cue. The question here is how do I move threads between cues? Think a little bit about how SJF worked. So one way to think about this is how do I ensure that threads in the bottom cue ever get a chance to run? The goal of this algorithm is not to put some threads into a dungeon from which they will never escape. So I have to set this up somehow so that there is a high likelihood that threads from the lower cues will actually have a chance to run. So what kind of thread would be a huge problem to have at the top cue? Yeah, one that has a history one that I think is just going to keep running and running and running. So I do not want Pi the Pi computing thread in my top cue because if that happens it's just going to run run run run and then it's going to still be runnable. So if Pi ends up in the top cue I never run anything else. In contrast so I don't want threads that are going to run for long periods of time continuously in the top cue. What do I want in the top cue? Yeah, a thread that's going to run for a small period of time and then sleep. So Bash is a perfect example. The thread of Bash that's rendering characters or when Bash is in the mode where it's taking input from you and redrawing the screen, that's exactly the kind of thread I want. What does it do? Wakes up takes the input from the console driver renders it using whatever font it's using and goes back to sleep. And then it sleeps for eons while your slow fingers are like reaching for the next key. Get on with it, come on. I know you're just typing get again I get it. So anyway, that's what Bash does. Now the nice thing is while Bash is sitting there waiting, then I can run Pi. Bash is not runnable because it's waiting for you and now I can run things from the low cues. So what I want in the top cue is stuff that's going to run briefly and then wait. And the way I do this is I reward threads that do exactly that. So if a thread does not run for the entire quantum, if it blocks, then I promote it. Eventually, I may not do this immediately. It becomes way too threads move between the cues way too often. So I don't want to do this immediately. I probably will have some hysteresis here. But like if the last ten times you ran you always blocked before you hit the end of your cue you're going to get a promotion. I'm going to move you up to the top. In contrast, if you hit the end of your cue and I have to yank you off the CPU I'm assuming that you're doing some sort of long stretch of computation. And then I'm going to move you down into a lower cue to get a chance to run once threads that are in the upper cues are waiting for something. Does this make sense? What is this? Yeah, so it is one of the classic problems with this algorithm is starvation. So depending on the pattern of threads it's very possible to starve threads in the lower cues. The solutions to this are all kind of gross. Like periodically some of the algorithms will just yank everybody up to the top cue again. You know, just like do over and that will give things a chance to run a little bit. Well, remember that's a great point. If I'm worried about that I can always not reward yields as strongly. Now I could certainly block like do some IO but at that point I'm waiting so who cares. This is a great question. There's no way for the system to know whether or not the program is doing anything useful. You can write a stupid useless program that does a great job of staying in the top cue and always being runnable. Because it acts like bash but it's actually just like reader writing from dev null or something. So there's no, again I'm assuming that you the user have not chosen to run stupid energy viruses on your computer that do nothing useful. Although maybe you actually didn't make that choice maybe a hacker from another country made that choice for you. But anyway, I'm assuming that the threads here are trying to do some useful work. The nice part of this is back to how we think about interactivity. Almost done. Is that... Oh, okay. I have more time. This is like the longest class ever. You guys are really quiet today. I think that's the problem. The nice thing about this algorithm going back to user interactivity. So remember one of the goals on these user facing systems is minimizing the delay that you experience as a user. So how does this algorithm accomplish that? Or how does it not accomplish? It's not perfect. It's not actually thinking about what you're doing. But why in general will this algorithm tend to prefer interactive threads? Yeah. And so the underlying assumption here is that interactive threads yield a lot. They spend a lot of time waiting for the user. And so all of those times where they're blocked waiting for you are chances for them to be promoted a little bit. And so the goal here, now again, you can certainly write a non-interactive program that will kind of maintain membership in the top queue by just doing useful stuff. But the goal here is that the patterns of activity that are characteristic of user facing tasks will cause them generally to rise up in the rankings and to be run more quickly. Whereas the patterns of activity of long-running background tasks may cause them to sink the bottom. That's the goal. Again, this algorithm is too simple to do a perfect job all the time. So here's the diagram. Everybody starts in the top queue. I run T3, T3 waits. So when T3 comes back, I run T3 going. It's going to still be in the top queue because it did not hit the end of its quantum. I run T5 and T5 does hit the end of its quantum. I have to yank it off the CPU. Where does it go? Let's assume I'm demoting it immediately. It's going to land in the middle queue. Yeah. So Yield Bomb could prevent the other queues from running. That's definitely true. On some level, the stuff in the top queue is going to run. I'm running that. That's the kind of starvation problem that this can suffer. So T4 waited. So it's coming back to the top. T1 yielded. In this case, I'm allowing it to go back into the top queue. There are some modifications I can make to this algorithm. Do you guys see any useful ones? We'll make it a little bit better. Maybe I know what the scheduling quantum is and I yield right before the end of it. So I get 99.9% of the time I would have had and I get to go back into the top queue. What's a straightforward way of making sure that doesn't happen? I could drop on a yield. Oh, yeah, I could trick it. That's not a bad idea. That's one solution. Really remember, what I want to do is I want to reward threads that do burst of activity and then a lot of waiting. So I don't have to reward anybody right before the end of the queue. I can maintain like a sort of a real priority that you lose points proportional to how much of the queue you've used up. Ideally, a thread that just does a very tiny amount of working go best back to sleep, that thread is a large likelihood of continuing to stay at the top queue. It's a good question. I'm trying to think of where make, yeah, potentially. So again, on its own, this type of algorithm is not perfect and not necessarily all that fantastic. A lot of times, those types of problems are solved by just simply manually making sure that long running background tasks that don't have a lot of input on interactivity have external priorities that are set low enough. Or sometimes I might say, okay, this was actually launched by the user rather than by the system and therefore it has power. Scheduling is a fun problem because when you start to explore the space of possible inputs that you can accept into a system like this, right? Is the task being launched by a user who is actually logged in? Right? So in a lot of cases, these background daemons run as other users on the system to make sure that they don't have elevated privileges. And so I can say anything that runs as cron just has very low priority. Yeah, so this is interesting. All right, Harasta, this is pretty obvious. Any questions about MLFQ? This is an example of a simple version of an algorithm that tries to use the thread's history to make decisions about how to improve a particular metric here. Right? And what I'm trying to do, if you think about what is the goal of this algorithm, I'm trying to make sure that interactive threads don't have to wait very long before they run because there's a high probability that the person who's waiting for them is you. All right. So on Friday, I actually am done for today. On Friday, we will talk about the rotating staircase deadline scheduler. So hopefully you guys will come on Friday. I mean, I know it's Friday, so it's the usual Friday problems, but Friday's lecture is kind of fun. We'll talk a little bit about Linux. There's a story behind this lecture and the algorithm itself is kind of cool. So I will see you guys then.