 It's Friday. Congratulations. Everybody's looking forward to a nice long weekend of working on assignment two. So today, we're going to talk about different ways to translate virtual addresses to physical addresses. So last time, we decided that this was a good idea. But we didn't know how we were going to do it. So today, we'll talk about multiple different approaches to doing this. We'll look at pros and cons of each. And we're driven by a couple of design goals, including, and most importantly, being able to do this fast, because this is going to happen a lot. Every time we use an address, remember, every address that a user program sees is now a virtual address. I never give them a physical address, so I have to translate every address, meaning that this has to be extremely efficient. All right, so there's one week left. Till assignment two is due. I can tell that at least some of you guys know that, because there are more people at office hours now, which is great. Sadly, there are no new members of the 8080 club to announce today. So maybe what happened is those three groups were the people that actually started when I told you to start the assignment. And so they finished, and the rest of you guys decided to wait until a week before it was due. And so I hope that you guys will get there by Friday. But we have plenty of office hours this week, and maybe we'll do something a little bit fun next Friday for the first big turn-in deadline, but we'll let you know. The midterm, as you guys know, is after break. The midterm will cover everything that we get through until the end of next week. So I'm planning, I have to see. I'm planning on lecturing through Friday next week. If we're a class ahead of schedule, maybe we won't have a lecture Friday. I know that sometimes people are traveling, but when you get back from break on Monday, we will do a review. The midterm will be on Wednesday. There are, at this point, multiple sample midterms with solutions up online from previous years. Those should give you a very good idea of what will be on the midterm and the format the midterm will be. Particularly last year is because we're pretty much on the exact same pace as we were last year as far as covering material. And the midterm is at the exact same point within the semester. Any questions about logistics? Hey. Okay, oh yeah, sorry. This was about no new members of the 8080 Club. The cat is upset. Please, make the cat happy. Do the assignment. All right, questions about virtual addresses before we go on and talk about how we're actually going to get this to work? Any questions about this whole new frightening realization that you had last class, which is that all the addresses that you've ever used have been fake? None of them were real. Yeah. Does this mess up caching? Does this mess up caching? Interesting. No. It does not mess up caching. Caching is typically done in a layer where it's allowed to deal with physical addresses. So the virtual to physical translation happens. The memory subsystem fetches memory. The cache locations are all done based on physical addresses. They have to be because at that level there's not necessarily even really a notion of a process. Remember my virtual address requires a process ID in order to make any sense. There's no need trying. If I gave you a virtual address to try to translate, you would have to tell me, I cannot do that. I need more information. And we'll talk today about the format that that extra information takes. That's a good question. Any other questions about virtual addresses? Yeah. Yeah. Are you going to do some live? Yeah, yeah. So the question was, why do we need extra hardware to help us translate addresses? And the reason is because software is too slow. It's just too slow. The only way that I'd be able to do this is imagine, again, if every memory instruction generated an exception that the kernel had to handle, that your computer would still be booting. It would still be booting if you started it a week ago. Memory's supposed to be fast. And today we'll look at, now some memory related instructions are going to generate exceptions that the kernel will need to handle. But the goal is to reduce that number to as few as possible. That's a good question. Any other questions? A little bit of review. So we talked about introducing a level of indirection. We talked about the fact that by forcing processes to translate a reference rather than allowing them to use some sort of lower level handle directly, this gave us a great deal of new control and it also gave us a bunch of new features, right? We talked about how we could stop a process from continuing to use a reference that we had given it. We can allow processes to share references that look different but might refer to the same underlying object. We can move the object that it's referring to because the fact that it has to translate this reference in order to gain access to it gives me a chance to undo whatever I might have done. This is something we'll talk about more next week. And finally I can also alter properties of the underlying object without the user's knowledge. So this was a good thing. The virtual apps, so we talked about as soon as we introduced address spaces we knew that there was something funny about addresses. And so we talked about the fact that now all addresses, the processes used are these virtual addresses and anything that a process uses that obeys the memory interface. So virtual addresses can actually point to things that will load, that can store data and will load data. Those things don't necessarily have to be bytes of memory. They can be bytes on disk. They can be bytes on another machine. They can be bytes that refer to a hardware port. So there are other things that I can now reuse the same interface for. And this is something that we'll come back to when we talk about files. Because the file interface is another interface that kernels have used and abused to refer to file-like objects that obey the file interface. All right, so we talked about how a virtual address points, a physical address points to memory. So when we talk about physical addresses we're talking about actual bytes of RAM. Whereas a virtual address just points to something that obeys the memory interface, something that acts like memory. And we talked about location, permanence, and protection. Whoa, slow down there. As new properties of virtual addresses, we could layer on top of virtual addresses because we've created this level of indirection. So here were the four places that we talked about where system calls that create virtual addresses. So the first one was exec. How does exec create virtual addresses? Yeah. Yeah, so it takes the L file and interprets the L file and the L file contains a blueprint that describes how this new address space is supposed to be created. Where are things supposed to go and what content's supposed to go at each virtual address, each range of virtual addresses within the address space. What about fork? So fork also created new virtual addresses. How did it do that? Yeah, just a copy of the parent, right? So these same virtual addresses, but with the new process identifier. So the process identifier virtual address pair that is required to uniquely refer to whatever the virtual address points to is now different. I've taken the same virtual addresses, those are identical, but there's a new process ID. And so I'm allowed to refer to different underlying objects. S break, these were two new system calls that we hadn't necessarily talked about. Or what does S break do? Yeah. What's that? Yeah, it allocates heap space. So it extends the process heap, it moves something that has been historically called the break point, which is the top of the process heap. This is used by Malik when Malik needs more memory. And finally we add mmap, which allows a process to create, to take a portion of its address space and ask the operating system to point it at a region in a file. And this is quite a clever thing to be able to do. I'm not gonna go over this in detail. We talked about the MIPS memory map. You guys will come back to this when you do assignment three. So don't worry, see this again in recitation. Any other questions about virtual addresses before we go on? Okay, so remember in order to get this to work, so I've introduced this level of indirection and it's been clear up to this point that this is awesome. It's really powerful, it allows me to do all sorts of cool things. It allows me, gives me all these new capabilities. But how do we get this to work? And today we're gonna talk about how we can efficiently translate virtual addresses because keep in mind, now every address that a process uses is fake. Every address requires translation. And the goal here is to keep the kernel out of the loop. So somebody mentioned this in the early round of questions which I thought was great. Why do we need hardware to help us? It's just the kernel is way too slow. So remember the goal here was the kernel sets what? Remember this is one of our famous systems splits. Hardware provides what? Yep, all right, you have answered the puzzle. Should really get like a wheel of fortune type display for these sorts of questions. Yes, he has solved the puzzle. The goal here is to get the hardware to do the work for me because the hardware is gonna do it extremely quickly. But at the same time I need to make sure that the kernel retains control over how the translations are performed. So here's one approach at doing this. Here's one attempt. So when a process wants to use a virtual address, it in some way tells the kernel, here's the virtual address. Tell me what physical address this refers to so that I can use it. Can I do this? Is this something that I would be able to get away with? Who thinks yes? Okay, why? Yeah, but what's the problem here? What have I done? Remember there's something I didn't wanna do, yeah. Well that's bad, but let's pretend there was a way to do this quickly. Remember there was a rule that I wanted to follow here. I never wanted to give the program access to what? To physical memory, so this requires exposing a physical address to the process. And this is a no-no, I'm never gonna do this. The process will never know anything about physical memory, nothing. Every address has to be translated, all of them, right? I looked, it took me like 10 minutes to find this photo. I'm sorry, it's a little bit washed out, but yes, all your addresses are belong to the kernel. They are all translated. So here's another approach. The process says issues what it thinks is a write or read to 0x10,000. The MMU tells the kernel, I have no idea what to do. The process said to write to this address, I've never heard of this address before, what should I do? The kernel says, and this is when an exception is now generated. So here's where the kernel gets involved. The kernel is now going to tell the MMU, the underlined hardware, what the correct translation should be. It says, I know that for this particular process it's running right now, what it calls, memory address 0x10,000 actually points to whatever. That's because I've allocated the memory for it. And then the MMU says thank you and the story completes. This is essentially what we're going to make happen. Okay. Now the pattern of how this happens and how often it happens and how that has impacts on fragmentation and memory allocation is the thing that we're going to talk about. But this is essentially the workflow that goes on frequently. Now what part of this do I want not to happen? What would be ideal? So this is sort of the slow case. Yeah. Yeah, I don't want this, right? When the process tries to store to some address, I want the MMU to know what to do already. So I want to limit the number of times that the MMU has to ask the kernel for help. Because if this happens every time, then essentially I'm back to that world where I'm translating every single memory address and your computer is just too doggone slow to ever use. All right, so here's essentially how this works. I have this virtual address. The virtual address goes to the MMU, the MMU. And now at the MMU, let's say at the beginning of the time, the MMU knows nothing. Doesn't have any idea where these crazy virtual addresses that the process is using are supposed to be pointed to. So it asks the kernel, through an exception, it says, hey, this process is trying to use this address and I don't know what to do. So the kernel might say, here's the virtual to physical mapping that I've allocated for this particular process. But keep in mind, this is where that extra piece of information comes into play, which is what process is trying to use this virtual address. So once the kernel gets involved, the kernel knows what process is running on that CPU. And so it can add that extra piece of information that's required to make a virtual address into a physical address. I can't translate a virtual address by itself. I need the process ID. Once I have that, though, then I have enough information to translate it, assuming that the translation is valid. Now the next time, here's the nice thing. I'm running along and a couple of instructions later, I use 0x10,000. What happens now? Same program running. So a couple of instructions later, nothing has changed. Does the MMU need to ask the kernel again? No, it knows, right? So now I can just translate this immediately and this is what I want to have. This is what I want to be the common case, where the MMU knows what the translation is. It doesn't have to ask the kernel for help. Now here's something else that can happen. Later in my program, I try to use 0x20,000. The MMU asks the kernel for help. What else can the kernel do at this point? So the kernel can tell the MMU, oh yeah, I know this process. I allocated this translation for this process. It's allowed to use this bit of physical memory. What else can the kernel do, though? Yeah. Okay, so that's a great point. So I could tell the MMU a bunch of different allocations at this point. But what's it possible that's happening right now? What happens if the process hasn't actually been granted permission to use this virtual address? What can the kernel do at this point? It could destroy the process with a cartoon explosion, right? Or, more specifically, it can shut the process down, right? So this is how I enforce the allocations that I've granted. When the process asks to use a new translation, I'm allowed to look it up. If the translation is invalid, if the process is buggy, if it's trying to poke around and see if it can get access to somebody else's memory, the kernel has control, and the kernel can say goodbye. I'm going to kill Kerr threat. And this is something that happens as well. So now let's talk about three different approaches. Oh, sorry, yeah. Yeah, so it's a great question. So on a multi-core machine, these translations are happening on a per-core basis. Now why is that the case? Why would I need a per-core, per-core MMU, yeah. Yeah, I've got a bunch of different processes running on different cores. So, and last I have some other way of telling the MMU the idea of the process, which in certain cases I might, depending on the hardware. But in general, I'm alluding translations for the currently running process. And so I want to be able to have different cores, have different translations. It's possible that if I have an eight-core system, every core has 0x10,000 translated to a different physical address, right? Because there are different processes running on all eight cores. Right? Mm-hmm. Yeah. So here's a great question. So let's say I got to here and it used it and let's say there was a contact switch. So what do I need to make sure I do every time the kernel switches between different threads and different processes? Yeah, I need to make sure that I reset the translations in the MMU. And I'm sure that you guys will make this mistake in assignment three. So in assignment three, you get to do this stuff. And one of the canonical mistakes in assignment three is forgetting to flush the MMU when you switch threads. And then it's really cool because the next thread is faster, but it's really kind of terrible because the next thread is referring to translations in the previous threads that are only valid for the previous thread. So that is not a good thing. But I guarantee a non-zero subset of people in this room will face this problem in assignment three. It's simple to solve. Yeah. Yeah, so OK, so this is a great question. So the address space has the same sort of potential size, right? But remember, going back to our ways of allocating, I'm going to regret this, ways of allocating virtual addresses, these calls have to be performed in order to create a translation for a particular virtual address. When I start running, like before I call exec, right, when there's a brand new address space, that address space has no translations that are valid inside of it. It's only through these system calls and also through the use of the stack that parts of the address space are loaded with valid translations. So in general, it's a great question. So in general, most of my address space is not mapped to anything. That's why, same thing with the bottom, right? We talked about why we don't set up valid translations at the bottom of the address space to catch no pointer exceptions, right? So that's a great question. Yeah, the address spaces are the same, but the parts of the address space that contain valid translations are going to be different in general between different processes. So great question. All right, I told you I regret this. Okay, so I did this. Okay, good. Any other questions? These are great questions, yeah. Yep? Uh-huh, mm-hmm. Okay, so the question is, how do I maintain all these mappings? Okay, welcome to the rest of the lecture, right? Like, I will, if I remember, you can ask me when we're done, but that's essentially what we're gonna talk about today. Or it's an aspect of what we're gonna talk about today. So clearly, doing this for every byte of memory, let's say I had a, you know, this is maybe the most obvious way of thinking about how to do this. I'll just have an array and the array will store for each byte in my address space the byte of physical memory that that translates to. And that's a terrible idea, because in general, I'm gonna have a lot of bytes in my address space, and so that table is gonna get really big. So one of the things that I wanna talk about today when we talk about multiple ways of translating virtual addresses to physical addresses is the space overhead of storing those translations. Because in general, we do not translate an arbitrary byte of virtual memory to an arbitrary byte of physical memory. We might want to do that, but that's not what we actually do. And one of the main reasons we don't do that is because you're right, most mappings would basically consume all of my physical memory. And this is sort of like scheduling. Any memory that I, the kernel has to allocate in order to do memory management is wasted memory. It's just overhead. So it's a great point. Let's talk about these approaches, and I think you'll see how this comes out. So in general, I'm not going to keep a huge mapping table that maps every virtual address to physical address. So let's talk about some other ways of accomplishing this. So the simplest is to keep, for each process, what's called a base and a bounds. So the base and bounds are both physical addresses. The base defines the bottom of the physical memory that I've allowed the process to use. And the bounds defines the size in physical memory that I've allocated for that process. And so for each one of these algorithms, we're going to talk about both, how do I check whether an address is valid? And second of all, how do I translate the address once I know it's valid? So here, what I do is I say, the virtual address is okay if the virtual address is smaller than the bound. Because the bound defines the amount of physical memory that I've allowed the process to use. To translate it, I take the virtual address, I add the base and I'm done. So this is very nice. Now don't worry, there's a picture. So here's how this works. So in physical memory, for this particular process, I've allocated it a chunk of physical memory. That chunk starts here and goes to here. So the chunk might start at some arbitrary physical address and it has some size, which is the bounds. You could also think of this as just top and bottom. But usually it's referred to as base and bounds. So the base is the bottom and base plus the bounds is the top of the physical memory region that I've allocated to this particular process. Remember the process doesn't see any of these addresses. What it sees is addresses like this. So when the process tries to translate this address, what happens? I just told the, I started at the beginning of time, the MMU had no information about what was going on. So it asked the kernel for help. All the kernel has to tell it for this process is two pieces of information. The first piece is the base in physical memory for this process. The second is the bounds, which defines how much memory this process has been allowed to use. After the MMU knows this, how do I translate this particular address? What does this address translate to for this process? Yeah, so base plus virtual address. Virtual address is OX 10,000, base is OX 40,600. And I've carefully chosen these examples to avoid any sort of carrying in hexadecimal, which of course everybody is terrible at. But anyway, so here you go. Pretty simple. Does this make sense? Very simple, simplest possible way of translating virtual to physical addresses. What about this address? I guess I lied, I didn't do that. This has an E in it, I'm sorry. Same thing though, right? So this one is now a little bit farther up into the address base that this one also translates. What about this address? What happens when the process tries to translate it? And so what happens? Boom, cartoon explosion, right? This is an invalid address. The operating system is gonna be notified. The MMU is gonna say there's an exception here. This process tried to translate an address that doesn't have access to and the operating system could do what it wants with that process at that point. So the nice things about base and bounds are that it's really, really simple. So on early computer systems, this was something that was extremely easy to implement. All the hardware needs to know are two numbers and it can implement this entire scheme. So the amount of information I have to store per process is pretty small and it's possible that I could even just store on the processor, as long as the processor has some idea of what the idea is, the kernel could just tell it this once and then I'd never have to tell it again. And so once it knows this one thing, it can translate all the addresses in the address space successfully. And it's also really fast to actually do the translation. So checking is just one comparison and the translation itself is just an addition. So if you think about just, you would implement this in hardware but still the complexity is roughly related to the arithmetic operations I would have to do. So the biggest problem with this is is this a good fit for our address space abstraction? Yeah, in general, so remember the address space was this idea that I had two GB or four GB or whatever of contiguous memory, pretend virtual memory and I could allocate things anywhere inside of that. And to implement this in a base and balance system, I would actually need to have two gigabytes of physical memory per process. Because the base, you know, so I'd have a base and then I have a balance two gigabytes up there and a lot of the memory in between would be wasted. So this is the other problem is that I have a lot of external fragmentation. So imagine this is my address space and imagine by some miracle I actually had four gigabytes of available RAM, contiguous available RAM on the system and I could put the base here and I could put the balance here and I might think, okay, I'm doing a great job but look at all this wasted memory. Actually I could put the base down here. I could even be a little bit smart, right? Look at and actually, you know, this diagram's not to scale. This would actually be a lot of virtual memory to have allocated. If you looked at, if you actually drew this diagram for bash to scale, you probably couldn't even see its code segment. It's so small. Just a tiny, tiny, tiny amount of data located in this huge massive address space. So in general, I can't use base and balance to implement the address space abstraction. It's just impossible. If I wanted to use base and balance or I'd have to give the process a much, much, much, much smaller address space and still suffer from all this external fragmentation. So in general, this does not work. Now if we're gonna follow, so one of our other system design principles that we talked about in this class is keep it simple, stupid or the kiss principle which is something I encourage you to apply to all aspects of your life but particularly whenever you're writing kernel code or any sort of code. So we tried to keep it simple, but that was too simple. Base and balance, sounded awesome. Only two things, really simple to think about. Not going to work with the address space abstraction. So is there an obvious way to extend this idea to support the address space abstraction? Yeah. Yeah, so what if we give each process multiple bases and boundsings, right? So instead of one base and bounds, it's expected to cover the entire address space for each segment, each area within the address space that contains sort of a coherent stuff like the code, the stack, the heap. I allocate one base and one bounds and that base and bounds is designed to span that entire segment. So this is an idea that's called segmentation and we're getting closer and closer to explaining that mysterious error that you've been getting your entire life when you write C code. So we can assign every logical region, so we don't want to do this to too many regions because if we do it to too many regions, there's a lot of chances that we're gonna have to load new bases and boundsings into the MMU at runtime. But instead what we do is we say for each segment within the address space, I allocate a separate base and bounds, every one can be a separate size and I can also do things like I can protect each one differently. So I could say, for example, that the code segment was read-only and executable, meaning that sorry you can't write yourself modifying code, which is a real thing. I'm sad to admit, but it protects that segment from accidental modifications by buggy code. So here's how this works. Every segment, so I need one more piece of information, because before with base and bounds, it was sort of implicit that the start was zero. So here what I'm gonna do, every segment now has a start virtual, a start virtual address, a base physical address, so the start virtual address maps to the base physical address, and then a bound. So in order to check whether or not a virtual address has been allocated and is okay to translate, the MMU needs to do two things. The first thing is, well, really one thing, it just needs to find does a segment exist that contains this virtual address? And by that, of course, we mean that the virtual address is between the segment start and start plus bounds or start plus the offset. And to translate it, once I've located the segment that contains this virtual address, I take the virtual address, I subtract off the start of the segment, and I add that offset to the base physical address. So don't worry, again, there's a picture. So here I've got OX 10,000, the MMU knows nothing. So the first thing the kernel has to do is the kernel has to tell it something about a segment that contains OX 10,000. So the kernel could certainly tell it about more segments, but let's just imagine it just tells it about the segment that generated the exception. So the process is trying to translate this, the MMU is now given enough information to do it. So what this says is that there exists a segment within the address space that starts at OX 10,000, that maps to OX 43,000 in physical memory and has bounds OX 1,000. So where is this address going to translate once the MMU knows this? This is an easy one. It's gonna translate to OX 43,000, right? It's the bottom of a segment. So remember, the segment start address always translates directly to the base in physical memory. If I'm translating something that's inside a segment, I have to deal with a little bit of math, but the segment start address is the easiest one in the segment to translate. What about this address? Does the MMU know where this address is? No, because it's not inside the segment that I've defined. This segment is from OX 10,000 to OX 11,000. So this is outside there. The kernel might, but the kernel might say, oh, by the way, for some reason, this is dumb because this is a pretty small address to be able to translate, but I've also allocated a segment for this process that starts at OX 100 and has bounds OX 500. And the base address for this is OX 16,000 in physical memory. Where does this address translate? So he's saying up. That's cheating, right? It's above, right? No, how about a number? Yeah. 16,300. Does everybody see how we got that? I just want to point this out right now before we go any farther, which is that I am potentially one of the nicest people on earth. When I ask you guys about things like this on exams, it's always in base 10 math. So you can just thank me right now. You don't have to add or subtract hexadecimal numbers on an exam without a calculator. I'm serious. I'm potentially the nicest person on earth. I don't know anyone else who does that. Anyway, so we're doing this in class, it's text, whatever, but we tried to keep it simple. What about this? What about this address? This is a valid address, by the way. It's a valid virtual address. It happens to spell something funny, but it is potentially a valid address. But just for fun, what do you think's gonna happen when the process tries to translate dead beef? Yeah, the cartoon explosion. But again, this could have been a valid address. It's not just the kernel wasn't offended by the semantic meaning of this address. It's just not in a cycle. And here it is. Finally, the one thing that you thought you'd get out of this class. So now you know what a segmentation fault is. That's why it's called this, because for a long time, operating systems used the idea of segmentation to translate virtual addresses. Now, we've moved on to something that's a little bit more modern, but the fact is that segmentation is still with us. And we still think in many ways about process virtual address spaces as being divided into segments. A segment for the code, a segment for the stack, a segment for the heap, a segment for the dynamically loaded libraries, even if we're gonna use a slightly better way of translating virtual to physical address. Okay, so this seems nice. This is clearly something that we can use to support virtual address spaces. We can implement address spaces using this translation approach. There's one problem with this though. There's a couple of problems actually. So let's talk about the things that are good about it. So it's still fairly simple for the hardware to perform these checks at runtime. Now, it's not quite as simple as base and bounds, because I might have multiple segments to look through, but the number of segments doesn't have to be very large, right? And the translation is very simple. Once I know the segment, I essentially am just doing an addition. Well, I guess I have to do a subtraction in addition to two operations. And it's also a nice thing that I can organize and protect the areas within the address space correctly. So I can assign, now that I've broken them up with this nice unit, I can assign different permissions or different protection to each segment. And it's also a better fit for address spaces because I don't have to allocate physical memory for those huge chunks of unallocated address space that are in between, for example, the code and the heap. I don't have to do that anymore. With base and bounds, I did. That's why it was such a terrible idea. Now, I can just cover the address space with little chunks, little segments, that are just sufficient to cover all of the addresses, the virtual addresses that that particular process is able to translate. But what's the problem with this? Yeah. What's that? What's that? No, so that's the good thing. I do not have to give two GB of physical memory to each process. How much physical memory do I have to allocate to a process using segmentation? With base and bounds, I literally had to give it enough physical address, enough physical memory, the amount of physical memory that was equal to the size of the address space, the size of the virtual address space. How much do I have to give it in segments? Yeah. Yeah, essentially an amount of physical memory that's equal to the amount of virtual memory that it is able to translate. So, as soon as that it requests permission to translate some region of virtual addresses, I have to back that region of virtual addresses with physical memory. So, it's better than giving it two GB, but what's wrong with that? Yeah. Yeah, so there's one problem, which is I have these segments now that are these different sizes. So, what happens if I've got a segment that I've allocated physical memory for, and let's say it's the heap, and let's say that the process calls sbreak and requests some more heap. What potential problem can I have? Yeah. Yeah, so it might be already penned in in physical memory by two other segments. And so now, I either have to move it, which is terrible, because I have to copy all of that stuff into some other area of physical memory, just so that I can give it a little bit more heap. Or in other cases, I might have to fail due to external fragmentation, right? I've got these little, it might be that I have these little chunks of physical memory that are located between allocated segments, and I have enough memory to satisfy its request, but it's not contiguous. Could, now here's the thing. Could I move all the segments around to create more memory? Can I do that? What's that? Yeah, so given how we've set the system up now, can the OS defrag memory? Can I move segments around in order to create more space? Of course, right? This was the beauty of introducing the level of indirection. In order to move a segment, what do I have to do? I'm gonna move a segment from one place of physical memory to another place of physical memory. What's that? So I have to change the mapping. Is that sufficient? And copy all the content. So that's the ugly part, right? Changing the mapping is easy, right? That's just a couple of bytes that I need to fiddle with. It's the copying stuff around that's terrible, right? But I can do it. If I had to, if I had to have a system that worked this way, yeah, I can move stuff. I have to make sure that the process isn't translating that virtual address. I need to copy all the content somewhere else, then I update the mappings that I'm done. Talk about this more next week when we talk about swapping. So that's nice. But the real problem here, so this is a great problem about having this potential for external fragmentation, but the real problem is I still require the entire segment be contiguous in memory. I'm covering the entire code segment with a single chunk of physical memory, or even worse, I'm covering the entire heap with a single chunk of physical memory. And so if the process is using a little bit of its heap, all of the heap is still there. And so, oops, sorry. And so the, as somebody pointed out, the fact that the segments have to be contiguous means that I can have external fragmentation. I can also have internal fragmentation because I can have large parts of the segment that are not in use, right? So imagine that the process is allocated a bunch of memory on the heap, but it's not using most of it. It's possible I'd like to get that back so I can do something else with it, but I can't because the entire segment has to be in memory. And so I'm not quite there. I think the squirrel is actually gonna be able to grab this nut, right? It's hard to tell, you know, it's like, it's hard to tell which direction its little arms are going, but anyway. So you can decide what you want about how well the squirrel is doing. But we're not quite there, right? We'd like to do a little bit better. As far as how we translate virtual audiences, yeah. What's that? Yeah, so it's a good question. So the question is, stack and heap grow in different directions. And so it's possible, you're right, it's possible that I might have to allocate when I'm expanding a segment, I might have to actually either expand it up in memory if it's the heap and it's growing upwards or down. But all that means is that I can essentially collide with either the segment that's that way or that. The translation is the same. You can convince yourself the translation works, right? But you're right, I might have to move the, for the stack, what I'm gonna do is I'm actually gonna move the start address rather than the top, right? It's a good question. Okay, so let's go back to something that we wanted, right? At the beginning of the class, we said this was infeasible, but what we'd really like is to be able to quickly map from any virtual address to any physical address. This was the goal. This was the pie in the sky idea. And we said we couldn't do this because clearly this would take too much memory. But the other, and the other problem was this was going to be potentially slow because this would, so the other, so let's go back to, well, I'll just go forward here, I'll go backwards. The other problem is if the MMU only knows how to translate one virtual byte to one physical byte, then any time I use a new virtual byte of memory that it doesn't know how to translate, it has to ask the operating system for help. So that was one of the nice things about both base and bounds and segmentation is I'm limiting the amount of information that the operating system has to provide to the MMU. So before we talk about a better way to do this, I wanna introduce you to a new piece of hardware. So this is frequently what operating systems do. The only, and as software developers, the only time you should ask hardware people for help is when you're desperate, right? If there's no other way to do something, you've tried everything, you know, because we just don't wanna give them the idea that we need their help, right? But sometimes you do, and sometimes they can actually do a lot for you. So let's talk about a little piece of hardware, a nifty piece of hardware that allows us to do a much nicer form of virtual to physical translation. And this is a common OS trick, it's an example of a common OS trick that we come to over and over again, which is when something is too slow, when a big thing is too slow, throw a small fast thing, put a small fast thing in front of it. We call those small fast things caches. And one way of thinking about your entire computer, actually, from the very, very top all the way down to the disk, is that essentially your computer is a series of caches, right? It's a Christmas tree of caches, from the top to the bottom. So here's one place where we're gonna use this trick. So we're gonna talk about something called the TLB, the TLB stands for translation look-aside buffer. No idea where look-aside came from, but it's a translation buffer. It allows the MMU to store translations and it also allows the MMU to search a number of different translations extremely quickly. And how this works is TLBs are a form of what's called content addressable memory. How many people have heard about content addressable memory? So, and you might think, what does it mean to be content addressable? So normally, when you address memory, you use a memory address. You tell memory where to look, and it returns the contents. Content addressable memory is backwards. You tell it the content that you're looking for, and it gives you the address. So it's almost like being able to search. And content addressable memory works extremely efficient. So here's an example. I've loaded these entries into my TLB, my content addressable memory. When I ask the MMU to look up this particular address, what happens is that essentially every entry gets searched at the same time. That's one way to think about it. There is a constant lookup. So rather than going one by one and saying, is this it, is this it, is this it, is this it, the TLB has hardware circuitry on it that allows it to look down every path at the same time. And that means that no matter how many entries are in the TLB, the lookup takes a constant time, which is awesome. That's pretty cool. So in this case, I'm gonna use the TLB to look up the entry that has the contents, OX800 and the TLB can tell me something about something else that's co-located with that. So in this case, it allows me to translate OX800 to OX306 or whatever. So you might be thinking, this sounds awesome. Why is it my entire computer memory, a content addressable memory? And the problem is that these content addressable memories are difficult to make really big. You can think, I mean, one problem is that essentially the hardware circuitry required to implement these scales with the square of the number of entries within the TLB. So it does not work. So it means that we have to keep the TLB or the content addressable memory small. I can't make this look up a billion entries all at once. That would be, that would cost an enormous amount. And probably you couldn't even build that. Okay, so now that we've done this new piece of hardware, let me sort of get to the end of today's lecture by sort of pointing out this tension that we've come to. So segments were too big. The problem with segments essentially was that they were too large. There's too much inside of a segment. It's really nice that segments, that there's not very many segments because there's not much I have to tell the MMU about the segment, about the entire address space really for it to be able to translate all the addresses. But the problem was that the segments were too big. Mapping individual bytes, on the other hand, bytes are too small. Okay, so here's the tension. My segments are too large. The problem is that there's unused memory inside those segments that I'd like to be able to deal with. Bites are too small. So the question I will leave you with for this weekend is, is there a middle ground? Of course there is, and we will talk about it on Monday. So enjoy your weekend, and good luck on assignment two. I'll see you on Monday.