 Okay, so today we're going to continue talking about why we need to multi-process, and then what we'll actually do is we'll get to the point where we can talk about how that happens. Somehow this year it feels like because maybe because of moving the synchronization material around, this seems to overlay a little bit better with the assignments you guys are working on. So some of you guys have probably looked, how many people have gone through the kernel entry and exit code in OS 161. Alright, I see a couple of hands. Well we'll look at that at the very end today and we'll look at how we actually switch between multiple processes or threads. Alright, so let's see. I'm still hoping that people who are working alone will contact the course staff. This afternoon, if you are working alone, you'll receive some instructions about how you're going to do assignment to. We don't expect you to do 100% of it and we're going to help you out a little bit and you'll need that as you write your design docs. So there's a reminder design docs and code reading questions for assignment 2 are due on Friday at 5. Any questions about logistics? Logistics question. Coming today, I promise. I'm so close. I was going to do it before class. Yeah, just keep complaining about it in Piazza because every time you do I feel bad. So it's working. Okay, so questions about interrupt handling, software interrupts, software exceptions, stuff from last time. Any questions about this before we go on? Because this is the basis of how we're going to implement preemptive multi-processed. So I'm going to try to hammer this into your brain one last time. Three things. Going to interrupt or when a software interrupt or when a software exception happens, what does the system do? Three things. So it enters privileged mode. That's one. What else? Yeah. Saves the process state. And we'll actually see exactly how that happens today at the end of class. And finally, yeah, jumps to a predetermined location. And technically, well actually that's not true. But the processor itself does save some state automatically that indicates what happened. And that's something that's needed by the operating system, for example, because I might need to know which hardware device needs attention. But what we'll see today is that the operating system, when it jumps to that predefined location and begins executing instructions, the first instructions it's executing are actually instructions that continue the process of recording state necessary to process the interrupt. So this is sort of a collaboration between the hardware and software. So let's say a program needs attention and needs help. What types of things would a program want the operating system to help it with? What kind of things would a program or user application, an unprivileged program need the operating system to do on its behalf? Yeah. Create a new process? Yeah. Yeah. Like reading from a file, asking for more memory, these sort of examples of system calls. And you guys are going to implement a subset of these for assignment too. So this is how user programs work. Not how they do everything. Obviously, we want them to do as much as possible without needing the kernel. But when they need access to some sort of protective resource, they usually do this through a system call. Okay? Now, I have a system call I want to perform. What do I do? How do I get the kernel's attention? There's really two steps here. Yeah. Yeah. So I generate a software interrupt. And on MIPS, I use the syscall instruction to generate a synthetic interrupt that behaves exactly like a normal hardware interrupt. What else do I have to do? It's one other thing. Anyone has looked at the code? Yeah. What's that? Yeah. Well, not only arguments, because the system calls will take different clushions of arguments, but at minimum, I need to tell the operating system what system call I'm trying to perform. So I arrange some arguments to the system call, and I also load a flag into a register that tells the kernel what I want. Do I want to do a read? Do I want to do an open? Am I trying to clone myself? Whatever. Right? Yep, that's part two. And then I execute the system call instruction. And that will trap into the kernel. The kernel uses the number I've loaded into a register to figure out what to do, and uses the arguments that I've loaded into registers replaced on the stack or whatever as inputs to the function. So again, this is a lot like making a function call. The big difference is that we're crossing a protection boundary. So I'm using system call to get into the kernel, which is now excluding in privilege mode. But in a lot of ways, this is fairly similar to how you make a normal function call. All right? OK. So remember, the key difference between interrupts and exceptions is that interrupts are voluntary. Interrupts indicate that the program is trying to do something that it knew it needed the kernel's help to do. That's why it's making a system call. Exceptions are in non-voluntary. An exception takes place without the program knowing, either because the program wouldn't have wanted to do that stupid thing that it did, or because the program did something that ended up needing the kernel's help, but it didn't know that at the time. And we'll come back to that when we talk about memory, because that's the basis for how we do memory protection and implement virtual memory. All right. OK. So now, keep in mind that we talked about various types of limitations that multi-processing is trying to overcome, but the biggest one was you. You're slow. And we expressed some of these delays that were built into early computing systems in terms of many, many millions of clock cycles on a gigahertz process. So there's lots of time for the processor to work without you knowing before you will start to realize if certain things don't happen, if the audio stops playing, if the screen stops painting, if the scroll stops drawing at a certain rate, you'll start to notice. Even without you noticing, there's time to get a lot of work done. All right, so let's go back in time, and we'll talk a little bit about where the need to multi-process computers came from, because the earliest computers did not have multiple users. They had multiple developers, but you had a bunch of guys that were essentially, I shouldn't say that, a bunch of men and women who were putting these computers together, programming them. And in general, it took a lot of effort to get the computer to do one thing. The general purpose computers we think about kind of arose in a weird way. I mean, they arose as sort of like a calculator on steroids, something that was designed to do a number of single things really well. So rather than having a specific circuit to do one kind of computation, at some point, we started to realize, hey, I can build this one thing that can do a lot of different types of computations. It was a little bit more general purpose calculator that was designed to do basically math. There's no notion of an interactive system back then. The way you interacted with the system was by poking it with wires, and hoping that it did the right thing. And this was sort of fun, because there was a small group of people who were in charge of working with these machines. If you read any of the reminiscing of the earliest guys who worked on some of these systems, you would be led into the room with the computer, and it was like you were in there, and you got to play with it. No one else cared. No one else cared. It wasn't even like a server now, because with the server, if the machine is interesting, normally people care if you do dumb things to it. If I let you in the room with the website for the class, you might be able to irritate your fellow students. But back then, that computer was just like a huge toy. It was like a video game system, except a lot more boring. It didn't try to shoot you or whatever. And so keep in mind, a lot of the multi-processing stuff we talk about wasn't needed at the time, because you had a single machine that did one thing. How many people did it? Again, I'm going to show my age. Has anyone here, did anyone here have or interact with an old Apple II computer? Like an Apple IIe, IIGS? That was the souped up one, the IIGS that had graphics. It had color graphics. That was a big deal. So you guys would probably find this machine to be really strange, because if you turned on the machine, it didn't do anything. If you turned on the machine without a disk and the disk drive, I don't know if you guys know what a disk is, it would just sit there. I think a prompt would pop up, and it would just sort of blink at you, but it wouldn't do anything. The machine didn't have a hard drive. It didn't have an operating system. What it had was the ability to essentially read a disk. And what you would do is the disk had the entire computer program on it. There was no OS. You put the disk in. And if the disk was winter games, then you got to try to do the ski jump or whatever. No one could do the ski jump. That was the hardest game ever, old winter games. I mean, I know that ski jumping is actually difficult, but it should be easier on the computer, not harder. So yeah, you put that in, and then you would get to play your games for a while. And when you were done and you wanted to go do your word processing, there was no way to minimize the window. You had to turn the computer off, take the disk out, get your word processing disk, put that disk in, and boot the computer up again. And then you could edit your text documents. And when you were done with those, actually when you wanted to save something, it was a little bit complicated, because you had to take the disk out that had the computer program on it. You had to put another disk in, save your file, take that disk back out, and put the old disk back in. So clearly, those computers were really fun to use. And I'm sure I'm sounding like the old grumpy man. And my day was uphill to school both ways in the snow. But this is indicative of what early computers were like. Except early computers didn't even have those disks. You spent a month programming them by hand. And then, if you were lucky, they would do one thing for a couple of hours, like calculate missile trajectories or something. So at this time, there really wasn't much of a need for an operating system, because the computer didn't do anything. There weren't multiple programs running at once. There was just one program. And actually, it's kind of funny. If you look at sensor networks and these tiny, little embedded devices that people have started to do research on today, in many ways, those are really similar to these early computers. There's no memory protection. There's no notion of having multiple things run at one time. There is something that we call an operating system. So there's something called TinyOS. And there's other operating systems people have developed for these devices. But if you come back, you remember at the beginning of class, we said the operating system does two things. What are those two things? Multiplexing resources and providing abstractions. So on a device like that, what does the OS do? It only does one thing. Which one of those things does it do? It provides abstractions. Because there's only one program running. So I don't need to multiplex resources. I don't need to share the device between me and me. I just need some tools. So if you look at these tiny operating systems, they're frequently just big libraries for doing useful things with the device. But you don't need to multiplex it all. So for a long time, when computers were like this, there was really no need for an operating system. And there was really no need to multiplex resources in particular. So but at some point, more people wanted to use the computer. And this was sort of a problem. For a while, it was locked in the closet with the computer. And it was all mine. I could do whatever I want. And then people figured out these things were interesting. And so they decided that more people wanted to use them. But for a while, what we did in order to satisfy these people is we just put their jobs in a queue. So you came to the computer. This is maybe, I don't know, probably earlier than the Apple 2GS days. But you came to the computer and you gave it a program. And some of my professors, when I was your age, would talk about programming computers with punch cards. Has anyone heard of a punch card? It's weird. You guys have the old stuff, but you missed like 10 years there. No one? Apple 2? Those were awesome. OK, so you came to the computer with a punch card, and then you went home. And at some point, someone called you up and is like, your error message is ready for you now. It was like, you have a bug in line three. It took you a week to find that out. So you think debugging is hard now. Imagine if every time I ran your kernel, it took a week to produce a panic on the first line. That would be pretty frustrating. But that's how things worked for a while. And again, here, despite the fact that I'm getting more use out of the computer, I'm not doing things simultaneously. I'm just sort of turning over the computer to one job at a time. I run that job to completion. And then I run something else. And on a certain level, this can be really effective. And there are certain cloud computing systems that essentially function this way today. Sometimes when I'm talking about non-interactive tasks, I don't really care about interactivity. There's nobody sitting there with their eyeballs on the screen waiting for it to finish. So sometimes returning to some of these batch scheduling ideas now can be pretty useful. They actually get better performance for certain types of work. But at some point, computers got these monitors. And then they were like, things were moving around. And at that point, particularly when you started to get to the point where multiple people wanted to actually interact with the computer at the same time. Now we have a problem. And this is sort of another area of computing, but pre-PC, where these dumb terminals. I don't know. I should turn my mic off for a minute. Sadly, we still have some of these around at UB. I don't know why. I would have thought we would have gotten rid of them forever. But for a while, our vision of how computing was going to work was there would be one big computer locked away in a basement somewhere. And there would be a bunch of wires that would run away from that computer. And you would sit down at one of these workstations. But essentially, that workstation, I don't know if these are actually thin clients, but not who cares. But that workstation was really just one of it. Imagine you took your computer and you hooked up like 500 monitors at the same time. And you put them all over the computing department. And anybody could sit down and log in and type. But essentially, they were all using your computer. This is kind of these old sort of shared multi-processing. And again, people who use these systems have great stories about them. You can imagine what would happen to this system an hour before your assignment to deadline. It starts to get really slow. And these systems, they were sort of just underpowered on some level. And so the screens would get laggy. And they'd be very difficult to interact with and stuff. But to some degree, no one, it's really fascinating. I mean, the personal computer was a pretty modern idea. For a long time, people thought computers would give these big, expensive things. And the idea that everybody has one, and everybody has one in their pocket, and they've got another one over there, and they've got another one because they like the form factor. That other one a little better would be pretty shocking to people like 30 years ago. Well, maybe 40 years ago. All right, so essentially, as more and more people want to use the machine, at some point, I was trying to make better use of system resources. And I also needed to focus on interactivity. So that's where operating systems came from. Partly to drive interactive tasks, but also partly to just make better use of limited hardware resources. Now, it's kind of weird because those limited hardware resources pretty quickly became practically unlimited. But for interactive systems, you guys still expect the illusion of concurrency. You still expect multiple things to be happening at the same time. And so the idea of multi-processing is still pretty important in this environment. Because, well, I don't know if I need to go through these carefully. So this is essentially what I just talked about. OK, so we did that. We did that. So let me go back to the idea of batch scheduling. So for a while, the way that we made better use of the computer was we just queued up jobs in front of it and we ran them till they finished. What's the problem with this approach? This goes back to one of the properties of the CPU that we discussed. So that's one problem, right? Is that I have jobs that have to wait. But look, if I can process, maybe that's a little bit of fairness issue, but if I can process every job as fast as I can, regardless of whether or not they're running at the same time, then who cares? Yeah, so the CPU in a batch scheduling system, unless these jobs are really, really carefully written, which is normally not the case, ends up spending a lot of time just hanging out, waiting for stuff to happen, waiting for the memory to get done with a particular operation, waiting for the disk. So the problem was that this particular approach doesn't necessarily make very good use of the system resources. And one of the things we're going to talk about back to scheduling is one of the goals of scheduling is to make efficient use of the system resources. You paid for that stuff that's in the computer, and you want it to move. You don't want to be that guy who's got one processor that's sitting there that might as well be powered off. You want to make all of the memory and the cores and everything on your system work together to move as fast as possible. Yeah, so essentially, any slower part of the system is going to cause the CPU to stall, and therefore there's an opportunity here to start to try to use that idle time. So you can imagine people that were running these early batch jobs, they're sitting there thinking, man, the CPU is only busy maybe 1% of the time. There's all these CPU cycles that I could do something else with if only I could figure out what to do with. So again, this is sort of where operating systems came from, particularly where multi-processing came from. How can I hide the latencies caused by slow hardware devices and keep the CPU busy? Now, today we're going to get to the point where we talked about preemptive scheduling. But I want to point out something, which is that hiding processor delays doesn't actually necessarily require that I be able to stop a job and start another job. All it requires is that jobs will cooperate to use the CPU more effectively. So for example, if I'm writing a particular program and I know that I'm about to do a disk operation, I can do that in a way that allows me to voluntarily give up control of the CPU and allow something else to run. OK, so let's go back to the illusion of concurrency. So if I'm going to run multiple tasks at the same time, then I have to be able to either, you know what, I have no idea what the slide means. That's old slides. OK, so imagine I have multiple interactive users on the same machine. Clearly, either multiple things need to be happening at the same time, or I need to be able to switch between things fast enough that the people who are watching the display don't realize what's happening. So now that I've got multiple people that want to use the machine, and I think we talked about this when we talked about synchronization, right? How do I actually do this? What was one of our properties of the way that operating systems work that gave rise to the need to schedule the system? How am I going to achieve the illusion of concurrency? Go back in time a week. You guys know the answer to this question. I've got one CPU. How am I going to make it look like I've got multiple things going on at once? Yeah. Yeah, so I'm going to switch back and forth between things. And more importantly, I'm going to do it so fast that you don't notice. That's the critical piece here, is that your perceptual limitations are what give rise to my ability to do this. Does anyone have a dog other than me? Does your dog like to watch TV? It does. So I've read that dogs actually, apparently, their visual processing is faster than ours. And so TVs apparently look really flickery to a dog. So you, like 25 frames per second, is completely OK to keep a human happy. For you guys, that starts to look like video. But a dog is like, why are you showing me this flip book? This is not an interesting flip book. So if you were able to process things much faster, the type of tricks that we're going to play here wouldn't necessarily work. But because you have these perceptual limitations, I can get away with it. So here's what it looks like to you. It looks like Firefox and VirtualBox and your terminal are all painting the screen at the same time. What's happening is, if we zoom in here, this. So at very small timescales, what I'm doing is I run Firefox for a minute, I let it get some work done, and then I yank it up the CPU, and I run the terminal for a minute. I run Firefox for a minute, I run the terminal for a minute, I run VirtualBox for a minute. This is going on constantly. Your processor is constantly stopping and starting threads and using that to make it look like multiple things are going on at one time. So that is what creates the illusion of concurrency. And we refer to the process of stopping one thread and starting another as a context switch. Any questions about this? This is the goal. This is what we want to accomplish. Clearly, right now it's just a fairly fussy picture, but we'll talk for the rest of today about how we're going to actually do this. So how do we switch between threads? How do we stop one thread? And keep in mind, we want this to happen often. So this has to happen rapidly. We want to keep the overhead of the slow. And it also can't involve, necessarily, involve the threads themselves. So the first problem is this. How does the operating system get a chance to run? Remember, the operating system is the program that wants to implement this feature. But in order to implement a context switch, the operating system actually has to have control of the machine. So remind me how the operating system gets control. What types of things take place? Interrupts, right? So for example, the disk re-completes, the network has to pack it, OK, so that's one source of time where the operating system will get control, what's the other one? Yeah. Yeah, software exceptions, software interrupts, right? So anytime a program makes a system call, anytime a program does something dumb or I have some sort of VM-related exception, so these are my three cases, OK? Now the problem is, what do I do if none of these things happen? Remember your perceptual limitations. Let's say that I've started running a program, and it's running, and it's running, and it's still running. It hasn't made a system call. It hasn't generated an exception. Nothing is happening with hardware for whatever reason. Just sitting there. Maybe the program is sitting there in a while one loop, doing nothing, OK? And pretty soon, it's getting close to the time where it's like, I've got to send that next audio packet to the sound card, or Britney Spears will stop playing, and the thing's just sitting there. It's running, running, running. What do I do? How do I get control of the machine? So this actually could happen, right? So I had a friend used to have an old Mac before Mac came out with OSX, and earlier versions of Mac apparently had these weird scheduling bugs. One of them was on his machine, whenever he burned a DVD, the whole machine would just hang until it finished, right? He would hit Start, and then the machine would just hang. And then 10 minutes later, it would just pop up again. It's like, I'm done, right? So for whatever reason, that one program that was writing the DVD was able to just take over complete control of the machine, right? Where essentially, you took what you thought was a general-purpose computer, and for 10 minutes, it became a DVD-writing computer. And then when it was done, it went back to do another thing. So how do I get control? How do I ensure that I get control? Yeah. Yeah, so timer interrupts. Timer interrupts are really the basis of what we call preemptive scheduling. What I do is I configure a timer that will fire at a regular interval. That timer will generate an interrupt. This is like a hardware time. There's a clock on the hardware somewhere that's wired to one of the pins leading in to the OS. And whatever it is, that timer will fire. And this ensures that there's a bounded period of time over which the operating system is ensured to get control, right? No matter what else happens. It doesn't matter if system calls aren't being made. It doesn't matter if there's no exceptions. I will get control within this amount of time, OK? And so again, preemptive scheduling means that I don't wait for a thread to stop. I don't wait for a thread to stop or to make a system call or to do something that needs my attention in order to perform a context which I can perform a context which any time the timer fires. Now, I'm not necessarily going to perform a context which every time the timer fires, but I have the opportunity to do so if I want to. It means that the operating system gets control at a regular basis. And you guys know how the rest of this works, OK? So this goes back to the things that we used. This is the reason for the concurrency challenges that we discussed a week ago, right? Timer interrupts mean that at any point in time, particularly user threads, but even the kernel, if I don't do anything about it, can be stopped, right? Now, when I restart the thread, I don't want the thread to know about this. To the thread, I want it to just look like it was just executing instructions and then it kept executing instructions. To do this properly, it should be completely transparent to the thread, right? I have to make sure that this is the operating system's job to make sure this happens, right? So the thread is just, from the thread's perspective, it has its own CPU, right? Its own CPU that it always gets to run on. Behind its back, I'm grabbing it off that CPU. I might put it back on a different core if I have a multi-core machine. It might be stopped for a while. But from the program's perspective, from the thread's perspective, there's nothing weird going on at all, right? Now, if you're clever, obviously, you can see this happening, right? I could start a timer in user space and I could execute a few instructions and I could be like, wow, it took me a long time to execute those instructions, right? What happened? What happened was that I executed a few of them, I got yanked off the processor for a while, and I got put back, right? So it's not, I'm not going to claim that I can make threads not aware of this, right? But I don't want them to have to care about this, right? If they want to measure it, they can measure it whatever. Not necessarily anything useful to do with that information, right? But if they want to just act like they have their own processor, that's fantastic. That's what I want them to do, right? All right, so how do we do this? The trick is we need to save all the state that corresponds to the moment that the thread was interrupted. So I'm the thread. I'm using the processor. I'm going along. I'm adding things and subtracting things and loading things from memory, blah, blah, blah. And suddenly operate system-taste control. So I need to make sure that when I take control, I save every piece of state that the thread could possibly care about. And then when I put the thread back, I'm going to do the exactly same thing, right? So the analogy I use every year is imagine someone came, someone was going to borrow your house for a week or borrow your apartment, and they were like, you know what? I don't really like your furniture. I have a larger television. I don't really like the walls. I'm going to change some things while you're gone. So they come in, but they say, I'm going to put everything back exactly the way I found it. So they come in, they write down where everything was, they take paint samples, they take a lot of photographs, and then they can do whatever they want. Then the house is theirs. They can repaint, they can put holes in the wall, whatever. Now the day before you get back, they start looking around and say, OK, what are we going to do to get this back to the previous condition? Because when you move back in, it should look the same. Or for example, let's say you're having to party at your parent's house over the weekend. You guys know how this works. So thread state, so here are the things that the thread is going to potentially notice if they're different. One is the registers on the CPU. This is pretty fundamental. This is what computing is on a modern processor. It's moving things in and out of registers, basically, and performing operations on things that are stored in the registers that are on the CPU. And the other thing is the stack. So essentially what the operating system does, the stack part we can come back to when we talk about VM. So I use memory protection to ensure that the thread's user stack does not change while it's stopped. The registers, on the other hand, I need to take care of myself. So here's an example of saving thread register state. Now remember when we talked about what happens when an interrupt is triggered, we said the first thing is the CPU records some state about what happened. I jumped to this fixed location, enter privilege mode, and begin executing instructions. So here's what I'm actually doing. Now I wish this wasn't rolling off the bottom. The saved state is sometimes known as a trap frame. The reason for this is that interrupts are sometimes known as traps. So this is the frame that was executing when a trap occurred. And that trap could be generated by a system call. It could be generated by a timer. It doesn't matter. Any time I have an interrupt or exception, I create one of these things because it saves the state that the user thread, it saves the state of the processor that users that saw when the operating system took control. Keep in mind, even if I don't stop the thread and start another thread, I'm still going to save this information. So sometimes what happens is the thread's running along, the timer fires. I jump into the interrupt handling routine. I enter the kernel. I look around. I say, hey, you know what? This thread can keep running. No problem. It's got more time. I don't have anything else to do right now. And what happens is I just go right back to that thread. This also happens sometimes when I handle things like memory-related exceptions. I may trap into the kernel, save the thread state, run some other instructions, and then go right back to the same thread. I just want to make sure you guys understand that this sort of saved state doesn't mean that I'm executing a context switch, but it is required to execute a context switch. OK, so if you guys have seen this code, I'll let Jinghao go through this in detail. Oh, here we go. So this is the beginning of a trap frame code. This is an assembly. And I think there might be, you know what? Actually, that's not true. I think this is essentially the first thing that the exception handling code does. So there's an entry point for the kernel. That stuff gets loaded at a predefined address that the processor will jump to when an exception or an interrupt happens. And this code starts. One thing I want to point out, if you guys have looked through the OS 161 source tree, particularly the assembly code is really well documented. So don't be afraid to look at it. David wrote this for people to use as an instructional operating system. So you're not going to find comments like this in Linux. That stuff's going to be like super arcane and weird. This stuff will tell you exactly what's happening. So what does he say? He says, OK, interrupts are off. The processor disables interrupts as part of the process of handling an exception. OK, so k0 contains the value for curthread. Not exactly sure what that means. k1 contains the old stack pointer. I think this is stuff that gets done right above this. And the stack pointer points to the kernel stack. So note that one of the first things I do when I enter the kernel after an exception is I stop using the user stack. Remember, I don't want the user thread to know that anything's happened. If I write a bunch of stuff into its stack, then it's going to know that something's happened. So when I'm allocating space on my stack, this is now the kernel stack. So the first thing it does is it allocates 37 words to hold a traffic. When you guys get into your case statement for your system calls, this is already on your stack. But it's actually allocated on your stack in assembly. So I bump up my stack pointer by, I think this is 38 times 4. And then what I start doing is I start saving registers. So at this point, I have not changed any of the registers from what the values I had when the user code was executing when the interrupt took place. I haven't written or modified any of the values that are guaranteed to be saved when an exception occurs. So there's two registers that a user program is not supposed to use because they're not guaranteed to be saved across exceptions. But the rest of the general purpose registers, the user program, is allowed to use and they will be saved. So here's what starts happening. This is my trap frame. So the trap frame contains all of these values. And these values are the registers that are exposed by the MIPS processor. You have A134, T0 through T3. There's a, I don't know exactly what those are. There's a return address. This is for function calls. So this structure represents the state of the MIPS processor that you're using. This is the trap frame that's created when you enter the code. Does anyone have any questions about this? I know this got a little, that's why I don't like to do OS 161 stuff in class. Any questions about what happened? So I wish I had more of the previous thing. Yeah, next slide. Oh, this one. What is the co-processor? Good question. I'm not even going to try to answer that question. Ask it on piazza and I'll look at it. I can't remember what the MIPS co-processor is actually responsible for doing. Yeah. Yep. Yep. No, no, so that's a great question, right? When an interrupt takes place or an exception takes place, I am not guaranteeing to the process that I interrupted that I can return the machine to the exactly same state that it was when the interrupt took place, right? Because stuff's going to happen. Time is going to go by. Packets are going to arrive. Other things are going to run, right? So in general, the larger state of the machine may change, right? A file might get created by another process. So I am not guaranteeing that the thread is going to see the exact. So for example, go back to our analogy of your trip away from home. When you come back to your apartment a week went by, right? The world is not in the exactly same state that it was when you left, right? There's stuff outside your apartment that's changed, right? All the tenant is guaranteeing is the inside of your apartment will be the same, right? In this case, the only thing the OS is guaranteeing is that your stack will not change. And technically, other threads in your process could corrupt your stack. But let's forget about that. And the processor has the same values in the registers, right? Again, there's no way for me to completely freeze the state of the machine. And in fact, doing so would mean that I couldn't let anything else run, right? Because it might change something. That's a good question. All right, so just a few more details before we wrap up for today. When I enter kernel mode, the kernel switches to a separate stack. Why would it do this? Why do I need a kernel stack that the thread is going to use when it's executing inside the kernel? Why not just keep using the stack it was already using? Yeah, we don't want to change the thread stack, right? Well, that's two reasons, right? One is we don't want to change the thread stack. The other reason is I don't trust the thread stack, right? The thread stack is out there in user memory. And who knows what the thread did with its stack, right? I just don't believe it, right? I don't trust the user memory. And I wouldn't write or read any of my own precious kernel information, right? So on OS 16.1, when you enter kernel mode, one of the first things it does is it switches the stack pointer to point at a kernel stack, right? And that's what's used to allocate local variables in the store state required to make function calls while I'm executing in the kernel, right? So, and again, let me go back to this piece of code. So down here where unfortunately you can't see, what starts happening is a bunch of instructions that take a value of a register, like let's say a zero. I take the value of a zero and I move it into here. I save it in the trap frame in that spot. And then the next instruction, what do you think it does? After I've done a zero, I move on to a one, right? And then a two and a three and then t zero, t one. So I save all of, I essentially copy all these values from the registers into these locations in memory, into the structure. Now on my way out, now I've done whatever it is I need to do, I've handled the exception, maybe I've switched threads, whatever. I'm about to start running the thread again, okay? The kernel is done whatever it needed to do. I want to let the thread continue executing in the user space. What do I do now? So if you look at these, there are two pieces of code, right? One is kernel entry that starts with a system call or an interrupt and saves all the state into the stack frame. And then there's also kernel exit, where whatever the kernel is doing is done and now I'm going back to user space. So what does that code look like? Yeah, it's the inverse of this process, right? So when I come in, I take all the registers and I copy them very carefully into this trap frame, right? When I go out, I take all the values in the trap frame and I copy them very carefully into the registers. When I'm done, the result is that the CPU registers have the exact same contents as they did whenever I first interrupted the process. And this is how I perform a context switch. So you might be thinking, well, what's the equivalent of syscall? Or essentially, what do I do to unwind the changes that I made in order to get into the kernel in the first place? There's a instruction called RFE. RFE stands for return from exception. And you could think of it as the inverse of what the processor did when it generated the, when it handled the exception in the first place, right? So it lowers the privilege level and then it jumps, it begins executing code in user, right? RFE is how I go from kernel mode back into user. So that's how I lower the privilege level so that I can begin to allow untrusted code to execute. All right, any questions about this before we talk about the overhead slide? Yeah. Yeah, yeah, so great segue. It's like, I feel like you're a plant in the audience that I put in there to ask that question. Yeah, so context switch overhead. So getting in and out of the kernel is not free. It requires work. I have to copy all of the state into memory and then there's a bunch of other things that happen. And so there's an overhead to perform the context switch. That overhead is incurred whenever I make a system call, whenever there's a hardware interrupt. I might, so if I'm handling a memory related exception, I might be able to get away with doing a little bit less work to get into the kernel, but still not great, right? So this is slow and this creates the cost to what we call transition between kernel and user mode or cross the kernel user boundary. You know, getting into the kernel, getting into privileged mode requires I save all the state and that creates an overhead. What that means, almost done, we'll get back to this when we talk about schedule, right? So ideally, you might think that, oh, this is great. You know, I had this way, now I can stop any thread and I can keep it stopped for an arbitrary period of time. So hey, let's make the machine look really, really concurrent, right? Let's start and stop threads really, really often. So you could imagine having a timer that fires really rapidly. That's constantly interrupting the CPU and I'm constantly going into the kernel and trying to decide what to do schedule-way wise, right? The problem with that is this overhead. Problem with that is that every time I have an interrupt or exception to handle, there's work that the kernel is doing, okay? And now, to some degree, it's not completely fair, right? What I was gonna argue is on some level, all kernel execution looks like overhead. It's stuff that the machine is doing that's not useful, okay? Now that's not entirely fair because the kernel does do some useful things. But most of the time what you want, you don't want your kernel to be running. Trust me, if I took your machine and I configured it somehow so that your kernel was running the most of the time, you'd be unhappy. The machine would be really slow, okay? You want user programs to run, right? You didn't buy Windows so that the Windows kernel could run all the time, right? You bought Windows so you could run Quake or whatever dumb game you wanna play on Windows. I don't know why people buy Windows. Anyway, but you had a reason for doing it. Maybe you wanted to use Internet Explorer. So you want Internet Explorer to run. You don't want the kernel to run, okay? Certainly, the context switch code, that's just pure overhead. That's totally useless, right? What am I doing? Again, it's like, if why don't people do this to other people's houses when they rent them? It's because who wants to spend two days of their vacation like moving stuff around, right? I just wanna move in and hang out, right? So I'll just be okay with where that share is because moving it is gonna take me 20 minutes that I'll never get back, right? So certainly, context switch is just pure overhead, pure overhead. I do it too much then there's a larger and larger and larger percentage of the time that the system is doing useless, useless work, right? Again, all I'm doing with all the saved state is I'm just gonna put it right back, right? So every one of those memory reads and writes is complete and utter wasted cycles, right? Okay, any questions about context switches? Yeah, what's that? Ah, great question, great question. So what happens if I get interrupted in the middle of my context switch? Is this, what I want this to happen? It sounds terrible, right? But remember, go back to our trustee comments in the source code. Interrupts are off. So one of the things the processor does for me when an interrupt is triggered is initially the interrupts are completely masked, right? So as the kernel starts to run it knows that nothing else is going to interrupt, right? And that gives me time to do some things that you're right. If I get interrupted in the middle of my interrupt handling routines you wouldn't want to write that operates, that'd be terrible, right? So the operating system controls the interrupt priorities to make this possible, but the processor's helping us out too, right? Great question. Any other questions? I think we're out of time. Okay, so on Friday we'll talk about threads, thread states and different ways to implement threading.