 All right, welcome back to 353. Thank you for joining me today. So today we get to start unraveling the mystery of virtual memory and how that works, which is one of the most important topics in this course. So if we think about it, how should we implement virtual memory mapping? So what are some ideas we have your ideas for actually implementing this? So remember, memory byte addressable, and I also want to be able to map multiple processes, virtual memory. So the same address and different process should be able to map to the same or a different physical memory, depending on whether or not I want to share that memory or not. Assuming everything's independent, each virtual address in a process should point to a different physical address. So how would I implement such a thing? Any ideas? Yep. Yeah, that one option, we could just block off some chunk of physical memory for each process and then do the mapping and maybe implement the mapping. Most people say like a hash table or something like that. Hash tables are slow, so you could essentially just like have an array of lookups. So you could like say, hey, this block of memory is associated with this physical memory. This block of virtual memory is associated with this block of physical memory. And that's basically how we do it. And we'll spend all of today unraveling that and all of next lecture as well. So remember our checklist for virtual memory. So the list of goals we want are we want multiple processes to be able to coexist at the same time and be completely independent. So even though they access the same virtual address, they should not share physical memory unless they kind of opt into it. And that's another way to do inter-process communication. But we'll assume we want things to be completely independent right now. Also, processes should not be aware of their sharing physical memory. So aside from maybe running out of memory, you shouldn't be able to read another processes memory or anything like that. That would obviously be some pretty bad security concerns. If I could just have any random rogue process, just start reading memory from, I don't know, my banking application or whatever. Probably a bad idea. So that's the third point. Can't access each other's memory unless you're explicitly allowed to. And that is, like I said, another way to do IPC and you can actually share memory, but too complicated for now. We'll just make sure every process is actually independent. The fourth point is we want performance close to using physical memory. So there's many ways to implement this. So like most people say hash table, but we want really good performance. Hash tables have to do a lookup and a hash function and all that stuff. You don't wanna do that for every memory access. It's gonna be really, really slow. And for some reason, people think hash tables are like some magical thing. A hash table is just an array. Hash table is basically just a compressed array. That's it. The only magic is that hash function. Otherwise, it's just an array. Also, we want to limit the amount of fragmentation so we don't want to waste memory. So if we have that block idea of just giving, each process a separate block of memory, we probably don't wanna give them each a gigabyte of memory whenever they're running, whether they use it or not. Otherwise, if my process only uses a few megabytes or kilobyte or whatever, I'm wasting all the rest of that memory. So I want to limit the amount of wasted memory I have. So remember, low level details from C and just computer architecture in general. Remember, memory is byte addressable. So an address is basically just saying, hey, what byte number is this? What byte do I access? And we can only access memory one byte as a time as like the smallest unit we have. We can't access individual bits without just reading an entire byte and then messing around with the bits and writing them back out. So memory one byte at a time should remember that at all times in any computing course. So just read and write one byte at a time at minimum each address. You can think of memory as just a gigantic byte array and the address is basically the index into that array. So, all right, it's what? Week four, we're walking in the wrong class. That's not good. All right. So each, yeah, so each address, it's like an index into a big byte array. Memory's basically just a big byte array, right? Any concerns about memory? We're all on the same thing. We all understand kind of what memory is because well, we have to implement virtual memory now. So good to understand physical. So back in the olden times, there was a way to implement it and it was called segmentation or yeah, just segmentations and they're not used anymore. I'll explain it just for the history lesson because anyone ever access invalid memory and you got a sag fault? Yeah, sag short for segmentation. Even though, funnily enough, we don't use segmentations anymore, but with computing, as soon as you expose a name to a user like a structure or anything like that, well, you can't change it. So any invalid memory access is called the segmentation fault just from now until the end of time because history. So how segments worked back in the day is they divided virtual addresses into like segments for specific uses. So like one segment for code, all of the instructions, another segment for data. So all of your global variables and everything like that. One for the stack. So any local variables you have in your function and then the last segment for your heap which is where your dynamic memory allocations happen. So this kind of looks like an elf file if we actually started exploring other elf files for whatever reason, they kind of look like this. They tell you where in that file is the code, where's the data, where's how much stack space to make, things like that. So each of these segments can be a variable size and you can dynamically resize them if you want. So they can be large and if you have to grow something, it's really, really costly to relocate it because well, you actually have to find a space in physical memory that's contiguous. Maybe you have to move all the memory there and it's really, really bad. So it leads to a bunch of fragmentation because well, if I have a gap in between two blocks of memory, well, maybe this gap isn't big enough to actually fit a new process running. So it's essentially going to be useless memory that I can't use for any purposes. So going to be fragmentation, fragmentation is just another word for wasted memory. So this is going to no longer be used in modern operating systems, but we get some things about it. So how it worked is each segment kind of looks like an array. So to describe where a segment is in physical memory, you tell it like the base address. So what address does this start at? What's its limit or size? Like how many bytes can I possibly are valid in this? And then some permissions. So like, can I read, write this memory? Can I execute it? Some memory permissions. So how is that? If you haven't seen the word offset before, it's just a generic term in computing just saying how many bytes from the beginning is something. So like, if I had, I don't know, an array of ints, well the first index zero would start at byte zero and an offset is always the byte number. So in that int array at index one, well that would be like the second element in that array, right? But it would begin since ints are four bytes, it would begin at byte four off. So byte zero through three would be the first element and then four through seven would be the next one, so on and so forth. So the MMU is the memory management unit, which we'll go into details later when we talk about real virtual memory, but that's basically going to check that this offset that you are selecting the segment is within that limit. So it's like a valid byte to actually get and then to get the physical address, it will just do the base address of that segment plus the offset you requested, do the permission checks and if it fails, you get a segmentation fault, otherwise you get a valid physical address. So for example, this would be your memory address, it'd be like segment one and then colon, I want byte ff is 255, so in your memory management unit that the operating system would control, it might have information like, oh, segment one, well the base physical address for that is 2000 in hex and it is one ff limit, so if we were to translate this, well ff is less than one ff, so it's a valid byte, so to get the physical address, it's just the base address plus whatever byte you requested, which in this case was ff, so our final physical address is gonna be 20 ff, yep. Oh, so the question is, why is the base two bytes in this one and a half bytes? So I'm not saying how big they are, like with normal numbers, they have leading zeros, so I'm not saying how big they are. Yeah, the limit's just a hex number, it might be like the limit would be the size of that, would be dependent on like the CPU architecture you have. So like 32 bit memory, it would need to be up to 32 bits, 64 bit need to be 64. So the maximum limit is like, how much memory is allocated for that section? So like the address is 2000, all the way up to 2001 ff, would be the valid memory for segment one. Yeah, yeah, so Linux, if you look at the code because x86, it doesn't get rid of segments, so if you have Intel or AMD laptop, segments still exist in hardware, but the Linux kernel, whenever it boots up, the first thing it's gonna do is ignore them. So it sets every base to zero, just in case you decide to use this, because you could if you really wanted to, and then sets the limit equal to the maximum amount of memory you have. So essentially just delete segments, so it becomes useless. So, yep. Why are they still there? So why are they still there? It's because x86, Intel, like everything has to be backwards compatible. So once it's there, you can't get rid of it, which Windows subscribes to x86 does. If you wanna get rid of it, you have to use an ARM processor. So our first insight we had was to divide memory into fixed size chunks. So I could just like have a virtual block of memory that's of some size, and I could just map that to a physical block of memory to figure out where that is in physical memory. And then even though some process might be using the same virtual block number, while the operating system could map those to different physical blocks, and then each process would be completely separate from each other. So our MMU or a memory management unit, that is the hardware on your machine that is responsible for actually doing the translation for you. So it will map a virtual address to a physical address, and also do all of the permission checks, like reading and writing, depending on what operation you want to do, because you can make memory that you're not allowed to write to. So one technique we can do is dividing memory into fixed size pages, and here the fixed size blocks that we had in the previous sides, while we call them pages when it comes to virtual memory, and the page size we've been using since the 70s is that magical number I used before, 4,096. So they decided that number was the perfect Goldilocks number to set the block of memory for, so we got good performance, and also we didn't have too much fragmentation. So some terminology you might see come up is a page, which is that fixed size block of memory for this course is probably typically always 4k or 4,096. A page in virtual memory, we just simply call that a page, you might see it referred to as a virtual page, and then that same page in physical memory, sometimes people call it a page, sometimes it's called a frame, whoops, my search and replace went bad, it's not called a slide, it's called a frame. Wow, that's a bad error on this. So a page in physical memory is called a frame, and I will fix that. So typically 64 bits for memory, that's a lot of accessible memory, so that's like what, like exabytes or something like that. So typically you don't really need all 64 bits, so you might have like different levels of virtual addresses you can use, but the implementation details are the same. So as we'll see as we go through the course, the more virtual memory you can actually map, the more expensive it is to actually keep track of that mapping, kind of makes sense, at least you'd need a bigger array. So we might wanna keep it small as useably, like small as possible so that it's still usable. So for most systems nowadays, we use a 39 bit virtual address space that's used by risk five, x86, ARM by default, and that allows every single process to address 512 gigabytes of memory. So if it was only a 32 bit machine, 32, so two to the 32, and that's how many bytes you could address, and if each, yeah, since each one thing you can address as a byte, that's four gigabytes you could address with a 32 bit machine, which is why we moved on from them because four gigs of memory isn't terribly special nowadays, and especially if you're using Chrome, four gigs is nothing. So had to move on to that. So right now we're at 39, and that system's called SV39 specifically for risk five, everyone else calls it a different thing, but this allows each process to address up to 512 gigs of memory, which should keep Chrome happy for like two more years. So how this is implemented is with a page table, and that page table is basically just a giant lookup array. So it's going to be indexed by a virtual page number, and what is stored in that array is the physical page number. So what that looks like visually is we'll have a virtual address here, that virtual address, while we could use up to 64 bits because we have a 64 bit machine, but since we're only supporting 39 bit virtual addresses, we're just going to ignore the top whatever bits. So we're going to split up that 39 into two sections. So the first section is called the offset, so that is where you are in a page. So this is directly related to the size of a page. So remember, our pages are 4,096 bytes, so in order to be able to address every single byte in that page, we need 12 bits, right? Because two to the 12 is 4,096. So the offset is directly related to our page size because it's telling you what byte you want to actually access on the page. So the other bits, so we just take 39 minus 12, so the other 27 bits are the index bits. So that's basically how many virtual pages we have to be able to store a mapping for. So in this case, we would have 27 bits, so in this page table, well, it would have two to the 27 entries, right? Because, well, powers of two. So this page table would have to have two to the 27 entries, so it would have to have a lot of entries, and each thing in this page table, its contents is that physical page number, and the size of that doesn't really matter. So it turns out the kind of only store 44 bits to keep track of the physical page number in this table. So whenever we need to translate an address, so if we have any virtual address, we treat these bits as the index, we would look it up in this page table, and then in this page table, we would get a physical page number, and then that would be the physical page number that the memory is actually located at, and then the actual physical address would just be the physical page number followed by the offset, because we don't need to translate the offset, it's where you are in a page, doesn't matter if it's virtual or physical, yep. So the question is how does it get to 27 bits to 44 bits? So the 27 bits here are just to use for an index into this table, and then this table, each element can be any size we want. So like, you can think of it, it could be like an array of ints, so then each one would be four bytes. In this case, an int would be too small, so it's basically an array of like eight byte entries. So each of them would be 64 bits, and turns out, I'll show you in the next slide why, but turns out we don't use all 64 bits, we only use 44 bits. So this is just a big array. So the page table is just a big array, and the operating system would be the thing that manages that, and the MMU just uses that to do the translations, yep. Yeah, so that's a good question. Do each process share the same page table, or do they have their own? So that each process would have their own page table. So that's how we can take the same mapping, because if you had the same virtual address and two processes, well, it would be the same entry in the page table, right? But the only way to make them independent is, I make each entry point to a different page, yep. So the unused part of this is just, right now it's just undefined, it's for future use if they decide to extend anything, right now it's just ignored on most machines. So there's some other space, because while it's a 64-bit machine, so this is what, 12 plus 44, that's 56. So there's six bits left over that aren't used, and they just say, we'll just leave it for now and figure it out what to use them for later if we come up with any other bright ideas. But that's just a implementation thing, yep. So do we, yeah, so the question, do we use binary or hexadecimal? They, I use what they both mean the same thing, like it eventually goes to ones and zeros. I use hex wherever it's convenient, and then whenever I'm forced to use binary, I use binary, yep, yep. But so the amount of 512 gigabytes of battery's memory that you mentioned, is that essentially the size of the index or the size of the offset that determines that? So how much memory you can access is just all of these bits together. So we take the address, so your virtual address would be 39 bits, right? And then the 39 bits get divided up like this, because while this is like which byte in the page and then this is what page, but there'd be in total two to the 39 bits we can address or bytes we can address here, which is everything we can address, yep. If, why are we using odd numbers of bits? So yeah, the question is, if everything's byte addressable on a computer, why are we using bits? So we're using bits here. Because you need to store that in a certain amount of bytes, right? So the bits here, the thing that actually operates on these bits is the MMU, which is hardware, so we don't actually have to operate on it. So the MMU does this translation. What we manage is this page table. So we manage this in software. And this is a nice size. It's also an odd number though. It's an odd number, but we'll see why in a second. We'll make it a nice number. All right, yeah. Also, by the way, that's like huge, right? That's a lot of entries to the 26. If anyone has a calculator and give me that number, that's a lot of entries. Probably not a good idea. So each element of that array in the page table, it's called a page table entry. So it stores a bit more than that physical page number. So what it actually stores is at least on this system, they're powers of two, they're nice. So each entry is eight bytes, which is 64 bits. So it turns out this is how it divides that entry up. So those 64 bits. So for this course, it has a bunch of permission checks. So there's has one bit here that says whether this entry is valid or not. So if this bit is a zero, it's not valid. If it's a one, it is valid. And then there's like some other permission checks, readable, writable. So is this page readable or writable? And is it executable? And that's probably all the flags we really have to be concerned about in this course. In fact, in this course, the only real flag we need to be aware of is this valid bit. It definitely has to be there. There's also some other bits on this architecture that make sure that, oh, this is only accessed in user mode. This is a global one. This is a dirty bit, which yet, don't worry about that term, like weird terms here. So we'll get up about what this dirty bit is later, maybe whether or not it's accessed. Then there's some bits here, which are reserved for the supervisor use. So there's two bits that the kernel gets to play with. And guess what, when you do lab three, and you actually kind of manage these entries, you get to use those bits for whatever you want as well. Then we have 44 bits, which is that physical page number. And then they decided to just reserve 10 bits for future use. If someone decides to come up with another flag or something later, then they can go ahead and reuse that without having to make this thing any bigger. But this is what the page table entry currently looks like. So the kernel, it handles like setting up the translations, setting up the page table so that the MMU can actually translate the virtual addresses. So the kernel would manage something like this page table, and this page table would be just a giant array. So I'll just write out the index in this left column, just to make it a bit easier to read. And then each entry, I'll just say the PPN number and assume it's valid. So I'll say that in the right column. So if I have this, well, if I do the translation between a virtual address to a physical address, say this is my virtual address on the left, turns out because if we go back, this offset, we don't have to translate it, right? It stays the same. It's just what byte on the page we want to access. Turns out it's a very nice number. So 12 bits, each hex digit is four bits. So essentially the last three hex digits, we don't have to translate if this is our page size. So in this case, the last three hex digits of this virtual address are A, B, zero. So we don't have to translate them. And the bits, everything before that is essentially that virtual page number or what index I should use in this array. So if I was trying to translate this address, well, I would figure out what the virtual page number is. So it'd be zero. And then I would look at the page table, which would be unique to a process and say, hey, at virtual page number one, that's PPN one, or sorry, at virtual page number zero, it's PPN one. So my physical address would be one. So I just essentially replace the virtual page number by the PPN I looked up. And then I keep the offset the same. So it's A, B, zero. Same thing for all the other translations. So for one, F, A, zero, well, F, A, zero, that is the offset. So I don't have to translate this part. And one is our virtual page number. So if I look it up, oh, at virtual page number one, it's physical page number four. So the resulting physical address is just four F, A, zero. So questions about that? Yep? Smallest units of memory that can be allocated? Yeah, so that offset the 12 bits. So in terms of memory allocation in the kernel, that's the smallest allocation it'll do, just one page at a time. And also that's why I use the magic number and a bunch of programs, because I know that's the smallest unit I get anyways. I may as well not ask for like a hundred bytes because I know I'm probably gonna get a page. So why bother wasting space? All right, so here is a typical question that you have to generalize any page translation. And these are fun ones to put on exams. Everyone loves these ones. So this question just comes up with a whole new virtual memory system. So it will probably tell you the page size, like the size of the virtual address, the physical address, and probably the page table entry size for other questions. And sometimes one may be left out and you have to figure out the other one. So let's go through this question. So that is not what I want. This is what I want. So in this question it says we have an eight bit virtual address and we have a 10 bit physical address. And it also says we have a 64 byte page size. So it goes through a bunch of questions. Like first question was how many, so how many virtual pages are there? Well, at least with this scheme where we just have a single page table, well we should divide up that virtual address and split it up into how many bits are the offset and then how many bits are the virtual page number. So remember the offset, how many bits we need for the offset is directly related to the page size. So for these, knowing your powers of two is pretty good. So anyone quick, what's 64 in powers of two? Six. Four, six. So I can only like multiply by two and divide by two like up to two times. So if you want to just remember like one or two good numbers to the power of five is 32. Just remember that and then you can figure it out from there and then two to the 10, you should also know that one, that's 1024. So knowing those, you can pretty much figure it out from there. Two to the five is 32. If I multiply that by two, I get 64, yay. So I'll put this back down here. So in this, my offset would be six bits. So how many bits do I have left over for this virtual page number? Two. So this asks how many virtual pages are there? So how many different pages could I index or how many different entries would be in that table? Four, right? So this would be to the power of two. We can keep it at that or in other words, four. All right, we are steamrolling. All right, other question. How many physical pages? Oops. So same idea, but now we would have like offset and then PPN for our actual physical pages. So our offset would hopefully stay the same at six bits because that's our page size. So how many bits do I have left over for the PPN? Four, because I said right here, I have a 10-bit physical address size, so it's just 10 minus six. So I have four bits left over for the PPN. And then to get the physical pages, same idea. I can actually have up to two to the four physical pages. So that would be two to the four. Or if you do the shortcut, hey, two to the five is 32 and then one less, I just have to divide by two once, that's 16. So I didn't have to remember that. All right, other one. Number three was how many entries in the page. So how many entries are there in the page table? In this page table for this question. Yeah. Four. Four, right? Kind of a trick question. Four? Yeah, four. Because it's just, I have to be able to have, for each virtual page, I need to be able to map that to some physical page. So it's just always going to be equal to whatever this is. So four. Yeah. Why the 4096 is the page size? So why 4096 is the page size is just because it was like a Goldilocks number that they discovered in the 70s, where it was fast enough if they used that number and didn't waste that much space. For no other reason than someone picked that in the 70s. Yeah. No, so the kernel only deals with memory and pages. It only cares about pages. Oh, okay. Yeah, so your program can ask for a byte, but the kernel is going to map, give it a page and it'll have a bunch of other memory it can use. Yeah. Yeah, so the question is, would there ever be the case where it's the opposite where I have like more virtual pages and physical and this is just a system I made up. So you could, it would probably be a bit silly because having a larger virtual address means that table gets bigger and it's more expensive to implement. And doesn't really make sense if it's bigger than the physical address size because then like can't access stuff, but turns out some systems use virtual addresses to map other things like map hardware and other things. So it might be able to actually use more virtual addresses if it wants. So you could design a system like that, but it'd be probably pretty weird, but you could. Yeah. So is that number of physical pages that we could have? Yeah, so the question is in the page table, like how big does each entry need to be essentially? So yeah, that's a good question of in the page table, how big does each entry need to be? So remember like the important things that entry stores. So like the page table entry, what does it store? It stores like the PPN and then like a bunch of permission bits for the purposes of this course. We can assume it always has to have like at least a valid bit. Oh, my voice is cracking. Nice. So if the page table entry contains the PPN and like a valid bit, it needs to be able at least that big, right? So in this case, our PPN is four bits and then the valid bit would be one bit. So in this case, yeah, so your PTE always has to at least hold the PPN in a valid bit. So my PPN in this case would have to be at least five bits. And because we store it in like a big array, it should probably be like bytes and powers of two. So this, we could just say probably just one, one byte is sufficient for this, right? But if my PPN was bigger, wow, that almost sounded really weird. All right, I should stop talking. So if it was like 10 bits or something like that, then if this was, I don't know what the YouTube rules are. So hopefully not. All right, so if my PPN was actually 10 bits, then it would be like 10 bits plus one. So it need to be at least 11. So next like power of two would probably be like two bytes. So yeah, your page table entry would be related to how big that thing I'm not gonna say again as. So in this case, I just wrote out an array for the page table. So in this case, my page table was like two, five. I just simplified it a bit where the page table just contains physical page numbers and not the valid bit, but a real one would have a valid bit as part of it. So this would be index zero, index one, index two, index three. So if I want to translate the address, what address? So I want to translate the address F1, and this would be like the virtual address. And then question, do PPNs get mapped to non-volatile memory if the RAM isn't sufficient for some large entries? So we'll get into that later, but essentially, yeah, you can actually fake having more memory than there actually is because you could map virtual page numbers to whatever the hell you want. So you could map them to like hard drive space if you really wanted to. And that's one way you can actually use more virtual memory than you have physical memory on your machine, but we get into that later. All right, so back to this. So I want to translate the address, the virtual address, F1. So in this case, I have to take the virtual address and then split it off into like VPN and the offset. In this case, I have to do binary because like hexes don't really work for this because I have two bits and then six bits, so it doesn't really work that well. So if by some unfortunate happenstance, you have to actually translate hex to binary, again, there's short little ones you should know. So the only real important one that I remember is A. A is equal to 1010 or 10. A's supposed to be 10, in binary it's 1010. That's how I remember it because I'm dumb. So if you know that one, you can pretty much figure out the rest of them and F is like the last one. So F is always 1111. So at least in this one, I was a bit nice. So F is 1111 and then one in hex. Well, each hex is supposed to be four bits. So this would be 0001. So F1 would actually be this. So the last six bits here, this is my offset and then these two bits are my VPN. So what is 11, I don't know, in decimal three, right? So this is three. So that's basically the index in this page table I'm supposed to use. So I'm supposed to use this one, right? So in order to translate this address, well, I keep the offset the same, so it's 110001 and then in here, this is where I put the physical page number and eight in binary, sorry yeah, eight in binary is 10000. So if I want to write this back out in hex, I start grouping by fours. So this is the last hex digit, this is the next one and this is the other one. So if I want to write that a bit neater, this is 10, this is 0011 and this is 0001. So what is 10 in hex two? So if I write out the physical address in hex, it's gonna be two, what's this in hex? Three and then what's this in hex? One. So with this page table, if this process uses address F1, it gets translated to 231. Yeah, so here there's no valid bit in table, I'm just assuming everything's valid, just to save space. But on real ones, I would probably tell you if it's valid or not, or just put another call in there or something. All right, questions about that? All right, so yeah, preempted me a long time ago. Each process gets its own page table. So this also lets us explain what happens in fork and why we actually, it's fairly fast. So when you fork a process, it copies that page table exactly as it is in the parent and how it is fast is an implementation called copy on write. So basically what it will do is it will turn off write permission. So if that new process tried to access the same virtual address, which would be the same physical address, then it would get an error. And then only at that time when it's trying to actually change something, would we actually make a copy of that page and then otherwise they share memory and it's really, really fast. So all we have to do is copy the page table. So the problem though is you might realize that that page table, at least for that 39 bit virtual address, so going back to the real implementation, it had two to the 27 entries and each entry itself is eight bytes or two to the three. So if we figure out how much space that takes, well, that's two to the 30, yeah, that's two to the 30 or one gigabyte. So probably a pretty stupid idea that each process actually just requires one gigabyte by itself for a page table. So I don't know how much memory your laptop has. Mine has 16 gigs of RAM. So if this was true, as soon as I had 16 processes running, I'd be out of memory just from the page tables. So obviously that's not a thing, but if you actually read all the hardware documentation and everything, it actually magically does that. We do have 39 bit virtual addresses and in this case, they map to 56 bit physical addresses. So that 10 bits to spare, so for future use. And our page size is actually the 4096, which is the size of the offset field. So it seems like a lot of work. So also in the sub process lecture, we did a fork followed by an exec. So why the hell do we even need to copy the page tables anyways, if we're essentially just resetting them when we ever we exec something. So there's actually a way to control this. There's a separate system call called vFork. And what vFork will do is not bother to manage to copy the page tables. So that means if you do a vFork by default, you share all the memory with the parent and you can screw up your parent's memory, like okay, not any real life analogy there. Technically, if you read the C documentation, it says if the child starts changing around memory, it's undefined behavior, good luck, you're on your own, you're probably screwed. So the reason they implemented that was only through like very performant sensitive programs that just did fork exec and that's it. So I don't really know why I went on this tangent, but there's some control you can do over for what happens during a fork. Yep, mm-hmm. When we did the fork bomb, we did not run out of memory, no. So how is that possible? So the page table turned out wasn't actually that big. So we'll get into that later. And a question is the child process marginally slower than its parent if it's not using vFork. So it'll take some time to set up the page tables, but that'll be done by the fork. And it will be actually the first process to modify the memory will be slightly slower. Yep, did you have another question? Okay, we're gonna be on virtual memory for the next lecture too, so don't worry about. All right, so what we did today, use pages for memory translation divided that memory into blocks and we only have to translate once per block. We use page tables, which was basically a giant array of page table entries to access the PPN and flags. We should at least know there's a valid bit and we have a new problem we get to solve in the next lecture, which is these page tables are stupidly huge. There's no way this actually is how it works, right? So with that, just remember, I'm pulling for you, we're all in this together.