 All right, we are really embracing midterm. So we got two people here, or sorry, reading week. So I guess people have started reading week early, but we still have some stuff to cover. So this is slash was going to be a fun lecture, but hopefully it's okay with two people because you'll have to help me out with all the coding stuff because a lot of it is just kind of free form. So today we will be talking even more about scheduling and page tables kind of as just review and a fun example. So we kind of talked about priority scheduling and dynamic scheduling, but we didn't see any example of it. So that's what we'll do and it matches up to previous midterm questions. So something like this is probably fair game for quizzes and stuff like that. So this dynamic scheduling where you dynamically change the priority of a process as you're doing a scheduling might also be called something like feedback scheduling. And you basically create an algorithms to manage the priorities for you. And otherwise your scheduling algorithm is pretty much the same as we saw before. So we use set time slices and the idea here is we have interrupts every so often within that time slice and we measure CPU usage across that time. And then we increase priorities of processes that don't use their full time slice so that are probably more IO bound and not CPU bound. And then we decrease the priority of processes that use their full time slice. And the idea of this is to give essentially priority to IO bound processes. So you have good responsiveness while still giving being fair and giving CPU bound processes a lot of CPU time so you have good throughput still. So we pick the lowest number as the highest priority kind of what we've been seeing before and what Linux does. So each processes gets assigned an initial priority when it starts. So PN where N is the process number. And then we're going to pick the lowest priority number to schedule. And then if it yields we pick the next lowest number or if there's a tie we pick whatever came first in arrival order. And additionally if a lower priority tasks not even with this current running one becomes ready we would switch directly to it and just switch directly to it and let it execute until it yields or it's done its time slice. So then within the time slice we record how long each process executes for the time slice. And in this there are many timer interrupts during the time slice. So a time slice will be a multiple of timer interrupts. So you basically just count the number of timer interrupts that that thread or process whatever you happen to schedule is actually executing for. Then at the end of the time slice you update the priority of each thread with the. So the new priority is the current priority divided by two plus whatever the value however many time slices you were executing for and then you just reset the time everything is executing for back to zero to keep track of it for the next time slice. And the idea here is that, hey well this is fair because we're essentially halving the priority so that will converge to zero and this case zero is our highest priority. So we know eventually that it will run and you could of course play with these numbers to get kind of different trade offs but this is kind of one standard trade off you would do. So this is how it would look and this is like the midterm question or final question. I forget which one it was but it says, hey we have four processes and let's assume they all have a initial priority of zero. Actually the midterm doesn't say that you have to assume the initial priority is zero or you can assume whatever you want. There's two answers. So we'll first assume a priority of zero. So the question says, hey assume we have four processes all ready to execute and they arrive in order X Y A B and the question doesn't say what the arrival order is but it should have. So between those four processes A and B are CPU bound processes so they'll never yield their CPU time or do anything like that and X and Y are IO bound processes so they'll only execute for 0.1 seconds and then they'll block for 0.5 seconds. Then the question says, hey okay so we have timer interrupts every 0.1 of a second and then each time slice is a full second. So if you divide those two numbers each time slice is made up of 10 timer interrupts that we have to keep track of. So in this question it says what is the scheduling and each box here represents a timer interrupt. So what's gonna be the first thing we execute if they all have the same priority number and their arrival order is X Y A B. Anyone in class? X, right? It's the first one that arrived they all have the same priority number so you just tie do arrival order. Yeah, we got X in chat, we got two Xs, lots of Xs. So we'd schedule X first so it only runs for 0.1 seconds and then it yields and would block. So after it's 0.1 seconds after the next timer interrupt we would switch to the next task because this one would be blocked. So what would we switch to? Y. So we would switch to Y and Y would execute for the next time slice. Now Y is done and we have to pick a new process to schedule. So what should we pick? A, how long is A going to run for? So A could either run all the way to the end of the time slice or X here went to sleep after the first time slice. So it blocks for 0.5 seconds. So within five time slices after X goes to sleep it would be ready again. So if we assume that, hey we should just schedule that because we should just schedule that because it is a high priority one. We would go ahead and schedule X when it wakes up but in this case it's not higher priority. So whenever X becomes ready kind of at five timer interrupts after it goes to sleep we wouldn't schedule it because it has the same priority as A so we'd only schedule X in if it had a higher priority. So in this case A would just execute for the rest of the time slice because they all have the same priority and it was already executing so by the time X becomes ready it's going to, yeah when X becomes ready at time six we don't switch to it because it's the same priority as A so we're not gonna switch to it. And then similarly at seven Y is ready because it just blocks for 0.5 seconds but we don't again don't schedule it because it's the same priority as task A so we wouldn't bother scheduling it. So at the end of our time slice which is at T time 10 we would recompute the priorities of everything. So the priorities of X is, its current priority is zero so we divide that by two, get zero and then add however long it executed for in terms of number of timer interrupts. So it executed for one timer interrupt so it's new priority is one, process Y, same deal it also executed for one timer interrupt so it's new priority is one and then process A executed for eight so it's new priority is eight and then process B executed for zero so it's priority is zero. So at T 10 we get to schedule something else what are we going to schedule to execute? B is the lowest priority nothing comes close to it so B is just going to execute for the entire rest of the time slice so this is what our schedule is going to look like it would just be X, Y, A and then B for the remainder of the time. Okay, so let's do the same thing except now A and B have an initial priority of six and X and Y's priority are zero so if we have this case well X and Y have priority zero A and B have priority six so at the beginning what are we going to schedule? Again, they're already in the same arrival order X again, right? It was the first one there and it's also the lowest priority it's tied with Y so between X and Y, X came first so we'd schedule X then X yields and blocks for 0.5 seconds so we don't have to worry about it until the end of the six time slice so then we would execute Y and then it would go to sleep now instead when X becomes ready because it has a lower priority we would switch back to X from A so now A would execute we'd pick A because there's a tie between A and B so we'll just pick A and now at T6 X becomes ready and because it has a lower priority we would just go ahead and immediately switch to it so now we immediately switch to it we immediately switch to it and then at the end of the seven time slice Y becomes ready so we would switch to it because it is still a higher priority than A or B and then they're both blocked at that point so the only thing we can schedule is A or B and we still pick A then at time 10 so the end of the time slice we recompute the priorities again so X and Y had an initial priority of zero so zero divided by two plus however many timer interrupts that they were executing for so the priority of X and Y are going to be two the priority for A is going to be six divided by two which is three plus six so its priority is nine and then process B is going to have a priority of six divided by two which is three plus zero so now at this point we have to pick something to schedule again so what are we going to pick to schedule at this time instant at time 10 so X we got an X and a B so in this case X we can't schedule because it is essentially asleep until one, two, three, four its back like here so X isn't scheduleable Y is not scheduleable and between A and B, B has the lowest number so it's a higher priority so we'll go ahead and execute that for two time units and then after that X is ready again and X still has a lower priority than B so we would switch to X and then Y becomes ready and then for the same reason we would switch to Y and then after that we're between they're both blocked so now we just have to pick between A and B and we would pick B again so B would execute for another four timer interrupts and then X is ready again so we'd switch back to X and then switch back to Y and then this is what our schedule would look like any questions about that? Nope, we're good okay so this would be a little bit of a different question if A and B had priority of four instead of six in which case I guess I'll leave it up for you to argue about but if A and B had priority of four instead of six then B would execute for this entire time slice because it would have the same tied priority as X and Y so you wouldn't switch to it when they become ready. Okay, any questions about that? Cool so I guess before we get to the fun stuff oh yeah so the question is how does this how does this work with setting the number manually and just static priorities? So this is dynamic priority so that's it's just a different algorithm so you can initially set the initial priority but the algorithm will dynamically change the priorities so you get kind of good interactivity with IO bound processes and more throughput with CPU bound processes okay so another thing we could do another question that's on the midterm for this same schedule as A what if it was round robin so we can do that really quick so if this was round robin instead we would have initially we'd have X, Y, A and B so we would schedule X and then at the end of it's it would go to sleep after one time or interrupt so it would be so it wouldn't be scheduleable so our Q would look like this Y, A, B so we would schedule Y and then whenever we schedule Y what's in our Q is A and B so this is round robin so we're just picking whatever is at the front of the Q so then we would schedule A and then at the end of that whenever A is done so this is round robin and you're just going to change every timer interrupt and just go through all the processes so A would execute for one timer interrupt and then it would be sent to the back of the Q because it's still able to execute it doesn't block or anything so you'd get X, Y, A, B and then here you would have A, B in your Q so you would have A and then again you would have B, A after that then you would schedule process B and then at this point right here X is ready again because it's been five time units so what we always do is we put B, whatever task just gave up its time unit if there's any ties we put it at the very back so right before when X is ready our Q would be A because it was still in the Q before and then after A would be X, the newly ready one and then B would be after that so at T7 we would schedule A and then our Q would look like X, B and then at this time unit Y is ready again so when Y is ready again we would throw it at the end before we reschedule B or A at the end so now our Q would be X, B Y, A so we would schedule X it runs and then it gets blocked so now our Q is B, Y, A we would schedule thread B then we would have Y A, B in our Q and we would schedule process Y and this would go on and on like this for the question has you do this for another time unit but it's going to be the exact same thing so it's just the exact same thing but longer alright and that was round robin show any questions about that before something hopefully more fun okay so more fun so Wilkes-War multi-level page table is live and there's only like four people here and three in chat so hopefully this is okay so I essentially just wrote an MMU simulator and if you want to play with it yourself you can go into this lecture 23 directory in your in your examples and just build it using those commands and there's an MMU sim command so let's go ahead play with that a little bit so so here is our MMU simulator so right here we would so how the MMU simulator works is it has a root page table and we're going to assume that SV39 uh... kind of multi-level page table format and we will resolve all the uh... virtual memory addresses there's this function that says allocate page table allocate page table will return you a page so it will be four kilobytes and then you're free to do whatever you want with that page so first we allocate a page that we're going to use for our L2 page table so that's our highest level if we're using SV39 and we would set that to our root page table because essentially our MMU is just going to use the root page table to go ahead and resolve whatever the virtual address is to a physical address so we set the root page table to an empty page so none of the entries are valid none of them point anywhere if we tried to resolve anything just using this as a root page table they would all be page faults so what we're going to do is try to resolve the address a-b-c-e or a-b-c-d-e-f so if we want to craft our page table and actually make it resolve to something so let's try and make it resolve to cafe-d-e-f so if we do that then so how we do that is if our address is a-b-c-d-e-f well this part because it's SV39 this would be our 12 offset bits because remember each hex character is four bits so three of them is 12 bits which is the size of our page so this would be our offset so whenever we're translating we don't touch those we'll be translating a-b-c to essentially point to some physical page so if we want to know what index is to use well unfortunately there's only nine bits not eight so it's easier if we just write it out in binary so if we write a-b-c out in binary starting from the end it would be 0-0-1-1 because this is c as it is up there b would be 1-1-0-1 which would be b and then a would be 0-1-0-1 which would be a and then everything else would have zeros appended to the front so it'd be like zeros so we just have a bunch of zeros for the remaining numbers so if we divided up and then remember because all of our index bits in this are going to be nine bits so if we take a group of nine this is our index for L-0 because it's the lowest nine bits and then if we go ahead and count the next nine bits one two three the next nine bits would be here and this would be index of L-1 and then one two three so one two three so all the other padded bits up here would be the index of L-2 so if we want ABC EDF to resolve to a virtual address we know what indexes we have to use so if we convert index L-1 to back to just decimal so we can read it that one is a four it's four plus eight plus sixteen plus thirty two plus what's that one twenty eight and if you add that all together it is one eighty eight and then for L-1 our index would be one plus four so we need to have an entry at index five and then for L-2 it's just a big old zero so if we want to resolve this so we want to resolve this is our virtual address here say we want to resolve it to physical address C-A-F-E and then the offset would not change so it would look like this so now this is our offset we want to resolve it to that so in that case we would need to have our L-2 page table which would be our root page table would need an entry at index zero so we'd need a valid entry at index zero and it would need to point to a L-1 table now if we want to resolve this we need to make sure that there's an entry at index five so there must be an entry at index five that points to an L-0 table and then in the L-0 table at index one eighty eight then we want the physical page number because that's our lowest level so we're going to use that for translation so instead of pointing to a page we know that it would point to a physical page number and in which case if we want the address to resolve to C-F-E then the physical page number would have to be C-A-F-E everyone agree with that? so let's do that then so that's exactly what I do so I allocate page table just gives me a page and because it's a page table and I know I'm using it as a page table I don't have to worry about offsets within that because I'm essentially just using the index to get the using the index because I know how many entries there should be so for the L-2 page table I make an entry at zero and I make it so P-T-E from page table all that does is essentially grabs so the page table is a page so all that allocate page table guarantees that the last three hex digits are all zero zero zero like it's all aligned to a page so P-T-E from page table would just essentially just get the physical page number from that and in the P-T-E one of the bits says that it's valid so that's all it's going to do so P-T-E from page table essentially just gets the physical page number of the page table and that's just shifting by the number of offset bits so we're just getting rid of the offset and then that's our physical page number and then if we have a physical page number well if you look at the format of a page table entry there's ten bits that are meant for all the permissions and everything like that so we just shift it by those ten permission bits uh... the physical page number by the ten permission bits and then we set the valid bit so we for this we're only going to care about the valid bit a real implementation we actually care about like read write execute things like that but we'll just set the valid bit which would be bit zero if case you're interested in this return the page table entry so so we create an L2 page table and then we create an L1 page table and then we make sure that index one of that L2 page table points to the L1 page table that we just got back in this case from the kernel or the operating system or whatever gives us our pages after that we need an L1 or L0 page table so we need an L0 page table so we'll allocate another page so now we have three pages and then we're going to make L1's entry for five point to the L0 page table in the same way we made the L2 point to L1 so we're going to do that and then in the L0 page table that's where we put our entry in the right spot so again we said that should be an index one eighty eight so if we get the page table entry so if we get the page table entry that represents cf cfe c-a-f-e then that would be able to go ahead and resolve in the MMU whenever it does a translation so if we use the MMU now with the root page table so it's just going to use the root page table and follow it around if we go ahead and use that now when we actually run this we should see that a-b-c-d-e-f resolve to c-a-f-e d-e-f okay so that's cool it works so what if anyone tell me what this might resolve to? will this resolve to an address or be a page fault? yeah yeah I just changed it to zero yeah yeah I just changed the last hex character of the address to a zero instead of an f okay but I'll still get a physical address and it'll look kind of like that okay so we have a page hit the physical address is going to be mostly the same but just have that change everyone agree okay so if we compile that yeah so if I do that it would actually resolve because I'm just changing the offset the virtual page number is the same I'm using the same root page table so it's doesn't have to do anything special if this was this would actually be as well cached in the TLB so it would have this translation already so if I access anything on the same page it's the same translation so it wouldn't translate it again which is what we saw before and it doesn't matter if it's anything on the same page it would still resolve so ABCD or ABC zero zero zero that would still resolve to the same address or the same physical address there would be an entry there okay well any questions about that so the question is does the page table stay in the same memory space as the programs so the page tables would be in kernel space so the processes aren't allowed to touch them so the kernel manages all the page tables in this I'm just kind of simulating it but in the real kernel the kernel is the only one that it can swap the page tables so essentially the kernel is going to manage all the page tables and then when you swap processes you'll swap it'll swap this root page table and then that will essentially switch address spaces so so the MMU is just doing the translations so that so the MMU is part of the CPU yeah so the MMU all of its job is it's on the CPU and all its job is to resolve virtual addresses to physical addresses and then the CPU would go ahead and issue whatever memory stuff it needs to do okay well in this case I'll leave this to you then so say I'm just going to access address 7 f f f f f f f f so if I try to translate address 7 f f f f f f f which is essentially all that is is all 39 bits are set to 1 so all 39 bits are set to 1 and right now if I compile that and run it I would expect to have a page fault because I don't have any translation for it so it would so if I run that I get a page fault and why because first it's going to look at the root page table anyone want to tell me what index spot it's going to look at in that table 7 f f so 7 f f is 7 f f is too many bytes it is 11 bytes or 11 bits so if everything's a 1 and all of our indexes are only nine bits it would be 1 f f which would be it's looking at index essentially the last one which would be 5 11 so first access is going to be the index 5 11 and it doesn't have a valid entry in it because we didn't put one in it so it's not going to result anything so we're going to have a page fault immediately so if we wanted to we could create another l 1 page table oh yeah okay check out it too there we go so if we create another l 1 page table well we create another l 1 page table and then our root page table is that l 2 page table so l 2 page table if we want to start resolving this we need an entry from 5 11 and then it would point to our l 1 page table 2 that we just allocated so now it's pointing to a new one again this is still going to be a page fault uh what the hell they do oh whoops pte from page table so i have to put in the right format sorry so this is still going to be a page fault because i resolved the first one i swapped to i made it to an l 1 page table but then that didn't have an entry so i couldn't resolve it but i got a little bit closer even though the end result is i still have a page fault so i need a l 0 page table for this now so if i have an l 0 page table then my l 1 page table that i just made needs to have an entry of 5 11 that points to the one that i just made so pte from page table l 0 page table 2 so now it's pointing to the new page table i'm a step closer because now i've resolved two levels of page table but now in that new l 0 page table there's no entries so again if i go ahead and compile it and then run it it is not it's still going to page fault so the way to get it to not the page fault is i need to make an entry in my l 0 page table one uh of again my index is going to be 5 11 and i would need a page table entry from physical page number what physical page number do i want to use anyone with any suggestions this could be anything any fun numbers what about uh that's fun so if i do this and compile it now yeah we got beef we got dead beef so if i compile it right now what am i going to see 7 7 7 f f f f f get or sorry just 7 f f f f f f get resolved to yeah so it's going to be dead beef so it's going to be dead beef and then i'm going to use the same offset there so the same offset in seven seven or seven f f f f f is the last three digits so i'm going to get those three so i'm going to get dead beef and then three extra apps at the end so i'll have a total of four apps so if i run that i go ahead and i resolve it to dead beef so yeah any questions about that or anything other things you want me to do so so yeah you can see here two kind of different scenarios and how many page tables you might need to have to essentially translate some virtual addresses so best case with three page tables so if i only have three page tables i can translate up to 512 virtual addresses if they all fit in that l1 table if it's all full if i was unlucky well then i would have my one root page table but for those you know say i had 512 page table entries again i could be unlucky and they're super spread out so if they're super spread out that means all of my l2 page tables my l2 page table would be full of entries and each would point to another page so it would point to 512 pages and then each of those page tables would have to point to an l1 so there'd be another 512 pages and to resolve and then each of them would have a single entry so that's kind of best case i would to translate 512 addresses i would need three page tables and then worst case i would need one plus 512 plus 512 so i'd need was that 1025 page tables to translate all the addresses all right any other questions well i'm here and you can play with this because i virtual memory is like the hardest thing to wrap your brain around so let's see so here two if i wanted to well if i want both of those virtual addresses to point to the same page well all i would have to do is so say i want both of my virtual addresses assuming they have the same offset which they don't so let's change this one so let's make both of our things have the same offset so if i want them to resolve to the same physical address because they have the same offset all i do is make their page table entries point to the same thing and then when i compile it and run it they're going to resolve to exactly the same address so i would have two ways to refer to the same memory location so essentially i'm sharing pages this could be done in the same process it could be across different processes it's up to the kernel to the side and manage all these page tables and yep this is all on github so you can play with it and understand it all right nothing else so thanks for the people that showed up today so uh just remember i'm pulling for you