 All right, I'm going to start despite the fact that we're waiting for the protective power cycle. So today we're going to finish up our unit on scheduling. I hope if we don't get through today's material, what I'll ask you guys to do is watch one of the lectures from past years just so we can be done. We had a little bit of a spillover from last time. And now here I am wasting even more time. So hopefully we'll get through today's material. If not, I'll just ask you guys to watch 10, 15 minutes of last year's video. So what we're going to do today, I'm going to be in trouble here in a minute without the slides, is we're going to finish talking about scheduling. So last time we talked about some simple schedulers that were good baselines. But we haven't really talked about a sophisticated scheduler. And today we're going to talk about two. One of them is a classic approach to trying to improve interactivity among threads. And the second is a much more modern attempt at enabling good interactive behavior in Linux. And that's kind of a fun story. So OK, this projector takes at least 10 minutes to cool down, apparently. So assignment two is out. You guys have less than two weeks to finish it. How many people are done? How many people have started? OK, that's good. Yeah, don't underestimate assignment two. It's quite a bit more work than assignment one. Obviously it's worth a lot more points. And so please get going on it. If you're done, you get done early, then you'll just be able to chill out, leave for spring, break a week early, no problem. But if you don't start till next Thursday, you will have a very unpleasant and ultimately very unsatisfying evening. The other thing is that we, I'm still trying to figure out exactly what we're going to do this year about distributing the assignment two solution set, but it will hurt whatever it is. So you do not want that. I will take points away from you. What we did last year, I think it was like 50 points or something. There's a big chunk of credit that you will lose if you decide after assignment two that you want the solution set. So the assignments in this class are cumulative. Oh, hello. We're all going to fall asleep. There we go. So again, don't rely on getting the solution set for assignment two, because it probably won't be worth it. OK, I'm just going to, since this isn't even up, I think I'll just skip this. Hold on. Hopefully the projector is about to come on. Oh, look at that. Any questions about logistics while we're in this awkward pause waiting for the room infrastructure to recover? Yeah. Yep. Yeah, sorry, I meant to mention that. So today, at some point, we're going to release a new version of test 161. So Scott has made a bunch of improvements to it, which is pretty awesome. I don't know why it's trying to HTMI. All right, there we go. OK, sorry, I'll go back. So Scott has made a bunch of improvements to test 161 that will push today. The older version should be compatible, but you'll want this, because it's better in terms of the output, error handling, and other things. And then we're also going to push upstream changes to the OS 161 sources that you guys will merge in that will have the target for assignment two in all the tests. So yeah, hopefully by today or tomorrow, you'll be at the point where you can run all those things locally. We have a little bit of server side testing to do, so you may not be able to submit until later this week. But for this assignment, all the tests that we run are things that you guys have. So for this assignment, unless you're messing with us, the score that you get when you run locally should be identical to the score that you get when you run remotely. I mean, no promises, but it's not like last time where there were some tests that we didn't give you. This time you had everything. OK, I'll skip over this. Some people don't like my videos. All right, so last time we got to this point where we were starting to talk about, we talked about shortest job first, which is the schedule that could predict the future. It knew how long each job was going to run before it blocked. And again, normally this is not possible. We can't predict the future. So what are we going to do instead? This is one of the major design principles of this class. Instead of when you can't predict the future, what do you do? Yeah, so use the past to predict the future. There's all sorts of things that can cause threads to behave differently. But in general, what we hope are that there are patterns of behavior that emerge over time or short periods of time that allow us to get a sense when we schedule a thread, particularly for how long it's going to run and whether or not it's an interactive task. So I look at what the thread did recently and assume it's probably going to continue to do something like this. So that brings us to, so you remember when we talked about the other schedulers, both random and round robin, they both had a problem. They had a problem on interactive systems. What was that? Anybody remember? So what's a characteristic of an interactive task? So it said that interactive tasks will frequently do what? Yeah, either one of you guys, or the person who mumbled back there, yeah. Well, OK, but the scheduler doesn't know that that's happening. From a scheduler's perspective, what does that look like, yeah? OK, we're getting closer. What's that? It'll block quickly and frequently. When I give it a chance to run, for example, again, if you're doing something like handling character input, you wait until a character is pressed, you wake up, you're unblocked, you get a chance to run, you do a little bit of work, and then you go right back to sleep, waiting for the next character to come. And so the key here was, from a scheduler's perspective, again, the scheduler doesn't necessarily have a lot of under information about what the thread is doing. But it can notice that the thread blocks frequently. So what was the problem with both random and round robin schedulers? What happens to a thread like this? Let's say that it's running, and it's blocking frequently, but it's also ready to run frequently. So the blocking period is short. And let's say it's running next to a thread that's computing pi or doing some sort of background processing. Which thread gets more time to run? Let's say they run the same number of times. So they were actually scheduled on the core the same number of times. Who ran longer? Why? Right, and so the interactive task, every time it gets a chance to run, it kind of blows it. It runs a short period of time and then gives up the CPU voluntarily, because now it's blocked again. The background process is not blocking, doesn't do that. It just keeps running, and running, and running. So it makes more use of its chances. So one way to address this is through a system. Yeah, sorry. Yes, what's the difference between blocking and yielding? From the good quiz about our thread states. So a block is waiting for something, but from a fundamental perspective, what's the difference between a thread that blocks? Now what's similar is that the scheduler gets to run and picks the next thread. But what's different? Yeah? A yield is offered. Right, so a thread that yields can run. If I schedule it, sorry, I'm going to go back to, I'm trying to avoid doing this. So I have this little tick. Maybe you guys have noticed. Somebody on one of my YouTube videos has noticed. So maybe I should start giving out all Jolly Rancher every time I say that word. I'm really trying. Yeah, I just said it again. OK, so we're talking about multi-level feedback cues. So the, I totally lost my train of thought. OK, so I'm so interrupted by my own verbal ticks. OK, so blocking and yielding, that's right. OK, so a thread that yields can run. It can make progress if I decide to let it. A thread that blocks cannot run. It's made some sort of synchronous call, so it cannot execute the next instruction that it's waiting to run until something else happens. Does that make sense? Yeah. OK, so from a thread state perspective, the block threads go to the waiting state, and they're there until whatever they're waiting for happens. The threads that yield go back to the ready state. They can run again. And they may run again. A thread that yields may run again immediately if there's nothing else to run. Yield is just a way to give other things on the system a chance to have access to the CPU if they need it. So now we'll talk about a little bit more sophisticated schedule that tries to give some boost or bonus. And this is kind of a theme of today's lecture, two interactive tasks. And this is called the sort of classic scheduling algorithm. It's called multi-level feedback use. So here's how it works. Like the other algorithms, I start by choosing a quantum. This is the maximum amount of time that thread is going to be able to run at any level. Clearly, there are multiple levels because I have a multi-level feedback queue. I established some number of queues. And this is an arbitrary number. Each one of these is an ordered queue. Remember what we do when we don't know how to distinguish between threads? Just use FIFO. So this is a FIFO queue at each level. And I have some arbitrary number of levels. Threads from the highest levels are chosen first. That's what makes those threads the levels high, as opposed to the lowest levels. So when I get down to the bottom, I'm only choosing threads from the lower queues if there are no threads in a higher queue that are ready to run. And then here's what I do. So to initialize it, let's say I put everybody, to start this out, you can put everybody in one queue. It doesn't matter. If you put them in the bottom, you're gonna get them from the bottom. If you put them in the top, you're gonna get them from the top. Let's say I put them in the top. So I start off with every thread in the high priority queue. So I choose a thread to run from the high priority queue. If the thread either blocks or yields before its time quantum is up, then I promote it. And most of the algorithms that do this actually have some hysteresis. You usually have to block or yield by 10 times and then I move me up a level. But this is the simplest version. So if you block or yield before your quantum is up, you go up a level. If you have to be preempted at the end of your queue, so if I have to grab the CPU away from me, you're still running, I move you down. So here's my fancy diagram. Let's imagine I put everybody in the top to begin with. Start off by running T3. And T3 finishes before the end of its quantum. So where does it go? And let's say, so let's say it's weighted. Okay, so let's say it blocks. So it's going on the waiting queue. When it comes back from the waiting queue, where is it gonna go? Where? It's going to the top, right? It's at the top level. I can't promote it anymore, but it blocked before it was finished. So let's say I run T5. T5 runs all the way to the end of its quantum. So T5 did not block. I had to preempt it. It's still runnable, but where does it go? Goes into the middle. I'm penalizing it for consuming the CPU. T4 runs, and let's see, T4 blocks. T1, now T1 yields. So this is an important difference. T1 can get back right into the top queue if it yields before it's time is up. It's not blocked. It's still runnable, but it decided to yield. So that's nice. That gives me a chance to run T2. T2 hits the end of its quantum, and so where does T2 go? Where? Middle, okay. And essentially I just keep doing this. So if a thread blocks, goes up. If a thread or yields, if a thread blocks, it eventually goes up. First of all, it's gonna go into the waiting queue, right? It's waiting for something. If a thread yields, it goes down. Sorry, if a thread yields, it goes back into the same queue or goes up. If a thread hits the end of its quantum and I have to preempt it, it goes down. And so what you can see this as is an attempt to give a boost to interactive threads. So remember, with round robin and FIFO, interactive threads were penalized. They didn't get enough as much of a chance to actually use the CPU. In this case, what's happening is I'm trying to promote them in the high priority queue. Now what does that mean? So when an interactive thread comes back from a block, let's say it's waiting for input and that input is now ready, where is it going to go? Usually. If it's highly interactive, it's been doing this over and over again. Why are, what queue, let's say it's blocked, what queue is it gonna go back to? Which queue is it gonna go back to? Middle? Top, why the top? Because it blocked, right? So if it was at the bottom, it's been moving its way up. But interactive tasks in MLF queue spend most of their time when they are ready to run in the high priority queue. And that's what I want because that means when they come back from blocking, waiting for something to happen, they're first in line or they're at the front of the line. They're in line with other threads that are like them and they get a chance to run pretty quickly. So in MLF queue, what typically happens is that CPU bound threads, like your background thread or your I'm computing pie thread, they just sink down, down, down. Eventually they hit the bottom because they're using the CPU and they never block or they very rarely block before they hit the end of their quantum. IO bound threads on the other hand, they go up. And this is actually kind of a problem with MLF queue. What's the problem with this? Yeah, right? Yeah, so starvation. And this is something that all these approaches struggle with. So no matter how quick, if I have one thread that's interactive on one thread, if I have a bunch of interactive threads, let's say my interactive threads, there's enough of them that they're cycling back and forth into the top queue fast enough that it's possible that I never get to the bottom queue. Remember, I always choose the thread in the highest queue that's ready to run first. And so it's possible that I can starve, you know, I can sort of like read it, you know, similar to what we talked about when you guys did your reader writer locks, I can construct a scenario in which a background thread never gets a chance to run. And that's a problem. The solutions to this are all kind of gross. What's one example of a solution to this that you can come up with that's pretty obvious? Yeah, Ron. Hmm, that's interesting. I'm gonna reverse it. So that's even hackier than what I had in mind, yeah. Yeah, I could do that. I could just rescue them. Another thing that these schedulers do sometimes which is even worse is periodically they just toss everybody back into the middle queue again and let everything kind of reset and start over. These are all hacks, right? I mean, there's a fundamental problem with this approach. So keep this in mind when we get back to talk about this later, right? Okay, any questions about MLF queue? Now in general, let me come back to this starvation example. Why are there common cases where this won't happen? Let's say I have one thread computing pi and one thread who's interactive waiting for input is the thread that's gonna compute pi gonna make progress. Why? Right, so the idea here is that the interactive task is blocked a lot. When it's blocked, the background task can make progress. When it comes back, it jumps right to the head of the line and gets to use the CPU very quickly. But when it's not blocked, sorry, when it's blocked, when it's ready to run, it gets to run quickly. So I'm shortening the time that it waits to get back to the CPU when it's ready, but it also spends a lot of time blocked. And during that time, I can do other things with the CPU. So in order to starve that background thread, I need a bunch of interactive foreground threads. Just one typically won't do it because it spends enough time waiting that even that level three task that's running that's doing some sort of background operation has a chance to run. Does that make sense? That's kind of what this approach relies on is that the interactive threads sleep a lot and that gives the background threads a chance to run. Okay, I feel like I should mention priorities before we go on. We haven't really seen these come up yet, right? Yeah. I mean, does anyone wanna try to answer Ron's question? So let's say it thread blocks. What happens to the CPU during the rest of its quantum? Absolutely, yeah. As soon as the thread blocks, the scheduler runs. That's one of the kinds when the scheduler has to run. So the scheduler runs in a couple of cases. One is when the timer fires, that creates an interrupt and I say, okay, maybe I have a chance to make a new scheduling decision, de-schedule the thread that's running and choose something else. But certainly as soon as the thread blocks and I have an empty core, I wouldn't let it sit there for, that just doesn't make any sense. Any other questions about this? So people, has anyone ever used priorities before on their system? Ever? Okay. Yeah, so priorities are, in theory, priorities, now priorities are in use on your system. So priorities are really more of a scheduling abstraction. Best way to think about priorities is, are they are external or exogenous input that the scheduler accepts that it uses in its decision-making. So, we'll come back to this later when we talk about RSDL, but the priority really depends on you making a decision about what's important and expressing that decision to the scheduler. So, priorities are, on Linux, Linux has this bizarre priority system where the high priorities are negative, that goes down to negative 20 is the highest priority. Makes no sense. I'm sure it makes sense for some weird reason. I just don't understand what it is. And then the positive priorities are lower. Now, I said, three of you guys raised your hands when I asked how many people had used priorities before. Do you think that means that there are no priorities on your system? How many people think that your system has priorities that it's using all the time? Where did those come from? You didn't set them. They just magically appeared. They sort of sprung from the head of a god or something. Where did those priorities come from? Now, the scheduler doesn't know, all the scheduler, the priorities are just like telling the scheduler what to do. All priorities give you is the ability to meddle in the scheduler's job. The scheduler cannot assign priorities on its own. It really has no way to do that. So where did those priorities come from? Yeah. The actual programs themselves? Yeah, that's a good point, right? So the actual program itself. So how many programs do you think would voluntarily say, I'm not important? Not very many. So it turns out, a lot of cases, these priorities come from maintainers who distribute software. So if you run a Mac or a Windows machine, there's a bunch of software running on there. And somebody at Microsoft or Apple or Ubuntu has said, you know what, this particular background service is not that important. So I'm gonna assign it a low priority. So there are these priorities in use, but a lot of them sort of come out of the box. They come baked into the system and they're there, they help things work. So you're probably pretty happy that the process that's indexing all your files doesn't have a very high priority because if it did, your machine would be slow all the time or a lot of the time. But because it has a low priority, it's there, it's running, but whoever installed it knew that it probably wasn't very important. It's on an interactive task. Okay, yeah, so these are examples of ways to do this. The other problem with priorities, of course, is that always relative. So somebody may think that they've assigned a low priority to something that may turn out to be higher than the priority for something else that they didn't want. All right, I'm gonna skip starvation. Okay, any questions on the scheduling algorithms who looked it so far? So we've talked about two very simple algorithms, round-robin and random. We've talked about one algorithm that can predict the future, which was shortest job first. And then we talked about a practical approach, reasonably practical approach, to trying to prioritize interactive work. Any questions before we go on? Load people into a stupor. Okay, so now for the rest of the class, let's talk about Linux. So this is kind of a fun, you know, at the time when I was developing these lectures a few years ago, I was, you know, looking at some old slides and I was reading about the Linux scheduler and the problems with that. And I was like, yeah, that's kind of weird. It's a little out of date. So I went back and I did more research on it. And it turns out that this is really interesting story that came out of this. Now this is about 10 years old now. So there's probably new stories about the Linux scheduler that have arisen since then that I don't know about. But I still think it's kind of a fun thing to talk about. And the scheduling algorithm itself is kind of neat. Okay, so Linux, right? How many people run Linux? Cool, okay, that's nice. Linux is great. Linux is, you know, I think when we think about software projects we probably take Linux for granted a lot. I mean, Linux is a pretty amazing example about what a group of people working together can actually accomplish, right? I think the internet is cooler a little bit, but Linux is pretty awesome. And we all, you know, we run it. It's awesome, it works, it does great stuff. And yet it's one of those things that's kind of working within our midst that we don't maybe take the time to appreciate that often. All right, so Linux, and this is, you know, a couple of years old. And this is probably over 10 million lines of code now. You thought OS 161 was big, this is huge. Most of it, like many other operating systems is our device drivers, right? So device drivers that allow Linux to work on like everything. I mean, there's probably a few pieces out of hardware out there that people have imported Linux to, but it's a very, very small number. So if you compare Linux with Windows, I mean, Windows tries to run everywhere. Apple does not even try to run everywhere, right? Which is an advantage. You gotta give them credit for that. They chose their battles, but yeah. So Linux, gazillion device drivers supports everything, 2,400 developers at the time. Imagine you were in this class and you were trying to do the assignments in a group of 2,400. I think that would be harder, actually, because pretty much what would happen is no one would do anything, right? You'd be like, oh no, you do assignment too. And they'd be like, oh, I'm gonna get the other 2,399 people to do it instead, right? Clearly there's a free loading problem. But these people are super motivated, right? They're not doing it because they're getting credit for a class. They're doing it because it's fun, which is neat. New kernel releases every couple of months. So it is very, very easy to get behind on your Linux installations. I was just checking on Timberlake. Timberlake is like on 2,6. That's like, I know. I haven't complained. I did complain about the version of Git installed on Timberlake, but I'm choosing my battles. I'm gonna come back to 2,6, because that's a problem. That's like years old. Anyway, so they release kernels a lot. And so there's a lot of active development going on, yeah. What is? Oh, I know, I know, yeah. Yeah, there's several major revisions behind. And Linux, unlike Chrome and Firefox, hasn't gone to this insane version convention where like, do you guys remember this? Maybe you were too young. At some point, version numbers were like Linux. It was like 27 and then 28 and then 31, 30, 31. And this was true for Firefox for a long time. And then, you know how I blamed for this? I think it was Chrome. Because Chrome was like, you know what? People like big numbers. So no one knows the difference between 2.2 and 2.3, okay? That just seems incremental, almost by the definition of incremental. So let's just go full out. Four, five, six, seven, eight, nine. Pretty soon it's like, oh, I'm on version 46, right? I mean, I think Chrome moved from like one to 1.2 in two years, and then from 1.2 to 30 in 12 months, right? Anyway, so thankfully Linux hasn't done this yet. Otherwise, we would probably be on like version 10,000. Okay, they still assume that people know their decimals. But kernels come out frequently, okay? So I don't know anything about this. Other than what I can find out online, you can find out quite a bit online about how this works. And this is sort of Linux kernel development. Not much of this has changed. This is a couple of years out of date. So like any other big software project, for every file, someone's in charge of that file. Someone's in charge of the functionality in that file. Every subsystem has a maintainer. And then at the top, there's a couple of people who are really in charge. And these people have, you know, and that group may be different now. But there's someone that your patch, no matter how many in the school, is gonna go through one, a very, very small set of eyes. So I worked at Microsoft years ago in their Windows performance group. And at Microsoft for Windows at the time, and again, I'm sure this is different now, that was 10 years ago, there were three people who were in charge. They each had one of them did all the VM, the other did all the scheduling, and the other did the NT file system and a bunch of other things. And it was kind of amazing. So I was at a meeting once and people were talking about, well, maybe we're gonna make this change to the VM system or whatever. And then Landy Wayne, the guy who was in charge of the VM system came into the meeting. And it was sort of like God had descended, right? And he was there for maybe two minutes. He was like, no, we're not gonna do that. And then he left. And then everyone was like, I guess we're not gonna do that, right? Because there was no way it was gonna happen. Like, if he didn't want it, it wasn't happening. So that's sort of like what the, you know, big people up at the top are like, right? All right, so the official, now you might wonder sort of like some of the features of Git really emerged from, and if you've watched any of Linus's talks about Git, sort of the challenges of doing Linux kernel development and their experiments with some other systems. So there's a mainline kernel tree and that's what you get if you go install an official version of Linux. And then there's like a gazillion, I shouldn't say other developers, like a bunch of people maintain their own Linux trees with their own features and their own experiments and stuff like this. And so it's really cool. And Git is really designed around this workflow. So I've got 60 different remotes where I'm getting patches from this guy and this person and whatever. And, you know, so there's all of this really parallel development that goes on where things get tried and they get discarded and, you know, things like this. And, yeah, and so good. Yeah, and to be better than emailing patches around which is what they were doing for a while to maintain versions. So one of the things that's really fascinating about Linux and about other systems like it is this tension between the desktop and the server communities that use this product. And now I would probably say it's really between server and other. So server and desktop, mobile, embedded. And maybe the embedded guys have a little bit of a leg up too, I don't know. But, you know, the differences between these communities. So server Linux, when people are using Linux on their servers, a lot of times they've got really well-defined goals. A lot of times they've got money and IT staff that are behind the project. And they use this to establish contact with Linux and sort of maintain it over years. So if you ever look at who's working on Linux, Linux includes a lot of developers that are paid by actual software companies. And part of their job is, you know, building Linux for the company and building features that the company needs. And part of their job is contributing to Linux. So for example, Android has Android-specific Linux features that I still think have never been actually officially merged into the Linux mainline that they use for Android. And so they have people at Google whose job is to work at Google but whose job is to hack Linux and make the changes to Linux that Android needs to work. Now on the other hand, desktop Linux, how many people run Linux on a laptop or their desktop? See, but you guys are weird, okay? So normal people aren't like you. Just keep that in mind. If your grandma is ever like, honey, I need a new computer, don't give her a Linux computer, okay? It's not what she wants. It really isn't. I've tried this, promise you. It does not work. Even my brother didn't want one. He hated me. Okay, so desktop Linux is this weird group of people that are basically too cheap to buy Windows, right? And too not brave enough to try building a Hackintosh. Has anyone ever tried building a Hackintosh? So now you guys know what it was like to try to install Linux back when I was a child, right? It's so like, there are these cryptic instructions. You're just cutting and pasting and you're doing weird things to the computer, like binary edits to files and you gotta get your soldering iron out and poke at the chip in these weird places. Anyway, it's terrible. Linux is so easy to install right now. You guys are spoiled. Try building a Hackintosh. You get a sense of what things were like. And the other, but the other big problem with desktop Linux is this lack of understanding. And I'm convinced that this persists to the present day of what constitutes good performance. So the desktop guys have all these benchmarks. You know, if you look at databases, if you look at web servers, you have benchmark after benchmark. So when a new version of Linux comes out, somebody takes it, takes the benchmark, runs it, looks at the results compared with other versions of Linux and says to their company, we should move to the new version of Linux because it's faster or we should not move to the new version of Linux because it's slower. Desktop people, it's like, eh, I don't know. Thunderbird feels more responsive now? I don't know. I mean, what does it mean? The workloads are you. And the workload is so different based on what you're doing and how people interact with the machine. It's very hard to quantify. So Linux maintains a mailing list. This mailing list is like a fascinating place. Has anyone ever tried reading it, subscribed to it? Yeah, it's impossible. I tried reading it once. There's like 8,000 messages a day and 7,999 of them are totally weird, intimidating, cryptic, bizarre. About some, it's like, I didn't ever even knew there was that kind of device. Much less that someone had a problem with it and fixed it, whatever. So it's, and the funniest thing is when people actually try to get help. See, that's where your grandma is going to email when she can't get her Ubuntu machine that you created to go to the webpage she wants to go to. She's gonna be like, oh, there's this group of people with a Linux kernel mailing list. Let me see if I can get the, not gonna get help. If you've seen, so this is a bit of a darker note, if you've seen Linus Torvalds, doesn't always come off as the nicest person. And the Linux community has this unfortunate history that it's struggling with of sort of behavior towards women that I think is indicative of a broader problem within computer science. So anyway, I just wanted to point this out. It's not all fun and games. There's some mean people out there and some of them work on Linux, like everything else. Okay, so let's talk about the Linux scheduler. And this is old. This is almost as old as Timberlake, right? Almost in terms of the version of Linux that it's running on. So there was a, remember one of the things we said about schedulers that we want the scheduler to be fast, right? If the scheduler takes a long time to choose what thread to run, it doesn't help. The goal is to choose a thread and run it. The goal is to not spend a lot of time deciding which thread to run. Pre-2.6, Linux had a scheduler that scaled an ON time, where N is the number of threads. So, what's wrong with this? When is this scheduler the most efficient? When it has no decision to make. When is it the least efficient? When the machine is really busy. So this is exactly the opposite of what we want. If there's only one thread to run, who cares how long it takes? If there's N threads to run and N starts to get large, what it usually means is you have an extremely busy machine and the scheduler needs to get out of the way. So this was bad. So in 2.6, the Linux scheduler moved to an O1 scheduler. So Ingo Molnar, who at the time was the Linux scheduler maintainer, and I think still is, wrote a O1 scheduler for Linux. So he's using some clever engineering and now it's a constant amount of time to choose the next thread to run, regardless of the number of threads on the machine. So the O1 scheduler combined two priorities, and again this is sort of an internal abstraction the scheduler is using. There's a static priority and that's set by you, or by the system, when the task is run and it can be adjusted over time. And then there's this thing called the dynamic priority. And the dynamic priority you can think of is trying to accomplish something like what MLFQ was doing, where it's trying to give a boost to the static priority of interactive threads. So given two threads with the same static priority, we wanna make sure that the one that's interactive doesn't lose out on chances to run because it's blocking frequently, okay? So this was the main source of troubles or at least according to one person who you'll meet in a minute with this particular scheduler, was that the code that was used to determine whether or not a thread was interactive got to be a nightmare. And so it was very hard to reason about the scheduler. Given a group of tasks to run, it was very hard to determine if the scheduler was doing the right thing. And this is something that you should keep in mind whenever you're developing a program or building a system, which is that complexity is usually not worth it. If you're gonna get a big improvement in performance, fine. If you're getting a small improvement in performance, making your code completely impossible for anyone else to understand or reason about because a lot of times you're the person who's reasoning about the code, not somebody else, is not necessarily worth it. Okay. And again, it was also hard to model this scheduler. I couldn't predict what it was going to do because there was this really complicated sort of black magic calculation that was going on internally. All right, so now, so Khan Khalibis is an Australian anesthesis. That's kind of a hard word to say. And he decided, you know what? I've got some spare time. Why don't I start hacking on Linux? People do this. And he became interested in scheduling for interactive systems. Here's a picture of him. So I'm not gonna read these quotes in their entirety. They're up on the website, but one of the starting points when he started to work on scheduling was trying to figure out, what are the goals? What do we mean by a system that's responsive? What do we mean when a system is interactive? And he spent some time thinking about this. He wrote some benchmarks to try to evaluate these components of the scheduler. You know, and I think it's, to me, his terms are sort of backwards, but what he's claiming here is responsiveness allows you to continue to use your machine without too much interruption to your work. So if there's some heavy background task going on, responsiveness means that I can still type email without the system getting really slow and laggy. Interactivity would allow you to play audio or video without any dropouts or drag a GUI window across the screen and have it render it smoothly. So again, these are both with a big sort of heavy background task going on at the same time. How many people have ever seen something like this break on their system? You've got the case where you're trying to drag the window and it's like, boom, boom, boom, boom, you know, and you're, oh, something bad is happening, right, that's a bad sign. And it's a sign to you as a user that something's going wrong. Okay. So 2004, Clevis released the first version of something called the Rotating Staircase Scheduler. And this scheduler aimed at improving interactivity while removing sort of the black magic. So he, this is his description, he took out 498 lines of black magic and replaces them with 200 lines of code that implement something that's a lot simpler. So I'll describe this in a minute. There are some similarities here to multi-level feedback cues. So here's his description of it. Starvation-free, strict fairness, 01, those are all good things, we like those words. Scalable design with interactivity as good as the above restrictions can provide. So there's no interactivity estimator. There's none of the sort of weird accounting that the old scheduler had to do to try to do the boosting. The goal here is to make it simple and interactive by construction rather than interactive by sort of fiddling. Okay. You can go, you can click on that link and go to the other webpage in the slides, not on my projector. So there's a nice full description of it. Here's my attempt at this. There's one parameter here, which is the round robin interval. So this is sort of what's called the epic. It's a big interval that determines how often sort of we restart everything at the top. And there's one input. So the schedule is still does take a thread priority. Now, in contrast with MLFQ, priorities define here the number of times that a thread is given a chance to run during a particular quantum. So the higher your priority is, the more slots in the schedule that you can run in, the lower your priority to fewer. And this will make a little bit more sense in a minute. At any level, so this, you think of this as a staircase, it's like a staircase, it has multiple levels. A thread can run for a fixed amount of time. And this is important because, and each level can also run for a fixed amount of time. And so what that means is when I start running this scheduler, I can bound the amount of time it's gonna take for all, for the entire iteration. And this is something that's nice. It's an important feature because at the lowest priority, I only get to, I'm only guaranteed a chance to run one per iteration. And so if I can bound the amount of time that the whole mess of tasks takes to execute, then I can also bound the maximum time between the times they low priority tasks will be able to get a hold of the CPU. So this is an important feature. Okay, so here's how this works. We started a schedule in Epic, which is the way we do that was we put all threads in a queue based on their priority. And then I run threads from the top queue round Robin. If a thread blocks or yields, it stays at that level. Okay, and when it comes back from blocking or when it comes back from, if it yields, it just goes back onto the queue. If it comes back from blocking, it goes back to that level. Now here's the difference between MLF queue. So if a thread runs out of its quota at that level or if the level runs out of quota. So if I've run that level for too long, the thread gets pushed down the staircase. So this is this idea of a staircase. And I guess the idea is that you're pushing someone down a staircase, which is not very nice. Don't try that at home. So when I run out of my quota at a particular level, I move down to the next level and then I still have another chance to run. So this is why threads that start at the higher levels, higher priority threads have an advantage because they can run in a bunch more levels than a lower priority thread that starts at the bottom. Now if the level runs out of quota, same thing. I move all the tasks that are still in that level to the next level down and I keep going. And I continue this until all my quotas are exhausted or there are no threads that are runnable. And then I start another schedule in Epic. And remember because of the construction here, I can bound how long that's going to take when I start each Epic. I know the maximum amount of time that an Epic can take. Now that's a maximum, it's not a guarantee. If all the tasks that are in the schedule are blocked very quickly, it may be that I can finish the Epic quite quickly because no one has any other work to do. Okay, so here's my, as always, the diagram is critical. So here's my threads. I put the threads, this is a three level staircase. So I'm gonna organize these guys into cues at each level. I've got, my priorities make a little more sense. Higher is better, lower is worse. So these guys are zero priority threads. Now each task is given a quota. So that's the maximum it can run at each individual level. But also remember that each level is given a quota. So what this says here is even if all these threads use all their quota, that level can only run for 15. Now at the top, this doesn't really affect anything because that's the maximum that those threads could have run. But once threads start to tumble down the staircase, then this starts to kick in. So if these threads are still runnable at priority one, then priority one will still have a quota of 20. All right. So now I just start running things. So let's say I take thread five, run that guy, he runs for five units. So his quota is up, the level quota is 10. Where does thread five go? Let's say that this thread is still runnable. So it started at the top. Yeah, so now he's gonna, this thread is going to move down to level one. It's still runnable. So this is why this thread is higher priority. It now has another chance to run. If I pick up thread one, let's say that he, I think he yields. So if he yields, it just goes back, right back on the queue for that level. I run my next thread. Let's say that this thread blocks. So this thread is not runnable. I don't know why I used red to indicate that. But when it comes back from being blocked, it will run at this level. Assuming the level has quota. Okay, so now at this point, you see, let's say that this thread is also blocked. So now what I'm gonna do, I have two blocked threads out level at priority two. Where's the next, what's the next thread I'm going to run? Yeah, I'm going to run thread three. So now I've fallen down the staircase. Now note here that priority two still has some quota left. So if one of these threads wakes up and goes back, then I'll run it. I always run a thread that's runnable from the highest priority queue. So this guy starts running, let's sort of go through this. And so now what you can see is that what's happened is these threads have blocked. And so now this thread that started at priority two now has another chance to run. The reason is that priority one still has a bit of its own quota left. And all the other threads at this level have blocked. And so now my, and let's say that this thread just keeps running. This is a high priority thread that's computing pi. So where does it go now? It's out of its own quota. Goes down, goes to priority zero. And this is what I keep doing. So now everything's blocked at the higher priority levels. I go on running things. If one of these threads unlocks and comes back, it gets a chance to run. Now let's say that the first two threads at priority zero, so remember priority zero started off with a total level quota of 10. Let's say that the first two threads that run in priority zero both use their entire quota. Now what do I do? Can I run thread six? No, so at this point, priority zero is out of its quota. And that is the end of an epoch. There are no threads that are high, there's no threads that are runnable anywhere because priority zero is out of quota. So at this point, what I would do is I would go back to the top, reset every thread to its original level and start over. Any questions about this? Maybe we need to work through the example a couple of times, but you can see that conceptually this is very simple. I have a quota at each level. I have a quota for each task. The accounting is extremely easy. When a thread finishes running or blocks, there are two things I have to update, and then there's a couple of things I need to look at to decide how to proceed. Yeah. The level for both was dictated by the total quota. That was just something that worked out that way. So that's how I did it. If you remember going back to the original example, that's how I set things up. I don't know exactly how this would be done in practice. This seems like a reasonable way to do it to me, but you could assign other quotas. What this guarantees is that every thread gets a chance to run once, somewhere. Once I've run at my own level, any other chances to run are kind of bonus. They depend on other threads blocking. So if all threads run through their entire quota, what's gonna happen is I'm gonna run five, one, two, three, eight, six, nine. This will be out of quota, nine, four. That will be out of quota, and I'll be done. So the ability of the threads from the top queues to run in the lower level queues really depends on threads blocking and not using their entire quota. All right, that's a good question. Okay, so, wow, this is a long animation, end. So the nice thing about the scheduler, right, before we started, when we had assigned the level quotas, we knew exactly the maximum amount of time that that epoch was gonna take. I think we had 10, 20, 15. So you add those up, and you know exactly how long the maximum epoch is, and so anything that's only gonna have a chance to run once, well, that's the longest it's gonna have to wait in between chances to run, right? So this, like I pointed out, it's extremely simple. A lot of fixed, there's just fixed accounting here. So in more recent versions, what you do is you see this interleaving. So what you've noticed is that if I'm at the bottom, I have to wait a long time to run, even if I get two chances to run. And so what newer versions do is if you get two chances to run, one of them is in the middle, and the other one is at the end. And what it does is it sort of balances out the wait times between levels a little better. Okay, so I'm gonna stop here for today. I will send you a pointer to this spot. The rest of it's just kind of a story about how this didn't happen, and then there's some bad words at the end of the video. So I will see you guys on Wednesday, and we will start talking about Ritual Memory. Get going on assignment two.