 Yes, Latentio. Thanks again. I appreciate your attention for the next 15 minutes or so. Again, I am Carl. I answered your questions, comments, complaints on this. The second part of this week's series of lectures on this, today's is going to be a little bit more conceptual than either Mondays or Fridays on that. We're going to be beginning by kind of doing a quick review of what we talked about on Monday. And then go over to a little bit about, if you will, the genesis behind the concept of, where did these modern preemptive, multi-tasking operating systems come from? What's the background to them? Why do we need to come up with them? Kind of some of the motivations that, if you will, led to the design decisions that produce current, well, macOS windows, UNIX, what have you on that? And then the balance of today's class is going to be, if you will, a little bit. We talked about this on Monday. The CPU and how it can be multiplexed. Other parts of the class are going to be talking about how other parts of the system resources, like memory and disk drives, can be multiplexed. But we're going to be talking about the CPU today and some of the mechanisms for how that actually plays out on that. So in terms of, there we go. Interrupt handling. Remember I had mentioned this last class, that essentially all interactions with the kernel begin with some sort of an interrupt. And again, I do apologize for the terminology. I'm somewhat sloppy. Interrupt, trap, exception, error. They're related. They're not quite the same. But whatever you call this thingamabob, everything essentially begins with some sort of an interrupt on that. Again, be it page fault, be it hardware, what have you. And what happens again with an interrupt on this? We go back into privilege mode, because we talked about, remember, we've got these two modes, if you will. We've got kernel privilege mode and user unprivileged mode on this. And we want to kind of keep the twain separate on that. And it's easy for the kernel to demote a user process. But again, the tricky part is, how do we get back into privilege mode on that? Essentially, the only way we can is through this, if you will, interrupt thingamabob. And it's kind of burned into the hardware whenever there is any sort of an interrupt, be it again page fault, syscall, what have you. As part of that, the privilege is raised back up to privilege mode on this. So why is that not a problem? Why can we allow untrusted user code simply to execute and interrupt and not have it potentially muck up the system? And we said that, essentially, it's this last point here that's the key here. Yeah, you can interrupt into interprivileged mode anytime you want, but you really don't have any control over what happened after that. Specifically, if you will, the kernel, I'm sorry, the system is going to start running, well, code from a fixed point in memory that user code can't change. And what's that piece of code in memory that runs when we have an interrupt? It is the, well, that can cause us to run this. OK. I'm sorry? Yeah, exactly right. OK, so a syscall can cause us, or a page fault or what have you, can cause us to go through this thing. And this fixed memory location, that points to the interrupt handler. And that piece of interrupt handler is in the kernel, and that's why the kernel still has control over everything that's going on. This kind of review of last week, I'm sorry, last class makes sense? Yes, no? See, OK. I should say point two I glossed over earlier, recording the state necessary. We're actually going to be dwelling a lot on that towards the end of the class about what this state is and how we go about doing this. Question? That's what I was curious. That's exactly right. In other words, what is this quote unquote state that I glossed over? Part of it is exactly as alluded to, this is where this trap frame gets populated, and we'll see how and why that's important. OK, other questions? OK, review making syscall. Ah, there we go. OK. Everything begins with an interrupt. We go to the interrupt handler, and again remember we have to determine what type of interrupt. One of those types of interrupts could be a syscall. It could be a page fault or what have you, but we're focused today on a syscall because that's also going to be the focus of the assignment two on this. It goes to the syscall handler, and then before it actually gets to that though, OK, how do we actually pass to the syscall handler the stuff that it needs to know what to do? This is actually something that goes on in user code here. So to access kernel system call interface from an application here, we have this thing down at the bottom called a syscall instruction, and again that's simply a machine language opcode. Again, it's OC in the case of MIPS on this, and this says generate and interrupt. But it doesn't tell me what type of syscall I want. That's why we're the stuff in the first two points here, OK? Namely what we have to have some information about the arguments that we're passing to the syscall. That's where we've been talking about in recitation about remember you have to save out the arguments A0, A1, A2, all that stuff, because you can think of a syscall as being a fancy-schmancy function call. And as a matter of fact, that's how it looks like in user code. If you call, let's say, syswrite from user code, from user code it really doesn't look any different from calling, let's say, a library function like printf on this. Behind the scenes, there's some magic that's going on, and this is exactly the magic. We've got this syscall opcode, but we've got a kind of nestled in with some other information, namely the arguments that we pass to, in this case, the kernel. And of course, most importantly, the identifying number, because the syscall handler, when it sees that it has to handle a syscall, well, which one? OK, is it sysfork? Is it syswrite? Is it sysfoo? Whatever it happens to be. So again, kind of layers to what's going on. We start with an interrupt. We have to determine what type of interrupt it is. If it's a syscall interrupt, we go to the syscall handler, and then the syscall handler decides where to go from there. And it does this based upon this additional information, namely the parameters. And most importantly, you can think of the syscall number as being kind of a type of parameter on this. Make sense? All righty. OK, software exceptions, hardware exceptions, software interrupts, all this stuff here. All right, I should say this. This is, again, where I've been a little bit sloppy, interrupts and exceptions on this. But a quick version is you can think of some of these things being voluntary and some of them is being non-voluntary on this. And you can think of it, what's an example of where I would want to generate an interrupt? As a matter of fact, this is what you're doing right now. I manually put in, I want an interrupt, please. Exactly, right? Sometimes there might be a reason why we might want to generate another. But by and large, these other things are like, hey, well, actually, the process just divided by 0 or tried to divide by 0. And immediately, the CPU will trap that and say, Colonel, what do you want to do? This is a really bad error. Or another example of an involuntary interrupt is something like a page fault on this. You'll know point or dereference on this. I try to reference invalid memory. OK, that's going to cause a problem. The upshot is the user program didn't necessarily plan or want to do the interrupt. Clear on the distinction here, in other words, it's basically sometimes a user program wants to generate an interrupt and sometimes stuff just happens, as they say. Why are we going through all this here? Well, OK, we talked a little bit about multiplexing here. And we want to actually see, again, how this ties in with the genesis of modern operating systems, again, what you have with either WinOS to wash my mouth out with soap or Mac OS or what happens on this. Preemption, context switching. The reason that is important is these are some of the tools that we're going to be using to implement CPU multiplexing. So for the terms, preemption and context switching, that's something, definitely when it comes time for something like the midterm of the final, you want to have a handle on all these things. You know vaguely it has something to do with saving out state, but you want to know the mechanisms. And let's see what we can do with this. OK, here. Problems, we talked about this last time here. Ah, there we go. OK, CPU limitations here. Essentially put, we have way too many things chasing one poor little CPU on this. Or even on contemporary systems, your laptop probably has maybe four, eight CPUs or whatever it is. But we know one thing, that number of tasks, threads, processes, what have you, is probably going to be way, way more than the number of CPUs that are out there. This is a reason because, well, again, why don't we have 1,000 CPUs? Actually, there are computers that do have 1,000. There are computers that have 1,000,000 CPUs on that. They're really great. So why don't we have 1,000,000 CPUs in our laptops? Huh? You can't afford it. Probably in terms of money, but also, it's going to be one big laptop that you're going to be hauling around. OK, you're going to need probably some turbo cooling liquid system or something to keep the thing from, well, frying your legs on this. So basically the upshot is too many things, case chasing, too few processors. So even though we do have more processors than one on typical computers, what that means is we still have to, if you will, divvy up resources among these fewer CPUs on this. OK, oh, yeah, again, graphical illustration. It's kind of musical cheers. Maybe we have eight cores, but you know what we've got. Whatever that is, greater than eight things chasing that. And essentially what that means, we can have eight happy processes running at any given time. And everything else is, at least for the moment, frozen out. All right, CPU limitations here. OK, I should say this. What? Limitation? I thought being faster is kind of something that's good on this. Because if you think about it here, you know what? CPU, it's faster than memory. Remember from 341? OK, Christianler talking about like caching. That's why we have caches. Because the CPU way, way outstrips memory speed on this. OK, way, way, way, way faster than disk on this. If you wait on disk IO, you are going to be twiddling your silicon thumbs for millions, if not billions of cycles, waiting for the disk drive to get back to you on this. And certainly far faster than you as an individual on this. So this to me, it kind of sounds like it's a good thing for the CPU. So why is it necessarily a problem on this? Well, OK, what? Ah, yes. More on just the concept of, if you will, the fact that these CPUs are just really, really fast on this. If you talk about it in terms of, let's say, what's a gigahertz, you can see that essentially it's, well, here we are, millions to billions of clock cycles to do stuff that even as a human we can barely perceive. Be it video processing, be it audio processing on this. Again, the point of these two slides is CPUs, the physical silicon processors, are essentially way faster than anything else on this. And this is a slide from way back when. And you can see they are actually hooking up wires to big, big computers on this. Why is that relevant? Let's take a step back in history and talk about how computers were in the land before time and how we get to modern operating systems. So basically, computers did one thing at a time. Because if you think about it back when, putting a computer together like this, they had individual vacuum tubes later on, individual transistors, what have you on that. They were not just extremely expensive. They took a lot of electricity. Even, I mean, they had problems with, let's say, literally wires separating, burning out, transistors frying out. They were expensive. So even the concept of a processor itself was extremely expensive, very difficult to implement on this. So we probably only had one processor to the extent that you can even think of it going on here. What's more important, though, is that we were doing one thing. How do you interact with a machine like this? I don't see any liquid crystal display. I don't even see a CRT on this. Essentially, the way you interacted with these old buggers here is, typically, you, well, eventually, you use teletype machines. But at the beginning, you literally hardwired the wires to program. That's how you program the machine, was with wires. And then later on, they got this nifty idea, hey, let's use punch tapes, those 80 column punch tape things. Do not fold, bend, spindle, or mutilate on this. And you had to feed it through a reader. And then the computer ran your program. And then the computer gave you a printout. And it was probably, you know what, syntax error. So you as, I'm serious, this is what life for programmers was like back in the Cretaceous era. In other words, you would spend several days, because of course, you weren't allowed to actually edit on the machine. I mean, that's ridiculous. So you would have to kind of type things up, give it to the secretarial typing pool. They would literally give you back a bunch of these punch cards. The operators would feed it into the machine. And of course, there's going to be bugs. So then you would have to take the results back, figure out where in your printouts there might be a bug, take a guess, and then that was the, if you will, the iteration of developing programs on this. So it was still better than adding things up by hand. So how can we actually, OK, essentially we are doing one thing at a hand here. So how can we actually improve this thing here? One thing is, let's see if we can't have this computer maybe use multiple users on this. Because while you are trying to figure out the source of that syntax error, maybe someone else can run this program and try to find out this is a syntax error. So we're going to kind of try to alternate back and forth between two syntax errors on here and maybe eventually solve one here. Now one thing here is that there are abstractions and no multiplexing. Why do we not have to worry about multiplexing at this point in time? Because are we running how many things right now? Exactly. So at the beginning, we have one huge expensive computer. It does your batch and only your program at a time here. So we do have the concepts of like IBM came out with disk drives in 1956. So you had file systems going way back when. You had the concept of, if you will, stack heap and all that fun stuff on that. But what you didn't worry about because they just didn't exist was multiple users on this. So again, one thing at a time and since we're dealing with one thing at a time, you don't have to worry about this thing. OK, here. Again, I think it is one long, really long irritated queue of people waiting to get their time on a computer. And this was often, there were many, many petty squabbles at places like IBM and whatnot of who was most important to get to the head of the physical line of programmers waiting to get their deck of IBM cards fed in and run the computer on this. So how are we going to actually make this a little bit better? Oh, yes. Let's actually see if we can't maybe have multiple people use the computer at the same time. Or can we do that? Well, let's talk about this. Essentially, this is where an operating system comes in. Back in, again, the Cretaceous here, or even when I was young and I was using 8-bit micros on this. An 8-bit micro essentially has things like a file system. It has things like a keyboard. And it has a CRT monitor. It has a lot of the tools that modern systems have. But what it does not have is an operating system. Reason being, well, there was only one user. But what else was going on? Not only was there only one user, I was only doing one thing at a time. So when I was playing Little Brick Out, which was Steve Wozniak's version of Breakout, that's all it was doing. And that poor Apple II didn't do anything except run Little Brick Out in basic on this. So it didn't have to switch back and forth between that and what I was torrenting over the internet or what have you. So again, we didn't need an operating system. All I needed was a bunch of tools. Make sense? The operating system comes in when I want to get into the concept of multiplexing on this. Scheduling to meet the needs of computer users here. Now, originally, what we want to do is schedule the humans not the computers. This gets back to what we were talking about earlier. Here it is, batch scheduling. Remember what I was saying is we've got these really cranky programmers that are really like a bunch. Well, they're caddy about how much time they actually get. Think of it as I want to run just Firefox. I can't do anything else with my computer. And then just a terminal and then just whatever that happens to be. I don't know these modern icons. The upshot is that we're doing one thing at a time. So I don't need an operating system with that. What's the problem with that? Or what are some of the problems with this approach? I want to run one program, then another program, then another program. What do we not like about that? Now, it takes a long time. I mean, literally, because we're going to have to have humans kind of feed the, if you will, the deck in. We don't have it automated, exactly, right? Are there problems? OK, yeah, let's talk about that here. I'm running Firefox here in a browsing session. And let's say that Firefox needs to retrieve a graphic off of the hard drive or something like that. Well, we know that the CPU is faster than the hard drive. So the CPU is going to be twiddling its electronic thumbs, waiting for that hard drive to load up the picture. What this picture on the screen right here glosses over is, yeah, it's running Firefox during this time slot. But you know what's going on during this period of running Firefox? There's a lot of downtime, at least for the CPU. Makes sense? So you can kind of think of this as being, if you will, invisible periods where it's officially running Firefox, but it's just waiting. And this is a huge problem on this, especially back in the day. I mean, literally, every millisecond, every microsecond, had a big cost to it. So if you were a computer manufacturer, if you were, let's say, a company that has a mainframe, and you heard that your half a million dollar mainframe was sitting idle, essentially 12 o'clock hours of the day when you added this all up, the CFO is going to have a fit on this. What can we do to kind of make this more efficient? So batch scheduling is simple to implement, but yeah, it is inefficient on this. I said earlier that being fast is good, but why is it a problem here? Exactly this. So kudos. This is the problem with being faster. It's not so much that it's a problem itself, but there is a lot of downtime, and the owners of these expensive computers did not like that at all. It's inefficient. Questions, comments? Does this make sense? OK. Solution, operating system here. What we're going to do, and maybe it's a little bit difficult, but let's pretend here that this Firefox session is actually one session. In other words, it's not one person googling Google and another one googling Nutscape, whatever it happens to be. And let's say that it's one person, and then at this point in time where this arrow is, we need to retrieve the graphic from the disk drive. So at this point, the CPU starts twiddling its electronic thumbs, and you know what we say? This is a waste. While the CPU is idle, I'm going to see if I can't maybe get some work done with something else. OK, I'm not done with your session. I'll come back to it later on, but I will do something else. Now that CPU is being put to good use. This is the concept of, if you will, time slicing, and we're making much better efficient use of the CPU on this. So later on, let's say the disk drive has finally returned what needed to that graphic. I can now, let's say, return it and the CPU can do whatever magic it wants with it. Make sense? All right. There we go. There we go. Basically, high delays. Keep the processor active on this. And by the way, at this point in time, we probably only had one processor on this. Oh, this is important too. In terms of co-operative, I had alluded to this on Monday's class, but there is a flavor of, if you will, kind of arranging how to do things, cooperative multitasking. In other words, this gets back to the whole thing about why do we need the police department and jails and judges and whatnot. Everyone obeyed traffic laws. Really, we would not need that here. So you know what? If we have cooperative scheduling, all the threads play nice with each other, and they never try to cheat each other on this. You know what? We really can get back, if you will. All we need to do is just have the threads cooperate amongst themselves. We really don't need a policeman to kind of club one thread over the head and say you're misbehaving on this. There we go. OK. Oh, multiple interactive users. By the way, what we're trying to get over with this particular slide here is there's different, if you will, different ways that we can have different time slices. We can have, let's say, one user running a whole bunch of different tasks, which is typical for pretty much everyone nowadays. Obviously, you've got your browser open, your torrenting something, whatever else it happens to be. So you're doing two official things, but you know what? You probably have pulled up Task Manager or PS. There's a lot of other stuff that's going on in the background, so even you one user are really responsible for a whole slew of threads that's going on. Add to that additional users, OK, and what you wind up with is number three here. Essentially, what we have to keep track of and ensure fairness and make sure that the whole system doesn't blow up is lots of users, each running lots of threads. So we've got, in effect, if you all think of it as, let's say, O of n squared tasks that we have to keep track of. And this graphic right here, what we're trying to do is we have, if you will, let's say, this Firefox session. We pause for a graphic and here it starts up again. But now we have, if you will, we're getting into something. Namely, you know what? We had that one processor. We're going to possibly start throwing on the equivalent of another processor. So we have, at this point, the concept of, if you will, not just time, but even at any given point in time, we're going to start to do more than one thing at a given moment in time. I wish I had. There we go. OK, illusion of concurrency. Has Jeff talked about this in previous weeks yet? OK, so real quick review. Essentially, if you have only one CPU, let me be clear about that. Let's say only one CPU, strictly speaking, there is nothing going on simultaneously. There is something going on concurrently. Let me be clear about this. Difference of simultaneous versus concurrent on this. When we're talking about simultaneous, I mean something that what is going on at any one instantaneous point in time. So if we have one CPU, it's doing one and only one thing. As opposed to concurrent, we already have, let's say, 10 processes that we're alternating among. So in other words, within a fairly short period of time, we are at some point touching or running a whole bunch of threads on this. So illusion of concurrency comes back because we as humans can't perceive this. It's kind of like the light bulbs flicker 60 times a second in the case of incandescent. We don't notice it because our brains are simply too slow to keep up with it. Same thing with the computers on this. There we go. OK, again, more on this. Here we go. Human perceptual limits here. Essentially, if we are alternating between two processes, we can't tell the fact that we're really only running one or another at the same point in time. These switches, the term for that, context switches. And the actual point at which we switch from one to another, how does that happen? If we're switching from, let's say, thread number one, running Firefox, the thread number two, running some sort of a terminal here, we have to switch out the context of a thread, more on that in just a second here, to the context of another thread here. And at the point at which this switch actually happens, how do we affect this switch from one to another? So the CPU is running one thing. We need to kind of, in effect, junk what that CPU is doing and replace it with something else, interrupts. This is where this all begins to tie together here. So in other words, if I am running process foo, and let's say the operating system decides, you know what, I want to run process bar, how am I actually going to go about doing that? Well, the operating system says, you know what, foo has run for long enough here, so I can do my magic and switch things over, more on the exact magic in just a minute or so. So question here. How do I actually, where do these interrupts come from here? Well, we were talking about this. There could be any number of sources of interrupts. You could have, let's say, the disk drive says, hey, you know what, the data you wanted is ready, so I'm going to poke the interrupt line. Or you could have a memory fault. Or you could have a sys call. In other words, the thread foo could voluntarily say, I need help. It falls into the kernel, and now the kernel gets control. But what happens, let's just play what if here. Let's say that there is no page fault. Let's say that process foo, it does not schedule any or run any sys calls on this, or what have you. Let's say that process foo is just being obstreperous and wants to be piggy about not wanting to let go of its time slice. How do we enforce? Again, this will work great as long as foo and bar cooperate amongst themselves. Back in the days of cooperate multitasking, we have to count on foo calling a yield function to kind of give up its time slice, if you will. But we can't necessarily do that. Why do we need this, if you will, police officer mechanism to enforce it? And how does that actually work? If we're not generating interrupts ourselves, how can we make sure that an interrupt actually happens? You know what? Let's go on here. Timer interrupt. In other words, there's one hardware interrupt that we know for sure will happen. This is the purpose of the timer. And again, you can think of it. It's sometimes dealt with separately, but essentially a timer interrupt is a type of hardware interrupt. It essentially says, we're going to have an interrupt for the sake of having an interrupt. And that is how the kernel knows for sure that it will eventually get control. And at that point, we can see, well, is that time slice really up? Maybe it isn't. Maybe we need to schedule something else on this. So we can count on it. When the kernel voluntarily relinquishes control to a user process, the kernel can be sure that it will, at some point in the not too distant future, get control because of a timer interrupt, also known as the timer tick on this. And on most systems, what happens is it's set up. There's like a quartz crystal that essentially, however many milliseconds or nanoseconds it fires. You know, boom, boom, boom, boom. And it's constantly going. It never stops on this. You can also schedule some additional timers to fire if you want to. And sometimes threads do that. They say, you know, kind of give me a poke in five minutes or something like that. And that's something you can voluntarily do. But something that's already kind of preset up by the system and that the operating system counts on is that there is a constant firing of, if you will, these timer interrupts essentially forever. Never stopping on this. OK. And this, people, is the difference between preemptive multitasking and cooperative multitasking on this. In cooperative multitasking, we're counting on the threads to play nice and calling that yield function on this. In preemptive multitasking, I don't care. If they want to cooperate or not, they are eventually going to get bumped on the head. And I'm going to get control on that. And I can see whether or not that thread is mispaving on this. And what happens when a timer interrupt fires? Goes through the same process as everything else. In other words, we go to the interrupt handler. What type of interrupt handler is it? In this case, it's a timer hardware interrupt. Well, if it put this way. If the interrupt were, let's say, the disk drive and it said that bytes were ready for you, you probably want to go to the interrupt handler for the hard disk. In the case of a timer interrupt, we want to go to think of it as the handler for the timer interrupt, which is what. I just talked about this yet. What's, in effect, the handler for a timer hardware interrupt? This is going to be a huge part of the course later on. So maybe I'm jumping ahead. Scheduler, scheduler, if you're north of the border. So in other words, how we handle a timer interrupt is, how do we want to run next? That's the point of a timer on this. And there's all sorts of funky algorithms as which types of threads to pick and what order and how much time to give them, et cetera, on this. Questions on timer interrupts? Yes? A question on context switching? Yes. Even if you still have just one processor and one processor has multiple threads, is context switching the one that enables the illusion of concurrency among multiple threads in a single process? It's part of it, certainly. In other words, context switching, the question is, is context switching what enables the illusion of concurrency? Is that kind of OK? It's part of it. It's necessary. It's not the complete picture. In other words, we need context switching to, in effect, take one thread, stop it. And remember in Star Wars, Han Solo gets put in the carbonite freezer. And then later on, when we want to reschedule that, we need to kind of hopefully Han Solo has survived the freezing process on that. Princess Leia would have been very disappointed. So what we have here is the context switching. The mechanism or point of context switching is to make sure the thread survives the carbonite freezing process. We need to save enough stuff so that it can kind of reanimate itself and continue on without figuring out, where am I? I don't know who I am. That's what the context switch is. It's the stuff that we need to save out so that we can, in effect, resume later on. That's necessary to provide the illusion of concurrency. The other part to it is, well, cool like what we have to do it fast enough and have enough, let's say, AV eye candy to make it appear to the user that this is all happening simultaneously on this. Kind of like, if you will, like a movie projector as in old-fashioned movie projectors. Remember, it displays one transparency, and then what is it, like 1, 24th of a second later. It pulls another one down, yada, yada, yada on this. So in other words, the context switch is the mechanism to switch threads back and forth, and that is a necessary tool that is a component of the whole bigger picture of context switching. Answer your question. Other questions? OK. Ah, here we are. OK. Ah, here we are. Oh, yeah. OK. By the way, this is tier two. If Jeff has not hammered this home, OK. Really, really important, threads can be stopped at any time on this, and they can be restarted in any order. That is why you people have been tooling around with these things called locks and condition variables and whatnot. You can't ever guarantee when a timer might or might not fire. It's really good that we now have all these CPUs. It's great that the electrical engineers now give us, as programmers, multiple CPUs we can crunch more data. The downside is it's a real pain to program these things. And we already had problems with timers firing at any time. We now have additional problems of multiple CPUs with multiple timers, and we got to synchronize this whole can of worms on this. And that's going to be a large part of subsequent lessons on this here. OK. Oh, here it is. This is exactly, actually, this is your question earlier here. OK. We wanted to appear that nothing has actually happened. Yada, yada, yada on this. So we want the CPU state to be identical on this. In other words, let's actually talk about the mechanics of this context switch on this. I think this is, yes, here we go. OK. Thread state, what actually do we need to stick into carbonite to freeze it? Well, obviously, one thing, the registers. Why do we need to save the registers on this here? Because remember, you took a look at, you may have already the context switch and the interrupt code. What's the first thing that gets saved out? Yeah, trap frame on. The registers get put on this trap frame. And we need to do this right away. Why? Because what are the registers? They're just kind of variables that are stored internal to the CPU itself. If I don't save out those registers, what's going to happen as I continue along running my code in the interrupt handler? Yeah, right? So the first thing I have to do is, if you will, save a pristine snapshot of where things are at this point in time, now I can go on and kind of start reading the rest of my code here. The other thing that I'm really interested in is, if you will, the stack itself. Think of a thread roughly put as an execution context here. In other words, this is what, if you will, the system, the computer needs to kind of swap around among, well, how can I run different things at the same time here? Again, think back to like compsci 115, 116. Remember Carl Alphonse talking about, let's say, object variables, static variables, and local variables on this. Why do we never need to worry about synchronizing local variables? Maybe I'm ahead of myself on this. Because we've been screaming a strong in a dance about, you people need to synchronize global variables. You don't need to synchronize local variables. Why not? They're private to the stack. Right. It's private to a thread. And what do we mean by that? Local variables are stored where? They are stored on the stack. And each individual thread has its individual stack. So that's why we don't have to worry about synchronizing local variables on this. One thread does not munge on another thread's stack. Well, if it does, we have a huge problem. But that's another story. So essentially, thread state is registered stack. There's some other metadata. If you haven't already, take a look at, let's say, that these threads struct in, I think it's like thread.h or whatever it is. You don't have to know it deeply, deeply, but you should at least be tangentially familiar with it. OK. Oh, yes, and one last comment here. How the stack actually gets taken care of is, well, that's the job of the operating system to make sure that the stack is saved in a place that is safe and won't get, if you will, mucked around with. OK. Ah, here we go. Let me get a minute here. Actually, I already covered this here. Basically, when a timer fires, the first thing we have to do is save out the, if you will, the registers. And the registers get saved out on the trap frame. And by the way, where does the trap frame get put? What do we have here? We have essentially two things for a thread. The registers and the, we want to save out the registers. So what's the police of memory that I do know that I can use that's private to me? Exactly. So that's where our trap frame gets crunched. So before anything else happened, I shove a whole bunch more goo onto that poor old stack. Now I can run my interrupt handler on this. OK. This already is, if you haven't done so already, you don't need, again, you never have to run, I'm sorry, write operating. Try this here. You people will never have to write machine language for this class. It does help to, again, be somewhat familiar with some of the assembly files on here. But in the exception, this is the assembly language program that gets run whenever there is, if you will, a trap or an interrupt. This is what gets run before trap.c. And what's the first thing that happens here? Save better registers. And you can see, we're actually saving register 98. What have you. And we're pushing them onto the stack, yada, yada, on that. By the way, what probably happens when you are returning? You're done with this trap. Yeah, exactly. You've got to restore that. Again, we've got to restore Han Solo from carbonite on this. In other words, we take the stack. We're assuming that it has not been munged on because the operating system is tasked with making sure that this thread, if you will, stack has not been touched. And we can pull these registers off, and then away goes our thread. Make sense on this so far? Oh, here it is. You can see there's a whole bunch of these things here. Ah, context switching. OK. What is a context switch? Roughly speaking, it is switching the thread state from one state thingamabob to another state thingamabob here. And what does that state consist of? Blank and blank. Blank, stack, and registers. Exactly right. So that's what we need to switch when we go from, if you will, one thread to another on this. Now let's say that I was in the kernel when a timer fires. That's going to be a piece of cake here. All I need to do is save out the registers, switch the stack, restore the registers, and away we go. Very simple on this. Add an additional layer to the kettle of fish if we are in user mode, though. And the problem is, remember the just slide about no trust or user or whatever it was there? Basically, the user has its own stack in user mode. There is a second stack for each process that is in the kernel. Remember again, take a step back. What's the difference between a thread and a process? A thread is an execution context, simply put. A process is one or more threads that share, if you will, a user context. And part of that user context is, if you will, a user address space. In other words, we've got a user address space with a user level stack, but we always have underlying that a kernel stack on this, too. So just if it seems a little bit confusing, there are two stacks for each process, if you will. All righty here, bunch of different reasons. Obviously, we want to keep them separate because we want to make sure that the user cannot muck around with anything related to the kernel on that, too. Oh, this is actually good, too. I asked earlier about what potentially happened. What happens when interrupt fires? First thing we do is we save out all the registers, yada, yada, yada. And what happens when we return? Again, we restore the registers on this. Jeff is posing a question here. Where can we find this? Do we have this code? Yeah. Treasure hunt. Actually try to find it. We already know where the code is to save the registers in that exception, whatever it is, file on that. And we had that in a couple slides back here. Take a look and locate later on the code that of, if you will, loops back up and restores Han Solo from Carbonite. Questions? Comments? Yes? Why does every processing have two stacks instead of the kernel stack, just because the kernel is going to be doing the same thing with only different arguments? Right. OK, the thing is that would be assuming that we have, in essence, one thread on this. In other words, let's say within the kernel itself, we are currently in the middle of reading the disk drive. So we're in, let's say, VFSread or something like this for one thread. But another thread is doing something else completely different. Let's say that it's writing out to the screen, if you will. So I'm going to be executing two different pieces of code. Makes sense. So in other words, where I am in terms of subroutines, it's going to be completely different. Does that answer your question? Other questions? Oh, I should say this also. Again, you're not responsible for implementing multiple threads in your processes for OS 161. Obviously, production operating systems do have multiple threads. So let's say that I had in Linux, Linux, Linux, however you say it. Let's say one process, and let's say that it has two threads here. How many total stacks is that process going to have? Two what? Well, I've got one process, but I've got two threads. And then for each thread, in effect, I've got a user stack, and I've got a kernel stack here. So I'm going to actually have four stacks that I would have to kind of keep track of. So for each thread, it goes up by plus 2. Even though you share the same address space, again, this is why we don't have to sync local variables on this. Because those local variables are on what the stack in user land for that particular thread. So they share the same global space, but not the same if you will stack. OK, other questions. We were a couple minutes early. Actually good for change. Questions I can answer for you about class so far? Threads, processes, interrupts. General stuff about OS 161. Check, 0.2.1. Yes? So you're saying, for a particular thread, there has to be a maintained additional kernel stack because, one, the thread is making system calls that requires a stack in addition to the user stack that is doing user stuff. Correct. Yes, again, to reiterate, for each thread that has a user context, you have two stacks. Correct. So in other words, I'm using my one stack while I'm in user mode. As soon as I trip to, I hit interrupt and I switch to kernel mode, I switch stacks. And I use my individualized kernel stack. That's why I say, for each thread, there is two additional stacks that is created and have to be tracked. There was a question over here? People, if we could have one conversation, we're almost done. Thanks. So when we switch to kernel mode, does all the data that the thread is currently having get copied onto the kernel mode? Kernel's user space or what specific things will be copied? OK. What happens when we switch to kernel mode? OK. Well, we're running in user mode, and we're using the user stack for that thread. We hit interrupt. So we immediately go to the interrupt handler. And what's the first thing that the interrupt handler does? It saves out the trap frame. And by the way, it puts the trap frame on which stack almost. It doesn't trust the user. So the trap frame gets crammed onto the kernel stack. So it does whatever it needs to do in kernel mode. Now it's using the kernel stack. And then before it returns, it pops the trap frame to restore the saved user level registers from the kernel stack. And now it can go back. The last argument, as a matter of fact, take a look. Remember, Jeff had the one slide up there. He said, go find the return from exception code. If you do that, you'll see the last register to be restored, essentially. It's the stack pointer to switch back to the user level stack. And now we can pull, if you will, our variables and whatnot from the user stack that we were using. Answer your question on that? Other questions? Yes. So you use the term context switching for like inter-process concurrency. I was wondering, do you still use the term context for like inter-process concurrency switching between threads? Excellent question. Everyone hear that? In other words, what do we mean by a context switch? Essentially, everything. In other words, if I am switching from one process to actually even within that, essentially, let's say, I am in, let's say, a kernel-only thread that doesn't even have a user context, and I switch to another kernel-only thread, that's a context switch on that. In other words, if I am switching, let's say, from one user-level process, I have to trap into the kernel switch to the kernel stack. Then let's say I switch to another kernel stack, which has a user process, and then I switch back to that thread, if you will, user context. That is also, if you will, a context switch. So in other words, the context switch, you can think of it as happening when we're going from, let's say, one, if you will, kernel stack to another. We could even argue, if you will, it's a type of context switch going from user land to kernel space, if you will. Because what are we doing? We're switching stacks. We're saving out, if you will, registers, and we're moving over to a user stack on this. Strictly, I mean, where you most often think about it, and this is like where the scheduler comes into play, it's always from the kernel stack of one threaded to the kernel stack of another on that. As a matter of fact, take a look, there are two types of, if you will, places where registers get saved out in OS 161. We've already talked about one. That's the trap frame. That gets stored whenever there is an interrupt that fires. There is another batch of variables that gets stored whenever we switched from one context to the other. That's the switch frame. So we've got, if you will, the trap frame that stores variables whenever we have a trap and we have a switch frame, which, if you will, is what gets crammed onto the stack whenever we do a switch. Make sense? Other questions? Oh, and I should say, strictly speaking, you know what we call is the switch is when we switch one stack pointer to another. Because now we're looking, instead of this stack, we're looking at another stack. Questions, comments? All righty. Oh, yes, one last question. And then? Will we have, will we also have two? Good point. Are there any such two, are there any such two? OK, in other words, how many trap frames do we have? Essentially, we have as many trap frames as we have nested levels of exceptions. That's an astute question too. Because in other words, we could be running in user mode. And let's say a timer fires. So we push a trap frame onto the kernel stack. While we are, let's say, well, actually, a bad example. Let's say we're in user mode and we do a sys call. A sys call generates an instruction. So we push a trap frame onto the kernel stack. And while I'm in the middle of doing the sys call, let's say I hit a page fault. That is in interrupt. So I'm going to put another trap frame onto the stack. While I'm in the middle of my page fault handling code, a timer fires. I push a third, if you will, trap frame onto the stack. So it can keep going down the rabbit hole as deep as you need to be on this. And then eventually, hopefully we don't blow the stack, we kind of pull these things out as they variously get serviced. Eventually, we'll come back. The scheduler will run me again. So I can pop the one trap frame on. Then I go back to my memory manager that I was interrupted in the middle of. And when I'm done with that, I can pull that trap frame off. And then I can finally go back to my sys call that I was doing to read some bytes from disk. And I can pull that trap frame off and finally get back to the place where this whole, if you will, tar pit began. Make sense? So basically put however many trap frames depends upon however many traps happen. And it can start nesting rather interestingly. All right. I'm going to stick along as there are questions. We're already a minute over. Thanks for coming. Friday.