 Okay. Hello. It's Friday. I'm sure you guys have a really restful weekend ahead. Look forward to. All right. So today we're going to talk about, so we talked about last time ways that the kernel gets control of the machine. And today we're going to talk about how the kernel uses its control over the machine to divide up one of the more important resources which is the CPU. So we got started here, because we were talking about multiplexing the CPU. And we haven't quite gotten to the point where you understand how that happens. So today we're going to talk about that. I also think that will connect with some of the things you guys have been thinking about for assignment one, because I was answering some questions today during office hours that were definitely, like, will be cleared up a little bit, kind of understanding context, switching in threading a little bit. So that's what we're going to do today, and then continue on Monday. Okay. So assignment one, remember, is do Monday 222. I don't know if that date is good for you guys or not, 222. There's something significant about that. So today we have office hours after class, and then on Monday we just have office hours in the afternoon. So I may ask to see if some of the ninjas can sort of move around their office hours next week to cover Monday morning, but this is one of the consequences of moving to deadline like this. The other thing I want to point out is the assignment two deadline is not moving. So essentially there are consequences that you have two fewer days to do assignment two. So anyway, please finish up assignment one, focus on your locks and condition variables. That's what you need to move on. Once you have those done, you know, you may only get 20 out of 50 points, but at least you can feel good about the fact that you can move on for assignment two and you have those working. If you don't have working locks and CVs, you're going to be sad later. But we'll help you get them in good shape. Okay, I just said that. So we're almost at the submission site for how you're going to test things is almost done. If you've been running test 161 locally, you have some idea of what we're going to do on the back end. The trusted testing is a little bit different. It's hopefully, again, our goal is that when you run test 161 run locally, that point total is an approximation of what you will receive when you submit your assignment. Not quite the same, particularly for this assignment because there's some tests that we gave you. Maybe you didn't write those tests or maybe you wrote those tests and they just print success every time or something like that. So in that case, there may be a little bit of a difference, but in general, the testing should basically work the same way. So next week, I'm away on Monday and Wednesday. So as a reminder, we'll show videos in this room. After I post today's lecture, I'll add the next two lectures from the 2015 playlist to the 2016 playlist so you guys can watch them on your own time. If you want to come here and bring popcorn or whatever, the TA will show those lectures here during class time. Okay, any questions about logistics? Yeah. In-representation variables have pretty much sound for their usage in the sign-up suite. I don't know. It's a great question. I mean, let's put it this way. Passing all the tests gets you all the points. It does not mean necessarily that your primitives are correct. Now, for example, the new CV test that we just added was there to catch a corner case that none of the tests have caught. And it turned out there was something really bad that there was a nasty way to implement CVs that would have passed all the tests. So I don't want to make any promises. My suggestion for your locks and CVs is that when you are done, and this is, and, oh, hello, that's probably my wife, oops, when you're done with all the tests, come, and if your locks and CVs are passing, come to office hours and show them to one of the TAs or the Ninjas or to me. I mean, I can look at your locks and tell you in 10 seconds whether or not they're right. CVs are even easier, right? I mean, the total minus K inserts, the total number of lines of codes required to do CVs correct is like 10, right? So that's my suggestion. Just have the TA look at them and say, okay, you're good, right? Please don't ask us to do that for the reader, writer, locks, or the synchronization problems. Those don't matter, right? But for locks and CVs, we will be happy to help you just sort of manually verify that is correct. But yeah, I mean, the goal of the testing suite is to try to catch as many problems as possible, but I won't promise that they're perfect, but we are trying to sort of improve them incrementally. And in fact, some of the CV tests that we added caused some problems with the solutions out actually, so that was kind of interesting. Any questions? That's a great question. Okay, so let's go back to the core thing here, this core mechanism by which the operating system gets to run. So when interrupts happen, what happens? Three things. Number one, anybody? Yeah? I enter privilege mode. So this is one of the ways that the privilege level is raised. When an interrupt happens, the CPU just automatically, that core automatically enters privilege mode. What else does it do? Save some state required to process whatever just happened. And if I'm in privilege mode, I better be running where? In the kernel. So it jumps to some memory location where the kernel has loaded its own code. We refer to those as the interrupt handlers. And the kernel starts executing. Remember, the privilege level just went up. So I better be inside the kernel. So we talked about hardware interrupts, we talked about software exceptions. To some degree, the kernel is like a normal program and has an interface, but it's not quite like a normal program because to access the normal library, I can just make a function call and push the arguments on the stack and do things the way I would normally do. How do user programs access the kernel's interface? How do they access the system call interface? How do they ask the kernel for help or get the kernel's attention when they need something done? I'm a user program, I'm running along happily, but at some point I need the kernel to do something for me. I don't have enough privilege to get this to happen. How do I signal to the kernel that I want its help? So the software interrupt, right? And this is the process of doing this on the MIPS architecture that you guys are using. This relies on a special hardware instruction called syscall. So Guru is preparing the screen cast for the next assignment. I hope you like Guru. And one of the things he's going to do is sort of walk you through the whole process from making the C library call to the C library generating the system call to the syscall instruction and then how you get into the kernel and end up in the actual system call case statement that you guys are going to be adding things to as part of assignment two. So we'll show you exactly all the way using OS 161 how this happens. But there has to be some instruction, this is a software interrupt, that a program can use to tell the kernel, hey, I want you to run. When this happens, the process is identical to a normal hardware interrupt. The kernel starts running and in order to communicate to the kernel what I want to happen, there's a calling convention where I need to put some things and registers or someone on the stack. So when the kernel starts up, it not only knows, oh, there was a system call that took place, but it knows what system call, so it knows what you wanted to do, and it knows things about that system call. Where the read is going to go, stuff like that. But again, you guys will have a chance to do this yourselves. So the difference between interrupts and exceptions. So in the interrupt case, I am asking, the program is asking the kernel to do something for it. In the case of an exception, I wasn't aware, actually. I just executed an instruction. I thought everything was going to be OK. And then the kernel started to run. And in some cases, that's because I did something that's unrecoverable, like divide by zero. In other cases, the kernel needs to play some games with memory management that we'll talk about later in the semester. OK. So any questions about the stuff before we go on? So it's a hard-coded memory address that either I have to program it to the core like right when it starts up, or it's just baked into the chip. Like the MIPS R3000 processor, as far as I know, has two locations where it jumps to for interrupts. One is essentially memory address zero. That's where it jumps to for, I can't remember, I shouldn't get it wrong. There's one location for one type of interrupt and another for another type of interrupt. And one is like zero and the other is like 40. And that's hard-coded. So when the kernel boots up, one of the things, and we'll show you this too for assignment two, one of the things the kernel has to do is put its code at those locations. So for example, I know that I'm booting up on a MIPS R3000 processor, I know what that processor wants. And so when I start to run, I say, OK, at whatever address is are hard-coded into it, I'm going to put code that's going to lead into the kernel. Usually the exception handling landing spots are pretty small, and so normally what I do is I just do right a few instructions and then jump somewhere else. But as long as the kernel knows where those spots are, it can make sure that those spots generate a series of instructions that lead into the kernel. Does that make sense? That's a good question. I should make that more clear. OK. So now let's talk a little bit about why and how we use how we combine some of these mechanisms to achieve one of, I would say, probably the fundamental illusions of modern computing. And it's not entirely an illusion anymore, but there's still a lot of magic to this. And this is called multiprocessing, or CPU multiplexing. This idea that despite the fact that I have a small number of actual cores, the computer can make it seem like there's a lot of things that are going on at the same time. And so today we'll talk about how that happens. So in the past, again, up until quite recently, you had one core. You had one core on a machine, one processing unit. Why was that? Why didn't I have, well, there's a couple reasons for this, but historically, in 1985 or whatever, why did most machines, why were they shipped with one core? Why not more? Yeah. No, so we're going to talk about this today. So those machines were doing a lot. I mean, you remember using these old interactive machines, and you had your music player going, you had a bunch of tabs open. So to some degree, they were making it look like a lot was happening. And that's what we're talking about today. How many people have ever built a computer from scratch? So you guys should be able to answer this question. Why was there only one core? They're super expensive, right? They're not going to buy 30 of those. That's 90% of the cost of the machine. It's a little bit of an exaggeration. But it's the most expensive component. And of course, one is the minimum number that you need to have to have a computer at all. If you have zero, you've got nothing. Just got a bunch of hardware components that aren't going to really do much together. If you have one, you have a computer. If you have two, then you have all these problems associated with having more than one and blah, blah, blah. So one is sort of the minimum. And this is the most expensive component by far, right? So now, again, so now you have many. So who knows what's driving this trend, right? So for a long time, cores, and you guys are, I wish you would live through this, because it's kind of a fun. I think I'm the ideal age to be a computer scientist. You guys are a little too young, because you guys have experienced some of the things I've experienced. So do you guys remember what happened for a while? You would hear a new processor comes out, and there was something that kept happening. Back in the 80s and 90s, what's that? Y2K, I like that. I was there too. I was scared. Suddenly, all the 7-Elevens were going to go down. No, no, no. So processors, right? So when Intel would release new processors, right? What was getting better about them? Speed, right? Yeah, so they were getting faster and faster and faster and faster, right? So the P2 and the P3 and the P4, the clock rates kept going up. And it was like, you just, it felt faster. Suddenly had a gigahertz processor. It was like, wow, that's more than a megahertz processor. That's more than a kilohertz processor. So that was a real intuitive way for you to feel that things were getting better. So that stopped at some point, right? Why? Yeah, so did anyone ever have a P4? So do you remember how big the heat sink on that sucker was? So you could, I shouldn't say this because I'm sure somebody did, but the old P4s, I mean, it was like, here was the chip, right? And then on top of the chip was this heat sink that was, I don't know, like 12 inches high, right? Because that thing would get so hot when it ran. And so eventually what happened, and you've taken courses on hardware, you've probably learned about this, is that Moore's law scaling, to some degree, starts to break down. So we've been very good. And all those earlier generations where transistors kept getting smaller and smaller and smaller and smaller, I was able to pack more and more and more onto the same size, die, and so everything was getting faster and faster. But at some point, there's what they call leakage current, that that starts to break down. Things start to get really hot, and it's bad. So what have I started to add instead? So at some point, I just ran out of the ability to just make one core go faster and faster and faster and faster. And so instead of doing that, I started to do what? And more cores, right? So if you look at the whole last decade of Intel products, this is how they're achieving more performance, right? Is with more cores, the cores aren't getting faster. You're just getting more of them, right? So first, you had four, and then now you maybe have eight or 16 or 32 on some or 64 on some high-end machines. Now, this is kind of a different type of scaling, right? So if I make, there's a certain kind of workload, you imagine. Like, if I'm computing digits of pi, if you make my processor twice as fast, I can compute twice as many digits of pi. I'm super happy. If you give me two processors that are both the same speed, it's actually not necessarily as easy for me to compute more digits of pi. I have to think about things differently, right? I have to break my program into two pieces that can run simultaneously rather than just getting it to go faster. So the move towards multi-core is really, you guys may not be aware of this, but it's a huge paradigm shift in terms of how we think about systems, how we think about software. And it's kind of neat, right? But that's where it came from. We didn't suddenly start to give you more cores because it was just like 2005, and that was the thing to do. It's because we ran out of other ways to make the system faster. And so we just decided to give you more computing of the same speed and hope that the system can take advantage of those resources, right? Awesome, I just said that, right? So we couldn't make transistors smaller anymore. We couldn't, that game sort of ended. And so now we're just giving you more transistors, more processing units, and expecting the system to be able to divide tasks well across those cores. Now in general, even if you have four cores, like your smartphone has four cores or eight cores or 32 cores, on a reasonably well-utilized system, there's usually many times where there are more things to be done than there are cores. So if you believe me, think about a 32-core machine. Now if you don't believe me, think about a four-core machine, but you've got four tabs open, you've got a bunch of browser windows, you've got compilations going in the background, you've got background tasks that are synchronizing your Dropbox folder, you're running three copies of Sys161 for some reason, you're running Test161, which fires up a bunch of copies of Sys161, right? So there's a bunch of, there's times where I can get to the point where I've got more tasks to run than I have cores to run them on, right? And so the CBU limitation historically has been number. That's one of the key limitations that the operating system is trying to address. So what the operating system is trying to do is to trick you into thinking that you have a lot more cores, a lot more independent processing units than you actually have, right? So even on these old single-core machines, you could still be doing a bunch of things at the same time, right? And that's because you're a human being and your eyesight isn't very good, right? We'll come back to that in a sec. Okay, so another limitation of the CPU that operating systems are trying to address is its relationship with other parts of the machine. So compared with memory, compared with the disk, what's one word that you could use to describe the CPU in comparison to every other part of the system? Faster, not the bottom, right? So every other part of the system is essentially conspiring to try to slow the CPU down. Memory is super slow, right? So have you guys studied sort of like out of order execution and stuff like that before? Oh, too bad, that stuff's awesome. So it turns out that modern CPUs are doing all sorts of completely wild things to try to hide memory latency. So how many people thought memory was fast before? Thought memory's fast, you know? It's not fast, it's from the CPU's perspective. It's like, I could die waiting for this memory read to complete. And so what it turns out is that the CPU is actually taking that stream of instructions that you sent in and playing all these games with trying to move the instructions around so that it can hide latencies that are caused by memory. It's super cool. If you haven't looked into out of order execution, look into it because it's mind blowing. It's really neat. It's the hardware guys at work, right? So faster than memory, right? So, and this is something that's addressed on the processor. So the operating system doesn't really have a role in addressing this. What happens is that hardware, the processor core itself tries to hide these latencies. It's way faster than the disk, though, too. And this is a case where the latency is addressed entirely by the operating system. There's another part of the computer that the OS is way faster than. What is it? I mean, I've got the memory. I've got the disk, yeah. We're getting there. Follow the input-output stream. Where does that input-out-out-out-out-out-out-out-out-out-out stream lead? You, right? Like, you're super slow, okay? You are. You have incredible cognitive abilities, but the CPU is great at adding, okay? So, while you've been thinking, it's been adding stuff, right? And it can do a lot of adding and subtracting while you're dreaming, right? So, while the CPU is waiting for you to figure out what to do, there's a lot of dead time, right? And this has only gotten worse as CPUs have gotten faster because humans are not getting faster, right? Our cognitive abilities are probably, I don't know, like, well, anyway. I won't make an unkind political remark right now, but. Anyway, so this, and there's obviously nothing that the operating system can do about those limitations, right? We're stuck with that. But there's certainly a lot of dead time caused by you, the user, okay? So, it turns out that there have been your limitations, your perceptual limitations are very well studied. They were studied by the old phone companies in particular because has anyone ever tried to talk with someone like in another country, maybe halfway around the world, where there's a really bad delay on the phone line? Yeah, so it turns out that there's a certain delay that humans will tolerate. And then there's a delay which you just cannot have a conversation, right? It's too slow. So this is kind of a rule of thumb in the world of people who care about this, which is that 15 millisecond delays are imperceptible. So if I click a button on my app and it takes it 15 milliseconds to kind of show that the button was depressed, if it takes less than that, you feel like the app is interactive. If it takes more than that, you start to feel like it's laggy. Has anyone ever used a laggy app before? Everybody has, right? So think about this from the perspective of a one gigahertz processor, that's 15 million clock cycles. Like that's a lot of adding and subtracting and moving things back and forth to registers that it can do during that time. So hopefully, so this is the thing that's sad about the fact that there are still laggy applications is that that's a lot of stuff, right? So this is kind of broken. Another way to think about this is that for video, 25 frames per second is a pretty standard frame weight that's used in a lot of movies. It's what people, it's a level at which people perceive the video to be smooth. If you reduce that a little bit, it starts to look like this, right? It starts to look stroby. In fact, I think for dogs, that number is actually lower. And so I think that's why dogs don't like to watch television because it looks like choppy, right? So maybe if we've recorded movies with higher frame rates, dogs can watch them too. So I think you guys should get on that. So this is another thing. And again, I mean, that's 40 million clock cycles. So you might have thought that when you start up a YouTube video, that's making the computer busy. It's not making the computer busy, right? The computer renders a frame, that takes a few clock cycles and then it sits there waiting for 40 million clock cycles for the next frame. That's a chance to do a lot of other stuff. Okay? And this was the rule for these old telephones, this was 100 milliseconds, right? And so that's 100 million clock cycles. So just to point this out, that the computer is way faster than you are, okay? So let me just do a little bit of computer histories and feel somewhat obligated, right? So because to some degree, some of the idea of multiprocessing computers emerged from some of the limitations of these early systems. So these ancient systems, like these old Mark I machines, if you guys seen these movies recently, like where they're building old computers and stuff like that, you should see them. What was the one about Turing? Imitation game? Did I not see the Imitation game? Oh good, okay. Well, that was one of those movies. That was a computer. You guys didn't realize that? You're like, what was that thing with the wheel spinning? Oh, I know. I was just in it for the love story, right? Okay, so anyway, so this is the Mark I. This was a very, very early computer. And these computers really, there was no notion of multitasking. These computers, so when these guys were programming the computer, and actually I want to point something out, which is that this picture is pretty inaccurate because many, many, if not most, early computer programmers were women, actually. Programming was considered to be an occupation that was below male scientists, right? Interesting. Anyway, so these computers were not the computers that you think about today. It was almost like a fancy, really powerful calculator. You set it up to do one computation and that's the computation it did. Maybe it computed a missile trajectory. Maybe it computed the settings for one of those bomb-bom machines that we needed to break German cryptography or something like that. It was a single-purpose machine and it just sat there doing that thing till it was done. And while the computer was running, I mean you would spend like a week programming, moving wires around to get this computer to do that one thing. And then it would do that one thing for a while. Maybe there were some inputs you could move around here and there. And then it would finish, and essentially this is almost in a way like a precursor to those old Apple II machines except for the fact that there was no display or whatever and what popped out was probably like a number. So this is the dawn of computing. These are probably, and again, I'm not sure that you guys, I'm sure that you guys would not recognize these as a modern computer. And there was no operating system at this point. If there was one, and I think Ron brought this up earlier in the semester, it was just kind of a library. It was just useful routines, useful design patterns that the programmers realized that they were reusing over and over again. So there was a particular part of the code that I could pull out, and every time I reprogrammed the computer when I needed to do something like that, I remembered that this particular series of switches or whatever is what got that particular thing to happen. That was it. So at some point, the computer's gotten useful and then a lot of people actually wanted to use them to do their own things. At this point, the trick was, okay, I have this one computer. So in the beginning, really there was no impedance mismatch between computers and the people who wanted to use them. But they very quickly became one. And so what we did is we just, let's see what happens here, right? These early computing systems were what we're called batch processing systems. So they still did one thing at a time, but they made it easier for you to tell that computer what to do. So some of my professors when I was in school and maybe some people who are still here in the department, remember the days of these batch processing systems and they usually associate them with a particular thing. Have you guys ever heard of this? What's a way that you used to program those old batch processing systems? Punch guards, right? So you would come down to the computer, it was probably in a basement somewhere, with your stack of punch guards, and you would hand it to the person who ran the computer. And they would put it in a line with other stacks of punch guards. And maybe a couple of days later you would come back and they would give you that stack back and it would say there was an error on card two and then you would try to fix it. And this, could you imagine how slow that is? I don't think I would have liked computer science back then. I like to iterate, right? But I mean, you had a lot of free time in between. It could hang out, read books, whatever. But it was torturous to get things to work. But the reason that this was happening was that there was no way to run stuff in parallel, right? Your job was just being run on the machine. You'd feed the cards in, it would do something, it would produce an output that would be put in the queue and I'd move on to the next machine. And this is not necessarily, in certain cases, a bad way to do things. And there are still plenty of echoes of this approach in modern computing. There are definitely systems that are in use by major companies and cloud computing providers that are pretty much batch processing systems. And one of the reasons we'll come back to this at the end of today's lecture is that there is a cost to switching back and forth between one thing and another. If I have something that really, really needs to run fast, sometimes the best thing to do is just give it all the resources on the machine and just let it run uninterrupted and sort of, you know, just give it the machine, say you can use this entire thing until you're finished. Just don't try to make it compete for that machine with anything else, okay? So now, but then the next thing that happened, of course, was that, you know, people got tired of standing in these lines, people actually wanted to be able to interact with the machine, they wanna be able to use the machine, and machines got fast enough that this was possible too, that's important, right? You couldn't interact with those old punch guard-based systems, they were too slow. But once they got fast enough, that it was feasible for you to wait. So think about it, I was just telling you how fast modern computers are. There was a day where it didn't make sense for you to wait for the result from the computer because the computer was so slow. Yeah, that's a good question, actually, yeah. SVG for the win, right? Yeah, this is, these are old, crafty icons. I thought he was gonna ask, why are they all Firefox icons, right? Which is a better question, right? Yeah, back from my days of romance with Firefox, it's over. Okay, good, I don't know, that's a good question. Oh, actually, that's the SVG icon out a bunch of, it's very sad, anyway. I'm glad I don't have to use that program anymore. Okay, so, and out of all of these struggles, emerge components of the modern operating system. So we got to the point where we had a lot of people who wanted to use the same physical resources, and the computer had gotten fast enough so that was feasible, right? People could actually wait, you could actually type something into a terminal that was connected to some big machine in another room, and the machine was fast enough that rather than go away and read your books for two days and come back and get a result on a punch card, you would wait, right? You'd wait for the prompt to come back and it would display something, right? So, you know, so think about it. The first thing we did was we scheduled the humans. We put you guys in a queue, you know, through batch scheduling. And, okay, let's, okay, so yeah, let's talk about this, sorry. So, so batch scheduling where we were running one thing at a time, I told you that this is still done today and it's still efficient for certain types of workloads, right? But what are the problems with batch scheduling? If I give one program, entire control over the entire machine, and just let it work, what is likely to happen? Particularly given that the CPU is and has been and always will be way faster than other parts of the system. So, I've only got one thing running. What is going to happen to the CPU? Yeah. Be underutilized, why? Yeah, so I'm gonna spend a lot of time, so you think about how your programs work, right? I mean, I do a little bit of computation and then maybe I read something from a disk or I read something from memory. Every time I have to do one of those slow things, like go to memory or go to disk, the CPU has nothing else to do, okay? Now, what's really interesting is if you think, and I'm just gonna make a modern analogy here, you know, go, which is Google's new programming language, tries to solve this problem even for single programs by just giving you the ability to create a lot of things that need to happen, right? And so by doing that, go tries to make sure the entire system is busy all the time. So you express everything to go that can happen all at once and go says, you know what, anytime anything is stuck, I'll find something else that could run, right? So this is a modern attempt to achieve an extremely high concurrency even in a single program. Now, of course, the problem is you have to break your program up into these little parts that can execute independently. If you just write a program that just does one thing at a time and has one stream of execution, every time it causes the processor to idle, it's just gonna sit there idle and nothing's gonna happen, okay? And so one of the first reasons that operating systems came about was people realized that this was efficient. This batch scheduling approach, where I didn't really need an operating system, just like the old Apple II, I could just put one disk in, let it run for a while, take that disk out, put the other disk in, let it run for a while, but I wasn't making very good use of the system which was in very high demand, okay? So now what I'm gonna do is start doing something called context switching. And context switching is this idea that I wanna be able to stop something that's using the processor and restart it later. And I also win something that's using the processor stops for some other reason, let's say it's waiting for a disk, I wanna be able to start something else and allow it to run and make use of that idle time, right? And this was really to some degree where operating systems started was as a way to try to make to improve the utilization of the CPU by being able to run multiple things at one time. This is way before you had laptops and interactive systems. This was just as a way to keep servers busy, right? Even the server, I mean, again, now you do enough with your own personal machine that you need an operating system, but back then this was just about these batch jobs, right? I've got three different missile trajectories that I'm trying to compute. I can do them faster in parallel because the CPU is gonna be busier. Now, to some degree, if I want to hide processor delays, all that requires is something that's called cooperative multithreading or cooperative schedule. So the way this works is that your program or program A runs along and at some point it needs to do something slow. Let's say it needs to read some data from the disk. So it does that and now it has to sleep for a while. While it's sleeping, the operating system finds another program to run and starts it running and it gets to run until it needs to do something slow and then it gets put to sleep, right? So in this case, and you see why this is called cooperative because the ability of a process to run depends on some other process going to sleep. And I have an anecdote about this. So when I was in college, I had a friend and this was like an earlier version of Mac OS and I'm not exactly sure if this is true but apparently those early versions of Mac OS did forms of cooperative scheduling in certain cases. So he was a music taper. He used to go to Dave Matthews' band shows and record them and he recorded a lot of those shows. And he would burn DVDs for people with his music on them. So he had a DVD burner program and when he started to run that DVD burner program, his whole system would freeze for like 20 minutes, right? So he'd start it, it would start to burn the DVD and everything else on the system would just stop happening. Why? Like the screen's not painting, nothing's happening. So again, this is a cooperatively scheduled machine. How can this happen? DVD burner never stopped running. It never gave another process on the system the chance to run, it just kept waiting for the next chance to write to the DVD. Now you can kind of argue that maybe this made sense because DVD burners had this problem where they have to keep up with the DVD so if they don't get scheduled often enough they can miss a track and then you waste an entire DVD. But anyway, so on these old machines that this could happen, right? If there was one process that refused to yield, it could keep the entire machine locked down and no one else gets to run, okay? And we'll come back to how we address that problem in a minute. So essentially, once I get to the point where I have multiple users or certainly interactive use, I need to be able to create this illusion that there's multiple things going on at one time. It's either one task for, you can imagine that I've got N people who are each doing one thing or I have one person who's doing N things, right? This was more common 20 years ago, the second case is normal now, right? You have your own machine and you're doing a bunch of things at the same time. Or in a certain case, I have full multi-tasking systems like everybody can be doing 10 things at once on Timberlake and it should work, hopefully, right? So how does this work, right? So this is, you can think of this as kind of being the illusion. So I can say I've got a single core system. This is the illusion that it's providing me. So I've got a couple copies of Firefox going on and then I'm using Firefox at the same time as I'm using my terminal and then I've got maybe two VMs that are running. I don't know why I'm using two VMs at the same time but why not, because I can. And this is my experience of the computer, this is yours, is that I'm doing a bunch of things at the same time. So what's actually happening? This is one core. So how is that core doing that? So you combine your limitations, your perceptual limitations with context switching, right? So this is what it looks like when everything is zoomed out, right? This is what it looks like on your time scale. Here's what it looks like on the computer time scale. This is what is actually happening on your machine today and certainly on single core machines in the past. On a single core machine, nothing is ever running at the same time. It's impossible. There's one CPU. If that CPU is executing instructions for Firefox, it is not executing instructions for my terminal. And so this is the reality, right? This is what I see, this is what's actually going on, which is that more rapidly than I can perceive, the operating system is stopping and starting those programs over and over and over again. To a degree where I can be typing at the terminal and it looks like the webpage is loading at the same time. In reality, again, terminal gets executed a little bit, webpage is drawing. So in reality, things are going back and forth really quickly, but the operating system is doing this so fast that it blurs together into me, it looks concurring. So we call the process of doing this context switching. It kind of makes sense, right? I mean, the operating system is switching the context of the core from one thread, one process to another, over and over and over again constantly. So now, so here, you know, this sounds great, but there's a couple of problems here, right? So the first problem is, how does the operating system get control? So we just said that if a process wanted to just start running on the CPU, and if it never did anything that blocked, if it never made a system call, if it never called yield or whatever, it would just keep going. So how does the operating system get control in order to implement this? Well, when does the operating system get control of the system? When it interrupt happens. So I've got software interrupt system calls, but I just told you that the program that's running isn't gonna make any of those. What other kind of interrupts do I have? Got hardware interrupts, but let's say that the disk doesn't have any work to do and the network card is idle, whatever. So I can't rely on any of my peripherals, but how can I use hardware interrupts to make sure that the OS gets control? Timers, right? So remember the timer? Are we talking about timer interrupts being critical to context switching? That's what the timer interrupts are for. The timer interrupts ensure that a certain amount of time cannot go by without the operating system getting a chance to run. If I start a timer at a 10 millisecond rate, that guarantees to the operating system that at minimum, after 10 milliseconds go by, I get a chance to run, meet the operating system. And that gives me a chance to stop something and start something else, right? Yeah. So the CPU has this constant source of chances for the operating system to take control and to use the machine, okay? So the difference between cooperative scheduling where the operating system has to wait for something to need help or for there to be some sort of hardware event to take control and what's called preemptive scheduling. So in preemptive scheduling, I don't wait for something to happen. I preempt a process from running, I take the CPU away from it and I give it to another process. And I use this, I use timers to do this. And essentially again, I mean timers just trigger the rest of my typical, my typical interrupt handling mechanism, right? Kerbal starts to run, figures out what happened, said, oh, it's a timer interrupt, okay, thread A's been running for a while, what should I do, right? So timer interrupts are how I create this illusion of concurrency. It also creates some of the problems that you guys have been dealing with when you've been using, working on your synchronization parameters and those problems. So OS 161 is a preemptively scheduled system. So periodically, every one of those threads that we start off during your tests is getting stopped, even if it doesn't call yield. So it could be in the middle of, you know, in one of those little compute loops that it uses and it gets stopped, moved off the core, another thread that's ready to run gets started up again. Now, the trick is doing this so threads don't notice. And this is something that I have to be able to do. So if I'm gonna stop you at any point in time, put you in a cryogenic freeze, and then revive you, you have to be like, whoa, that was weird. Like a year went by, but I just, I'm in the same classroom, faces are different. That'd be kind of interesting actually. Someone can try that on me right now. So essentially the thread can't be able, now the thread will be able to tell, right? I can see, oh, a bunch of time has gone by. But in terms of what I was doing, that has to be the same. So if the operating system messes up and like some of my stack is gone or some of the things that were in registers aren't the same, I'm in trouble. So to be able to do this safely, I need to make sure that I save all the state of the thread that it relies on being saved and restore it when the thread comes back, okay? So what is the state? What is thread state? The thread is running along, it's using the CPU. What are some of the things that I need to save to make sure that I can start it up again later? What's that? People are mumbling something, yeah? Registers. Yeah, so the contents of registers. Remember I mean, this is the fastest storage on the machine and so threads are using it constantly to store intermediate results and whatever, right? So I need to make sure that all the registers are the same, what else? What's that? I'm trying to address this just usually in a register, but yeah. Yeah, I need to make sure that I'm, make sure that this instruction pointer is correct, right? So if I stop you after a particular instruction, I need to restart you at the next instruction. If I restart you a little bit too far before, a little bit too far after, weird things will happen, right? So I've got registers, I've got the thread stack as well, so this is something else that I need to make sure that I, now, the stack is something that I just rely on memory protection for. So every thread has its own private stack. When the thread stops running, that stack should not be accessed by any other thread. Yeah? The stack pointer is in a register. So the stack pointer is one of the things I have to save. Yeah, so the stack pointer gets saved as part of the register set, right? But the contents of the stack, does that make sense? Yeah, the memory contents of the stack like your saved variables and stuff like that, that I need to save. Okay, so let's look at this. This is one of the few times where we need some OS 161 code in an example. So every time, if you look at the exception handlers that lead into the context switch code, every time I enter or leave, every time I enter the kernel because of an interrupt, the first thing I do is save all of the state. Why is that? I mean, why couldn't I be like, ah, I'm gonna do some work for, so I'm gonna get around to saving the state later? Why is it so critical? Let's be the first thing I do when I enter the kernel, when I perform a context switch. Yeah, who's gonna change the state? Who else wants to use those registers? Me, right? I'm the kernel, I'm like, you know what? I cannot operate without those registers. So the kernel is gonna totally pollute all those registers. It's gonna use them for its own purposes. So the first thing it has to do is say, okay, as soon as I start a context switch, before I overwrite any of those registers that were in use by that thread, I need to save all of them, okay? And then I can use them. Once I've saved them, they're mine, right? I can overwrite any of their contents as long as I restore them later, okay? So that saved state is sometimes referred to as a trap frame. It's kind of like a stack frame that you would think of for a function, but it's generated by a trap or an interrupt. So here's the actual code and the exception handler that does this. This is at the very top of the OS 161 exception handler. What is this code doing? It allocates states on my own stack to store this object. I need somewhere to put those registers I'm about to save. I'm gonna put them on my stack. Then I'm gonna save all the general registers and if you look down here and you can find this code, what this code is doing is just copying those registers into that memory region that I just allocated right here on my own stack. By the time this gets into C code, there's a struct trap frame that you have access to that has all the saved registers that were there when the thread entered the code. And so here's an example of the trap frame, right? So as you can imagine, there's a one-to-one mapping between this trap frame and the saved state that was at the registers, okay? All right, so let me try to get to this one last slide. So when I'm executing in the kernel, the thread switches to a different stack. Why do I need to do this? Yeah. Well, there's two reasons, right? One reason is I don't wanna trust the user stack. That user stack is in user memory and who knows what's in there, right? It could be, it could point into garbage memory, right? You know, the user could be trying to trick me. It's like I'm gonna set my stack pointer to some bad memory and then enter the kernel and see what happens, right? So I don't want that. But I also, in the case where the user thread isn't malicious, I don't wanna pollute it stack. It's stack has held the saved state. That's part of the state that I'm trying to save over a context switch. So that's something else that we do when we enter the kernel. So we switch to a kernel stack, okay? So all of this code that we're going through today is something that you guys will see for assignment two. And you can certainly find this in your kernel while all of this is there, okay? All right, so I will, I'll just point you to the last little bit of this online somewhere on a video from last year. It's like two more slides. And then Monday and Wednesday video lectures. Good luck finishing up assignment one. I will see you next Friday.