 Attention, attention. Appreciate your attention. Again, my name is Carl. I'm going to be covering the lectures for Jeff for this upcoming week. Any questions? Again, feel free to catch me during, after, or before class on that. You may have already seen me in recitation about that. What this week kind of consists of is a whole batch of stuff that is kind of fun up my alley on that. In a sense, we're going to be talking about the end of some of the SIS calls that you people will be implementing as part of assignment number two. We're going to be talking about the beginnings of how we can kind of implement multiplexing and concurrency for the CPU. And we're going to be talking about the beginnings of what we'll be talking about on Wednesday and Friday, namely how we can actually switch back and forth among context and threads on this. So kind of by way of beginning here, well, I guess I've already gone through that here. So let's just simply go straight on to a simple shell on this. I assume you guys have already seen this before. We're going to be looking at another example of this later on. Let me say this right now. The simple shell that Jeff has put on the board, honestly, my strong suggestion, even though this is in pseudo code, code it up yourselves. If you haven't done so already on Timberlake, on your home computer, or something like that, because honestly, you need to have a functioning shell that is, if you will, the ACID test for a complete assignment number two. So if you're unfamiliar with, let's say, what some of these SIS calls are, again, if you will, fork, exec, wait, we'll be talking more about this. This shell program will be using all four of the big process calls on that. And you want to be familiar with actually how they work before you actually go about coding. Makes sense? If you haven't ever used it, well, let's try to use it before you actually do the implementation of it. Presumably, you were talking about exec before. And in essence, it's kind of one process, kind of trying to reinvent itself as something else. Because if you think about it, why do we need exec in the first place? We have fork to create a process. Well, you know what? I can spawn as many additional processes as I possibly need. So let's say that I want 1,000 processes and maybe 100 users or what have you. You know what? Why don't I just simply fork them all off? As a matter of fact, that's kind of what Android does. What's the purpose of exec on that? Can I talk about this here? Well, what does fork do? It duplicates what? State what? Biltable, user state, if you will. It's kind of a clone. So if I simply, let's say, use fork to, let's say, fork off 1,000 processes, I'm going to have, in effect, 1,000 of the same, clones, copies, a wheel. And sometimes that's what you want, but most of the time it's not. That's where exec comes into play on this. We're actually going to kind of, if you will, take 1,000 of these processes and turn them into what each unique process needs to be on this. One thing, this actually goes more to your coding assignments than to the theoretical thing. And that is, watch it when you are coding up exec. There's kind of two trip up points, one of which is, if you will, importing the parameters into user space and kind of monkeying around, I'm sorry, into the kernel and back out and all that, there's a lot of mechanical details. But the other one is, it just has up here a gotcha that you need to be aware of. If exec succeeds, the process reinvents itself based upon another disk file. That's great. But if it flunks. You've got to return to the old process and say, hey, there was a problem on this. So as a gotcha, just watch it here. Don't, in essence, destroy too much until you're absolutely, absolutely sure that exec has succeeded. You know how we talked about how Fort duplicates things like the process table and the address space on that? Can't. You don't want to nuke that until you're sure that the rest of exec has actually succeeded. Because otherwise what will happen is you'll paint yourself into a corner and you can't return. Make sense on this? OK. In terms of end of license calls, OK, fork is how everything begins. Well, we hope, OK, except for what? What's the one process that does not get created by fork? Exactly, OK. Which presumably you're already working on right now. Again, kind of to jump ahead, remember, assignment checkpoint 2.1 is due this upcoming Friday. And that is, if you will, monkeying around with the init process to kind of give it some additional functionality to pass the console test. Anyway, I digress. Let's talk about the other end of life, OK? We talk about how a process is born. How does it eventually die? Well, we've got this exit six call on this. So in essence, in most of the time what happens is the process itself commits suicide. It commits harakari by saying, you know what? My time's up. I'm going to call exit. And presumably the kernel will take care of the actual process, if you will, to clean up. Well, almost here, OK? First off, we need to kind of think that this is being the equivalent of relieving the mortician a cause of death, OK? Either I was depressed with life, or I just wanted to take a flying leap off a curve, whatever it was, OK? We need to be concerned with what the cause of a process is death was, OK? And that actually is one of the things where exit gets involved here, OK? When I call exit, I call it with a parameter saying, this is, if you will, why I'm dying, OK? It's an integer value. And, well, I leave kind of a breadcrumb as to what happened to me. What happens to that? Glad you asked, all right? OK, wait, OK? Fork, if you will, begins a process. Exit is how it dies, if you will, OK? And if you will, this wait thing. It's a little bit of a misnomer, because there definitely is waiting involved here. But this is actually something that, if you will, there it is. The parent process is the one that is interested in the exit code. Why would the parent be interested in the exit code of a process here? Think about that. Let's say that you are doing, let's say, a huge run crunching numerical data overnight here, OK? And somewhere in the middle of the night, 3 AM, if you will, you've got all these worker threads that are turning away, and they eventually, if you will, finish up. You want to know, did it succeed or was there a problem with it, OK? So this is really important for the parent to determine what happened to its children, OK? Did it complete successfully? Did it not complete successfully? If it didn't complete successfully, what exactly went wrong with it? So again, basically, the child process commits to a side. It calls with an exit code. And then the parent process calls wait to get the exit code from the child, OK? Now, one of the things is we're saying, OK, we called wait. Why are we interested in possibly waiting on this? I kind of gloss over something, which is when does the parent, if you will, call wait, and when does the child actually call exit, OK? And OK, they are a pair, if you will. And wait a minute. Ah, here it is. Hold on, hold on. OK, I'm skipping ahead of myself or just a little bit here. OK, get back to that in just a sec. OK, yeah. A child process decides it wants to die, and a child process calls exit. It doesn't completely vaporize, OK? There is actually a good analogy, if you will. Let's say you in real life die, OK? You can clean up your estate, OK? You can decide where your money goes. You can give away your house, all that stuff. But eventually, there is one last trace, you yourself, that has to be taken care of. Hence, your next of kin actually has to make arrangements for that. The same thing is actually true vis-a-vis computer processes on that. In other words, when a process dies, OK, you think about it. If I'm running and I want a kind of proof out of existence, there's some last trace of memory that I can't completely get rid of. That is actually what we talk about when we're talking about a zombie here. So you can clean up almost everything, but not quite everything on this. That's actually important for, well, for one reason. What might we want to actually keep in the zombie? Yeah, right? For one reason, we need a zombie if the parent is going to be able to get the exit code. But there's another thing is we can't quite get rid of it without some help from some other process on this. OK, oh, I should say that. This is the slide I was waiting for here. Wait and Exit presents a really cool synchronization problem. What Jeff is trying to get at with this slide here is we don't know which of these two calls is going to hit first. Makes sense? In other words, you know what? In theory, I was saying that, you know what? The child might call exit, and then later on, the parent calls wait to get the exit code here. That makes sense. OK, I can get it right away. But what happens if it's the other way around? Let's say the parent calls wait, but the child is still chugging away. Because I, as a parent, don't really know yet whether or not the child has exited yet. In that case, what do we do? Well, we block. We wait. So that's kind of where the term of this comes from. We want to get the exit code here, but the other big point of waitPID is, if you will, to block until the child actually exits. Makes sense on this? So two points, if you will, or two major facets to the waitSys call. By the way, it's called waitPID in most operating systems including OS 161 on that. Number one is to get the exit code. Number two is to block. There was, I think, Ashish, did you have a question? Excellent question. Everyone hear that? In other words, who can actually call wait on this? In one sense, it's kind of up to the system to determine that. Quick version is read the man page, and that will actually specify what it is, again, what the exact, if you will, requirements are. In most cases, it is going to be the parent which can have access to it, and, if you will, other relatives can't. Possibly because of security reasons or others on that. And that's actually going to be something that you're going to be implementing, and that the test code is going to be checking for. Makes sense on that? So again, this is another example. Definitely read the man pages as to what you can do. I should say, let me mini digression again. In general, for all the man pages, for those of you who have gone to my recitations, you already heard me sermonize you about this. The man pages are your friends. Read them. Read them very closely on that. When it talks about return these error codes, I can guarantee you we will be testing all of these error codes at some point or another. So just you want to make sure that you're up to speed with this. Questions on, again, fork? Wait, yes. OK, you're talking about like forking off a worker thread possibly? OK, the question is, well, why would we want to perhaps maybe block the main thread, if you will, when we could potentially fork off a worker thread to do that? I guess a couple of questions or responses to that. Back when UNIX ones invented in the Cretaceous era, we really didn't have so much the concept of multi-threaded processes on this. So I would say that's part of it is you really didn't have the ability to fork off worker threads like that. The other reason I would say is, sometimes you only have one thread. So we're stuck with legacy implementations on this. Like if you look at the p-thread library, it implements blocking an entirely different manner on this. But again, partly legacy reason, but partly also it's kind of one of these just because. I should say this, in case Jeff has not mentioned this yet, but for the purposes of your projects for assignment two and assignment three, you do not have to implement multi-threading. In other words, all your processes are going to have one and only one thread. So you can kind of think of it as being one to one. That said, I can also tell you it is very much fair game for the exams. You need to know about the concepts of threads versus processes. They're not quite the same, although they certainly are related on that. And as far as what are some of, if you will, what are the implications of having multi-threaded processes? So far so good on that? All right. What happened at prayers? Let's do it the other way around here. I was talking about if the child exits before the parent, we block. What happens, for example, if the parent exits before the child does? We wind up with an orphan process on this. And what you need to do in most operating systems is, well, the parent of the process, what is by definition the parent? That's the one that called fork. If the parent goes away, what happens is the parent of the child process then becomes the init process. So that's just, in other words, the init process is, if you will, the master undertaker for all these orphans that are out there. So if the parent leaves, the orphanage is, if you will, the init process. Now, one thing also, I was talking about zombies earlier. In other words, this is that little piece of code that you can't quite get rid of. When a child calls exit to say I'm done with my life, I can clean up most of myself, but not quite everything on this. And again, we need this little piece, for one thing, if for no other reason, to leave the exit code for the parent. There's a downside to that, and that is that these zombies eventually build up, build up, build up. You signed on to Timberlake. You've probably seen these dire warnings about, if you don't clean up your zombies, you will be hauled out, flogged, and eventually executed. So what Ken Smith and the other cis admins are concerned about is these zombies, they take up resources. And if you're constantly forking off, if you will, worker processes, and not cleaning up after yourself, it's just a huge memory and resource footprint on this. So you want to make sure that you're doing it. It's kind of just good coding practice. So how can we actually avoid that? A bunch of different ways, one of which is, guess what? How do I find out what the exit code is? I call weight, OK? And what does weight do, among other things? That buries that zombie, sticks a state of hockey, holly through it, and we're done. Maybe the hockey stick is made out of holly. I don't know. There are other things here. Namely, if you will, the parent process, automatically on most systems gets a poke from a signal. It's called sigchild, saying that you know what? Your child just died. What do you want to do about it? Interestingly, most of the time, the default behavior when you get the sigchild is to ignore it. So that is the default behavior on Unix. So it's still up to you to make sure that you reap these stupid zombies. They're annoying. You can also do this, though. And Jeff is getting it. You can set a flag to say that you know what? I want them to be kind of automatically reaped on this. Questions on this so far? So we're talking about the end of XX so far. In other words, I said that's what replaces one process with another. I said that there is a little bit of a gotcha. Make sure that you don't, if you will, destroy too much when you're in exec before you want to make sure that you can successfully move on to the next process. To the next, if you will, context before you get rid of the old, if you will, program. I talked a little bit about exit. That's how a process dies. And that's how you can pass, if you will, an exit code to the parent. Talked a little bit about the wait syscall. That's how the parent gets that exit code. And it's also how the parent kind of hangs around. Because very often, the parent is simply twiddling its thumbs, waiting for the child to finish on this. And I also said that, hey, you guys are going to have a little bit of a synchronization issue, because we don't know which one of these two calls is going to hit first on this. And those people are the big four syscalls on that fork, exit, exec, and waitpid. Oh, what happens if I don't want to wait? Minor digression here. You can call waitpid to see whether or not a child has exited. And if so, get the exit code right away. And if it is not, again, how can I find out more about this? How can I, let's say, I want to see whether or not the child has exited. And let's say if it has not exited, I don't want to block yet. So how can I maybe call waitpid and find out about that? I can do what? Read the, what do I find out, information about syscalls? Bingo. Bingo, OK. And there is an option on there. Take a look at the OS 161 man page for waitpid. And you'll see how you can actually, what Jeff is talking about here, peek to see whether or not that child has exited. And if it's not, return immediately on that. It's an optional implementation. It's kind of fun to do it because you know what? You might as well, you're nerdy. You might as well figure out how to do that. It was a few lines in my waitpid code. So it's worth it. Ah, here it is. This is that simple shell I was talking about earlier. This, honestly, go home, code it up. I mentioned in a couple of my forum posts already about the importance about writing tests, user code to test, your kernel code that you're writing. What this is is an example of just test code to make sure that you're familiar with and comfortable with the functionality of the syscalls and what you need to do here. So this sort of thing, honestly, you can get a replacement for bash written in not much more than this. It's not going to have all the bells and whistles and do hickeys, but it will give you the basic functionality of, if I type foo at the console, it will then run foo. And when foo, if you will, exits, I get my prompt back again. So again, acid test. If you can get a working shell in OS 161, you are probably 80% to 90% of the way done with assignment 2. Questions, comments, complaints? Going once, going twice. OK. Ah, error now. Wait a minute. There we go. There we go. Has Jeff talked to you about the role of library code in the three levels of the metrarchica? OK. Let me go over this. Roughly speaking, and this is really crude and janky on this, there are, if you will, three levels in terms of software to assist them here. We've got, obviously, a user program, be it them, be it Angry Birds, what have you. Then we have below that, if you will, the platform or library code, be it, let's say, something like libc, be it Android, what have you. It is a bunch of code that is not kernel code, but think of it as support routines, if you will. That's how you call printf. You don't have to write printf every time you want to print something to the screen. It's already done for you. That's kind of in a middle layer here. And then below that is, if you will, the kernel. So in other words, we have applications, we have library code, and we have kernel code. And it's kind of important, think of it this way. The top two pieces, if you will, applications and library code are, roughly speaking, thought of as user code. And the bottom layer, if you will, is, roughly speaking, well, the kernel is kernel by definition. You may have also heard the term like system programming. That's, if you will, the bottom two layers. In other words, kernel plus library, that's the system as opposed to the application. Well, duh, it's the application on this. Now, the reason this is important is a lot of the stuff can be confusing. There are analogs between syscalls and library functions on that. As a matter of fact, if you've looked at the official UNIX documentation, you may have noticed there's these man pages like man2 and man3 on this. There is, roughly speaking, the man2 man pages are the documentation for all the syscalls. And then the man3 pages, if you will, that's the documentation for the library calls. Like printf, that is something that you would see in, if you will, the library code. Something like syswrite, that's something that you would see in a syscall on this. Back to the slide right now. Exit. Have you ever wondered why there on some of the syscalls there is this, if you will, underscore thingamabob here? Essentially, it's to distinguish one form the other on this. Simply put, there is an exit library call, and there is an exit syscall on this. And that's important for a couple things here. Number one is so you just don't get confused. But also, something like Erno is something that is implemented in library code itself. People have started looking at assignment2 already. You've looked at syscalls.c. Hopefully, yes, everyone. OK, on this. If you have not done so already, dig deep into syscalls.c. Again, take a look at the slides from last week's recitation on this. But you want to know how you can pass in parameters to the syscalls, sysfoo, sysbar, whatever it is. You also need to pass some stuff back to userland. And you may have noticed that there are two thingamabobs that you need to pass back to userland on that. Number one is kind of an error flag, and another one is a return value. And it's not rocket science, but it's going to create a little bit of weirdness. How do I get two things back to userland on this? That's important, because if you think about it, we need to know whether or not there is an error. And if there is an error, that's where this thing comes from here. So that's why you're passing back these two things to userland. Because Erno needs to know what the error is if there is an error. So point of this slide is, number one, don't get confused. There are library calls. There are syscalls on this. But also, sometimes in this particular case, you need both in order to implement what error number is. Questions on this so far? OK. Questions about processes in general? Oh, processes versus threads. What's the difference on this? 30,000 foot view, if you will. Think of a process as being a bunch of threads that share, if you will, a user context. In other words, roughly speaking, what does a user context consist of? Jeff's talked about this probably in the last couple of weeks. What I've been talking about, like what does fork do? It duplicates. Address space. Files, yeah. There's a bunch of other stuff, signal handlers and other whatever it is. Anyway, whatever it is, there's basically user context. Address space, files, if you will, or file table, I should say. And a bunch of threads that are running together can share, if you will, this process, if you will, this user process context. Last part of the class, and this is we're getting into multiplexing and how we can actually swap among processes. I don't really have much. I wonder if I can. How can I raise this stupid screen? Ah, wait a minute. There we go. Multiplexing, CPUs, memory, resources on this. Why are we concerned with multiplexing in the first place? Well, how many processes do I typically have on a machine? By the way, I'm going to be a little bit sloppy when I talk about processes versus threads, so bear with me on this. Let's say that I have, I don't know, 1,000 processes. If they each had their own processor, it would be very simple, right? Assign one to one on this. But as we all know, typically, processes are going to greatly outnumber processors on this. So this is where Jeff is talking about, like giving the illusion of concurrency. If we have only one CPU, we can kind of do time slices about a whole bunch of processes. Well, what we also have nowadays is sometimes more than one CPU. So let's say if you've got opera glass. Oh, wow, that's great. Bear with me on this. Think of this as just being, let's say, a bunch of ladders here. In other words, this is in the time domain. And let's say we're just proceeding downwards in time here. So each of these three things, let's say we have three separate physical CPUs on a system here. Well, each CPU, if you will, has particular time slices, in which case it's kind of flopping back and forth among threads slash processes, if you will. And at the same time, two other physical CPUs are kind of doing the same thing here. So people, if you've taken like 341, you probably heard about, let's say, temporal versus spatial parallelism. That's kind of what's going on here. In other words, a particular process could be, let's say, executing here. And then, you know what? Later on, it might be moved to another time slice on another CPU. Or maybe it might be rescheduled on the same CPU, but at a different, if you will, point in time. So where a process is, it can kind of appear in essentially any of these areas. Well, obviously, it can't appear at the exact same time on two CPUs, but you get the idea on that. So a process can appear later on on the same process or it can also be moved among processors on this. And we'll be talking more about why and how that's important. But kind of makes sense on this in terms of the general idea. OK. Well, the screen is taking forever to go down. OK. Problems with the processors, again, essentially put, the processors are outnumbered by the processes. So we need to kind of figure out a way to, if you will, share them. The mechanism, how we are going to move threads or processes around. Remember the three ladders that's behind the screen here. It's with interrupts and context switching. In other words, at the end of every time slice, the kernel says, ding, your time is done. We're going to kind of move you over. And the context switch is actually how we change state. We're going to be talking a lot more about that later on in this class. So again, if you will, the point of this is we're trying to multiplex, if you will, CPUs. Use a resource among a whole bunch of different things here. And we can do it temporally. We can do it spatially on this. And again, the mechanism, we'll be going into more detail later on, interrupts. That is, if you will, how you, roughly speaking, how you move from one process to another. And then context switching is actually how you change the state on this. We've got this crossed out right now, just one very quick remark here, in terms of software programming for kind of everyone sitting in this room. If you will, the issues of synchronization and concurrency are becoming much, much more important, thanks to the fact that essentially every computer now has, if you will, multiple physical CPUs. Like 15 years ago, unless you were working on high end computing, you could pretty much be content that there was one CPU in a computer on that. And that makes things a lot easier on that. You didn't have to twiddle around with spin locks and all these other funky stuff. Well, now you do. So just FYI on that. Oh, forgot about that. Processor scheduling. What Jeff is trying to get at here is remember I was saying earlier is that how we move a process around. In other words, does it stay on the same processor? Does it get moved to another processor? Does it stay on the same processor but get more frequent time slices or all that? That is something that is determined by the scheduler. And a lot of thought goes into that because you can imagine you need to get into issues like you've dealt with them already, starvation, if you will. Some stuff is more important than another. But again, that's kind of a whole different ball of wax on this. Questions, comments, concerns about just multiplexing in the abstract? Basically, how do we share a bunch of processors among a bunch way more processes? OK, privilege. This is definitely relevant to this. Well, almost. In the abstract, maybe not really. Do we need special privilege for the operating system or not? In the abstract, not really. In other words, in order to implement the functionality of, if you will, concurrency, lots of processes, sharing resources, not really. In order to implement in the abstract the functionality of fork, if you will, duplicating processes, weight, all that stuff, we really don't need the concept of privilege. So why do we need the concept of privilege on this? Glad you asked. Multiplexing does. What do we mean by multiplexing on this? Well, I'm going to skip ahead and hold on. There we go. Enforcement. Think about this. If everyone obeyed the traffic laws, would we need cops to enforce them? In other words, if everyone nicely came to a stop at a red light, if everyone never texted while driving, if everyone never got drunk, if everyone were perfect drivers, we would need no really expensive and, if you will, inconvenient enforcement mechanism, policemen, courts, what have you on this? This is kind of what we're driving at here. In terms of, let's say that you were trying to, I don't know, you got a bunch of friends and you want to divide up a birthday cake. If everyone agrees, you really don't need an arbiter, like a parent to come in there and say, OK, Joey, Sarah, I need you to give your friends equal pieces on this rather than taking three-quarter of the cake for yourself. So this is actually what we're getting here. No trust-o-process-o. Simply put, you don't know what the user is going to do. Repeat. Never trust the process. Repeat again. Never trust the process. This is going to be really important for assignment number two, because in dealing with the input that you get from the user, for a bunch of reasons, it could be very problematic. There we go here. Sometimes the processor, I'm sorry, a process could be downright malicious. It's trying to bomb the system. It's trying to get security codes or, if you will, jank passwords on you. Sometimes a process is just stupid. The coder actually has a null pointer in there. Whatever it is, the upshot is that there's going to be bugs sometimes inadvertent, sometimes malicious. But we can't trust what's coming in from user code. Simply put, we have to be the policeman. And that's where the concept of enforcement, and this is where the concept of privilege comes in on this. In terms of, let's say, a user process could say, you know what, I want a lot more time. Maybe there's a very good reason for that. But we need to make sure that the user process is actually justified in that request. We can't simply grant it because the user asked for it on that. So again, to repeat on this, we've got this concept of abstracting. Already presumably have you talked about abstracting memory, but abstracting the CPU, it's being shared among a bunch of different processes. In the abstract, we don't need privilege for that. If all the process is agreed as a matter of fact, there is a type of, if you will, multi-execution, if you will. Instead of preemptive multitasking, it's called concurrent multitasking. If all the process has played nice, we wouldn't need these enforcement mechanisms. But the problem is they don't. And again, maybe not because they're malicious, but they could just have bugs in it. Think about it like this. It's bad enough if a program crashes, what happens if your entire computer crashes? Think back to the days of, let's say, old DOS. Well, I can date myself. I'm old enough. Dinosaurs were roaming the earth, and I was programming in DOS. And the problem with that is if, essentially, if there is a bug, your entire computer goes down in flames. Sometimes that still happens. Blue screen of death, kernel panics. But the idea is we want to limit that to when there's actually something wrong with the kernel, which is hopefully never because it's a try to true and bullet-proofed piece of software. OK. Privileged execution. How can we actually manage this here? There's a bunch of different enforcement mechanisms. So this is actually how the police department works in a real modern operating system. First off, there's a bunch of special op codes, or if you will, instructions that are reserved to the kernel. Only the kernel can execute them. And the other thing, too, is essentially put, the kernel has a complete view of the entire system. Memory has access to the hardware, the whole shebang on that. That is something that is unique to the kernel, or if you will, that is reserved to the kernel. Wait a minute. What happens if I, as a user, decide that I want to misbehave, and I'm going to try to, if you will, grab more memory, or maybe execute one of these special instructions? Well, let's see in the next few slides here. The special instructions, as you can kind of imagine, involve moderating, if you will, how resources are divvied up. In other words, to make sure that one process can't peak at another process is if you will address space. So some of these op codes are going to be to how to set up memory and how to partition memory among the different processes. Now, when you actually do try to execute one of these special op codes, you'd better be in kernel mode. Or, OK, wait a minute here, let me go back to that in just a sec. I cannot. Sorry about this. Basically, the kernel runs in kernel mode, the user runs in user mode here. I should say this here is that if you do have a leak between the two, that was actually what kind of led to some of the stability with old operating systems, like, if you will, Windows 95. Windows 95 sometimes allowed user processes to access certain areas that, realistically, it should not have, and that led to a lot of instability here. All right, ah, here we are. OK, fine-grained protection. OK, what this slide is trying to get at is remember I was saying that there are special reserve op codes on this. For the purposes of coding for OS 161, you can think of it as, essentially, there are two levels. OK, there is kernel mode and there is user mode. As a matter of fact, in practice, that's the way most modern operating systems are set up for simplicity's sake. In practice also, however, there is hardware support for multiple different levels on this, that it has been there for a number of years and it really has not been used. In other words, you can have kind of like user light mode, user mode, kernel mode, Uber kernel mode, what have you on that. And just recently, some of this stuff has begun to be used. Talk about that later on at the end of the class in system virtualization. Jeff does a really, really good job about that in terms of how you can run in essence an operating system within an operating system. Think about the movie, The Matrix, and it's really cool how a lot of that stuff gets implemented. So TLDR on this slide is there are essentially two modes, privileged and, if you will, unprivileged. But if you will, underneath the hood, there are a bunch of different levels. That we're increasingly starting to use. Questions, comments, complaints about this? Yes? Practically, how is it different like user mode and kernel mode? How does it execute differently in the world? OK, the question is practically, what is different on this? OK, well, this is what I was alluding to earlier. If you try to kind of let the cat out of the bag, if you in user mode try to do something that is quote, reserved to kernel mode, like let's say I try to peek at a memory address that is not, if you will, been granted to me by the kernel. Well, think about this. If I as, let's say, a police officer can put on the lights in siren and burn through a red light, if I try to do that, what will happen? Probably going to have a huge crash, and probably what I will wind up with is I'll be in a court of law, I'll be stopped by the police officer, right? Same analogy here. If I as a user code try to do something that I'm not allowed, well, boo on me. Essentially, I'm going to almost immediately be taken down by the kernel. And OK, wait a minute here. Hold on. Ah, OK, here we are. You know what? Bear with me on this. Essentially, you're going to get a trap. You're going to go right into the kernel, and the kernel is going to see that you tried to do something that you shouldn't have, and you're going to be executed. OK. OK, again, more on this application, roughly speaking, something that runs in user mode, unprivileged, kernel, something that runs in privileged mode or kernel mode on this, OK? Interesting, too, what is special? Remember, Jeff talked about the first day, what is an operating system? What's special about it? Well, actually, what's not special about it? It's a program, OK? So what's special about it? Among other things here, it's the one thing that we do allow to run in privileged mode or kernel mode on this. So OK, question, how does it get to be the kernel, if you will, OK? So in other words, why can't I, as a user, say, I want to be the kernel and kind of get rid of the kernel on this? Now, let's take a look at this here. Essentially, this is how you start your computer. When a computer, you turn on the power switch, the computer typically runs in, if you will, privileged mode from the get go. And what's the first program that actually loads in here? You install your machine, and what boots? The kernel, the operating system, OK? In other words, the operating system is the first thing that gets cracked to, if you will, this pure as the driven snow machine that is running in kernel mode. So that's why the kernel gets kernel mode. And then take a look at this here. The kernel is responsible for lowering the privilege mode. So roughly speaking, what happens is if the kernel wants to execute an unprivileged process, it calls call syscall fork, yeah, OK? And then, before it actually, if you will, allows the user to run, it sets the privilege on the user process down. So essentially, the user process is demoted. And now that it's been demoted, it can't get back into privilege on that. Makes sense? So in other words, the machine starts up in kernel mode. And the operating system gets first crack at it. So the operating system becomes privileged. And as needed, as it runs user processes, it lowers the privilege on these user processes that it's actually running, if you will. Which is great, except that sometimes user processes need to do stuff like access the disk drive. They need to, or just access the hardware, or they basically need something that they're not allowed to actually do on this. In other words, how do I, as a user, get back into privilege mode? Sometimes there is a very legitimate reason for doing that. Sometimes there is an unlegitimate reason for doing that, if that's the word. And this is what we were talking about earlier. How do I enforce this? Let's say that I, as a user who knows, want to munge on another user's memory on this. Immediately this thing happens called a trap. And again, here too, I want to be really, really ugly. Traps interrupts faults, exceptions. They're the same. They're not the same. You've probably heard these terms thrown around. Basically what happens is if something that kind of is weird happens, you get this thing called a trap. And one way to think about an operating system is a gigantic interrupt handler. I mentioned this in recitation last week. But essentially, if you will, all of your interaction with a kernel starts with some form of a trap or an interrupt or what have you on this here. So if we are in user mode here, and let's say that I want to, I need help. I need to access something from the disk drive here. Well, I need to go into privileged mode on this. So I'm going to go through this interrupt. We'll talk about that in just a moment or so. But essentially, I'm now entering the kernel. I'm trapping into the kernel. I'm running into the kernel on this. And now I'm running in privileged mode. How can I simply elevate my privilege? This kind of is a little bit weird here. So I was running in unprivileged mode. Now I'm trapping into the kernel. I'm somehow running in privileged mode on this here. Now this is kind of important here. And hint, take a, well, take a hint from this in terms of how OS 161 is designed here. Simply put, there is a user state, and there is a kernel state for user processes. Because we want to keep things separate, if you will. Remember, we don't want the user as a user process to be able to jank around with the kernel or with other processes. So if a user suddenly gets privileged and is now trapping in the kernel, well, we want to make sure that we have a separate state. In other words, a separate stack, if you will. What Jeff is also trying to say here is it's kind of one CPU that is simply executing a series of opcodes. I'm in user mode, user mode, user mode, user mode. I need privilege. Now I'm in kernel mode, kernel mode, kernel mode, kernel mode. So it's kind of the same CPU. I have not hit a timer. I'm still within my time slice, but I'm now running in the kernel itself. And since I'm running in the kernel in privileged mode, I can actually now access the disk drive. Make sense on this? The point is, I can't do it when I'm running in user mode. I have to kind of get elevated, if you will, into kernel mode. And then when I'm in kernel mode, I can now access the disk drive on this. And by the way, if I'm getting access, if you will elevate it into kernel mode, what am I probably doing? Let's say that I want to read bytes from a disk drive and probably executing what routine? You're writing it. A sys call, yeah. And specifically, probably which one? Yeah, read. Exactly. In other words, as a user, can't read from the disk drive. But you know what sys write can. Sys write can kind of call down to remember, if you will, the file system that you're given, and then kind of, if you will, the data back up to the user levels. Kind of see how this works on this? So in other words, same bit stream, but again, if you will, different states on this. And you can also see, I should say this, when it returns, let's say we're done with the sys call, we kind of block off that kernel state and we go back into the user state so that the user state can't munge with the kernel state. OK. Privilege transitions here. Here we go. Oh, here we go. OK. How can I actually, if you will, go back and forth? Well, going from the kernel to the user mode is easy. The kernel just demotes the user process. Or the kernel schedule just starts the next user thread and it's time slice. Piece of cake. But again, how do I actually, let's say, go the other way? In other words, how do I promote myself, if you will? Well, it's kind of hard to think of it. You don't promote yourself. You have to, if you will, ask for help on this. And again, this is where, well, we have here something weird happens. We could have, let's say, a hardware interrupt, which could be a disk drive saying, you know what? There's some bytes that you wanted earlier. They're now ready for you to use. It could be a timer. Well, to the extent that a timer is not hardware. But you know what? Your time slice is over. In other words, these are all examples of, if you will, the hardware poking at the thread and saying, you know what? You need to get into the kernel and we need to muck around with some stuff. Sometimes the software itself, if you will, needs attention. And I should say that, take a look. There's requesting and needing attention. What's an example of needing attention? Yes? Segmentation fault. Segmentation fault. Okay, because I did what? I tried to access memory that I wasn't supposed to. Okay, excellent. Another example of needing acceptance. By the way, what's the most common cause of a segmentation fault? You didn't initialize your object. So you got a what when you tried to de-reference it? Null pointer. Exactly, okay? I didn't intend to do it, but you know what? Something bad happened. I referenced memory I'm not allowed to. I need attention. And you can kind of guess what the kernel is going to do to you. Boom! Okay, segmentation fault. Now what about this other one here? Software requesting attention. Sometimes, you know what? I need memory from the disk, or I need data from the disk drive here. Okay, so I want to say pretty pleased with sugar on it. Okay, can you, the kernel, if you will, read some bytes from the disk here. That, by the way, okay, I'm requesting attention. It's an interrupt. Basically, a software, I'm sorry, a syscall really is a type of interrupt. Again, back in the Cretaceous era when dinosaurs roamed the earth and I was young, there was such a time. Okay, you know what? Did any of the people like program like DOS machines like int13x, all that stuff? Or am I just speaking a foreign language at this point? Probably a foreign language. Okay, anyway, TLDR is, if you want to execute a syscall, a syscall is, in most architectures, simply an opcode. In MIPS, it's hex 12, okay? And it's a special opcode that generates, when the CPU hits it, it generates the same thing as if, let's say there was a hardware line that was pulled low by the disk controller or by the timer or what have you. So in essence, the same thing happens and we go into, if you will, this interrupt mode on this. Makes sense? So in other words, if I need help from the kernel, I have to in effect generate willingly an interrupt. Examples of hardware interrupts again, disk read. Okay, timer fired. Oh, interrupt lines here. Okay, more on that I just checked. Interrupt handling, okay, here we go. How do we actually handle an interrupt? By the way, this is kind of cool, especially for you people who are low level wire head nerds. Take a look in trap.c at, if you will, the exception handler code and this is kind of what's going on here. A lot of the stuff happens in assembly code but basically whenever there is any sort of an interrupt, be it let's say syscall, be it a page fault, be it let's say a timer, whatever else, okay? The MIPS processor, the x86 processor, it enters privileged mode. That is how you get back elevated, okay? In other words, you go from privileged to unprivileged when the kernel demotes you, that's easy. But the only way to go from unprivileged to privileged, the only way is to have a trap, okay? And when some sort of a trap occurs, the hardware is, it's burned into the silicon, it elevates itself back up to kernel mode. Why is this safe? Why can't I simply do this? Well, let's take a look. Okay, it enters privileged mode, it saves out the state and then here's the key thing, okay? It goes to a predetermined location in memory. In other words, if I as a malicious user say that I wanna elevate my privilege, yeah, I can do that, I just execute a syscall. But what I can't do is control what happens after that. Simply put, I have to go to the kernel interrupt handler and do what the kernel tells me to do. And that's why this is safe. Questions? I think we are, oh yeah, so here we are, software interrupts and exceptions. Thank you, people. Get started on assignment 2.1, do Friday. Oh, one other thing. Two seconds, actually maybe 30 seconds. Let's do it again. Okay, Scott is or will be pushing out an update to test 161, you're gonna need to update your code in order to submit. I'm sorry? You did? Okay, we'll do it then, okay? Thank you.