 All right, everybody, let's get going today. It's like people are still recovering from their weekend. Some of the people in this room look like they're still recovering from their weekend. And I'm assuming that some of the people that are not in this room are maybe actively choosing to spend this morning recovering from their weekend. But it's good you guys are here because today is a really important lecture where we're going to cover a lot of really important stuff that'll end up in really important places in this class. Whether it's assignment three or the midterm or something like that. All right, so my goal today is to basically get through page translation and paging and some of the data structures that are used in the kernel to do this. So this is a little bit ambitious. I'm going to start off with a little bit less review than usual today, partly because we didn't cover as much material on Fridays we normally do, which is fine. That was good. Let me go through the announcements briefly. Assignment two is going to be coming out today, at least the text, at least the actual description of what the assignment is. So compared with assignment one, assignment two is much more, I won't put it this way. It's less well specified. So for assignment one, there were a series of functions that you were supposed to implement. And we gave you skeleton code for those functions. And we gave you examples and things like this. And basically, it was kind of filling in some blanks in terms of finding where the code needed to go. For assignment two, much more of your time is going to be spent just understanding what you need to do. The assignment specification is very clear, but how you actually implement that is going to be more up to you. You're going to have to add files to the kernel. You're going to have to change a lot of the data flow. You're going to have to design data structures and use them, rather than just simply adding things to some templates that we've given you. So assignment two is the kind of assignment where you're going to need to spend some time reading it over, talking about it with your partner, coming up with a plan of action before you sit down and actually start writing code. Because if you sit down and start writing code, you're really not going to know what to do. So how many people in here have not chosen a partner? Do not have a partner yet? So we're going to work on helping you guys find partners. We'll also put the form online. But maybe some of the people that don't have partners might be a good idea to kind of hang around after class today, meet some other people that don't have partners, and maybe form the beginning of a beautiful friendship. So maybe if you don't have a partner, that'd be a good thing to do. Because the faster you have a partner, the faster you can get on this assignment, which is great. So we're behind on grading. We're still working on assignment zero, hopefully next couple days. I'm prioritizing getting this assignment out. So you guys can start it. But I do apologize. We are. I know we're behind. So this is a priority for us. Finally, how many people would like to see some kind of bulletin board on the course website, where you could post questions and sort of discuss things with each other and stuff like that? OK, this is what I thought. I think this sounds like a good idea. And we'll work on putting this up. Particularly for assignment two, I think it would be helpful. So this way, rather than emailing the core staff and just getting back and answering individually, you guys can post this online. Maybe you can help each other answer questions. And when the core staff does answer questions, the those answers will be visible to the entire group. So this probably would have been a good idea to do a while ago, but again, we've been busy with other things. All right, so really all we got through last time that we're not going to cover again was base and bounds address translation. So are there any questions on base and bounds? Base and bounds is pretty basic, pretty simple. But anybody have any questions on this before we continue? So today I'm going to review segmentation, again, just sort of from the beginning, because we didn't quite get through it. And then we'll go on to paging. Any questions on base and bounds? Yeah? So I'm going to get to segmentation. So this I'm talking about single, base, and bounds. Single, base, and bounds. That was our simplest straw man, not a good idea. We talked about why. But any questions about how this works and how we translated addresses using this approach? All right, so let's do this. This is easy. This is simple. What do I assign to each process? Base and bounds. The name is a clue. The name is a big clue. I assign to each process what? Back here. What's that? You have no idea. It's called base and bound. What do I assign to each process? Two things over there. And then base and bound addressing. I assign a base and a bound to each process. Again, the name is a fairly large clue. So a base physical address. Does anyone know what K-I-S-S means? Keep it simple. Stupid. This is a classic systems design approach. And actually, it's a good thing to keep in mind when you do assignment two and assignment three. Don't outsmart yourselves. The way that we build complex systems is we build something easy first. And then if we understand that it doesn't perform well, we understand why it doesn't perform well, then we go back and we make it work a little bit better. This is particularly relevant today because we're going to see some more complicated approaches to storing page state that people have sometimes decided to use for assignment three when it is completely unnecessary. So again, first pass, something simple. This is the simplest thing. So I assign each process a what physical address? Again, base and bound. Back here, Jason. Base physical address. And what else? A bound. Base and bound. OK, this is like, I know it's Monday morning. So we're kind of everybody sort of booting up their machines. So let's start warming up. And then to check an address. It's very simple to check a virtual address. I just make sure that it's within the range that I've assigned to this process. Essentially, it's lower than the bound. How do I translate a virtual address in this scheme? Anybody? Well, the MMU does it, but how? I just take the virtual address, and I add it to the base. The implicit assumption here is that my virtual address base has started zero. So what were some pros of the base and bounds approach? Were there any pros? Name me one pro. It's simple and fast. There's very little state that the hardware has to keep around. Just has to know the base and bounds per process. Two things. And then it's very fast. Check is one comparison. And translation is just one addition. So this is very, very simple. It's nice. Now remember, is this a good fit for our address space abstraction? One base and bounds for the entire process. Why not? There's a huge amount of internal fragmentation. I have this virtual address base abstraction where I'm telling processes, I'm going to give you the illusion that you have two or four gigabytes of memory. Most of that memory is unused, but allows processes to spread out. Base and bounds doesn't do that. Base and bounds, in order to perform well, the base and bounds have to be small. And that forces processes back into this little box that I was trying to let them out. And yes, as Michael pointed out, we will lose memory to internal fragmentation. I'm going to give out a base and bounds, and a lot of that memory isn't going to be used. What about, what's another problem here? Remember, I have to make these large, contiguous allocations. What else might what happen here? What other kind of fragmentation could I be in danger of? External, there's only two kinds. External fragmentation and internal fragmentation. Both are a problem. So any other questions on base and bounds? Again, this was a short review, but I'm going to start over on segmentation. So you'll see some more of this right now. Any other questions on base and bounds? Going once, going twice? Let's talk about segmentation. So one base and bounds wasn't a good idea. It didn't work well. It's not a good fit for the address space abstraction. But who remembers how we extend this idea using segmentation? What does segmentation essentially do? Rather than one base and bounds, we have multiple bases and bounds per process, each one of which we call a segment. And the nice thing, there's a lot of nice things about segments. We can essentially assign each part of our address space. Remember the address space picture we had? Unfortunately, it's not on this slide. But I had a little sliver of the address space that was used for the code, little sliver that was used for the stack, little sliver that was used for the heap. Most of it's unallocated. Now I can essentially assign each one of those regions to its own segment. And I can assign a lot of protections and other features to those segments that are appropriate for their purpose. So the heap is going to be protected differently than the code. The code might be read only, really to be useful. The heap has to be rewrite. So this is one of the nice things about segments. And also, each segment can, of course, be a separate size. So a process that has a lot of code, but not much heap. We'll have a large code segment, a small heap segment, or a vice versa. This is nice. And again, separate permissions, depending on what's loaded into those segments. Are we raising hands here, or are we like? I feel like doing that sometimes. My hands are full. So how do we do segmentation? I assign processes a bunch of segments. Now remember, with base and bounds, the implicit assumption was that the base and bound, the start virtual address of every process was 0. But with segments, I'm breaking that assumption. So now there's three pieces of data I need for each segment. I need the start virtual address. I need the base physical address. Where does that beginning virtual address map to in physical memory? And then I need a bound. How large is the second? To check whether or not the virtual address is OK, I try to figure out, is there a segment allocated for this process that this virtual address maps into? If there is, then to translate it, I take the offset from the beginning of the segment, and I add that to the physical address where that segment maps to in memory. So let's walk through our example. I have my kernel, my very faded MMU. Now, process tries to use address 0x10,000. What happens? What happens next? Right up here. The MMU asks the kernel. The MMU doesn't know anything. This is the beginning of time. So showing what happens next. So if this is a valid address, the kernel needs to tell the MMU something about the segment that this address maps into. And in this case, it tells it the start address, which is 0x10,000. That's a virtual address. The base address, which is 0x43,000, that's a physical address. And then a bound. The bound is just a size. So what happens next, Michael? So let me point out, before we get to this, the base and bounds here, the base physical address and the bounds essentially defines an area of physical memory that's associated with this segment. So every segment that a process is using is going to map into some chunk of physical memory and with different sizes depending on the bound of the segment. And exactly as Michael said, to translate this address, what address does this actually translate to? 43,000. How did I compute that? I took the start virtual address. I subtracted this from the start. I got 0. And then I added the base physical address. So this translates to physical address 43,000. Does anyone not understand this example? All right, let's do another one. 0x400, what is the next thing that happens right here? Well, I'm asking the MMU to translate this address. But what is the MMU going to do? Well, it's a good question. Is this virtual address inside the segment that the MMU knows about? Is it between 0x10,000 and 0x11,000? That's the bounds of this segment in the processes virtual address space. Is 400 between 0x10,000 and 0x11,000? It is. Do you want to disagree? No, it's not. So the MMU doesn't know anything about this. And so what does it have to do? It has to ask the kernel. Is the process allowed to use this virtual address? And let's say it is. So going down the row, what happens next? I've asked the kernel, hey, the process must use this address 0x400. What's up? And the kernel knows that the process is allowed to use address 400. And so what does it need to tell the MMU? Right, something about this segment. Something about the segment that this falls into. In this case, the segment starts at 0x100. It maps to 0x16,000 in physical memory, and it has a bound or a size of 0x500. So back in up a row, what do I do next? And then what does this map do? 16,300, exactly. I took 400. I subtracted 100. I got 300. I'm doing easy hex for you guys. There's no carrying that would force us into the letter part of hex. So I take 400, I subtract 100, I get 300. I add it to 16,000. I get 16,300. This is how this address maps. Now what else can happen here? So let's say I have this address. Now what's going to happen next? So it's going to ask the kernel, is this address valid? Let's say the address is not valid. What might happen? Boom. The process might be killed. If the process is trying to access some memory that the kernel hasn't given permission to access, then the next thing that might happen is the kernel decides that the process is misbehaving in some way and has to be killed. There are other things that can happen too, but this is just this example I came up with. And I like the kaboom thing. Any questions on this example? Any questions on segmentation? So let's talk about segmentation. So as far as pros, as an address translation mechanism, we're not quite as simple as base and bounds, but we've traded off a little extra complexity for some really nice things. So the first thing to notice is that this isn't that complicated. A process is going to have some number of segments. And now rather than just doing one comparison, I have to do n. So a process that I have n segments, I need to look, and I need to see to figure it out. For the MMU to figure out whether or not it knows about this address, it has to compare the address against the base and bounds of potentially up to n segments. Once I find the segment that I want, translation is very simple. I just subtract off the start and add it to the base of the portion of physical memory where the segment maps. So these are good things. So this happens. So this is partially determined by the ELF file. A certain process might have multiple different code segments that it wants to protect differently. But the processes that we've talked about and the processes that you're going to run for this class potentially have maybe three segments. They have a code segment. They have a stack. And then maybe they have a data segment which contains global variables that the process uses. My question is, in some way or another, I need to guess from any of these segments. Absolutely. So that's a great point. We're going to come back to this in a minute. So clearly, the MMU is a piece of hardware. And hardware has limitations. And one of the limitations that a segment-based MMU would definitely have is that it would only be able to know about some number of segments at a time. So I might be able to tell the MMU something about, I don't know, 128 different segments. Once I got over that number, what would happen? What's that? It would have to start replacing. It would only be able to hold 128 translations. And so what would happen is that when the kernel loaded a new translation, an old translation would fall off. And the next time the process tried to use an address in that part of its memory, it would have to ask the kernel. So exactly, essentially what's happening here is the MMU is caching a small number of translations so that it can do those translations quickly. We'll come back to this thought when we talk a little bit later about translation look-aside buffers, TLB, because it's the same thing. We're using a piece of hardware to cache and rapidly perform a small number of translations. It turns out that a small number of translations turns out to work quite well. And I'll come back to why that is later. But you're exactly right. There are limitations to the number of hardware. There are limitations at heart that would restrict us to only be able to translate some number of segments rapidly without involving the kernel. Good question. Any other questions about segmentation? So another pro of segmentation is that, again, we can organize and protect regions of memory appropriately. That's nice. So in addition to telling the hardware, the base and bounds of a segment, I would probably also tell it something about what the process is allowed to do. So the process is not allowed to do a store into the segment, only loads. So that would be a read-only segment. This is a much, much better fit for our address space subtraction. This is really the biggest win here. Because all that unused part of the address space that I had before that was wasted when I did a single base and bounds, I don't have to cover that with a segment. So you could think about it, whatever parts of the virtual address space the process is using potentially, I have to create a segment to cover all of those. But anything that's not used, and that's going to be 99.9% of the virtual address space, I don't have to cover with the segment. And so that's a really, really nice thing. So one of the problems with segmentation, so now let's get to the cons. We still require the entire segment to be contiguous in memory. So as segments get large, I need to find larger and larger pieces of contiguous physical memory for that segment. And that could potentially lead to what kind of fragmentation? External fragmentation. So I might have little pieces of memory all over the place, but the process now has a 10 megabyte heap, and I need a 10 megabyte contiguous set of physical addresses to give it. And I may not be able to find. Or I may have to start shuffling things around, and I make it really gross. So exactly, potential for internal fragmentation. So let's step back a second and think. We've talked about a couple of practical ways to do this, but what would we really like in an ideal world? What would we like the system to be able to do? Yeah, continuous segment for the square into two segments. So that's a good point. I can't start to fracture segments. And essentially, but the idea of a segment is that there's something nice about being able to store everything in that one segment. But again, you're getting a little bit ahead of me. So come back to this idea, because essentially, what we're going to do is kind of exactly that. But what would we really like? Does anyone want to fantasize about exactly what they would like their MMU to do? And how would we do that? I mean, ah, don't get ahead of me. Ideally, what we want is we want a completely fast and flexible mapping that allows me to map any physical byte, any virtual byte, to any physical byte. What we've come up with are ways of doing this in the aggregate. Segments map a whole range of virtual addresses to a whole range of physical address. The base and bounds map a huge chunk of virtual addresses down to physical addresses. So we've come up with a bunch of ways of doing this in the aggregate. Well, we haven't found a way of doing this at a byte granularity, which would be awesome. And we've also talked about the fact that the operating system doesn't want to get involved too often, because why? Why can't the operating system get involved in translating every address? It's way too slow. If I had to trap in the operating system every time I translate an address, I would never get anything done. Now, we haven't talked explicitly. We've talked a little bit about what the hardware would be required to do with segmentation. And with base and bounds. But if I wanted to do something like this, where I essentially wanted to map any virtual byte to any physical byte, does anybody know about hardware that might exist that would help me do this? Anybody seen some of these pieces of hardware in other classes? So the first thing we're going to do is, and we've been doing this all along, when the operating system is too slow, this is a common systems trick I just want to point out. Whenever the operating system is too slow, a common approach. When any part of your system is too slow, a common systems approach is put a cache in front of it. So is anyone familiar with L1 cache on your processor, L2 cache? These are all caches for main memory, which we think is fast. But a lot of the way systems work is that you put a small fast thing in front of a big slow thing. And you try to make the whole thing look as fast as the fast thing, but as big as the slow thing. And if you're good, you can do it. We'll talk actually Wednesday about how we do this with the disk for memory. So what we're going to use here is a specialized piece of hardware that's called a translation look-aside buffer. It's kind of a strange name. But what you can think about it is what's called content-addressable memory. So content-addressable memory, normal memory is addressed not by its contents, but by location. You tell the system, give me the contents of this byte. But the address you give it is, I'm trying to think of a good analogy, it's like a street address. It just maps down to some point in the memory space. Content-addressable memory is very different. What it does is it asks the system, give me something associated with memory that contains this content. So it's more like being able to query and find out, map me to Jeff's house. Rather than giving it my address, you ask, tell me the place where he lives. So content-addressable memory is going to help us out here. And maybe this will become more clear. Well, content-addressable allows me to do. So I'm going to use something on the TLB. The TLB is going to cache address translations between virtual addresses and physical addresses. For now, let's not think about pages. Let's just think about, in general, any virtual address to any physical address. So here's my TLB. And here's some virtual to physical address translations that the operating system has set up for a particular process. What happens is, when the process tries to use an address, what the TLB does is it searches all of the virtual addresses it knows about in parallel. So you can think about this search is happening in constant time. It's not quite accurate, and TLBs differ in how they work. But just think about this as a specialized piece of hardware that allows me to search, in parallel, a bunch of virtual addresses. And what will happen is, if it finds one that matches, so this is the content-addressable part. If it finds one that matches, it'll return something about it. In this case, what I'm loading into the CAM or the TLB is the physical address that this address maps to. So it allows me to very, very quickly figure out, for OX800, what physical address does it map to? Does everybody see why this is content-addressable? I always find that it's something I have to think about a little bit. Instead of at, so imagine this was an array. This would be slot 0, slot 1, slot 2, slot 3. And what I would say is I would ask the TLB for what's in slot 2. But I haven't done that. What I've done is I've asked the TLB, I've addressed it by its content. I said, give me OX800. And it searches everything, finds this quickly, and returns the physical address that's associated with this. Now, unfortunately for us and for your computer and every computer on earth, this magic trick only works so well. Content-addressable memory is limited in size. And this is just a feature of hardware. And it has to do with the fact that these things are really, really good at doing these parallel searches. And to some degree, the complexity of these things tends to scale as the square of the number of elements that you can search in parallel. So typically, cams are both expensive, and they also scale very badly. So I can't do this for every byte in my address space. If I had a cam that allowed me to translate individually every byte in a 2 or 4 gigabyte address space, that'd be great. I'd be done. There'd be no problem. I can map byte to byte. It would be glorious. That's not what I have here. What I have here is a limited, limited device. So normally, no, no, no. So normally, a cam, depending on how it works, they don't like duplicates. Some TLBs will basically just kind of like punt if you give them duplicate entries. So one of the things the operandism is responsible for doing is ensuring that there aren't duplicate entries because some cams will not work. There are probably some that will work OK. I don't know what they would return. Maybe both entries, maybe the first one that they find. But normally, duplicate entries are not a good thing. Carl, no, well, that's a great question. Do we think that the application, so if the process had access to the TLB, what could it do? What could it do? Remember, we're trying to, first of all, we don't want processes to ever see physical addresses. But we also, the kernel needs to control and protect processes from each other. So if I could change the contents of the TLB, what would that allow me to do? Well, not between threads, between processes. Any process could essentially, so let's say I'm a process, and I know that the kernel has given me permission to access 0x800. If I can change the TLB, I can essentially change this address to point anywhere. And then if I want to let any bite on the physical memory on the system that I want to address, whether it's mine or somebody else's, all I have to do is change the mapping from 800 and I can access the entire machine. Right. But the kernel has to tell the TLB what to do. This is very similar to what we did before. Once translations are in the TLB, they proceed without kernel assistance. But the kernel is in charge of loading and unloading these entries. Yeah. I'm going to say, as if I'm fetching, you would take me. So it's a good question. Something we'll come back to. For today's lecture, I want you to think about the kernel as directly managing the TLB. The kernel does manage the TLB directly. But it does so in one of two ways. On some systems, when the TLB doesn't contain an entry for a virtual address, it will ask the kernel directly. There are other systems that the TLB and the MMU try to be more smart. And what they actually try to do is they try to read kernel data structures that the kernel has set up and find out what should be loaded. If they don't find an entry, they'll still ask the kernel. And the kernel is still in control because the kernel sets up those data structures the way it wants. But for now, I want you guys to think about what's called a software-managed TLB, which is that the TLB and the MMU don't care or know anything about how the operating system organizes information about these translations. And if the process tries to access a virtual address that's not in the TLB, the system will basically just say, kernel, I need help. Trapping to the kernel and ask the kernel for what to do. But it's a good question. Any other questions about this before we go? All right. So now, I want you guys to see kind of the trade-off that we're making here, because I think it's kind of clever. So our problem with segments is they're too big on some level. They can get too large. I could split them, but that starts to get messy. And at some level, making segments big leads to all this internal fragmentation, because there's pieces of the segment that aren't in use. And I can't really do much about it. On the other hand, mapping individual bytes is not feasible, because what would happen is that if I mapped individual bytes, the TLB would only be able to cache maybe 128 entries. So what that would mean is there was only 128 different bytes of memory that the process could address without requiring the kernel to help it, or whatever the size of the TLB was. So that's bad. That's too small. So can we find a middle ground here? Is there some sort of design point that allows me to map enough memory but avoid some of the problems with segmentation? And there is, as it turns out, not a rhetorical question. And the solution is what's called pages. So what pages are, you can essentially think of pages as fixed size segments. And that size is chosen to balance two things. So we want the size to be small enough to limit internal fragmentation. If the pages were too big, in order for a process to use two bytes of memory, it would have to allocate this huge two megabyte page. Most of it would be unused. But we want the pages to be big enough so that the TLB can cache enough virtual address translations to cover a large enough piece of memory to allow the process to make forward progress without involving the kernel. You guys understand this trade-off. If pages are too small, then I do great on internal fragmentation, but the TLB has to, I constantly am missing in the TLB. The kernel has to constantly be telling the TLB about new entries. If pages are too big, the TLB works great, but there's a huge amount of memory that's lost inside those huge allocations. The other thing that's nice about, the other thing what we want to do with pages that forces us to find a middle ground is that there's a lot of kernel data structures that essentially grow with the number of virtual pages allocated by all processes on the system. And if I make pages too big, this works out great. The kernel data structures get small, but then I had these other problems. But if I make the pages too small, then these kernel data structures get really big. And if the kernel data structures involved with memory management get so big that they start to compete for memory with processes themselves, then this is terrible. The kernel is trying to let processes do their work. It doesn't want to, if the kernel is consuming 80% of the memory under a system, it's not a good design. The kernel is supposed to stay out of the way as much as possible. So I want to come back to a comment that somebody made before about the size or the number of entries that I can cache in even a small TLB. It turns out that processes that are executing show a great deal of what's called locality in their memory access. Typically, not always, but typically. What this means is that processes memory access are very, very clustered to a couple of regions of their address space. What would one of those regions be? What would be one area where a process would be doing, for example, a lot of loads? Snare stack, not so much. Loads, just loads. Anybody want to make a guess? Code, right? Exactly. The code area, I'm basically load, load, load, execute, execute, execute, execute. That's what I'm doing. And there are breaks in control flow. There are times in which I'm going to branch to some other function. But a lot of code execution is completely sequential. I load an instruction. I execute it. I load the next instruction from the next memory address. And I just keep doing it. So that's one area where there would be a lot of locality. What about an area locality where you would see a lot of loads and stores? Somebody mentioned one over here. The heap. If I have a heap data structure that's small that a piece of code is using, most of the writes are going to be within that one page in the heap. What about the stack? The stack is very similar. The local variables that are being used by a function are all allocated pretty close to each other on the stack because they're created when the function starts executing. And this is not always true. Again, I can create massive data structures and then maybe jumping all over them at random. But that's not that likely. And normally, there's a lot of locality. What this means is that normally, at least over a very short time window, the pages or the areas of memory that a process is using are pretty well confined. And what that means is that even a small cache of address translations works really, really well. It's actually surprising how well it works. You take this thing, you put a teeny, really little cache in front of it, maybe 128. I think on your system, it's like 64 entries in the TOP. It's not that many. But that works really, really well because of locality. It means that once I load a translation, I get a lot of mileage out of that one translation before I have to reload a new translation. All right, so let's talk about page size. This is one of those things that I don't really know if there's a rigorous reason why this is the case. But on a lot of systems, 4k is the page size. Yeah, you mean the page size? Yes. No, no. So page sizes definitely are usually a power of two. But I don't know why they're not 8k or 16k or 3k. And actually, 4k pages have been around for a long time. That's the other weird thing. Systems have gotten much, much, much bigger. Page size, now there's some systems that have crept up to 8k. It's not, sometimes when you see things like this in systems, what really is the case is it just doesn't matter that much. As long as there's a long window in which you can have a page size and it works fine. But 4 8k pages are typically used. And if you think about it, so 4k page with an 128 entry TLB, that allows me to cast translation spanning one megabyte of the processes virtual address space. It's just 4k times 128. And again, surprisingly, that can be enough. And that can actually do really well. And again, one way of thinking about pages is just fixed size segments. So when I went from base and bounce to segments, I added a start virtual address. When I go from segments to pages, I take out the bounds. The bounds is implicit. The bound of every page is 4k. It's always 4k. That's the page size. So on some level, you could think about these kind of like segments. Dachi, yeah. OK, but we don't. So we don't, probably. So there are, with 4k pages, there are these things that just kind of work out well. Like for example, we're going to maybe get to this today, maybe next time. Some of the kernel data structures associated with paging, if you use a 4k page, turn out themselves to be 4k. And that's kind of nice. Because essentially, it means for multi-level page tables, each level in the page table turns out to be a 4k array. And that's kind of clever. But in reality, I don't know. Any other, good question. Any other questions about page size? Yeah. Well, it goes back to what we talked about before. The larger pages are, the more potential it is for internal fragmentation. So you need a 32-byte structure. And so you call malloc. Malloc has no heap. Malloc calls the kernel. Malloc gets 8k, whether it needs it or not. And so you're only using this 32-byte data structure that you call malloc for, but you have an 8k page. So that's the problem with larger pages. With smaller pages, the problem is twofold. A, the amount of memory I can cache translations when the TLB goes down for a fixed-size TLB. And the kernel data structures associated with paging get really big. Because the number of pages goes down, the number of pages in use by a process goes up. If I have the same process and I take it from 4k pages to 2k pages, it's going to be using roughly twice as many virtual pages. And so there's twice as many virtual pages that the kernel has to keep information about. And we'll talk in the rest of the lecture about some of the information I need to keep about pages in order to do translation. That's a good question. All right. So let's talk about how we do page translation. So now, again, I have this fixed-size for pages, so that's nice. And what that allows me to do is essentially any address that I'm translating, any virtual address, I can split it into two parts. The first part is what's called the virtual page number of VPN. That identifies a page, a virtual page, that the process is using. That might be 4k, it might be 8k, whatever. The rest of the address is the offset. And what this implies, as you should hopefully be able to see, is that all addresses with the same virtual page number map to the same physical page, potentially. The offset is the only thing that changes. But when the offset changes within the same virtual page, it still maps to the same physical page. So all addresses inside a single virtual page map to the same physical page. So let's see how we do this. So let's say I have 4k pages. Again, very, very common page size, and the one that you guys will use for your assignment 3. So I split a 32-bit address into two parts. So the bottom is 12 bits. Why is the bottom 12 bits? Because it's 4k, right? 2 to the second is 4, and then 10 bits is 1k. That's the easiest way to remember. Everything else, the top 20 bits, is the virtual page number. So in order to translate it, I split it into the virtual page number and the offset. And then to find the physical page, all I care about is the virtual page number. Essentially, I went from translating virtual addresses to translating virtual page numbers, because the offset doesn't matter at this point. The offset will only get included later when I finish the translation. So now what I do is I look and I see, is there a virtual page to physical page translation defined for this virtual page number for this process? If there is, I fetch that physical page, and I just add the offset to it. So this is pretty straightforward. It's a lot like segmentation with fixed size. And fixed size is what allows me to do this partitioning right here. With the segment, I couldn't do this because segments are different sizes. So there's no way to say a priori with a specific page size that the bottom is always the offset. It would have the offset varies based on the segment. All right, so let's do an example of how to translate addresses using 4K pages. So here's my TLB, very similar to the last TLB. And it's caching virtual to physical translations. Now what is the TLB caching? Is it caching virtual addresses? Virtual page numbers. So at this point, the TLB is not caching a 32-bit wide address. It's caching however large it is. And because it's 4K here, we're going to say these are the top 20 bits of the virtual pages. And this is the top 20 bits of the physical pages that that virtual page maps to. So let's go through one example, and then I'll have you guys walk through the second one. So I've got 0x, I don't know how to, 800,346. This is a virtual address. The first thing I'm going to do is split it into its two parts. So this is 12 bits. Remember the hex, each character is 4 bits. So this is the top 20 bits. You don't see all 20, because I just cut them off, but whatever. And now I'm going to translate the top 20 bits. So I go to the TLB, I find the translation. It turns out that the kernel has loaded the translation for this process, mapping 0x, 800, 0x, 306. I replace 0x, 800 with 0x, 306. That's the physical number. And I stitch the address back together. I slam it back together. This is equivalent here, because I'm on a bit offset to adding and subtracting the offset. But you can think about it as pulling the address apart, translating the top 20 bits, and then slapping it back together. Questions about this? Yeah. So what if there is another process including the same virtual address here? So OK, this is a great question. Again, remember, TLB is particularly the one you're going to use, don't like duplicates. But in general, there are a lot of duplicates between processes when we're talking about virtual addresses, because every process sees their virtual address space the same way. So what happens is that the entries in the TLB are only valid. There's one of two cases. One case is that the operating system always keeps entries in the TLB only for that process. So when I do a context switch, what do I have to do to the TLB? I have to flush it and potentially load it with some entries for the other process. Sometimes I don't even bother to do that. Sometimes I just flush it. And then when the process starts running, I start loading the addresses that it needs. The other approach here is that there are TLBs that allow you to load another process identifier. So what would happen is that I would have OX800, and I might have that twice, but I would have different process IDs. And the process ID is what allows the TLB to distinguish between those two addresses. So for assignment three, that's not something we ask you to do, but some modern systems find that to be helpful. Because what it means is that if I'm context switching between processes, I can leave the old entries in the TLB. I don't have to clear them. The new process could start running. I might kick them out eventually, but I don't have to clear them right. That's exactly right. Remember, virtual address translations are only valid for a particular process. And I should have fixed this. But there's really no such thing as translating a virtual address. It's always done within the context of a particular process. OK, good question. Any other questions about this? So let's do another example. OK, OX10336, let's start right here. What's the first thing I do? Split it in half. I take the top 20 bits, which is OX10, and I separate those from the bottom 12 bits, which is 336. What's the next thing I do right here? So I'm going to look it up in the TLB, right? And where does it match? It's OX10. And what's the physical page number for this one? OX50, I find in the TLB, I replace it, and I stitch the address back to go. Any other questions about this? I thought about doing the third example. Maybe I should have, but I only have two. So questions before we move on. 336 is the page offset. It's not considered when doing the translation, but clearly it's part of the address. So we talked a little bit about this, so I'll go through this fairly quickly. The TLB is that the operating system is responsible for loading the TLB. That's one of the main things the operating system does in this model and on your system to allow virtual addresses to be translated, right? When the TLB doesn't have an entry, it asks the operating system for help, the operating system looks up the entry and loads it into the TLB, okay? And we've talked about this. This is Kaboom. Sometimes it's Kaboom, sometimes that address, that the operating system may adjust the processes address space to make this happen, right? Oh, sorry, okay. If it tries to access an address that's on the TLB, it will ask the operating system at which point. The operating system has to either load a valid translation for that process or figure out what to do, right? Okay. So let's talk about paging pros and cons, right? So again, one of the nice things about paging, you see the progression that we've been on here, right? We've been, what we've been doing is we've been essentially making the hardware a little bit more complicated in every step, right? But what that's allowed us to do is gain some benefits, right? Some significant benefits, right? So paging maintains a lot of the pros of segmentation and many systems actually layer some of the ideas from segmentation on top of paging, right? So what they'll do is they'll have pages that they consider to be inside of a segment, right? The protections are the same for the entire segment, but the pages inside of it may be moved in and out of memory or they may be moved around as the operating system sees fit, right? So you can think of segments. Segments are a nice way of organizing information about the address space, right? And protecting each part of the address space appropriately. Pages have advantages when it comes to actually being able to reduce the internal fragmentation issue with segments, okay? Again, this is the same thing for segments, same thing for segments. Now remember, the nice thing about this is there's even less internal fragmentation here, right? Because in general, 4K is probably likely to be smaller than most segments, right? A lot of systems, when we looked at the output of PMAP before, most of the time, even for something as simple as Bash, right? It only, it has several code pages, right? So all the code doesn't fit into one page, right? The segment that that code would have been loaded into would have been larger than 4K, right? Now here's another brilliant, brilliant thing that we get out of this, right? No external fragmentation, right? Fixed allocation size, right? You know, every allocation that a process makes for memory to the system is always 4K. 4K, 4K, 4K, 4K, 4K, right? And that's glorious because it means that I'm gonna have a 4K piece, right? Unless I do something really dumb, you can essentially think of it, take the amount of memory that you start with, break it into 4K chunks and hand out and revoke those 4K chunks, right? So the external fragmentation just completely goes away, okay? What about cons of paging? What have we been forced to do here as we've gone along? Anybody have a guess? Yeah? Right, so yes, that's a great point. There is actually a lot of extra management, right? It also requires per page hardware translations, right? So we've actually talked already about how we get this to work, right? So one con with compare with segmentation is I have to do more translations, right? My solution to that has been to find hardware to help me, right? What we'll talk about on Wednesday with the amount of time today is how we keep all the state around, right? So that's a great question. So now what I've done is with my old system, with the process that had code, stack, and a little bit of heap, I might have had three segments, right? Now I might have 30 or 50 pages, right? So the second con of paging requires is that it requires more state at the operating system level, right? What we're gonna do on Wednesday is talk about how that state is managed, right? Let me just give you a preview here of what we're gonna do, right? There are two things that we have to be able to do to get this scheme to work, right? So essentially, again, what we did here is that for this we got hardware to help us and now we need to do some engineering at the operating system level, right? And again, there's two things I need to do. The first thing I need to do is every virtual page on the system has some state associated with it, right? The most important part of that state is what? What's that? No, no, no, even more important than that. Code, no, no, no. Virtual page. What am I gonna do with it? I'm gonna translate it, right? So for every virtual page, there's a separate translation potential, right? So that's the most important thing I have to keep around, right? There are some other things and you're onto what some of those other things are. So the first challenge is storing all that information efficiently. The second challenge is locating it when I need it, right? So the TLB says, I've got this virtual address this process tried to use, what do I do? And the operating system, the faster you can respond, right? As soon as the operating system is involved, I've already lost some time, right? Because if the TLB had known about that translation, it would have happened like that, right? But if I use clever data structures in the operating system, I may be able to avoid using, losing even more time, right? And essentially again, what I wanna do is very quickly retrieve the translation for that page, okay? So we will spend Wednesday talking about this stuff and I will see you then.