 Hopefully, you're all done with 3.1. Today, we're going to start with 3.2. And just to have a quick review on what we've already done. So we've covered in a 3.1 the physical memory management. We said that you should implement core map. And we said that the core map is data structure that keeps track of the physical pages. And that you should initialize your core map before you start calling the K-Malloc. So you can keep track of all the physical pages and the physical memory. And we've said that the first K-Malloc call comes into the PROC bootstrap. And this is where you should, before that, you should initialize your core map and have it ready. And this is how your memory should look like. So there should not be any kind of memory leak or stolen memory. And we've presented you with the core map structure. So the core map structure basically part of your physical memory. But it maps the whole physical memory in it. And the information that you need in your core map, you need the page state. You also need, whether it is fixed or free, you need the chunk size. And for later on, you're going to need the owner information. We've also talked about the maps in MU. That's not required, but it's a good idea to have a general view of how the virtual memory maps into the physical memory. So we have four segments. We have user segment and three kernel segments. We said that the user segment is a TLB map cached. And for the SIG-0 and SIG-1, and SIG-2, which are kernel segments, SIG-0 and SIG-1, these are directly mapped memories. And for SIG-2 is, again, TLB map. But that's not using your OS161. What you really should care about is the KSIG-0. And as you can see for the kernel segments, these are basically half a gig. The size of these segments are half a gig. And that's why, since it's a direct map, that means you need a half gig into your physical memory. So if your physical memory is not a half gig, it's less than that. If we map a virtual address that is more than the, let's say you have a 4MB memory, then you're going to get an undefined error for that. So today, we're going to overview, again, the assignment three. We're going to cover the page table, user address space, and TLB. So it's going to be either space management or TLB management. So we should all be done with the first checkpoint, which is the core map. And your next checkpoint going to be, not this Friday, the one next. And so to start with the page table to give you a general idea, some general information you need before you start really getting into the page table. We know that the page size is 4K and that the virtual address size is 32 bit. We refer to the portion that virtual address identifies the page as the virtual page number, the VPN, and the remainder of the virtual address going to be the offset. And typically, every virtual page should map to have a physical page. And also, all addresses within a single virtual page should map to the same physical page. So you're going to have a starting address, whatever between within the page and a single virtual page goes to the same physical page in your memory. And for 4K pages, this is how we're going to divide the virtual address. We have a 32 bit virtual address. We're going to take the first 20 bit, the top 20 bit, as the VPN. And we're going to use the bottom 12 bit as the offset. And things going to, so this is going to be fixed. But for the part where you identify your VPN, things are going to change based on your implementation of the page table. So for the physical address, how should we get a physical address after we get the virtual page number? And then we translate it through the page table. We're going to get the physical page number. And then we're going to add the 12 bits, which is the offset to that. This will give us, identify us with the, or return to us the physical address, the 32-bit physical address. So what is a page table? A page table is a data structure that maps the VPN to the PTE, which is a page table entry. The VPN is the virtual page number, as we said. And each process should have a separate page table. Why do we need that? Because for every process, we know that different parts of the memory maps to different parts on the physical memory. So the page table is going to be private to every process. And we've used similar data structures before. So we've used the file table. We've used a process table. For file table, we've said that it is a data structure that maps file descriptors to file handles. We said for the process table, maps PID to process structure. The same goes here, but you're going to map VPN to PTEs, which is virtual page number to page table entry. What are the requirements for implementing a page table? So for page table, it will get a virtual address and an operation. The page table should be able to check if that virtual address is valid, just like it falls within a defined segment on the address space. And then it should check if the operation on that segment is valid. So you need to check for every region, you are going to have some permission set for that region. So you need to check if the permission that you received matches the permission for that region that is defined on the address space. If these two are fine, then you're going to get to retrieve the physical. The page table should be able to return to you the physical address or the, let's say, physical page number. And then you're going to add the offset. You're going to get the physical address. So every entry in a page table refers to a virtual page used by a single process. And comparing page table to core map, so we said that the core map is a data structure that manages the physical pages, while the page table is a data structure that manages the virtual pages. So every entry in the page table handles a virtual page, while every entry in the core map handles a physical page. So pretty much similar data structures, but each one is handling the virtual pages, and one is handling the physical pages. However, that doesn't mean the structure, just like whatever you have in the core map will be the same in the page table. No, the structures will be different. So what are some of the information that you need to maintain in a PTE structure? You need the virtual page number, and you need the physical page number, which is the translation of that virtual page number. And then you need whatever permissions for that page. You need the permission. You need the state that tells you if that page is now in memory or it's on disk location. And you might need some other fields while you implement or move on to the 3.3 checkpoint. So any questions on the page table before we move on onto the different design options that you have for a page table? So there are different design options that you can use for a page table. One of them you obviously cannot use. So the first one is the flat page table. Flat page table is an array that holds PTEs for each process, and it should have entry for all pages in your physical memory. So let's say if you have a 4MB memory with a 4K page, that means 1,000. So no, no, sorry. So you're going to, virtual addresses are 32-bit, and then you have the 20-bit of the first 32-bit of the physical address that will identify the virtual page number. That means 2 to the power of a 20 will give you around 1,048,000. So if you use a flat page table, you need around 1,000,000, and for all S161, you're going to run out of memory, most likely. And most entries, it's going to be a waste of space because most entries are going to be unused. And, but yeah, it has the advantage of O1 retrieval, since it's an array. So you have the flat page table that this is the simplest thing, but again, for OS161, you cannot use that. You will most likely run out of memory. The options that we really recommend is using a link list page table. And that means you only allocate an entry whenever you allocate physical memory or physical page for that virtual page. So it's via link list, list of PTEs for each process, search on translation, what's not good about a link list. We all know that it's O1 retrieval. If you want to search, then we have to go through all of the whole list for every translation. But we're going to save a lot on the space because whatever entry we have in the link list is used. We know that it is used. We don't have any unused entry in the link list. The third option is a two-level page table. That's a more advanced option if you want to use it. And you're going to build just like a tree-like structure mapping VPNs to PTE. Now, for a two-level page table, you have, as we said that you all, for example, with a flat page table, you're going to use the VPN, which is a 20-bit. That's going to be an index into your array. But with a two-level page table, you need to break down this 20 bits into 10 bits. And then you're going to have two-level page tables. The first level, you're going to identify it through the top 10 bits. And then the second 10 bits you're going to use for your second level page table. So to make things easier, this is how it should look like if you want to use a two-level page table. So this is the virtual address that we will get. And this is a 32-bit, the size of that's 32 bits. As we can see, the VPN is the 20 bits, the first 20 bits. And for two-level page table, we said we divide those into two 10-bits fields. And then each 10-bit is going to be the index into each level that you have. So for a two-level page table, for the first level, this is you always need to allocate. And since you have a 10-bit addresses, that means around 1,024 entries, so not so much space. So for that structure, the first level, basically, what it will include is a pointer to the next level. So you're going to get the index of the first level using the first 10 bits. And then once, then you're going to get the pointer to the second level, to the starting point of the second-level page table. And then you're going to use the second 10-bits as an index into that second-level page table. Now, for the second-level page table, these should be allocated once there is a hit happen on that field. So for example, let's say you have a hit on the third index and that index is basically null. Then you need to, at that point, you need to allocate the second-level page table. We, for multi-levels page table, we always keep stuff at the leafs. So as we said, for starting from the first level up until 1 minus n, let's say if you have n levels, it's going to basically be pointers. And the last level is where you put your PTE which is your physical page entry. So this is as of the 10-bits, the first 10-bits and the second 10-bits. Then you have 12-bits remaining for you. So in the PTE, you're going to retrieve the physical page number. And that's the starting point of your physical page. You're going to add the physical page number to the offset, which is the 12-bits offset. And you will get the physical address. So this is how the two-level page table should work. So as we said, break VVN in two parts, each used as an index at the separate level of the tree. A valid entry in the first level page table should contain a pointer to the second level while a valid entry in the second level should contain a PTE structure. It will give us a constant time retrieval. But for the space, it depends on how sparse is your address space. But obviously, it's much better than the flat table but worse than the linked list. So one change for this year we have is that if you're going to use two-level page tables, that's going to put pressure on you the memory. So if you want to really head into such implementation, we do recommend using four-level page tables. Because as much as you increase the levels, you're going to use less memory. But the recommended design option for you is the link list. And if I were you, I would go with the link list, get things working. Maybe then you need. If you want to improve your performance, you can just change it to four levels. Because with two-levels page table, your 3.2 test will pass, but not the 3.3. Most likely you will not pass the tests because it will put a lot of pressure on the memory. So keep that in mind if you want to go with multi-level page tables. It would be a good option if you consider using four-level page tables and instead of two-level page tables. So any questions on the page table design? So going into the address space, we should be familiar now with the address space. You have several regions, code, data, heap, and stack. And it's basically the address space is a range of virtual addresses that the operating system makes available for the user process. And now this is the time where you need to implement your address space interface. And you need to complete the address space structure that you already have. So what are the information that you need to maintain in an address space structure? So first of all, you need to keep track of the region information. So you need to have an idea on where does every region starts. You should have the region size. You should have the region permissions. So we have different permissions for different regions. For example, the code region is on read-only region. It's not a read-write region, so the permission for that is different than, for example, the data region. You should also keep track of stack and heap region information. Why do we keep these regions separated from the other regions? Because other regions are basically of a fixed size while the stack and heap region are a valuable size. So you need a little bit more information about these regions other than what you have, which is in common with other regions. One important note is that make sure that you set your heap region correctly. And that means this should be after all regions. So whatever region defined should be before the heap. Why is that? Because let's say code and data regions, these are fixed regions. The size of it doesn't change while the heap does change. So if you don't keep, just like so, we know where the stack starts. It's at 0x80 million growing downward. The heap region should be after all regions and growing upward toward the stack. If you keep it somewhere else between regions, between fixed regions, then at some point you run out of memory or overwrite the other regions. So make sure that you set your stack correctly, your heap, your stack and heap regions correctly. And then you do need a pointer to the page table. For example, as we said here, this is the page table base is a pointer. You need to keep track of that pointer. And depending, for example, if it's a link list, you need to keep track of the head of that link list. So this is where you keep it. You keep it into your address space structure. So any questions about address space structure? Yes. Why would you want a link list for multiple segments? So I think this is a design implementation option. So you should find a way to keep track of all the regions that you have. Not necessarily, your VM subsystems should support multiple regions. So I could define as much as I want. This is the dumb VM, which has only two regions defined. But with your VM, you do need multiple regions. You need to support multiple regions. So using a link list, I think that would be one of the options that you could use. If I give you more than four regions, your VM should still be able to handle it. You could have a variable number of regions. Your VM shouldn't support a fixed number of regions. Actually, that was one of the limitations of the dumb VM because it was supporting only fixed number of regions. As I remember, it's only two regions. But with your VM, you should support a variable number of regions. So I think that would answer your question. And that's why you need a link list. Adder space interface. So we've been through that several times. These are all defined in your address space dot edge. So AS Create will create a new address space, an empty address space. AS Destroy will destroy an existing address space. Keep in mind that you need to free each and every page when you destroy an address space. AS Copy will create an exact copy of the past address space. And here you need to copy the pages. You can also, as an advanced option, if you want, you can implement copy and write. But you're not required now to do that, which basically means you create an exact copy of the address space copying every page. AS Activate will make the current process address space the one currently seen by the processor. That means invalidating the TLB entries. Deactivate, unloading, define stack, set up the stack region. Define region will set up an un-stack region of memory. And whenever after you define a region, you want to load the content into it, you need to use or call as prepare load, which should be called before loading an executable. And then as complete load. You have more information on these in address space.h and you can see an actual example given to you in load elf.c. So I think other than the structure of the address space, there is no much to discuss here. But do you have any questions for the address space management? So for a TLB, we know that a TLB is basically a translation like a side buffer. It's a cache that is used by the MMU, Memory Management Unit. That is part of the CPU to just like improve the virtual to physical address translation. And the TLB entries, the TLB stores only 64 entries. And that's fixed. The size of every TLB entry is 64 bits. And we need to understand the breakdown of a TLB entry. So let's say you have 32 bits, two 32 bits. And every 32 bits is assigned to either for virtual or physical number. So the first 32 bits, this is what's going to handle the physical address. So the first 20 bits of the first 32 bits will be representing the virtual page number. And then you're going to be left with 12 bits that are unused. Then going into the second 32 bit of 64 bit TLB entry, you're going to have the first 20 bits again going to represent the translation of that virtual page number, which is the physical page number. And then you're going to have some other bits that some of them are unused. For example, TLB low, no cache. That's not used in OS 161. TLB low, dirty. That will tell you if that page is a read-only or read-write. So if it's set to 1, then that means it's writable. That page is writable. If it's set to 0, that means it's read-only page. For the TLB low, valid. That would tell you if that translation invalid, valid or invalid. So let's say when you invalidate the TLB entries, this is the bit that will tell which invalidate an entry in the TLB if you need to flip that bit. So this is the breakdown of a TLB entry. Any questions on the TLB entry format? OK. Now, we have different kinds of a TLB false. Of a VM fault, we have a TLB fault, we have a page fault. We know that the TLB fault happens when there is no valid translation of a virtual page number in the TLB, but a page fault means that the content of that virtual page is not present in the memory. So every page fault is preceded by a TLB fault because if the content of a virtual page is not present in the memory, that means there should not be any translation present for it in the TLB, but not the other way around. Not every TLB fault will generate a page fault. So if the page is in memory, then you should have translation for it in the TLB. If the page is not in memory, then you shouldn't have a translation for it in the TLB. So you need to differentiate these two kind of faults. And also, keep in mind that you need to support on-demand allocation. That means if an entry into your page table is not yet allocated, you do not allocate it at a time where you initialize your page table. You allocate it whenever there is a VM fault on that entry, on that address. So we're going to go through that in the last slide. We have some helper functions used with TLB management. So for TLB fault, if you check that header file VM.h, you will have three types of TLB fault that will be passed to you. You have VM fault read. That means a read attempt was done on a virtual address that is not present in the TLB. VM fault write, there's another way around, which is a write attempt was tried on a virtual address that's not in the TLB, while if you get a VM fault read only, that means there is problem with the permission. So if you're trying to write into a virtual address that is present in the TLB, but the permission of that address is read only. And how we would know that, this is from the TLB low, dirty bit that you have in the TLB format. You have also some MIPS-specific TLB access functions defined for you in the TLB.h file. You have TLB random, write, read, prop. These are the functions that you can use to manipulate the TLB. So any questions on the TLB management? So this is the general flow of your VM fault. This is what should happen when you get into a VM fault. You will get an address, a fault address. And then the first thing you should do is to check that address is valid. What does that mean? You need to check if that address falls into one of the defined segments that you are already defined in your address space. If this is the case, then you need to check if the operation on that page is valid. So you need to check that region permission through the address space. So in your address space structure, you have the region's information. And one of the information, we said that it's the permissions that you need to keep track of. So you need to check the operation you received and cross check it with the permission you have in your address space structure for that region. If it does match, then you move on. One thing to mention, so if the fault address is not valid, that means the fault address doesn't fall within one of the regions that you are already defined. That is a segmentation fault. This is what generates a segmentation fault. So if the first two steps go through, then you need to check what kind of fault you have. Is it a TLB fault? Is it a translation that does not present in your TLB table? Or is it a page fault, which means the content of that virtual page is not in memory? Then so if it's a TLB fault or if it's a page fault, you need to make sure that allocate physical page if needed. So not with on-demand allocation. So with on-demand allocation, you need to allocate the virtual and the physical page at the same time. There are some designs options that were used by some other students previously, which is just like you have a virtual entry, but you don't have a physical memory allocated for that virtual page. And this is where we were saying allocate physical page if needed. But with the on-demand allocation, that means whenever you get a hit on an address that is within one of the defined segments, but in the page table entry, you don't have an entry for that address, then you need to allocate the virtual page and the physical page fault. Once you do that, then you need to update the TLB entry with the addresses for the virtual page number. And it's corresponding physical page number. So that's all for 3.2. This is basically what we need to do for 3.2. Next week, Carl's going to give more overview. This is a complete overview of the 3.2, but he's going to go again over the same concepts with maybe some more tips if he has. And so next week we're going to do a review on 3.2. The week after, we're going to start with the 3.3. So any questions up until this point? Page aligned? OK. Virtual address? No, virtual address, 32-bit. So you're going to get the first 20 bits out of that 32-bit. You pass it into the TLB. The TLB will give you the PPN for it, with the physical page number for that. And then you're going to add that to the offset, which is the last or the low 12-bit of the virtual address that you've got. This will get you the physical address, the 32-bit physical address. Any other questions? OK. That's all I have for today. Thanks for coming. Please come through the office hours if you have any questions. Thanks.