 Get off to a nice, slow start today. Get the feeling that maybe people were up late last night working on some terrible programming assignment given out by some horribly cruel professor in some crappy class that you're taking. Look at Robert. Robert's got his headphones on already, man. It's like tuning me out. All right, so today we're going to finish the week where we've kind of introduced one of the more important, probably the most important, abstraction that we use to manage memory. And that's address spaces. We've talked about address translation, which is what we have to do in order to get address spaces to work. We introduced the idea on Wednesday of virtual addresses. So virtual addresses are addresses that point to things that act like memory but that might not be memory. But the big challenge with virtual addresses was that they require translation. And in order for the system to kick along at a good rate, we need that translation to be efficient. And in order to do that transition efficiently, we need some help from hardware. And so today we're going to talk a little bit about approaches to address translation. Some simple ones, some of these are kind of historical in nature, but we're going to end up with a modern approach to address translation that is found on most real systems. So we'll talk a little bit about some of the hardware that's used to do that. So as of, well, originally it was midnight last night, but I guess it's noon today or something. So you guys have written your first bit of kernel code. So I think you should give yourselves a round of applause. Everybody give themselves a round of applause. OK, so this is great. And you savor the moment for like 30 seconds until I move on to the next slide. Oh, wait, oh, wait, hold on. There's something out of, we'll come back to this. Don't look, because there's something out of order here. And that's this. OK, so just a general reminder. So here's what I think happened. I think that I spent some time trying to convince you early on in the semester that this class was hard. And you guys didn't seem to really have believed. And then I think you guys started on assignment one. And some of you guys started on assignment one on like Tuesday. And I think, look, I mean, from the emails that we've gotten on the staff list, there are some people who got kicked around by assignment one a little bit. And that worries me to some degree, because assignment one is actually not hard. Assignment one, compared to the assignments that are coming, is not hard at all. The core synchronization primitives that you were supposed to build for assignment one, 40 lines of code total, if done correctly. We gave you a fantastic model of how to write those in the semaphore implementation, which we encourage you to use over and over again. Many of you still chose not to do that, despite the fact that we asked you over, please look at semaphores, please look at semaphores, look at semaphores. Semaphores would be a great idea to look at. You could actually write your lock code by just cutting and pasting this stuff from semaphores and making a few small changes. But no, people decided that they wanted to do different things. But anyway, so look, if you were resisting the idea of taking some of this stuff seriously, then maybe assignment one was a little bit of a wake up call for you, which is good, because assignment two and assignment three are much, much more serious animals. So I haven't done this calculation, but my gut feeling is that assignment two is probably four times as much code to write as you wrote for assignment one. And then assignment three might be another two or three times as much code as you're going to write for assignment two. So this stuff gets harder and harder. If you guys struggled on assignment one, I would really encourage you to come talk to me, come talk to the core staff. Some of you guys from the questions that I've seen have been struggling with some pretty basic C issues in terms of understanding how to use the language, Robert. Yep, all right? So conflict there. Now, where did I go wrong? Do you say, Syllaphores and, OK, do they say the future is exclusive with UTX? I'm going to assume there's some kind of thing that I missed. So yeah, basically. I mean, we gave you the semaphore code as an example of how to write those primitives. Now, if it was as simple as cutting and pasting the semaphore code unchanged, that wouldn't be a very interesting assignment. We gave you those as a model of what to do. Your locks are expected to provide mutual exclusion. That's the fundamental property of a lock. So I don't think there's a huge conflict between the advice we gave you about how to get started, which was read and understand the semaphore code. Don't just look at it, glance at it, actually sit down and read and understand it. I mean, that's not super complicated code. And I think if you guys are in a position where you don't feel like you can read, sit down and explain to me or explain to the TAs line by line what every line of the semaphore implementation does, then you're not ready to write locks and you're really not ready to do some of the other programming in this class. So I don't think that's a huge tension there. We gave you the semaphore notation as a big hint, as a big piece of help as far as how to structure this code, some of the little things that you need to get right in terms of bridging between locks and stuff like that. That's all in the semaphore code. And there's actually big comments in the semaphore code too, that are there for instructional purposes to help you understand why that's being done. But when it comes to locks, yes, locks are new taxes. They're expected to provide mutual exclusion in a way that semaphores don't. So you do need to change the code to accomplish it. Any other questions? I'm happy to feel questions about the assignments. If you guys are upset or angry about the assignments, let's talk about it now for this builds into some sort of real big thing between us. Anybody else want to complain about the assignment? It's been a lot of choir, but obviously it's really how long it's been. I don't know. It seems that it can get easy the easy way out by just using, not anything, but using it. All right, but we discussed this in class as well. So what's the difference between a binary semaphore and a lock? Yeah, no. And at that point, what you have is a lock, right? Oh, yeah. But I'm saying that probably was the confusion because you can go a little level and start playing with how the semaphore works, but you can just use what you gave us. OK, fair enough, right? So what we didn't ask you to do is to change the implementation of a semaphore, right? We want the semaphores that we gave you to continue to work in the way that the semaphore has worked. What we asked you to do is to look at the semaphore code as an example, as a template, right, for how to write the lock. Here's the condition that the semaphore checks, right? Here's what the semaphore does if that condition isn't true. Here's how the semaphore uses spin locks and weight channels internally to accomplish this. So fair enough, yes. But again, I mean, you could have taken the code, you could have essentially implemented a binary semaphore with ownership, and you would have had a lock. That's what a lock is, you know? But at some level, when it's a binary semaphore with ownership, we don't call it a binary semaphore with ownership. It's not a semaphore anymore, right? It's a lock. Any other? Yeah. Yeah. So we will give you the test code for the reader to write a lock. So I think it's important here because, again, I think that some people kind of got a little bit of an assignment, right? So the most important thing going on for assignment 2 and assignment 3 is that you guys have synchronization primitives that work, OK? Because we don't want you getting to assignment 2 and being trying to implement things where you're going to need to use locks and need to, in one case, maybe use a semaphore and condition variables. And if they don't work, you're really going to be tearing your hair out, right? Because you're going to have enough problems in this assignment getting the other things to work. If you can't rely on your synchronization primitives, then you're really going to be frustrated. So we will, and I don't know if we'll push this out to everybody, I'd rather not do this because other classes use this code base. And so I'd rather just hand out things one at a time. But if you want an implementation of locks that is known to work, that has been tested, written by the core staff, please ask us and we will provide you one along with semaphore, condition variables, and potentially reader write a locks. Reader write a locks, I'm not sure you actually have to use at any point during this class. Yeah. It's not wrong, necessarily. If you achieve what the lock is supposed to achieve, it's just kind of disgusting, right? I mean, it's a little gross, right? But look, and maybe this is a good thing for some of you guys, my intention was actually not to read any of your lock code, right? So whatever it does, if it provides mutual exclusion, then we'll probably be OK with it. So again, we won't be necessarily exposing ourselves. Again, I mean, yes, can you use the semaphore? It's a bit of a hack, right? I mean, the semaphore code is not that complex, right? So in this case, I mean, it's a good question. And a lot of times people say, well, you have something that works, I'm going to build something else on top of it, right? But at some point, when you're building something on top of it that breaks so many of the semantics of it that it starts to become a different thing, sometimes it's worth just writing it for a scratch and having it work, right? Especially when it's as simple as locks are, right? I mean, again, if you look at the semaphore implementation and you take out the k-asserts and you take out the big 10-line comment, it's like five lines of code, right? It's maybe six. I can't remember if they lose it. The locks are also about six lines of code, right, to do correctly. It's not really a big thing. It's one extra function code, or is it one extra function? That's true. It's also an extra function code, right? It's getting in line. What's that? It's getting in line. Whatever. I mean, we're getting into the little itty-bitty things here, right? But again, we may or may not. We may look at your lock code just because I kind of like to see what people wrote, but we're not going to be brutal about deducting points for ugliness or other things, as long as the implementation is actually correct. Any other questions? All right. OK, and then finally, if you guys didn't get around it, I don't think the forum made you choose a partner or submit an SSH key. If you haven't done that yet, we'll pre-post that part of the forum on the main part of the website so you guys can do that. If you guys are going to need to do that really before you can start working on the assignment too. So it's a good thing to get done next few days early next week. Any other questions about assignment stuff? So I just want to warn you, too. I'm spending a lot of time here, but I think it's a good thing to do today. On assignment 2, the challenge for assignment 2 is going to be that the period of time that we are giving you to do assignment 2 will span spring break. If you wait till after spring break to start assignment 2, you are going to be screwed. So that was your plan. I would encourage you to not do that. We will have that assignment out either probably Monday. And that will give you essentially two full academic weeks to look at it and start thinking about it and start writing code before you leave for spring break. But if your plan is to start it when you come back, success is going to be difficult to achieve. Robert? I think that's going to be a problem for me. So maybe I could use you as a primary resource to get it all straightened out in my mind. Right. I'll remember that before spring break. OK, sounds good. Any other questions about this stuff? Again, yeah. What's that? Do we have a midterm exam before spring break 2? We do have a midterm exam before spring break 2. Just how things just. When is spring break? God. It starts on the 10th, I think. Yeah. We have two more full weeks of class starting next Monday. Yes. The 9th. The 9th. That was sent out via email to the class list. How many people got a list, email to the class list about the midterm? Did I send that? Interesting. OK, well, I'll send that. Well, actually, now that people are here, I mean, I can actually ask people something that I've been wondering about. So spring break starts on Saturday the 10th. How many people would like to have the exam on Friday the 9th? The advantages to this is that it gives you another day to review. I will do an extra day review in class. And the disadvantage is that it's the Friday before spring break. So how many people would rather have the exam on Friday? The alternative is Wednesday. How many people would rather have it on Wednesday? The Wednesday before spring break. All right, it's going to be Friday. So look, my plan for the next week is to get through as much of the rest of VM stuff as we can cover. And then the week before spring break, I have one lecture on Monday reserved for catch up so I can maybe finish up some stuff that I haven't got to. And then on Wednesday, we'll spend the lecture I'll be reviewing for the midterm. All right, sound good? OK. All right, so let me go back and we'll do our review. OK, so any questions about Wednesday's material? Virtual addresses. We talked about virtual addresses. We talked about breaking the address abstraction so that we can have things that look like memory and they might not be in memory. They might not be memory at all. They might be devices, might be somewhere else on some other machine. Might be a file, might be moved onto disk. Any questions about virtual addressing before we do our little review? All right, it's the back of the room crowd. OK, so remember, translation is control, right? The reason that we use references is that it allows the kernel to, A, control the access that processes have to various resources and also to play games with how those resources are stored and allocated, right? So I can revoke a resource, right? If I don't want to process to use something anymore, I can essentially stop translating its virtual address that points to that resource, right? That's a way of revoking it. What else can I do with references that gives me this power, that gives me this control? I can grant them, right? OK, I can share the referenced objects, right? Because I can give out multiple separate references, right? Distinct references that point back to the same object, right? Anybody else remember any of the other? There were four things here. I can share them. I can revoke them. I can move and alter resources, right? And we're going to talk about that with virtual memory in particular, right? The data that a virtual address points to may move around to different parts of the system. It may end up in different parts of physical memory. It may end up on the disk. It might be compressed. There are some new systems that do some compression to reduce the amount of size, devote it to it, et cetera, right? So this abstraction, this extra level of indirection, gives me a lot of power, right? And this is why we use it. This is why we go to all the trouble that we're going to go to today to make it efficient, right? Because it is really, really cool and very powerful. And next week, we'll talk a little bit more about all the fun games and tricks that we can play once we have this power, right? All right, so virtual and physical addresses, right? So in order to implement address spaces, right? I had this great abstraction, memory looks uniform. Every process sees memory the same way every time. What does this require, though? What does this require about addressing? Address space abstraction requires what, right? The title of the slide is a hint. It requires translation, but why do I have to translate? Because addresses don't point to physical memory anymore, right? Virtual addresses may not point to a specific piece of physical memory. I've broken this direct connection, right? So now I have to translate things, OK? So the data accessed through the memory interface in this way we refer to as using virtual addresses, right? When we talk about virtual address in this class, it's an address that has to be translated, right? Physical address is an address on memory, right? An address that points to a hardware byte of memory, right? A virtual address can point to a variety of different places, right? Physical address points to memory, virtual address. Now, what is a virtual address 0.2, right? But more generally, right? It's an address, right? And so it's accessed using the memory interface, right? Load and store. So what does that mean? It has to point to something that what? Acts like memory, right? If I'm going to allow processes to load and store to virtual addresses, those load and stores have to have meaning, right? So the data has to end somewhere and the data has to come from somewhere, right? But again, as we said, this is the fun part about this level of indirection. I can assign all sorts of new semantics to these addresses, right? They can move around. The data can move around. The permanence that the process is allowed to assume about the data may change. And finally, the protection that I assign to these addresses can also vary, right? So this, again, this is the power of this translation, right? It allows me to associate all sorts of other metadata and other features with these addresses, right? And the kernel is essentially responsible for making sure that these semantics work, yeah? Something that acts like memory, right? So again, so for example, if I, well, we talked a little bit, actually hold on, save that question for a minute, right? So remember, we talked about four different ways, four different system calls that have the effect of creating virtual addresses that a process can use, right? So which one creates virtual addresses using an elf files of blueprint? Exec, right? Which one copies the virtual address space of the parent process? Fork, right? Which one extends the process heap? S break, not malloc. Malloc uses S break, right? Malloc is not a system call. Malloc is a library function provided by the C standard library. S break is the system call that Malloc uses internally when it needs more memory, right? This is an important distinction, right? And finally, what's the last one here? What creates a memory region that points to a file? Somebody has been saying this like repeatedly. M map. M map, all right, cool. So back to your question. What's something that acts like memory, right? If I M map, if I call M map and say to the kernel, I would like this area of my virtual address space to point to this file, to point to this region of this file, that's a part of a file that now acts like memory, right? In that I can do loads and stores from it, right? But it's not actually memory, right? Those loads and stores are supposed to end up in a file on disk, okay? Does that make sense? Now in the short run, the kernel may actually allow those loads and stores to proceed into memory, right? But the point is that memory has to then end up on disk, right? So when I M map an area of memory, the kernel is responsible for making sure that any data writes and reads from that hit or come from disk, yeah. Excuse me. In MIPS we use addresses. Right, right, so. So that means we don't use addresses. Okay, good, right. So let's talk about MIPS addresses and how MIPS addresses work, right? So the machine that you guys are using, System 161, right? Depending on the address you're using, the addresses can fall into four categories, right? So there's this, and the first two are the only ones that you guys really care about, right? So on MIPS, the lower two gigabytes of the 32-bit wide address, addresses in that range are virtual addresses that are used by processes, right? And these must be translated by the kernel, right? In using methods that we'll talk about today, right? The next 512 megabytes of address space, right? Addresses in this region are translated directly by hardware and they're translated by simply subtracting off 0x80 million, right? I lop off the top bit and it points to some piece of physical memory, right? And I can only use this to address the first 512 megabytes of physical memory, right? And in addition, these addresses are only used by the kernel, remember? I never give a process access to direct physical memory, right? All the addresses the processes are allowed to use in this range, all of them must be translated, right? These are all virtual addresses, right? On some level, these are also virtual addresses but they're just translated in a very, very simple way, right? A very, very simple way that is guaranteed by the hardware always to work, right? Lop off top bit, that's the byte of memory you get, right? Up to 512 megabytes, okay? And then again, there's two other address reasons which are less interesting, right? But they include a portion of virtual addresses that can only be accessed by the kernel. So these are virtual addresses that will be translated by the kernel but are only accessible to the kernel, right? And there's cases in which we might wanna use those and we could talk a little bit about that next week, right? But those second two regions of memory are in many ways less important for this class. All right, any other questions about virtual addresses? Before we talk about how to get virtual addresses to work? Virtual addresses, last call for virtual addresses, okay. And I'd already did that, okay. Right, so remember that our goal is that we want almost every virtual address translation to be able to proceed without involving the kernel directly. Why? Efficiency, speed, overhead, yes, exactly, right? All three of those together. The kernel's too slow. Remember, when I have an exception, I have to trap into the kernel. I've got all this code that needs to run before I finally get into kernel code that's gonna handle this exception and then it's gonna try to clean up and make the machine look like it looked when the exception happened. If every process address that access memory generated an exception, I mean, like, you're running a simulator on, you know, potentially a very fast machine but I guarantee if you did this, you could try it, right? Your kernel, I mean, you'd be sitting there waiting for the menu to load for like 10 minutes. So slow, right? Kernel is too slow to translate every address, right? And so what we do is we rely on hardware to do this fourths, right? The kernel tells the hardware how to translate the addresses and the hardware actually performs those address translations. And now what we're into is a nice situation where the only time the hardware needs to interrupt the kernel and enter the kernel is if there's an address that a process tries to use that it doesn't know how to translate, right? So if an address comes up and the hardware says, oh, you know, kernel, you haven't told me how to use that address, then it will ask the kernel for help, right? And that's essentially assignment three is you just implementing that, right? So all you do for assignment three is figure out what to do when the hardware asks for help, right? And that'll take a month, okay? It's not easy, right? All right, so I just wanted to, this isn't another one of those maybe stupid mems that I don't even need to kill off explicitly, right? But so somebody might say, somebody who's not taking this class might say, okay, well, this is a nice way to do virtual address translation, you know, I'll just have a, maybe it could be a system call or something where the process asks the kernel, I've got this virtual address, OX 10,000, but what physical address does it match, right? Is this what we do? Is this what we do? Why not? Variety of reasons, right? Main reason, we never allow processes to use physical addresses, right? We always translate every address, right? And a lot of the games we'll talk about this week and next week rely on the fact that all these addresses always have to be translated and the kernel is in control of those translations, right? So we would never allow process to, we never expose processes to physical addresses at all. Remember, go back to the MIPS memory map, right? All addresses that are ever used by a process running in user mode are all translated, right? And address can't even, there's no way for a process even to address. Like if a user process wanted to address the fourth byte of physical memory, there is no way for it to do that, none, right? It might end up using that byte if the kernel lets it, but there's no way for it to explicitly say I want physical byte four, right? I mean, it's like what the guys in the army say about their rifles, right? This is my byte of memory. There are many other bytes like it, but this one is mine, you know? Like there is no, we consider memory to be completely uniform and completely undistict so the process shouldn't care whether it has byte four or byte 4,000, right? And it doesn't know, it cannot know, right? All right, so we don't do this, right? Here's what actually happens, right? So the process, right, which has this great illusion that it's actually writing something into byte 0x10,000, right? It says hardware, do a store into this byte, right? And the hardware says I have no idea what to do with this address, right? Kernel hasn't told me what to do, so I'm gonna go tap the kernel on the shoulder and say I need some help. I need you to tell me what to do here. This causes an exception, right? This, now we're inside the kernel, now the kernel has to take action. Now this isn't strictly always true, and next week we're gonna talk about cases and architecture is where the hardware is actually able to itself access kernel data structures that allow it to do address translation. But on MIPS, this is what happens. Anytime that the hardware doesn't know what the address is, it just throws its hands up and says I need to go into the kernel, right? The kernel tells the hardware what to do. It says this is the physical byte of memory that virtual address 0x10,000 for this process is supposed to point to, right? And then the MMU is allowed to complete the store, right? Does anyone have any questions about this process? This is the fundamental process, put in a kind of a cartoonish way, this is an important step, you know? But this is what happens, right? This is what we're gonna talk about today. It's getting this to work, right? How this happens, but this is the process, right? When your machine, when System 161 encounters a virtual address that doesn't know what to do with, it asks the kernel for help and the kernel is in charge of either telling it what to do or figuring out what to do with this process, right? In case that the process was trying to access some memory it didn't know what to do with, right? So let's go through this in a little more direct way, right? Using a nice animation, right? Let me come over to the giggle corner. All right, so I've got an address to 0x10000, you know? It gets sent over the memory bus. The MMU has no idea what to do. It asks the kernel. The kernel says here's the physical address that that translates to and then the store could complete, right? Or the load could complete, right? Again, any other questions about this fundamental mechanism, right? Yeah, right here. Before you said that the translation doesn't go to a kernel, how long is it going to take to a kernel? So in this case I'm going to the kernel because the kernel has not yet told the MMU how to translate this address, right? So if later I use the same address, right? So see, look at my cool animation. It's continuing to go. It anticipated your question. It's like, my slides are a lie, you know? Okay? Next time the process tries to access this address, the MMU knows what to do and it doesn't have to interrupt the kernel at all, right? Does that make sense? Yes. Only when the MMU doesn't have a clue about what to do with an address, that's when it asks the kernel, right? Once the kernel is told it, it knows. Ben? I actually have a few questions. Yes. So the general answer to that is when the kernel hasn't told it, right? And this is a little bit of a facetious example because as you probably know, the kernel doesn't tell it how to translate every byte of memory, but we'll come back to that today and we'll tell them more about more general approaches. Question two. Oh, okay. That was my plan, you know? It was my Jedi mind tricks, you know? I blanked your mind. I mean, the MMU is a piece of hardware, right? It does things that hardware does. It's not a general purpose processor, right? It has special functionality. We'll talk about some of that today in terms of special functionality designed to make this happen fast. Yes, the MMU has to have some way of knowing, right? How to translate this address, right? Yeah. Who enforces the partition so that one segmentation for an hour? Right, so, okay, here we go, right? So, oh, wow, this is awesome. You guys are great, okay. Again, the slides have anticipated your question, right? So there's another thing that can happen here, right? Which is that the process may try to use this address so it's 20,000, right? And the MMU may ask the colonel what to do and the colonel may go, kaboom, you're done, right? You were not allowed to use that address and so I'm gonna kill you. So that's another thing that can happen, right? If the process is not allowed to use a particular virtual address, in certain cases the colonel will just kill it, right? There's some exceptions where, you know, again, it's just a misbehaving process, right? So in this case, like, if this was address zero, OX zero or OX zero plus some small offset, the likelihood would be if you set up your address space in the way we talked about that that address is not one that the process is allowed to use and so the colonel would kill out the process via a segmentation fault. Question over here? Yeah. So the process is executing instructions, right? But every load or store, right, you can think about it as those get sent out on some memory bus and the MMU gets involved at that point. But the actual hardware instructions are to be executed whenever it's a load or store instruction, right, and in this case, and on MIPS in general, any load or store instruction is always processed by the MMU, right? The ones that are in the process address space require that the colonel tell the MMU where to map them in physical memory. These are great questions, any other questions? And I promise that my slides are done so I'm not gonna anticipate any more questions, yeah. Yes, yes, they are, right? So every process is its own distinct, remember, this is our idea, right? I give every process on MIPS an address space that starts at 0x0 and goes to 0x7, f, f, f, f, f. And every process has that same set of addresses, right? So process A, 0x10,000, and process B, 0x10,000 are completely distinct addresses, right? So on some level, that's actually a great point, right? So a virtual address by itself is meaningless, right? A process virtual address by itself is meaningless. It's only meaningful in the context of a particular process. So there's no way for me to translate virtual address 0x10,000. I have to know what process is running, right? Because that process defines the translations. Good, great question, any other questions? Yeah? So it's 0x20,000? Yep. It is asking for address outside its virtual space? In this particular case, because it's about to get warped, yes, right? The idea was that this address is not inside its address space, right? And it's also not in a region where the kernel might allow it to use it even if it's not, right? Any other questions? Yeah? Uh-huh. No. No, no, no. Remember, the virtual address spaces can potentially look the same, right? So there's two answers to that question. One is that if you run like two copies of bash, the L file for those two is the same, right? There's one L file that gets loaded to generate both instances. Some portions of the virtual address space will be identical, right? The code, stuff like that. Anything that's in the L file will probably be the same. What part of the address space can potentially be different between two running processes, even if they're loaded with the same L file? The heap and stack, right? Because those are generated by behavior, right? So if I'm doing something at one terminal and doing something different in another terminal, the stack, holding local variables and the heap, and actually even the global variables can potentially be very different, right? But, and now, there's one caveat to this, right? So on some systems, right, they're clever enough to know, for example, the code section of bash is potentially identical between two running processes, right? So, and we'll talk about this next week. So rather than loading two completely identical copies of the same memory into two different physical memory locations, the operating system plays this trick, right? Where it actually realizes the contents of those pages are the same and only loads the page once. And this is particularly effective when we're talking about read-only pages, because those pages will never change. So code pages are generally, code pages, by that I mean pages that have instructions on them that the program is gonna execute. Those generally do not change. And so if I have eight running copies of bash, rather than having eight copies of the bash code, I have one copy, and all of them are sharing it, right? Despite the fact that they don't know that, right? When they load an instruction, they're just saying, what's the next instruction to load? And essentially I have, you know, virtual addresses, they're all poking the same piece of physical memory, but it's completely transparent to the process, right? And it only works if those are actually identical, right? But again, we'll come back to this next week when we do our fun lectures on clever VM tricks that we can do. There was another question over here. Did it expire? Okay. All right, cool. Great questions. This is good. All right, so let's talk about some ways of actually doing these translations, all right? We're low on time today, so maybe we'll only get through one or two of them, all right? Again, what are our goals here? Our goals are that this be fast, right? It's something that I have to be able to tell the hardware how to do, right? And Malik brought up a great point. Is the MMU a CPU? No, the MMU is dumb, right? It's a dumb piece of hardware. Hardware has the, you know, hardware tends to be dumb to the degree that it's fast, right? Like when hardware has a very, very small number of capabilities, it can be optimized to do those things very, very, very efficiently, right? So what I want is I want the simplest possible way of doing this because the simplest is gonna help generate speed on the hardware, right? So the simplest possible way, and one that's really only of historic interest and help us to think about this is what is called base and bound, okay? And base and bound in its simplest form assigns every process a base physical address and a bound. The base physical address says this is where the physical memory that's in use by this process starts and the bound dictates how much physical memory that process is allowed to use, okay? And there's two things that I need to do when I translate a virtual address to a physical address. The first thing is I need to check to see is that virtual address okay, right? Is the process actually allowed to use that virtual address? Is there a mapping defined for? If there isn't, then the system may need to take some other action up and including potentially killing the process, right? So with base and bounds, this is really, really easy, right? I take my virtual address, right? And I just check it against the bounds, right? The implicit assumption here is that the virtual address space starts at zero, right? So I've assigned a certain process 10 megabytes of memory to use. If the virtual address it tries to use is larger than 10 megabytes, then that's outside of the address space that I've given it and that address is not translatable, right? And then to translate it, right? All I do is that I take the virtual address because again, they're all assumed, the address spaces are assumed to start at zero and all I do is I add the base physical address, right? That's all I do, okay? Now I've got a graphic, don't worry. And this is a little, all right. So here's my example, right? OX 10 million comes into the MMU, all right? The MMU says, the MMU has no state, right? This is brand new. This is the first time the process has, maybe this is the first instruction this process ever ran, right? MMU says, Colonel, I don't know what to do, right? The Colonel says, here is the base and bounds for this process, okay? Now you'll notice that the address OX 10 million is inside the bounds. It's less than OX 30 million, right? So the address is okay. So that's my first thing that MMU needs to check. The other thing to notice here is that the base and bounds essentially delineate a portion of physical memory that that process is allowed to use, right? There's some chunk of physical memory that the Colonel has allocated for that process that has a specific location and a size, all right? Now, can anybody tell me how do I translate this address? What physical address does this translate to? OX 10,000. Base and bounds are up on the board. Zero X, five, zero, six, zero, right? Does anyone not understand how that just happened? Because we will go through it again. Did everyone understand how that just happened? All right, cool. What about this address? Can anyone do hex math quick in their head? No, neither can I, okay? But here's how to translate it, right? Pretty straightforward, except I decided to use real hex and so, you know, eight plus six and hex is E, right? You know, I should have used decimal. I actually think my theory with memory management is one of the reasons why, if people find it complicated, they find it complicated, it's just most of us don't add hex, right? So, sometimes when I've done this in the past, I've done it, you're just using decimal, right? Because decimal's a little easier for us to add. But anyway, so here's how this works. Rob? So you're doing it, you know it. Exactly, exactly. And this is, okay, but let's do one more address. We'll come back to that. Those are good points, right? So you're starting to evaluate this technique, right? Which is great, yeah? Yep? So how is that pretty, because if... It does not, and I'll come back to that, right? This is kind of a straw man, right? But it's good you guys are seeing big, big problems with this approach, right? Okay, let me do one more address. What happens to this address? I think we'll just start with that one. Kaboom! Yeah, this address is outside of the bounds, it's specified, and so here I've decided to whack the process. What is that? I guess that looks like something from a cartoon or something like Biff, you know, or POW or whatever. Anyway, okay. So let's talk a little bit about base and bounds, right? So one of the pros here is that again, this is maybe the simplest possible way of doing address translation, right? There's two things that the hardware has to know, and there's two things the hardware has to do, right? Base and bounds, and it's fast, right? Protection is one comparison. I compare the virtual address against the bound, right? Simple. And translation, as Robert pointed out, is one addition, right? So this can be done in hardware very, very, very easily. There's not much state, it can be done very fast, right? So that's great. Now, cons, is this a comeback to your observation? Is this a good fit for our address-based abstraction? No, why not? What's that? No, no, so it's okay, right? Again, I'm gonna have hardware help me with this, and this would be really fast, right? And it's great too, because I only have to tell hardware once for any process, what the base and bounds are, and it can do all of the translations, right? But what's the problem here related to the address space? So it's basically this problem that we had before, right? Because address space has encouraged this discontinuous allocation, right? I've given the process this illusion it has two gigabytes or four gigabytes or whatever, a virtual address. It's got all this space to spread out, right? So it's like you move into a huge house, like put the TV two miles that way, right? Put the couch two miles this way, right? And then you can let the pizza boxes pile up until they meet in the middle, right? So I've got all this space to spread out, and now basically, and I just gave it away because I whacked the slide with my hand, but the problem is that the base and bounds, it's just every process, in order to give it that two gigabyte address space, I would have to allocate how much physical memory to it? Two gigabytes, I can't do that, right? That's just, this is a non-start, right? And essentially, remember the density, so if you calculated for most programs, right? The amount of the address space that they're actually using over the total size of the address space, it's very, very, very small, right? So address spaces encourage this extremely undense allocation, and with base and bounds, I've got to give it this whole thing despite the fact that it's not using very much memory. So this comes back to the problem we had before, Rob. So you're right, if I wanted to use this and the way that this used to happen is I would have to build programs to be much, much more compact, right? But remember, I have this heap that wants to grow, right? And I have the stack that wants to grow, and the nice thing about the address space is I can start them way far apart from each other and let them grow together. Here I can't do that, right? Here I would need to have a much better understanding of exactly how much heap the process might need and exactly how big the stack was gonna get, right? And I don't wanna do that, right? Okay, and again, the problem here is that I could lose a lot of memory to internal fragmentation. There's large, large, huge, like Montana-sized swaths of address space that have nobody in them. That the population of Montana is about the same size, now about the same number of people is living in Providence, Rhode Island, right? So that's the kind of density that you see in an address space, right? Huge, a big country, right? And again, just to show you, right? And unfortunately, this fell off the bottom of the slide, if I have my base and my bounds, which would be required to do this, I've got this huge amount of memory that's just waste, right? So, okay, bad, bad, bad idea. And, okay, but can we, let me try to get at least through a little bit of this, right? But can we, so one base and bounds is clearly a terrible idea, right? All this memory lost to internal fragmentation. But can I extend the idea, right? This, like segments, for example, right? So what if I have multiple bases and bounds per process, right? And on some level, this is a really nice fit for this abstraction, right? Why? Because I can have one segment for my code, another segment for my heap, a segment per stack, you know, maybe another segment for my static variables here, right? I can essentially allow the segments to cover these portions of the virtual address space that are actually in use while leaving them empty in the large, large, large expanses of space within the address space where nobody lives, right? Okay? And so this, yeah, so this is essentially the idea of segmentation. We call each base and bounds a segment, right? And let me just walk through it and I just set it, right? We assign each logical region of the address space its own segment, potentially. Potentially its own segment, potentially multiple, right? But the easiest way to think about it is one segment, right? Each segment can be a separate size, right? You know, so I don't need to have the same segment size for everything. The code, maybe I have a lot of code, maybe I have a lot of data, right? The segment size can also grow over time. So for my heap, I can start out with maybe nothing, right? And then as the process starts to use heap, I can let that segment grow, right? And I can also protect each segment differently, right? So this is another nice feature, right? For the code, I can mark the code as read-only because I'm not expecting there to be any changes and the changes probably indicate that something bad has happened, right? For the data, I can mark it read-write, right? Malik, are you considering a question or? Okay, just scratching yourself, good. All right, so let's walk through an example of segmentation. I'll continue this on Monday, right? So each segment now has three pieces of information associated with it, right? The first is the beginning of the virtual address space that this segment covers. With basic bounds, remember, we always kind of assume that the start for the base and bounds was always zero, so it wasn't important to specify it. But for segments, it is, okay? So each segment has a start virtual address, a base physical address, and a size or a bound, right? So you can see that segmentation itself is essentially an extension of the base and bounds idea, right? And to check whether a virtual address is okay, I just, there just needs to exist a segment that that virtual address falls into, right? So the process has to be, has to have a segment that the kernel has created for it, such that the virtual address is between the segment start and the segment start plus the bound, right? And to translate it, what do I do? Well, I calculate the offset between the segment start and that virtual address for whatever segment that virtual address is in, and then I just map it onto the, I add that onto the physical base, right? So again, because this is hard to understand, let me walk through another example, okay? So here, again, I start out, I've got my kernel, I've got my MMU, OX 10,000, right? What happens here? What's that? It has to ask the kernel, right? MMU doesn't know what to do, right? And the kernel says, for this process, there's a segment that it's allowed to use, starts at OX 10,000, base is, base physical address is OX 43,000 and the bounds is OX 1,000, right? So again, as we saw before, this defines some region of physical memory that this process is allowed to use, okay? And the size can differ, okay? How do I map this address? What is this address mapped to? Anybody? This is an easy one. Zero X 43, 43,000, right? I take the virtual address, I subtract off the start, that gives me zero and I add it to the base, right? It gives me OX 43,000, okay? What about this address? What happens here? I ask the kernel for help, right? I don't know anything about this address. And the kernel says, this process has another segment, right? That starts at virtual address OX 100, the physical address base is OX 16,000 and the bounds is OX 500. So how does this address get translated? Don't worry about alignment, all right? Any, what's the answer? And all right, so this defines another segment, right? With bigger arrows for some reason, that's important. And then I translate this as OX 16,300, right? I take the address 400, I subtract off the start, I get 300 and I add it to the base, right? So this is segmentation, right? Simple, simplest to think about it is multiple base and bounds per process that define different logical regions, right? What about this address? What happens? Well, potentially there could be a segment that this address would fall into, but I've named it this way to give you some idea what's about to happen, which is, yeah, kaboom, right? Okay, so if there's not a segment defined, then it's possible that the process will be killed, okay? Okay, so let's, let's, you know, we're out of time for today. On Monday, we're gonna talk about paging, right? So segmentation is a nice idea and there are lots of regiments of segmentation that are still left over in systems. Like for example, what reminds you about segmentation every time you write a C program? Segmentation fault, right? So there's still a lot of ideas from segmentation that are around in modern systems, right? But there's actually an additional step forward that we can take that addresses some of the pros and cons of segmentation and we will cover that along with other glorious things on Monday, probably, yeah. Come up here.