 Everybody, it's midterm week. All right, so today's the last day of material. We're going to do this week. We're going to try to get through address translation, a couple of methods of address, so until soon. Partly because address translation problems are a good midterm problem. So the material in the midterm will cover through what we get through today. On Wednesday, we'll do review. And then on Friday, we'll have the exam. That's most of what I just said. Yeah, so on Wednesday, we will go over the practice midterm that's posted on the website. And recitations this week will cover one of the other two midterms. I don't know if it has just decided which one or not. It'd be a surprise. So there are solutions for those up there. The practice one there aren't, but we'll go through it on Wednesday. And assignment two is up, but of course, there's pieces that are missing. Those pieces will start to be filled in, because that's kind of approximately where you guys should be at this point in the semester. So questions about virtual addressing. So remember, Wednesday, we looked at problems with direct physical allocation of memory. And we came up with this great idea for an abstraction that would solve these problems that we call address spaces. And then on Friday, we talked about address translation, this idea that rather than giving processes direct access to physical memory address, we wouldn't introduce this level of interaction that would give us all of these nice properties. So today, we're going to start talking about how we actually do that. So this ideal view of memory that we're going to be able to provide to processes, how do we actually implement that? How do we get it to work? There's a lot of fun challenges here because there's a lot of performance issues. So any questions on the material leading up to today? Thor's got his shades on, man. I must be like, must be so bright, he's got to wear shades. All right, so remember that we pointed out that translation is control. By giving processes references to objects, in this case, to memory, I force them to translate them, which gives me some special power. So what are some of the things that providing a reference to a process allows me to do to that object than that I wouldn't be able to do otherwise or wouldn't be easy to do otherwise? Wembley. Yeah, so I can share references. I can give multiple processes references to the same object, which is kind of nice. They don't need to know that they're sharing that object, but they might be. What's something else I can do? Kevin. Yeah, so I can change the reference itself. I can change what it points to. What else does that allow me to do that object back there? What's a form of alteration that I might be particularly interested in? Let's see. Thor. Yeah, so I could revoke references. So that's a good example. I could say, hey, I'm not going to translate that for you anymore, because the process never really knew what was behind that reference. There's no way for it to get at it anymore. I put myself in control of the process's ability to use a particular resource. And then what's something else I might want to do? Sirach. In the vein of altering. Yeah, so we're going to talk a lot about this after spring break. So I can move things around. I can take something. I don't have to tell the process where it is. And the process may, all I give it is a reference. And I say, when you try to translate this, I'll make sure that this is somewhere where it's useful to you. So I think we got all of them. I can revoke them. I can share them between processes. I can move them around without altering the reference itself. And I can alter other things to do other things to their reference object, without altering the reference. So when we start thinking about virtual address is in physical address. So the address space abstraction fundamentally required doing something. When we start talking about address spaces, we said we're going to give every process this nice uniform view of memory. They're all going to see memory the same. What was the direct implication of that abstraction? What can we no longer do? Yeah, I can't be talking to a physical address, because we talked about every process sees their address space starting at 0, running up to LXFFF or whatever. So there's a fundamental implication there, which is that processes aren't seeing real memory addresses anymore. If they were, I'd have to give them every one of them 4 gigabytes of separate memory. And that would be an awesome machine, but it would be very expensive. So we don't do that, right? And then so we refer to the data access via the memory interface as using virtual addresses. And how do we distinguish between virtual addresses and physical addresses? What do we know about a physical memory address? What does it point to, for example, Dan? Yeah, it points to memory. Imagine that, right? A physical memory address points to memory. What about a virtual address? What does a virtual address share with a physical address, and how is it different? Gina, OK, so what is virtual memory, right? Just follow the string of your, somebody want to clarify this answer, Peng. OK, so it can point to a number of different things. What does it share with the physical address? I mean, what's the same? Isn't that quite what I wanted? They share the same interface. Loads and stores, right? I perform loads and stores on physical memory addresses, and I perform loads and stores on virtual memory addresses. Where those loads and stores come from, right? So where the loads come from, where the stores go, guarantees the kernel makes about the permanence of that data. And even when we started talking about memory that points to devices, some of the semantics of how those memory addresses change can be different, right? But virtual addresses extend the physical address at interface, right? But they allow the kernel to have a great deal of freedom and how things work, right? And as I just said, the virtual addresses have much richer semantics, right? So virtual addresses can point to different things, to memory, to another machine, to a device, to a file, right? They can be both permanent and impermanent. They can look like memory. Sometimes they are memory, right? Or they can look like a disk. Sometimes if you write something into them, the kernel will actually persist that object, right? And then finally, protection as well, right? I can revoke them. I can share them between processes. I can make sure processes only have access to certain ones, right? So there's a lot more richness here, right? All right, so where do virtual addresses come from, right? There were a couple of different times where we create virtual addresses during the process life cycle. Some of these are most, all these are system calls. Masakazu, what is, what creates virtual addresses using the elf file as a blueprint? What's that? Still can't, I don't think that's right. I'm not hearing the right thing, look. Exact, right? This is where a big bunch of virtual addresses get created, right? It's by exact. Exact starts up, exact tells the operating system, here's what I want my virtual address space to look like and big bunches of virtual addresses get created at that time, Jeremy. Jeremy's jumping ahead, right? Fork, okay, so Fork creates a copy of the virtual address space of its parent, right? So there's a big place where lots of virtual addresses get allocated. And then what are these two last ones here? Should definitely get the third one. Somebody in the back, Anurag. Yeah, S-break, right? So S-break is a system call that extends the heap. It tells the system I'd like to move the breakpoint between my heap and my stack, and that's usually used by malloc to get more pages for the malloc alligator. And then what's this last one here? Anybody remember? We didn't talk much about it. Actually, I want to take a guess. Not a brave man, Jen? M-map, right? So M-map allows me to create virtual addresses that actually point to regions of a file, right? And this can actually be a pretty nice way to use files. So again, think about it this way, right? If you want to use read-write, read-write have their own, is a certain interface to a file, right? You read, you get a certain amount of data back, you write, there's semantics with how the file pointer moves around. M-map gives you a very different view of a file, right? It looks like memory, right? It allows you to do some different things. It frequently simplifies working with files in certain cases, right? All right, so brief digression into the specifics of system 161 and the MIPS machine that you're working on, right? So we're emulating or simulating a 32-bit MIPS architecture. Address is our 32-bits wide. What are the four address regions that the MIPS architecture defies? Two of these are particularly important for you to know about. Do you know? You're so close. There's just one word about that answer that was wrong. There's that there is a segment from 0x0 to 0x80 million. What is that segment reserved for? I'm hearing muttering, but no answer. Those are user virtual addresses, right? So process virtual addresses, these are addresses that can be translated while running in unprovenged mode, right? And they are translated according to mappings that the kernel sets up and we'll talk more about that today, right? So this is two gigabytes of process virtual addresses. So every process virtual address space lives in this area, right? Every process on the MIPS machine that you guys are developing on, process virtual address space is started 0x0 and they ended 0x80 million, right? Every process has a uniform and identical view of memory, two gigabytes of it, right? That starts from 0x0, runs up to 0x80 million, okay? What's the next region? Also fairly important for our purposes, tau. Next memory region on MIPS, okay? Yeah, so there's a direct mapped area, right? So first of all, everything above 0x80 million are kernel addresses, right? Kernel virtual addresses that can only be translated while running in privileged mode. An attempt to translate an address above 0x80 million when you're not running in privileged mode will produce an exception, usually a fatal exception. It means that the process was trying to get it memory that it's not supposed to get it, right? The next 512 megabytes are kernel direct mapped addresses. They are only accessible by the kernel and the translation is done using simple arithmetic, not even arithmetic, bit masking, chop off the top bit and you're done, right? So 0x80 million maps to 0x0 in general addresses in this region mapped to the address minus 0x80 million. So for example, if you're running in the kernel, right? And let's say the kernel wants to touch the byte of memory at address 32. How would the kernel construct a virtual address that it knows will map down to that byte of memory? Josh, I'm running in the kernel. I want to touch physical address 32. Which virtual address do I use? I want to touch 32. I don't know why. Just sounds like a good address. How do I get to 32? Exactly. I add 0x80 million and I get what I want, right? So if I want to touch physical address 32, I take 32, I add 0x80 million and I'm done, right? And the processor will subtract 0x80 million and get me back to 0x32. You guys will get used to this when you're doing assignment 3 because this is how you guys get access to the first 512 megabytes of physical memory. And on your machine, we'll never run it with more than 512 megabytes. We won't even run it with more than 4 megabytes or 8 or 16 or something, right? So you'll always be able to use this region to address all of the memory on your machine, right? There's two more regions that I'll cover a little bit more of an excitation that we won't slow down to talk about too much today. There's a direct mapped address area. This is typically where we put devices and other things that kernel wants to communicate with that look like memory but don't observe memory caching semantics. And then the top 1 gigabyte are kernel virtual addresses. So these are like virtual addresses that are used by user programs. They're translated in the same way, but they're only accessible to the kernel, right? All right, any questions about virtual addresses before we talk about how to implement virtual addresses? OK, so the goal with all the translation mechanisms we're going to talk about, our goal is very simple. It's performance. So user programs and the kernel and other things access memory a lot, right? And we want these translations to proceed almost all the time without any help from the kernel. So I just gave away the answer, but what would happen? I mean, what would happen if instead of just making this fast and efficient, every time I wanted to use a memory address, I had to make a system call? Well, what happened to your machine? Mooged it. Yeah, so what would happen overall? Yeah, it'd be terrible, right? I mean, it would be really, really slow. Memory's fast, right? I mean, it's not as fast as registers, but we're still talking maybe a couple hundred cycles. It can take thousands of cycles to get in and out of the kernel, right? So if every time I had to translate a byte of memory, remember, we're still talking about how to do this, right? We've set up this level of indirection. We're not giving out addresses to physical memory to processes. We've created virtual addresses. We're going to use them. We're going to translate them. We just don't know how, right? But this is our problem, because if we force the kernel to be on the path for every address to handle this, then this would just be way, way, way too slow, right? And this is another nice case of an example of the split between policy and mechanism, right? And this is another nice example similar to some of the stuff we looked at on the CPU, where there's been cooperation and sort of co-development of hardware capabilities designed to support system policies, right? So the kernel wants to be in control of address translation. That gets us all of those nice features, right? We're going to talk about ways that the hardware can help it do that, right? One of the ways the hardware helps is by making this process fast while still allowing the kernel to be in charge, right? It says, you're in charge. You're telling me where these addresses map, and I will help you make that mapping really fast, right? So here's an example of one way that we could do this, right? So when a process tried to translate a byte of memory, there could be some sort of way for it to ask the kernel what physical, I know I've got these address spaces and I know these virtual addresses aren't real and that's all nice and everything, but like I want to store a byte, you know? Like tell me where this, where is this byte? You gave me this mysterious address, oh it's 10,000, and just tell me where it is, you know? Just tell me where it is and then I'll just, and then I'll just use that address, right? So what's the problem with this? What's the problem with this? There's a bunch of problems with this actually. AJ, well, right, but the point is that I'm going to hand it back a physical address, right? And that physical address won't be shared, right? So remember this is a process, a particular process asking the kernel and so it's translation of OX 10,000 is going to be unique, we think, right? What would be the problem with this, Tim? Yeah, so now I've handed out this address, right? So how do I revoke it? I told this process, hey, you can, first of all, this requires that I allow processes to use physical addresses at all, right? And I said I didn't want to do that because now, you know, maybe what happens is later I want to use this physical address for something else, right? But now this process is allowed to use this address. So this is not good, right? In order for us to get this level of indirection and all the nice properties that are associated with it, all physical addresses have to be hidden from user processes. I can never give a user process the ability to translate a physical address. If I do that, then it's maybe it looks small, right? But it's some small chink in my dam that eventually is going to destroy, right? So every address must be translated, okay? So here's what happens instead, right? So a process executes a store instruction, right? Like whatever it is, store word or whatever, and it says store to address OX10000, right? What kind of address is that? It's a virtual address, right? And it's unique to this process, right? This process has this address, some other process may have the same address but they're not the same, right? So remember, I need those two pieces of information to identify a virtual address. The process ID, some information about it, and the virtual address. The virtual address is no longer meaningful by itself, right? The process says store to this. So now we're gonna introduce a new protagonist here, the MMU, or memory management unit. So the memory management unit is a piece of hardware that is on the kernel's team and under the kernel's control. But the MMU is there to help us make this process fast. Then we'll talk a little bit about what the MMU does, right? But the MMU is the kernel's buddy, right? And it does the kernel's bidding, right? The kernel is in charge of telling the MMU what to do, right? However, it's sometimes the MMU doesn't know what to do, right? So in this case, for example, this may be the first time that this process used virtual address OX10000. So the MMU doesn't know what to do. And so what it does is it says kernel, what the heck am I supposed to do with this address, right? I had no idea where it goes. You never told me anything about this, right? This causes an exception, okay? This is the first time I use this address. Now what happens? I just, I have an exception that's caused by the processor, in this case by the MMU. What happens when there's an exception? What's that? I jump into kernel mode and I jump into the exception handlers and I start processing the exception, right? So the kernel wakes up, looks around and says, oh, heck, this is problem, I shouldn't say that. There's this problem with the MMU. The MMU needs my help, right? The MMU is asking me, how do I translate this, right? The kernel is in charge of doing one of two things. Either it can tell the MMU how to translate the address, right? So that's the common case, right? What else might happen here? Dan, well, so I could give the address, right? I could say, hey, okay, this is an address I know about, right? But what else might happen here, right? What might the process be trying to do? Book, you want to help them? Well, we'll talk about when things start moving around a little bit, but that's not what I have in mind. Yeah, actually. Yeah, what if this process is trying to use an address that I haven't given permission to use, right? So it's possible there is no valid translation for this. It's possible simply that this address is not part of the process's address space. It's never been loaded in the process address space and the process is therefore not allowed to use it, right? Maybe if this address was 0x0, right? And this was an all-pointer exception that would look a little bit more familiar, right? So the kernel has to do one of two things. Either has to tell the MMU, right? So keep in mind, right? The process is in the middle of executing this instruction, right? This is a hardware instruction. It's doing a store. Either that instruction has to complete, at which point the process is allowed to assume that the byte of memory that it stored to that address is there or something else has to happen, right? There's no way to raise an exception, right? To throw an exception or something, right? Like this is a hardware interface, right? So either the store has to complete or something else has to happen, right? So let's say that this process isn't allowed to use this address. What happens next? I'm pretty sure you guys have seen this before. Maybe. Spudzer. Yeah. Segmentation fault. Core dumped, right? That's where this comes from, right? You try, what's that? Oh yeah, well that's, yeah. If this happens, okay. If this happens in the kernel, then something very bad's gonna happen, right? But if it happens in your user program, the kernel will trap it. It'll kill your program. And if frequently if you're running C, what'll happen is that that will cause the C interpreter to dump some in state about what happened, right? So segmentation fault, core dumped. That's what happens if you try to, use your process, tries to translate an address that the kernel doesn't know how to translate. It just gets killed, right? All right, so in this case, we're assuming that we could find a valid translation for the address, so all is well. The kernel is gonna tell the MMU this address, right? This address maps to physical address, whatever it is, right? And the MMU will say, okay, great, right? And then it will allow the store to complete, okay? The nice thing about this, right? So let's walk through what just happened, right? I'm a user process. I'm translating 0x10,000. The MMU doesn't have any state. It doesn't know what happens with this address. It asks the kernel. The kernel tells it how to translate it, right? And then I allow that, now the next thing that will happen is that store will actually complete, right? To the address that the kernel specifies. Note here that the process has no idea what physical address is being used, right? This is never communicated to the process. The process thinks it just did a store to 0x10,000. That's all it knows. The nice thing is, later, assuming that, you know, the state on the MMU hasn't been cleared for some reason, let's say the process now does a reader store from 0x10,000, right? So now what happens? Yeah. Well, is the, so okay, here's the question. Does the MMU need to ask the kernel now? No, right? This is the whole point of doing this, of having an MMU, right? If I had to ask the kernel every time, then I'd be back to that world where the kernel was essentially involved in every translation and your system would take weeks to boot, right? Instead, what happens is, once the MMU has this address translation cached, it no longer needs to ask the kernel for help. And then this translation could proceed extremely quickly, right? So what I've done is, again, the first case here was a chance for the kernel to set the policy about how this address was translated, right? Then, as the process continues to use it, as long as there hasn't been a change in how this object is mapped, those translations can be done quite efficient, right? What about this address, though? 0x20,000, what happens here? Quiet group today. I'm gonna go, what is your name? Gee, what happens? Right, I don't have a mapping for this, right? So I'm gonna ask the kernel and then what could potentially happen here? Let's say the kernel says, I don't know anything about that. I never told the process it could use that address. What happens then? Lovely. Core down? Boom! Kill process. You know, the kernel just loves doing that to break out that graphic. Okay, there's a little bit of the process left, right here. Not much, pretty much total annihilation. Okay, so yeah, if the process tries to translate something it doesn't know about, that's the only thing I can do, right? There's no way to return an error from a stored instruction, right? I just have to kill the process, right? All right, so now let's start talking about some ways to do this, right? Yeah, Wembley. Okay, so here's the idea, right? So let's say, let's go through this example again. Yeah, so let's say that this, okay, let's try to use this graphic to illustrate Wembley's point, right? So let's say that this address is now used by a different process, right? What does the kernel have to make sure of? So first of all, if this address is being used by a different process, what has happened in between the use of those two addresses? Yeah, there's a context which has taken place. And what does the kernel have to do on a context switch now? At minimal, it has to clear out the MMU, right? Because those translations are only valid on a per-process basis. Now there's, sometimes you can save them and reload them later, right? But the simplest approach is just to tell the MMU, forget everything I ever told you, right? I'm starting a new process. What this will mean is when a process starts to run, what will happen at the very beginning when the process is first executing? I perform a context switch and I start the process going, what will happen probably in the first 10, 15, 20 instructions it executes? Sam, what will I see a lot of? Yeah? Right, so the MMU is gonna be asking the kernel a lot, how do I translate these addresses? Because now the MMU knows nothing. So if I clear out the MMU on a context switch, any address that's used by this process will produce a memory related exception that the kernel will have to handle. I haven't told you exactly about the semantics for how these get translated in ranges, right? We'll talk about that for the next week or so, right? But when a process starts to run, again, the MMU has no idea potentially how to translate any addresses. So it has to sit there asking the kernel a couple times and then hopefully as the process continues to run, those translations start to happen automatically, right? So when a process begins after a context switch, it takes a little while to warm up the MMU, right? We think of, if you think about the MMU as a cache, this is warming up a cache, right? Bringing stuff into the cache and then hopefully as I run, I get to use that warm cache, right? Good question. Any other questions about this? Yeah, actually. Yeah, so, okay, so we will talk much more about how physical memory gets allocated, right? So this is assuming that you're right. At some point, the kernel made a decision and said, for this process, I'm gonna allocate this piece of virtual memory, right? Sorry, a physical memory that's gonna be translated about this and we'll talk about how physical memory actually gets shared and how these mappings get changed over time. Yeah, Sean. Okay, good question. How does the MMU stay secure? What do people think? Clearly, there's a way to communicate with the MMU. And what, yeah, same. So it could, okay, so sometimes MMUs will allow you to attach a process ID to a translation, right? What this allows you to do is it actually allows you to have two translations in the MMU for the same virtual address, but distinguished by process IDs, okay? So that's not quite what I was thinking about, Jeremy. Exactly. So the MMU is controlling the MMU, loading and clearing translations requires privileged instructions that only the kernel can access, right? It's a great question because if users could control the MMU, then they could basically have access to any memory on the machine they wanted, right? All they have to do is load a translation that points to it and then use it, right, AJ? Yeah, you can do that, right? And sometimes that will help, right? So you can say, okay, you know, rather than clearing out the MMU every time you're gonna save the translations that are loaded and then reload them. And in fact, if you want to for assignment three, you're welcome to do just that. It turns out that it's surprisingly, it doesn't create that big of a performance difference, but it does help with reducing the number of translations that have to be reloaded, yeah. Ah, good question, yeah. So we'll talk more about this, right? So how does the MMU decide what translations to store? So this becomes very hardware specific, right? On the MIPS architecture that you guys are programming for this class, you actually have control of the translations that are in the MMU. That's not always the case, right? On some architectures, including x86, which is, like I've heard a little popular, the MMU is actually loaded by hardware, right? And we'll talk in about a week about how that happens, right? What happens is that the kernel tells the MMU something about the data structures that it's using to store these translations, but hardware will actually go into those data structures when it needs to and pull out translations and put them in the MMU, right? Your system has what's called a software managed TLB or MMU, but other systems actually use hardware to load these. But yeah, these are all great questions and we're gonna get to this now, yeah. Yeah, I don't, you know, that's a great question. I mean, on the machine you guys are programming, there's a specific set of instructions so they're provided by the processor for manipulating that we're gonna call this a TLB in just a minute, right? A translation look-aside buffer, right? Where these translations are stored. On other machines, I don't know, you're right. It could be, I could have a little memory, a piece of memory that when I write into those translations end up in here, that would be another way to do it. I don't know if it's actually done that way or not. What's that? Yeah, I don't know, that's a good question. I mean, you probably have to hardware those addresses so the MMU always knew where they were, right? Great, so let me go on and let's at least get through kind of one way of translating addresses. So again, remember our goals are we want to, so there's two goals, right? One is that we wanna cover a fair, we wanna allow the MMU to be efficient, right? So if we look here, right? Let's say we had to load a separate translation for every byte of memory into the MMU, okay? So I do a store to 0x10,000, I have to load a translation, I do a store to 0x10,001, I have to load a translation, right? What would be the problem with that? Yeah, I mean, I'm doing, now I'm doing a lot of loading into the MMU, right? And to some degree, that would be terribly efficient. If you did things with that granularity, you would essentially be translating almost every memory address, right? So what we want is a way to tell the MMU efficiently how to translate not just one address, but a whole set of addresses, right? A whole region of memory, right? A whole region of the address space, okay? So one, yep, boom, I still like that. I just wanted to see that again. So one way of doing this is to use what's called base and bounds, right? So we're gonna build up from the simplest possible scheme and then we'll come up to things that are more modern and actually used today, right? So base and bounds mapping is very, very simple. I assign each process a base, right? A base physical address and a bound. So the way, and we're gonna talk for each of these, how we perform a couple of things, right? So the first thing we have to do when we load a translation is we need to check to make sure that translation is actually valid, right? We talked before about what happens when the translation wasn't valid, but we didn't say how the kernel actually has to check that or how hardware checks, right? So on base and bounds addressing, the virtual address is okay if the virtual address is less than the bound, right? That's just, this is very, very simple and I'll show you an example of this in a second, right? Translation is extremely simple, right? Extremely, extremely simple, right? It's just one operation. I take the virtual address and I add the base to it. I add the base physical address. So what I've essentially done is I've taken the address space and I've loaded it starting at some physical address and just moving upwards until I get to the bound, Jeremy. No, well, we'll come back to paging. It's a good question. So here's an example of how this works, right? And you guys are probably thinking this doesn't work with address space and you're right, but let's go sort of talk about why. So OX 10,000 heads to the MMU. The MMU is gonna ask the kernel, how do I do this? The kernel is gonna say for this process, its base physical address is 40,600 and its bounds is OX 30,000. So there is a region of memory starting at 40,600 with size 30,000 that this process is allowed to use. How do I translate this physical address given base and bounds? So first of all, how do I check that it's a valid virtual address? Who remembers the slide before this? Wembley. If the virtual address is less than the bounds. If the virtual address is less than the bounds. What does this mean about the size of my virtual address spaces? What are they limited by in this scheme? They're limited by the bounds, right? So this guy's virtual address space is only 30,000 bytes big. And that could be a problem, right? Because we wanted these huge sparse address spaces. So this is not going to, this is not super compatible with our address, our address abstraction. Yeah. Sure. No, no, the check is on the physical address, on the virtual address, right? And it's just whether it's lower than bounds, but then the bound, right? Because what we're going to do next, so how do I translate this now? Okay, so I've said this is lower than 30,000, it's okay. How do I actually perform the translation? Yeah, so what is this translated to? 50,600, right? So my base and bounds identify an area of physical memory that this process is allowed to use and the virtual address simply addresses into that, right? So 10,000 from the start of this, it looks about like about a third, right? That's where this byte goes in physical memory, right? What about this address? Where does this go? Anyone do hex math rapidly in their head? Yeah, me neither. It turns out it goes here. Who knew? Right, but again, base plus bounds, sorry, base plus physical address, I check it against the bounds, it's lower than the bounds, just barely. And then I add the base, okay? What about this one? What happens now? What's that? And then what? Boom, there we go, right? So this address cannot be translated and this produces a fatal exception, right? So what's good about this? What's good about it is primarily that it's very simple. There's only two things I need to tell the hardware for each process, that's great, right? That means that the MMU can potentially store a bunch of different base and bounds for lots of different processes and I don't have to tell the MMU what to do very often, right? When the process starts running, I say, here's the base and bounds and then basically the MMU should be able to translate every address, right? In the process's address space, that's nice, right? The other thing about this is that the translation at the hardware level is very, very simple, right? So we also have to think about the fact that again, every address that the process is using is being translated every single one. So anything that goes on this path is, this is a very hot path at hardware, right? Like this gets hit every time I do a load or a store. The nice thing about this is it's very, very easy, right? I mean, these two hardware operations can be done, you know, potentially in parallel and very, very, very rapidly, right? So this is good, okay? What's the big problem with this though? I hinted at it a couple of times, Jeremy. Yeah, this is a terrible fit for our address space abstraction, right? Address spaces, remember address spaces were this nice idea that made everything look uniform but the price that we paid for that is that we gave every process this illusion that it had, you know, like four or two gigabytes of completely contiguous address space, right? And so in order to support this illusion we had to figure out how to handle the sparseness, right, of this address space. The idea that most processes are gonna have a tiny, tiny, tiny fraction of their address space in use and there's gonna be huge gaps between it, right? On this, you know, if I wanna give a process a two gigabyte address space I have to find two gigabytes of contiguous memory, right? It's probably on a machine that doesn't even have two gigabytes of memory, right? So this is a total fail when it comes to our address space abstraction. And if we even did this, right? What will we lose, what's our big problem? Where do we lose memory? We're gonna have fragmentation, what kind? Nick, we're in external? Who thinks we're gonna have external fragmentation? What are you saying? What about internal fragmentation? Yeah, I mean, remember these address spaces are mostly empty, but I've gotta allocate them contiguously in physical memory and so I'm gonna have lots and lots of areas inside those that aren't being used, right? But I can't reclaim, right? I have a big internal fragmentation problem. So again, let's say that I did this and maybe what I do is I say, well, I can't give you four gigabytes because I only have four megabytes of memory, right? So now I make my address space like, I don't know, a megabyte big or like half a megabyte or something, right? It's gonna be very small. Even if it was that size, I'd still have all of this wasted space in there, right? So if this was what the allocations look like, even on a much smaller address space size, I would still have all of this wasted memory inside my address space that I can't reclaim, right? So the base and bounds, one base and bounds is clearly, you know, a terrible, terrible fit for our address space abstraction. But can we, the question is, can we extend this idea? So what we're going to do instead, right, is we're gonna look at this picture, right? And we're gonna say, hey, look at this. So I have an area of the address space that's used for the code. It's set up by exec, right? All of that code pretty much gets packed and they're pretty good by exec when it starts up, right? Then I've got another area here that's used for the heap, right? And that, you know, grows a little bit when I call S-Bright, but that's pretty contiguous as well. And then maybe I have some libraries in here and then I have a stack for each one of my threads. But in general, I've got this address space that's mostly empty, but then I have a few parts of it that have some useful things, right? So rather than using one base and bounds, I use multiple base and bounds. I say a process can have multiple base and bounds and what I do is I call each one a segment, all right? And so now we've come to the point where you can understand that mysteriously error, segmentation fault. That's where this comes from, right? Sorry, that's where the error comes from. The error comes from the fact that these are segments and we call the process of setting up an address space in this way segmentation. So, and a nice thing is we can use segments, there's a nice mapping between these logical areas inside the address space where I'm using to store code and data and stacks and things like that and separate segments, right? Each segment can be a separate size, right? Each segment can be protected differently. Remember when I looked at the PMAP output for a process, I saw that some segments were marked as executable, some are marked as read only, right? So now the nice thing is I can assign each segment, right? Portion of the address space with different permissions. Well, and that's the end of the slide, yeah, Jeremy. Ah, okay, so we'll talk about that, right? Like I should be able to change the size of a segment, right? Now, figuring out how to do that in physical memory can be tricky, right? But how do I, so here's a good question. Base and bounds addressing, how do I change the size of a segment? What do I do? Let's say I've loaded the segmented to the MMU, I've told it the base and bounds, and then the process wants to make it bigger, yeah, on it. Change the bound, right? I say, okay, well, you know, you called S break, you wanted a few more pages, I need to make your heap a little larger, so I'm gonna tell the MMU, and move the bound up a little bit, right? Jeremy. I guess you would, yeah, yeah, that's annoying. Yeah, you'd probably have to move the base downward in that case, right? That's good. Yeah, so the translation process is the same, right? Let me get through this, and then I think what we'll do is we'll finish this on Wednesday before we do review, because there's just a little bit more about segmentation, but I wanna get it done for the exam. Good, good, good for exam questions. So this is very similar to base and bounds. Every segment now has a start virtual address, right? So this is different from base and bounds. Why do I need a start virtual address for a segment? What was this, or let's put it this way, what was the start virtual address implicitly for my base and bounds addressing? It was zero, right? But now I not only need to tell the MMU where this is in physical memory, but I have to tell it what segment is this in virtual memory, right? So where does it start in the address space? Then I give it a base physical address and a bound, right? The bound indicates both the size of the virtual address space that I'm allocating for this and the size in physical address space, right? Those two sizes are the same, right? So when I try to translate a virtual address, what I do is that I need to see if it's inside some segment. So does there exist a segment where the virtual address is between the beginning of the segment, the segment virtual address start, and the segment VA start plus the segment bound, right? So does this address fall into some segment that I've allowed the process to use? If the answer is yes, then I know how to translate it. If the answer is no, then kaboom, right? And then the translation progress. So once I find the segment that contains this virtual address, the physical address, I take the virtual address, I subtract the segment virtual address. So that gives me the offset within the segment and then I add that to the base, right? So here when I talk about segment start, I'm talking about the start virtual address and the base is the base physical address, right? Does this make sense? This is a little extension of what we did for base and bounds, right? You can think about this again. For base and bounds, these segment starts with all zero, because there was only one segment that started at zero. Segment start is a virtual address. Yeah, I should make this slide a little more clear, right? Segment start is the start of it in the virtual address space. Segment base is the start of it in physical memory, right? So remember, the goal is to translate my virtual address to a physical address. So I find the offset within the segment and then I add it to the physical address, okay? So we will finish this on Wednesday, for room. So yeah, so what would you do? So the idea is, you're saying I have a segment that gets a little bit bigger, right? But maybe there's another segment next to it or something. So what do I need to do, fundamental? Yeah, right? No, no, I understand your question. I'm asking you what the answer is. Right, so I need to make it a little bit bigger and you're, what do you mean? I don't have any physical memory left.