 All righty, welcome back to Operating Systems, wow, that's loud. All right, so I'm here for you, what do you want to do? It is midterm Eve. There's no questions whatsoever. Well, we want to go over page table stuff, like one of the questions from past years. All right, page tables. Let's see. From any specific exam? Let's see, here's a new one. So here's one that was the 353 final last year. So there's a virtual memory question, which should be able to do. You may have not seen this yet if you've only looked at the past midterms, but hey, the final is cumulative. So you can look at past finals and see if it is relevant. Let's see with this one. So we created some silly machine that has a 12-bit virtual address. It's got no 8-bit physical address, page size of 16 bytes. If we see a page size, well, we should probably just write it in powers of 2. What's 16 in powers of 2? 2 to the 4, right? So before even reading any more, I will just put to the 4 there. And we have a page table entry of a single byte. So that is how big our page table entry is. That says our page table entry has the following layout. So if it's a byte, that means it's 8 bits. And our page table entry looks like this. The top four bits are the PPN. And then the bottom four are just the permissions. So that's valid, read, write, and execute. And in this, we don't really use them. Yeah, we just either turn them all on or turn them all off. So this is a physical memory dump. So this tells you what every single byte is in memory. So at address 0, that's this byte. At address 7, that's this byte. If we're going at address 2, 4, well, that's this row. And then it's sequential offset from the column. So at 24, it would be this byte here, which would be 3f, which that would be a page table entry if this is part of our page table. So it's nicely divided for you. So you don't have to write it out in binary. So here, the 3 would be part of the PPN. And then this byte, which is the four bits, would be corresponded to these bits up here, which would be all the permissions. So if it's f, it's just 111. Means it's valid for everything. So any questions about this little setup that we have right here? Silly little system. Yeah, so the question is, is there a reason we're putting the PPN at the front? Doesn't matter. Doesn't matter. You can do the page table entry however you want. Usually, people put the bits at the very bottom, like the permission bits at the bottom, and PPN at the top. But I could switch it around. Doesn't matter as long as you're consistent. Yeah, so for this, the row just says the base address. And then on the columns, that's the offset. And this just shows you a physical dump of memory. So these could represent page tables. They could just represent normal memory. We don't know unless we know which one of these are page tables. So in here, well, it says our page size is 16 bytes. So how many entries could we fit on a single page? In this case, 16, right? Our page table entry is one byte. So we could fit 16 entries on a page in this case, which makes things a bit easier. So yeah, here we go. First question, how many bytes would we need to store a single-level page table if we used it? Yeah? 2 to the 8, right? Any concerns about that? See how we got that? So how many bytes would we need to store? So we have a 12-bit virtual bits. And we can just essentially divide that by our offset or minus our offset. So in this case, because our page size is 16 bytes, well, our total virtual memory is 2 to the 12 bytes because we have a 12-bit virtual address. And then while if we use a single large page table, we would need to address every single page in there. So we need to figure out how many pages would be in virtual memory. So we just divide that by the page size. And then we get 2 to the 8 because we can do powers of 2 math. So any questions about that? And here, in this case, that's how many page table entries we would need. And page table entries are only a byte, so we don't have to do anything there. Yep. Yeah, so here I pretty much figured out how many bits for the virtual page number. And then I need that many page table entries. In this case, my page table entry is only a byte, so I don't have to do anything special. But if my page table entry was bigger, I'd have to multiply. I need a page table entry for each virtual page number, right? So I would need to multiply it. But in this case, multiplying by one, pretty simple. All right, any questions? Where are we at so far? So yeah, virtual memory is always a fun topic. Everyone loves virtual memory, right? All right, tough crowd. All right, so for the remaining questions, we're assuming multi-level page tables because more page tables equals more fun. So if we use multi-level page tables, we'll assume each page table fits on a physical page and ask what's the minimum number of levels of page tables we need in our system. So we need to figure out how many page table entries we can actually fit on a single page. So how many page table entries can we fit on a single page? 16 or 2 to the 4? So we got 2 to the 4 entries on a single page. So that would be, we would need 4 index bits to be able to select which one if we're using multi-level page tables. And then our equation is just virtual bits minus the offset bits divided by the index bits. In this case, well, from the question, we have 12 virtual bits because that's how big our virtual memory is. Our offset is 4, and our index is also 4. So if we take that and take the ceiling of that, well, luckily it divides evenly. 12 minus 4 is 8. Divided by 4 is 2. So we need two levels of page tables for this. Yep, so it's a minimum number of levels to be able to map all the entire virtual address space because if we only had one level, we would only have one page table with 16 entries. So we could only address up to like 16 times 16 bytes, which would only be 256, which would be like 2 to the 8, right? So I can't actually address everything if I have just one small page table. All right. So any other questions with what we have here? That's good. We need two levels of page tables, and then we assume that for the rest of the questions. All right, so now we're going to translate the address 1, 2, 3, all in hex. And our highest level page table is currently at ppn2. And remember, it takes up an entire page. So if we go back to this virtual memory, we know that, well, ppn0 would extend from address 0 all the way to 10 here. So I'll just divide this virtual memory essentially into pages. So every page is 16 bytes. So we'll start with the first 16 bytes and draw a line for every page. Let's make it look nicer. So essentially, we'd have four pages, right? Four pages of 16 bytes. One would start with 0 all the way to 1, 0, then 1, 0 all the way to 2, 0, do, do, do. So any questions about just dividing that into pages? No? OK, good. So now it says, our highest level page table is currently at ppn2. So what's our highest level page table if we have two levels of page tables? Is it L3, L1, right? So if we have two levels of page tables, it's L1, and then L0 that just contains our actual mapping for this address. So it says, our highest level page table is currently at physical page number 2. And it's a page right. So this would correspond to all the addresses from 2, 0 all the way to 2f inclusive. Or if you want to do it exclusive, it would be 2, 0 all the way to 3, 0, not exclusive. So in this case, that would correspond to here. So this is our L1 page table, all of these entries. If our ppn is 2, 0 corresponds to this being our L1 page table. So any questions about that? So this is our L1 page table. Now you can tell I was also nice here because I didn't have you write it out in binary. Everything is just four bits. So for this number, the most significant hex corresponds to the L1 index. The next corresponds to the L2 index. And then this 3 is just the offset because it is the least significant. And they seem to all, in this case, they're all the same size. So any questions about that? OK, so what's the L1 index here? For virtual address, 1, 2, 3. Just 1, right? Just 1 in hex. So that is our index into our L1 page table. Which byte would that correspond to here? Yeah, 0x21. Because while our L1 page table starts at 2, 0, and then we need to go to the first index. So this one would be index 0. This one would be at index 1. So we're essentially using the byte right here. And because this is our page table entry in our L1 page table, well, it points to what to use for the L0 page table, right? So we can check the permission bits. So in here, f is just 1, 1, 1, 1. And then the ppn is the top number. So in this case, if the valid bit is 1, it means this entry is valid. And this should correspond to a physical page number of 1 for our L0 page table. So where would our L0 page table be then? Right above it. So because the ppn was 1, that means that, well, essentially I'm using page 1 for my L0 page table. So that means this thing highlighted in cyan, I guess that's the color. That is our L0 page table we have to use. So that's what our entry in L1 told us. The entry tells us what L0 page table we need to use. So what index do I use in the L0 page table for this virtual address? 0 or 1, 2, 3. 2. So where would that be here? What byte would that be? Yeah, so it's ff, so it should be 0, 1. And then if we have to go over into the entry at index 2, well, this is entry at index 0, 1, and 2. So this would be our entry in the L0 page table. So again, we would look at the permission bits. In this case, well, this f represents the permission bits. So they're all 1, 1, 1, so it's valid and we're all good. And the ppn is just f. Because this is our L0 page table, that contains our actual mapping. So we have found the physical page number to translate from the virtual page number. So what's the other question? So what is the value of the final pte? If it was valid, hopefully we got to that point. We were in this situation and the value was ff. So the last part is, what is the resulting physical address? So again, remember we started with the virtual address. Whoops, that was our virtual address. And we found what physical page number this should correspond to, so what is the actual physical address that we would resolve from 1, 2, 3? Yeah, 0xf3. Because remember, if we break up this address, well, let's just break up the hex part of it. This essentially was our virtual page number. And because we were using multi-level page tables, we broke that into two indexes. And here was our offset. And if we want the physical address, well, we keep the offset the same, so it's still 3. And we just substituted in the virtual page number for the physical page number. So our physical page number from the entry, because we got ff. And here, the top four bits were the physical page number. So we just substituted in, because we found it by following all the entries. So in this case, we would get f3, because that is the physical page number we found out. So if we write in hexes, just 0xf3. Any questions about that? I divided it into pages. Byte alignment, or page alignment? Yeah, you always assume page alignment for these. Yeah, so that was part of it when we went through virtual memory and talked about alignment. It's really important that all the pages are aligned. Otherwise, it makes this math become way worse, because we're essentially not doing any math because we don't have to translate the offset or anything, because it's just within a page, and the pages don't shift around any. If it started at just some random address that wasn't aligned, we would have to keep track of it all the time, and it would just be a pain in the butt to deal with. So this only works because everything's aligned. Yeah, sorry? Yeah, so for why this is to the power of 8, so it's essentially saying if we use a single level page table, we need an entry for every single virtual page number, right? So this is just essentially asking, well, how many virtual page numbers do we need if we have a page size of 16 bytes and we have a 12-bit virtual address? So it's basically saying, how many virtual pages do I have across all the memory? So in total, for virtual memory, I would have two of the 12 bytes. That's like my total accessible virtual memory, and then you just have to figure out how many pages are in that. So in this case, since my page size is two of the four, that's how many pages I have, and then this tells you how many virtual page numbers there are, right? And then to get the how many bytes you would need, you would also need to multiply it here by the page table entry size, because each of those virtual page numbers would need a page table entry if we just had one gigantic table. So in this case, PTE size was one, so we didn't really have to do anything. All right, any other questions? That was virtual memory. It's a good thing I brought this one, because cough, cough. It's similar. Yeah, this is the final from 353. Winter 353. I only did 353 once so far. Are there any more virtual memory questions? So I think locking, Santa Fores, threads, file systems, I think the other one was fairly lame. File systems, virtual machines, no. Not really, this is probably the hardest one there is. Where'd it go? Yeah, this one's probably the hardest instance of it. Yeah, no, we haven't talked about this recently. Use a page replacement, that's next. Not really. Yeah, you don't really need to know what WC is other than it's just a program that you can execute. You don't really care what it does. It's like LS. Yeah, it does some stuff, whatever. All right, yep, that was yours. The midterm from CS111, that was yours. The last, this one? So the two in this formula, it says, this formula says recall for the single level page table. So the two in this formula is because it's only a single level page table. It's the page table lookup and then the normal lookup. So specifically, I only just gave them, so this was also closed book. So I gave them this, so I said, here's the equation for a single level page table and then I said calculate it for multi level. So that's why it's two in this case because it was specifically only for a single level. All right, anything else? Any other fun things yet? For this one? Yeah. Yeah, for this? Yeah, I need 21 offset bits. No, this is in bytes and memories byte addressable. Yeah, very important to know that memory's byte addressable, so I'm not addressing individual bits, otherwise it would throw things off. No, so this is just like, so here I took liberties, this should just say page size. So page size is two megabytes. Yeah, that's why I went by it. I probably was to, why use few word, why, you know, that's stupid me. I tried to use little words, no work did it. Yeah, 2022, so the, wait, this one? Can you show me it quick? It's the same question as this one, the pre-use we gave you, so. Oh, okay. So, we can, so we want something that looks like main and i equals four. All right, this'll be fun without syntax highlighting. All right, so we essentially have this program and it says essentially what the hell does it do? Yeah, well, let's go through it. So we will have, you know, some original process, I usually like start them at 100 just to make life easier. So process 100, we'll start executing. Creates an int called i, it is equal to four. It will be on its stack and it will get a value of four. Going to the while loop right now, i does not equal zero. So it will fork and then create a copy of itself. So if it creates a copy of itself, we'll probably get process 101, right? So what are the difference between process 100 and 101? Yeah, yeah, they'll have different return values from fork and then they'll create PID and assign their different ones. So will process of 101 have a variable called i? Yes, what will its value be? Four because it's a copy of a parent. So then after, do we know what we'll execute next? No, so let's just assume they both just returned from fork just for fun. So PID in process 100, what would that be assigned? 101, 101, what would PID be assigned in process 101? Good old goose egg zero. Do we know which process is going to execute next? No, so i can see into the future. So if I see into the future, let's just say process 100 executes. So if process 100 executes, well, PID is not equal to zero. It would go into this else and then it would wait. Now it's waiting on process 101, which is its child. So we'll put the process tree up here. So what would this wait call do? Yeah, yeah, process 100 would get blocked, which means it cannot execute anymore until process 101 is terminated. It calls exit or, well, in this case, we assume it calls exit. Maybe someone kills stash nine's it, but that's kind of beyond the scope. So it's stuck here, it's blocked, it can't do anything. So now process 101, well, it would continue executing. In this case, it's PID is equal to zero. So it would go into this else and it would decrement I. So it would change I from three to, or four to what? Hopefully four to three, right? So it decrements its I. Is it going to affect process 100s Y or I? No, right? Because they are independent. They are different processes. They have their own virtual memory. So in this case, process 101 just decrements it. It would go for the while loop and then go fork again. In this case, process 101, okay, it forks. So it would probably create a process called 102. In process 102, will it have a variable called I? Yes, what will the value of I be? Three, because that's what it was at the time of the fork. Trick question, will it have a variable called PID? Yes, what will the value of PID be? Of what? Zero, right? So it is an exact copy at the time of the fork and now fork returns and then that's going to overwrite the PID in both the processes. So immediately, what is the PID going to be overwritten in process 102? What's it going to return from fork? 102, just zero. So it's just going to rewrite zero with zero, right? So all right, what about 101? What's going to happen in 101? Yeah, it's going to get the value 102 and then overwrite its value of PID. So now what's going to happen? Well, in this case, I know that process 101 because its value of PID is not equal to zero. It's going to go and hit this wait call. So let's say process 101 executes, it would eventually hit this wait PID. So it's going to wait on process 102. What does that mean? It's blocked. So process 101 is now blocked, waiting on process 102 to terminate. So process 101 and 101 are both blocked. And now process 102 can go ahead, decrement I from to do, decrement I from three to two, and then it would go back up for this while loop and then fork again. And then it's going to get kind of boring. So it would create process 103 and then essentially going to be the same thing. So it would overwrite fork or PID from fork, it would get 103, then we would have process 103 as I equal to two and PID equal to zero, assuming it just returns from fork. So then in this case, well, if process 102 executes, it would get crowded at that wait call. So now it's going to wait on process 103 to execute and it can't do anything at all. So after that, do we need to go through the excruciating detail or do we need, can we skip some steps? Let's skip some steps, okay. So 103 is going to create 104. So that creates 104 and process 104, I would be equal to two, PID would be equal to zero, process 103 is going to hit that wait, it's going to wait on process 104. And then in 104, well, we're going to decrement I all the way, whoops, we're going to decrement I to one. And then go through, in this case, I is still not equal to zero. So 104 is going to create 105. And then in 105, I would be equal to one, PID would be equal to zero, and this, well, would be go to this wait call. All right, so I think I've adequately skipped a number of steps. So let's just make sure we're on the same page. So at this point, I will have process 100, 101, 102, 103, 104, all at this wait PID call, waiting on its direct child. So any questions about that? Yep, sorry, in 103, yeah, whoops, I skipped a step. I went too fast. Sorry, you're saying it should be this, 105 shouldn't exist. That, okay, whoops, skipped too many steps. All right, so process 100, 101, 102, and 103 are all that wait call, and now I have this process 104, which is essentially right here at this point. All right, so any questions about that? We're all waiting on each other and process 105 doesn't exist. So now, at this point, what's the only process I can actually execute? Hopefully 104, right? Because all the rest of them are blocked on that wait PID call. So if 104 executes, well, it would hit here, it would check this while, in this case, I is equal to zero. So it would just hit here and hit return. What's returning from main the exact same as doing? Exiting, right? So exiting was zero. So now it's process 104 done so. Yeah, so 104 is done so. So that means this is dead. At this exact moment, what is the state of process 104? Is it a zombie? Is it an orphan, or is it just chilling? Technically a zombie, right? So we haven't completed the wait call yet. So trick question, that's why I said at this exact moment because it's quickly going to be cleaned up. It's not going to be a zombie for very long. So 104 is terminated, not cleaned up. So if we unblock process 103, then that means we completely clean up process 104. So 103 would wake up and then print. What would it print? One. So it would just print out one and that's the only thing that could have executed. There's no other order that could have happened because all the other processes were blocked, right? There's no way I could have printed four at this point. So now, well, this would print one and then immediately exit. So if 103 exits, it's now terminated. It is temporarily a zombie, but now that will unblock 102. And if 102 executes, well, it returns from its weight and what would it print? Two. And then it would go ahead and exit and then 102 would wake up or 102 finishes and then 101 can wake up. It's going to print in this case three and we have no other choice. Then 101 is going to exit and then if we unblock process 100, well, then we clean up process 101 and then process 100 will just print out four and then terminate and we're done. So we have to print in the order one, two, three, four. There's no other choice because of that weight call. So I could have executed the child programs first if I wanted to, but we made a difference. We got the same result anyways. So any questions about that? Nope. Okay, so something that I'm really bad about keeping secrets about exams if you ask me questions. So let's see how much I say. All right, so here's this question which is like technically valid. So this is processes and threads and it doesn't have anything to do with data races or anything. I, this would be valid to ask you. I also have a bad short term memory so that actually goes in my favor. So I did not ask you something this complicated. I asked you something about threads so I forget what it was. Definitely a short answer question. So unless my memory is really bad, I don't think it's that bad, but in this case we could actually answer this question. So this just has to do with forking and making new processes. So at the beginning of this, let's see if we can, let's see how good we are. So let's say process 101 starts executing main and calls fork immediately. So what's that going to do? Yeah, create process probably 101 and it's going to be exact clone and we're gonna have two processes here that want to start executing after the fork, right? So now two processes that are otherwise the same. The only difference is the value of PID. In 101 PID equals, or 100, PID equals 101 and 101 PID is equal to zero. So in this case we have no idea which one is going to execute first. Say 100 executes first. They're independent at this point. It creates two threads. So we have the main thread and then two other threads that want to execute this run, one with a value of one, one with a value of two. At this point, do I know which of these threads is going to execute first? No, no idea. I have no idea, well after I make it, I have no idea if the main thread is going to execute next or whatever, but the main thread would hit join. So it has to wait for thread one to actually finish before that join happens. But I don't know in what order anything is going to happen in. So what could happen is I would have thread one here, thread two, and these would be from process 100. So I'll just put 100 under them. And they would get their value from arg. So thread ID for thread one would be one, thread ID for thread two would be two. And then they fork. Anyone remember what happens if a thread calls fork? Yeah, yeah, I'll just create a new process. It's a clone of whatever the thread is that called fork. So in this case, this was in process 100. So this fork probably made process, here I'll draw the tree up here. So process 100 probably this thread would have made a process called 102 or something. That is a child of process 100. And that process 102 only has a single thread in it, which is a copy of, in this case, which thread did I say called that fork? Thread two? Yeah, sure, thread two. So thread two called that fork. So in process 102, well, it's only going to have a single thread and it's going to be a copy of thread two. So in process 102, I can just shorthand it cause now it only has a single thread in it. Well, it would have a thread ID equal to two because it's a clone of whoever called it. So it had thread ID equal to two. So in this case, I don't know which of these is going to execute. Let's say process 100 runs, let's say. So in that case, I would have a print that says thread two run in 100 something like that. That could happen. And then we would move this. It would check if PID is equal to greater than zero. In this case, thread two created that process 102. So it would have had a PID equal to 102. And here PID would have been equal to zero. So it would wait on process 102 to finish after it actually printed. So that would wait, it process 102 executed. Well, what would it print? So yeah, if 102 executed, it would print thread two running in process what? 102, right? How much time do we have? We don't have that much time. So we've processed thread two running in 102 and then it would eventually check its PID. It's equal to zero. So it would hit return null. So for a thread, what is returning from the thread function the same as? Not quite, yeah. Yeah, P thread exit. So it's a dual for thread. So if you return from main, that's the same as calling exit. If a thread returns from a thread function, that's the same as calling P thread exit. So in this case in process 102, well, because we forked it, there's only one thread in that process. So if it calls P thread exit, similar to your threading library, if it's the only thread that's in the process, it just ends the process because there's nothing left to execute. So in that case, process 102 would be terminated and then process thread two in process 100 could get unblocked at this point and then return, it would call return null and then that thread would exit, thread two would exit in process 100 and then in process 100, it could pass this join and go ahead and start waiting on what, it couldn't because, sorry, the first join is on thread one. So thread two is terminated, but this join is on thread one, so it wouldn't make any progress yet. So the main thread still blocked on thread one. So eventually, yeah. So any questions about this in last like two minutes because I don't think I have more time to go through it. Yeah, yeah, you have no idea. So eventually, in this case, thread one running, so that would also create a new process, so probably in 103, thread one run in 100, something like that, but in this case, there's no ordering on them. You have no idea which one's going to order because the wait happens after the print statement. So there's gonna be no ordering whatsoever. Yeah, so because I fork in main immediately, process one on one will do the exact same thing. So it will create two threads and then two of its threads will go ahead and create two new processes. So eventually, I mean, a solution should be on it. So this would be like my total process tree. So this would get created from thread one, thread two, thread one, thread two in their associated parent processes. And I would see essentially this. But this is just an ordering. So there's actually no ordering whatsoever. All right, we're good. All right, so also on Wednesday, during the practical hours, depending on whenever I get to Toronto, if there's a lot of people there in demand, I might show up there and answer any like super last minute questions. But with that, just remember pulling for you. We're on this together.