 Welcome to the Void of Review. So hopefully people are in Discord or watching. So what do we want to start with? So the first thing was walking. So this question, wait, how many people are actually watching? 14. So yeah, go in Discord, go in the final exam channel, and let me know what you want to do. So there was this question from the 2023 fall, you see, 344 final that we went over in the last lecture. So do you want me to do this again or what? Or just try and find some other random threading questions. Do you have other examples? Sure, we can see what else we got. We got, let's see, threading processes, processes and threads, locking. So we got this one. So we can start with this file directory. All right, so let's start with the file system one and then we will come back to this. So we got the file system. All right, so whoops, come on. So it says a file system block size of 4,096 bytes. I always like to just write powers of two. And for byte block pointers, again, just write powers of two and 128 byte inodes. So that is what to the, well, we shouldn't need powers there, but that's what to the seven. Yeah, that was right there just in case. I nodes of 12 direct pointers, one indirect pointer, one double indirect, one triple indirect, analyze the output in the directory. The output format includes the I-node numbers in the first column. So these are I-node numbers. All right, so it says when editing the file, which I-nodes when editing the file, C, which I-nodes contents are modified. So since C is a sim link, so C is just a name. So what hopefully you got from lab six is that I-node 22, that is the I-node that stores the name B as part of the sim link and not the actual I-node with the contents modified. If you open the file and go ahead and edit it. So since C is a soft link or a sim link, and it points to B, then we would go ahead and find B's I-node, which is 20, which is a natural regular file, and that is what you would be editing. So we would be editing I-nodes 20's data. So all good with that part is just like, hopefully, we understand what the sim link is, and have another, wait, are my monitors messed up? Yes, yeah, and the exam's cumulative. Let's see. So everyone good with the first part of that? OK, thumbs up, great. All right, so next one is, is it possible to store all the I-nodes associated with this directory in a single block? Provide an explanation supporting your conclusion. So this is where we actually use the power of seven for something useful. So remember when you did lab six, hopefully, like the I-node table, it's basically just a big array of I-nodes. And they're one index instead of zero index, because the zero index is meant to say invalid file descriptor. So we need to figure out how many I-nodes we can fit on a single block. So we have our block size, which is to the 12, divided by the I-node size, which was to the 7, which equals 2 to the 5, which means we can store 32. I-nodes on a block. And if we remember, we start off with 1 to 32 on one block, and then I-nodes 33 to like 64 on the next block, so on and so forth. But the question says, is it possible to store all the I-nodes associated with this directory on a single block? Provide an explanation. So all of the I-nodes are between 1 and 32. Therefore, they could all fit on the block. So yes, they fit on the first block in the I-node table. All right, next one says, determine the number of data blocks that file E will occupy. So this is the size column here. So it is 5,000 bytes large, so it needs to go ahead and take up more than one block, right? So it takes up one block, which is 4,096, on its block 0. And then, well, it would have to consume another block, but it would have some internal fragmentation. So it would store the remaining whatever that is, like 904 bytes on block. 1, therefore, 2 blocks total. And you didn't even need to write that much for the answer. It was a one marker. You could just write 2, and you're good. All right, calculate the number of bytes lost to internal fragmentation for file E. And well, this is how many bytes we're using. So on block 1, so we're not using the entire block. So the byte lost to the fragmentation is just the size of the block minus what we're actually using, which is what, like 3,192. Also for a shortcut on the exam, if you leave your answer like this, whatever, that's fine. If you don't have a calculator for whatever reason, I mean, you don't really need a calculator for this exam. You probably go faster with that one. But some of us like our comfort calculators, so that's fine. All right, next one. Determine the number of directories contained within the parent directory represented by iNode19. Describe all the names of the directories linked to this iNode. So this is the one that we need to do some reasoning about. So iNode19, which is the parent of this one, has five links to it. And we can probably determine all of them. So there is one entry. So let's try and figure out what all five entries are. So the first entry is dot, dot, in whatever this directory is called. Did I even say what it was called? Execute in a directory, in current directory. And then if you back up and go into iNode19, well, then it has a dot in the parent directory itself. And then it would have some type of name in the parent's parent directory, because it needs to have a name at some point. So that's what it would have to be. And then the other entries would just be just other names. So the other ones are probably just dot, dot, in dir1, in sum dir2. So it says determine the number of directories contained within the parent directory represented by iNode19. So that probably means we have three in total. So they would be, and they would all have a dot, dot in their directory to get back to the parent. And then the parent would name them somehow. So we would have three directories in total. So it would always have, every directory starts off with a baseline two links to itself. So like dot and then the name and whatever directory it's actually in. And then every directory you make in that will have an dot, dot entry to get back to the parent. So you can figure out how many additional directories are within it just by the number of links and knowing that. So we got three in total. All right, next one. What occurs when you execute RMB? Is it possible for the file to be deleted? So let's look at B. So in B, well, it is a hard link. So it's got two names pointing to the iNode. They happen to both be in this directory. There's A and the B. So if we do RMB, all we do is we, okay, cat, just remove the name B. Decrement, number, hard links from okay to, from two to one. Right, and nothing's deleted. We only delete things if the number of links to it goes to zero and then the operating system could delete it if it wants to, but it's probably lazy and just won't bother. We'll just mark it unused and then just overwrite it. And then it says, assuming B is removed, what is the impact on C and the associated iNodes? So C was the soft link to B, not gonna change anything with C. So nothing's going to change at all. Nothing changes just if you were to try an access file C now, you'd probably get file not found error because the name B does not exist in this directory. All right, next one says, if file A is expanded to use 13 data blocks, are only 13 blocks in total required for its contents, explains the storage mechanism for these blocks. So the answer to this is new. The iNode can only store 12 data blocks and 12 direct blocks. For the 13th, it needs an indirect. So, how it would look like is you would have your iNode here. So here's my fancy drawing of an iNode. It would point to 12 blocks directly. Then we'd have to use an indirect block. So it would point to a block that could be full pointers. In this case, it would only have a single pointer in it. Wow, that's an eraser. That is not what I meant to do. So that is my indirect block, which would be stored on a block. And then that would point to the 13th data block of the file. So in total, we would have to use 14 in total. All right, then it says, how many data blocks are needed if a file grows to 1,040 blocks, explained again. So same idea. Okay, my cat's being very needy, yes. All right, so 12 blocks would be direct and then we would have our indirect block. And we can figure out how many pointers we can fit on a single indirect block. So if we go back here, well, our block size is 4,096. Each pointer is only four bytes. So we can store it to the 10 on each one, right? So to the 10 is just 1,024 blocks. So that brings us up to a total of what? So that's 1,036. So yeah, we ran out of space on our indirect block, so we need a double indirect. So it would have one double. Like, likely only have one entry. That points to another, like kind of like page tables, like this is your level one, this is your level zero. So that would have all the entries to the blocks. And in this case, what? We need four more. So we need four blocks. To point to four more data blocks to get us to our 1,040. So the number of data blocks in total we need to store this is the 4,040 for the actual file and then we need one block here for the indirect and then one for an L1 and then one for the L0 double. So that means we have three additional ones. So that brings us up to a total of 1,043. All right, good on file systems or anything else? You want me to go over file systems before we change topics and do something else? All right, we're good on that. So we wanted what, a thready, a thready type question. That was all. Let's start with this one. So I'll give you some time to read it. So this, or you can pull it up yourself. This is the 2,000, what is this? This is the 2023 winter ECE 353 exam. So give you some time to read this and then we can go over it. Oh wait, these answers are available elsewhere, right? Yeah, yeah, everything is on my site here. I will throw a link in there. So if you haven't done it yet or you just want to browse through them, here I'll paste the link. So there is a link in the Discord for the course archive of all the courses I've taught. So 2023 fall, ECE 344, good example. 2023 winter, ECE 353, good example. The 2022 ECE 344, I did not write the exam so I can't post it. I wrote parts of it, you can find it on school but it's not terribly relevant. And then the 2021 CS111 from UCLA is also relevant. But you can see I've kind of changed style since that one and the other CS111s are not terribly relevant. They were like online ones during COVID times and I mean, it's the same course. You can go look at them but the style of the questions is probably a bit different. I think for one of them, I only had like one TA2 to help grade. So that was a nightmare. So, all right, let's go over this one. All right, and anyone joining late? Again, use the Discord, let me know what you wanna do. Otherwise I'm just gonna sit here and I guess do random questions. So I'm here for like what? Another hour 40, so we can do whatever and I just wrote it. Then yeah, questions, there's no solution for the 344, 344-2022 file there is not that I know of. I did not write the solution, so I can't post them and I believe the course website doesn't even exist anymore. Like I wasn't the one hosting it. So yeah, I wouldn't worry about that exam too much. I didn't write all of it, so I wouldn't worry about. All right, let's do this locking question. So 10 points should only take us 10 minutes, should be fairly quick. Looks like I kinda just threw this in here just because we need some type of locking question. So we have three threads, create three independent mutexes and each thread executes a different function above. So we got thread one executing this, thread two executing this, thread three executing this. Then it says identify, explain the deadlock condition that can happen in this program. Well, if we look at the dependencies between all the locks, so thread one tries to get, after getting mutex one, it tries to get mutex two, thread two, well, after getting mutex two, it tries to get mutex three and thread three after getting mutex three, it tries to get mutex one. So we have a circular dependency here between the threads. So what could happen is thread one acquires mutex one, then before it acquires mutex two, we context switch over to thread two. It acquires mutex two, so now thread two has mutex two, then thread three goes ahead and gets mutex three and then gets context switched and now no matter what, no thread can make progress. So if we context switch to thread one, tries to get mutex two, which is held by thread two, can't make any progress. We context switch to thread two, tries to get mutex three, which is held by thread three, so it can't make any progress. And then thread three tries to get mutex one, which is held by thread one, so it can't make any progress and we are dead in the water. So there are those two that says, propose a solution to avoid deadlock in this code, modify the code and describe your proposed solution and briefly explain how it addresses. So we could just swap these two lines. Then if we swap these two lines, we break circular dependency, right? Break. So all the mutexes are always acquired in the same order. We don't try to acquire mutex one. We don't try and acquire mutex one while we have mutex three. So if we just swap these order, so it looks like this, then, well, we can't have a deadlock. So even with that swapped, thread one could acquire mutex one, thread two could acquire mutex two, then thread three, if it tries to acquire mutex one, it can't because thread one already has it, so it can't make any progress. If we context switch to thread one, it can't make any progress because thread two has the mutex. But now if thread two, we context switch eventually to thread two, it can make progress because someone else doesn't have mutex three, right? So it can always make progress. And then, boom, it can make progress. We're done. We don't have to do anything. And then the question, is there a canonical method for finding deadlocks, or do we have to think really hard and explore all the possible? So just like programming, just like real programming, huge kind, I just have to reason about it. So the two main, like there's four conditions for deadlocking. Two of them are like a mutual exclusion and no preemption. But we assume that those things hold anyways. So of the four conditions for deadlocking, the only two are relevant really for us that we can do anything about. We can either break circular weight, or anyone remember what the other condition for deadlocking is, as a review to you, because I already know the stuff more or less. But yeah, you kind of just have to think about for all the threading questions if you have to reason about it. Something like waiting for a lock while holding a lock. Yeah, close. I mean, it's that idea. The official name for it is hold and wait. So that's the other condition. So we could either break circular weight or hold and wait. So we could propose another solution where we go ahead. And if we have the lock and we could try to acquire the other lock, if we don't get it, we can give it up and then put ourselves to sleep or something like that. So that's generally the other solution. All right. Centiphores. So here we go. It says, considering the following code, we got eight threads. Centiphore that, well, initially we have to pick what it gets defaulted to. Then we have a question we says, we have a run function that gives us thread ID. And we only want thread 0 to execute this initialize everything function. And then everything else should wait for that to happen. So it says, using a single center force M, enforce the synchronization complaint specified in the comments, you may add code directly above in C or pseudocode. And be sure to give the center for initial value. Try to make a solution that supports any number of threads. Assume that thread 0 already exists. For a maximum of seven points, you may use total threads and assume it's the number of threads executing the run function. So two different solutions we could do for this. One, the simple one that gets you the maximum of six is, OK, well, I could initialize my center for to 0. And then my code could look like this. If thread ID is 0, then initialize everything. And then I could have 4 in i equals 0, i is less than. I could use total threads. Then I could do my post here. So after it's done initialize everything, it can post it total threads times. So if there's eight threads, it would increment it all the way up to eight. And then unconditionally, every single thread can go ahead and do a sum weight here. So initially, if we're executing a thread that is not thread 0, then we've skipped through the if. Then it would just weight current value 0, can't make any progress. And then eventually, we'll have seven threads at that weight, all waiting. And then thread 0 come along, call initialize everything, and then post eight times. So the seven threads can then make progress. And then it posts once for itself because thread 0 is going to weight as well. Or you could put an else statement and then only post total threads minus 1 time. Doesn't really matter. The other solution you could do is a solution that is given in the question. So same idea except we have every other thread except thread 0 waiting. So if they might all make progress, they might all wait there. And then as soon as thread 0 is done, it would post. So it kind of starts a whole cascade. So it would post that value from a 0 to a 1. And then one thread is going to be able to progress through the weight. When it progresses through the weight, it posts and increases the value from maybe 0 to 1 again. So that another thread can make progress and they all kind of help each other along. And eventually, all threads will make it past that else statement. And the final value of the sign for will just be 1. But everyone will make it through and reinforce their synchronization primitive. Joy. All right. So any questions, comments, concerns about this one? Or we're good? Or am I myself in the void? Hopefully not. Anyone discord any life? We're all good? Next part? OK, good. Got at least one. Thank you. All right. So let's consider the following solution without center force. So if the thread ID is 0, initialize everything. Otherwise, yield. So it assumes we have cooperative user threads. So there's only one real thread. We can't have any parallelism or anything like that. Explain why or may not the solution is correct. And this actually is correct if similar to Lab 3. If it's a FIFO queue, then this would actually work. So either thread in the best case, thread 0 is the first one. And then it initializes everything and we're done. In the worst case, it's the last thread that would run. So say we have eight threads. What could happen is we have thread 1, 2, 3, 4, 5, 6, 7. And then thread 0. But if it's a FIFO queue, well, all the threads, if they're not thread 0, they just yield immediately. So in this case, thread 1 would go to the back of the line assuming FIFO, then thread 2, then thread 3, then thread 4, then thread 5, then 6, then 7. And then finally thread 0 will run. So they all give up their turn once to make sure that even the worst case, thread 0 will execute. Then it would initialize everything and then we're good. So that would actually work. There's no parallelism. There's no anything like that. And then the next part just says, OK, let's assume we have cooperative kernel threads running on multiple cores. Explain why we're not. This is correct. So no, this would not work. So since the kernel threads, we could just have eight cores on our machine such that everyone gets scheduled on its own core, basically its own CPU. And then, well, if you yield and there's nothing else to even execute, it's not even going to do anything. It's just going to keep on going. And you don't have control of the scheduler or anything like that, so you don't know what's going to happen. So definitely not. All right. Should we do a threading question? I don't think we've done a threading question yet. Or anything else you want to go over? Details or whatever else we want to know. We can do this. All right. So threads, consider the following code. Let's read it quick. So we'll start at main. So main, we create num threads. So what's that? Eight. So we're going to create eight threads. We initialize some array with some values. What is the array? Well, it's some global variable, right? These are our globals. So we create a bunch of worker threads. So we create eight threads. And then we join them all and then have a global sum at the end. So in here, each thread gets a thread ID. And each takes a chunk of the array. So it tries to divide the array up, depending on its thread ID. So the chunks are the rate size divide num threads. This divides nicely. We can just assume it always divides nicely. So it would get thread ID times that chunk to know what chunk it should operate on. And then the end is just the start plus the end. And what they're doing is we create a variable called local sum. So because it's in the thread function and it's called local sum, which is kind of a hint too, it's on the stack. So each thread has its own independent copy. It's not shared. It's not global variable. It's not on the heap or anything like that. So they would all have the local sum and then some their part of the array. So from start to end, so they sum all their part of the array and then add it all together to the global sum, which should be raising alarm bells. So array, well, everyone's just reading from it. So even if it did overlap, doesn't matter. The most important thing with threads is that we want to prevent data races. And so important that we should know that the definition of a data race to concurrent accesses to the same memory location with at least one being a right. So in this case, our globals are the array and global sum. So the array we only read, we don't write. So we're not in danger of a data race there, even if the array didn't overlap. In this case, it doesn't overlap. So even if we wrote to the array, as long as this is all of our code, we would be okay. Then local sum is no problem. And then global sum, we have a plus equal. So it's reading the current value and then writing an updated value. So this is like red alarm bells should be going off. But this is probably a data race. We have multiple threads all updating the same value. So very much alarm bells should be going off there before I even read the question, right? So let's see what the question is asking us. So it says, is it necessary to protect access to the array variable during the execution of the threads? Explain why or why not? So no, it's only red. And on top of that, not only is it only red, each thread is accessing like its own different part of the array. There's no overlapping between them. So it's fine, no problems at all here. So then it says, is it necessary to protect access to a local sum variable during the execution of threads? Explain why or why not? So no, I mean, it's on the thread stack which is independent. So no concurrent accesses, it's just one thread. So we're all good. Then last question, well, not the last last question but you know what I'm, last question on the page. Is it necessary to protect access to the global sum variable during the execution of threads? Explain why or why not? So like we said before, yes, it's accessed concurrently by multiple threads and it's doing a right. So we definitely have a data raise. So it says, propose a synchronization mechanism to prevent all data races between threads executing run while ensuring the correct calculation of global sum. You may propose changes directly on the code itself, use pseudo code, describe your changes or a mixture. Like I said, you don't really have to write that much code as long as you have the right idea and can explain it, that's fine, you're not a compiler. So in here, well, what we could do since it's a global variable, we can just create, you know, a mutex, so static p thread mutex, mutex equals some initializer stuff. And then we could just do a lock to the mutex and put it all in a critical section. So lock the mutex, unlock the mutex around the global sum. So now, because of that and now of dating the global sum, it's a critical section, only one thread can be there at a time. So we can't have two concurrent operations happening with that variable. So we're all good, we prevent our data race. So another thing you could say is this, well, some other students answered that, we kind of talked about atomic a little bit, like you can have atomic integers that do increments atomically, but didn't really talk about them in the course. That'll probably be a later course, but if you said that, that is also fine. Then next one says, if threads were changed from joinable threads to detached threads, would this lead to any issues? Oh, so anyone think of this one? So right now all of the threads are joinable. So we create them and then we wait for them to join. So what if threads? So anyone remember what a detached thread does? Oops, in val, nope, you don't get an error from detaching threads. That would just be like after we create the threads, instead of joining them, so if instead of doing this, we just p thread detach thread. Oh, if you join in there detached, no, wait, did I say that? From joinable threads to detached threads would this lead to, okay, so yeah, I guess one, if we did not delete the join calls and we made them detach, we'd get an error from that, but more having to do with what the output of this global sum would be. So yeah, detached thread, something like, let a thread run to completion, but we don't need any return value from it. So kind of like that. So joinable threads, right? When they're done, when they terminate, they're finished their function. They're kind of like zombie threads, so we have to join them in order to clean up all the resources, but we know that they're done, just like waiting for processes. So if we make threads detached, that means that after the thread terminates, it just gets cleaned up immediately and we won't know whether or not it's finished. So if we have detached threads in this case, well, we're not going to know that they finished. What could happen is we detach all of our threads and then since we're not joining anything, so detached just means they run and they'll get cleaned up whenever they're done executing, but in this case, in order to get the correct global sum, we needed to make sure all the threads were finished, so the join was actually helpful for that because if you join it, we know it's done, we know it's terminated, right? So in this case, if we detach, what might happen, maybe unlikely, maybe likely, who knows, we detach all the threads, they run and then what could happen is the main thread after it creates and detaches all the thread, just prints a global sum and it's initialized to zero. It could just print the global sum as zero and then return from main, which exits the process, which exits everything and all the threads are dead, so what might happen if you make the threads detach is, well, it just prints out the global sum is zero, process ends and nothing happens, so any questions about that? That would obviously be bad, so in this case, it's kind of nice having joinable threads because we know when they're finished, so detach threads, generally, we don't really care when they're done, that's when you would use a detached thread. Yeah, the question is it because the main thread might run before the other threads if they're detached? Yeah, so just like with processes and threads, you don't know the order that things will run, so if we have joinable threads, well, the main thread creates eight threads and then because of this for loop that joins, well, the main thread's gonna hit join eventually, maybe the thread's already terminated, maybe it's not, but if it's not, it's gonna wait for it to terminate and then the main thread can't make any progress anymore and then the only way for the main thread to make progress past this for loop is if all the other threads are terminated because it joins on all of them, but if they're detached, well, what the main thread might do is just create all the threads, they're detached and then it just keeps on executing, right, and then prints off whatever the current global sum is could happen that none of the other threads execute at all, so prints off global sum is zero, returns from main, which then exits or terminates the process and then it's gone. All your other threads die because they all live within a process. All right, all good with detached threads and all of those fun things. And yeah, you can ask general questions too if you want. I'm here for you for a while, but there's anything you want me to do besides just doing questions. Whoops. But I'm happy to do questions too. Question on condition variables, that's a good question. So we had a question on condition variables on the other exam we did, we can revisit that quickly. On this one, I don't think we did. Yeah, let's check the CS111, CENIFORS, memory allocation, no, so here. Oh, wait, I shouldn't say anything. Crap. Condition variables, calculation stuff for blocks and inodes, calculate how much memory we should need to store page tables. So for calculations for blocks and inodes, we did one question, like where is it? So calculation for blocks and inodes other than this one, like did this question cover the inodes stuff or what are you wondering about? Oh, I'm being harassed by a cat. Can you show us your cat? Oh, geez, let's see, he's grumpy. Hey, Dexter, yes, okay, yes, you're grumpy. He just wants to be pet, not displayed. Yeah, I'll blame him. Poof. All right, so is this okay for blocks and inodes or is there specific blocks and inodes question? Also, his eyes look weird because they're green and there's a green screen, so they look black. So here, first let's do condition variable because we went over one kind of, so we did do condition variable for this question where we tried to implement a CENIFOR with a condition variable. So I'll just go over it quickly again, but we did do this on the last lecture. So, so it's given a struct and functions for CENIFOR implementation using condition variables and the provided code, explain any issues, assume the existence of CENIFOR init that initializes the CENIFOR's mutex. Poof. Condition variable and value focus primarily on thread safety and correct CENIFOR behavior. So with the condition variable functions, remember you can think of a condition variable kind of as a queue. In this case, it's what did we call it. This is called is positive because it's going to wait. We're implementing a CENIFOR, so it's gonna have to wait if it can't decrement the value because it's not greater than zero, so it will go ahead and go to sleep. So we use the CENIFOR to go ahead, sorry, a condition variable to manage its place in queue. So I like to just think of it as a queue and then we can have our mutex here and just say that it is unlocked. So it says, assume that the initial value is zero, so value zero. Describe a sequence of context which is between two threads currently calling post. So we have two threads calling this function, thread one and thread two. Some students when we were grading, like they just didn't read the question of two threads concurrently calling this and then like some students like just didn't execute the lines in the order they're written, which C is going to make sure that happens or more or less. So yeah, code can't do impossible things. So it says describe a sequence of context which is between two threads concurrently calling post that could lead to unexpected output, specifically consider the final value of the CENIFOR after these operations. So we have two threads calling post and if the initial value is zero, well, if it looks, if it atomically increments like it should, well, it should increment it twice. So it should go from zero to two. So if we can describe a sequence of context which is such that it does not have a final value of two, that would be bad. So for this, so plus plus value, that means it will read and then increment in a register and then update the value in memory. So it's a read followed by a write. So what could happen is thread one reads value zero, then we have a context switch and thread two reads value zero and then we're screwed. All right, then they're both going to update their own value in the register from zero to one and then they're both going to update the value as being one and then that's it. So the final value is one, which is bad. And then, yeah, so let's finish this quick. So this CENIFOR initialize to value zero, describe a scenario where a lost wakeup occurs due to concurrent execution and post and weight by two different threads. So what could happen for the lost wakeup is well, thread two goes here, acquires the lock. So thread two has the mutex, then thread two reads the current value of zero and goes into the while loop, but we context switch right before it calls weight. So it is currently not in the queue and then in thread one, while it updates the value from a zero to a one and then it calls signal, which will wake up a thread in the queue, but currently there is no threads in the queue. So it does nothing and then thread one is done. Then when we context switch back over to thread two, it calls weight, which puts itself in the queue. So it would look like it would put itself in the queue and then also atomically give up the mutex. So now it's just in that queue, it's asleep and nothing's ever going to wake it up. So does it work? And then other question is value plus plus atomic or is it the same read-write issues as plus plus value? So both of the increments post fix and prefix are the same thing. So they both do a read and then a write. They are not atomic unless it's really ugly in C but you make the integer atomic, but we didn't do that. Can you go over how to calculate memory? All right, memory, let's see. Let's do a virtual memory question. Everyone wants virtual memory. So with virtual memory, there's not too many cat hair. There's not too many different things you have to actually memorize for that. Like there's not, this isn't really a math course in most of formulas you can kind of figure out. So if we have a 39-bit virtual address and we also have a four kilobyte page size, so that is four kilobytes is two to the 12. We have an eight-byte page table entry, which is two to the three. Sorry, I'll just erase this. So our page table entry is eight bytes or two to the three bytes and our page size is four kilobytes or two to the 12 bytes. So in here, it says we have a 39-bit virtual address. If we have a, if we write out the bits of an address, well, the lower bits are always the offset or like what byte on the page do you want to access? So it's just log two of the page size. So in here, if we have two to the 12 bytes, well, we need 12 bits here. So 12 bits or the offset. And then the remaining bits in the virtual address, so we have 39 bits total. If we subtract that from 12, then we get what, 27 bits, oops, 27 bits. And that's how many bits we need for the virtual page number, right? Assuming like just in total, not talking about multi-level page tables or anything, we just know we need 27 bits for the virtual page numbers because, well, it should make sense that there are two to the 27 virtual pages. And there's a, I mean, there's a few different ways to verify this, that makes sense. So if we have a 39-bit virtual address, that means we can access or address up to two to the 39 different bytes. And then if we divide that by the page size, which is two to the 12, well, then that also equals two to the 27. All right, it's just basically saying how many pages do you need, like how many virtual pages do you need to span the entire address range? So we could also do the same thing and divide up our physical address. So this is our virtual address. Now with the physical address, same thing, all the page sizes are the same. So it'll have 12 bits for an offset, but the number of physical pages we can go ahead and address depends on how big, how many bits we have our physical address. So we have 59, sorry, 56 bits for the virtual address. So 56 minus 12, that means we have 44 bits for the physical page number or PPN. So with just that and figuring out those, we already answered some questions. So how many bits are needed to store the physical page number? Ow, sorry. Store the physical page number in the PTE. Well, we need 44 bits, right? So what other essential piece of data is required in the page table entry? Well, there's all of those permission bits and everything. At minimum, you need a valid bit, but likely there's gonna be like a read bit, write bit, execute bit. There's like a custom bit that we use to implement copy on write. Now we know that there's also like a reference bit, which whether or not that page was recent SRAM, accessed recently-ish. So that would be used for like the clock algorithm, everyone's favorite. So lots of different things, but the essential is the valid bit. So that says what is the limitations of the SV39 virtual memory system when handling a one terabyte of physical memory, which is 1,000 gigabytes, which is two to the 40 bytes. So because of this, well, I mean, our physical address is clearly bigger. Like it only needs 40 bits to be able to address every single byte of physical memory. So we have 30, or sorry, 56, which is bigger. So we can address all physical memory, but because our virtual address is only 39 bits, that means only, oops. And remember, virtual memory is what our processes sees that actually like execute what we care about. So processes are, so the processes would be limited to 512 gigabytes of memory, which is our two to the 39. So a single process couldn't use all physical memory, but if you're running multiple processes, which everyone is in their operating system, right? One process can use 512 gigabytes, and the other can use 512 gigabytes, and then you can use all of the physical memory. So just kind of depends, but any one process can only address up to 512 gigabytes of memory. All right. So yeah, that says determine the physical address. So yeah, any other calculations, the only other calculations you really have to worry about here are like multi-level page tables. So if you have a multi-level page table, the key idea for a multi-level page table is always that each smaller page table, like the size of each page table is going to be equal to the size of a page, right? So since each smaller page table is equal to the size of the page, and while all they do is contain a whole bunch of page table entries, you can figure out how many page table entries fit on a single page. So you always want to figure out the number of PTEs per page. So the number of PTEs that can fit on a page is going to be the page size, like just assume it's just a giant array, right? So however much memory you have, which is the size of a page, divided by the page table entry size. So we're assuming they're all contiguous, they all fit, there's nothing else we have to worry about. So in this case, it would be two to the 12 divided by two to the three, which is two to the nine. So that's the number of page table entries per page. And well, then we have to figure out that, oh, okay, so if we have a multi-level page table, how many bits do we need to index all of the page table entries that fit on a page? So that is just log two of the number of page table entries per page. So in this case, if it's two to the nine, then we need nine index bits. And then if you need to figure out the total number of levels you need to use, well, you can do the total virtual address virtual bits minus offset divided by index bits. Or you can just use the number of bits you need for the virtual page number. It's the same ID, it's the same number, right? So in this case, oh, sorry, bunch of cat fur. So in this case, we have 27 virtual bits. Oh, and it's gonna be the ceiling too, because well, if it doesn't fit nicely, then well, we still need an additional level. We might not use all of it, but either have a page or you don't. Or you have another level or you don't. So you always have to round up if it doesn't divide evenly. So we have 27 divided by nine index bits. So that equals three levels of page tables. That's pretty much all of the calculations you'll ever need to do for page tables. So you can figure out from the virtual address, how many virtual pages there are, from the size of the physical address, how many physical pages there are at most that are supported by the system. And then if we have multi-level page tables, figure out how many page table entries fit on a block. If we were using just a single large page table, well, we need a page table entry for every single virtual page. So we go ahead and just multiply them together, like just assume it's a big array. So if we wanted to figure out how much storage it would take to, if we just didn't have multi-level page tables and just had a single gigantic page table, well, that'd be to 27, which is the number of virtual pages and each of them needs a page table entry. So that would be times two to the three. So we would get two to the 30 bytes or one gigabyte if we just had a single massive table. So does that dispel all of our page table stuff or is there any other questions I should go over about page tables or any confusion or anything? Or are we all good? So anything else? So we did a blocks and I notes question. Is that okay? Or do you want me to go over that again? And then memory, so memory's okay. Anything else we need to go over? So, all right, everyone's okay with the clock algorithm? Everyone's all clocky? I don't know why I said that. That doesn't make any sense. We could do clock. Most of us were good at it during the lectures. Yeah, kind of running out of ideas for the final exam. So you can expect some questions kind of similar. I would assume the clock question. I can't be that creative with it. So you can probably expect that. But yeah, also with exam writing, typically I'd try and intentionally kind of try and come up with new questions that are a bit different. So you're not just studying the exam. Hopefully you should like, the intention is if you know the content, you should be okay and not just like study the format of the exam. That makes sense. But yeah, I don't think your exam is not too bad. I aired on this side of being shorter. So yeah, your exam is not, is slightly, I believe it's probably slightly shorter than other ones. All right, anything else? Or we're all good. Uh-oh, there's some dogs. Oh, and then do we need to know about disc and raid? So disc, you don't really need to know about disc too much. Like maybe that weird SSD thing where like you can only erase blocks at a time and not pages and those little silly rules. Like there wouldn't be a major question about that. Maybe a short answer. So I wouldn't worry about it too much. And for raid, you should probably know what the raid levels are and kind of know their trade-offs generally. But again, probably be a shorter-ish type of question. Your major questions are pretty much like the style of it's more like the 2023 fall EC344. So like expect some question on processes, threads like Senate 4s slash condition variables, virtual memory, probably some file system stuff, and then some short answers. And as a warning, yeah, your short answers are short. You got an error in your favor because I tried to be a bit more careful about the time and made the other questions worth more. And then by the time I got to short answer, I miscalculated and I didn't need quite as many as I thought, so they're very, very short. All right, other questions or things we should do. Hopefully this is your last exam. Pretty much major questions. Locks analysis, CIFOR analysis, page table calculation, file system and inodes. Sorry, was that a statement or you want me to go over more of these? So yeah, like locks, I mean, you can bet there'll be some type of, hey, is there a data race here? Oh, okay, yeah, fix it, identify it. Yeah, so locks, fix it, something like fix it, probably a deadlock question, resolve the deadlock, identify the deadlock. CIFOR, like either condition variables or CIFORs try and make things go in a certain order. Yeah, some of the questions are like that. There'll be a virtual memory question. So like something to do with page tables, trying to understand the system. Like any of the demos I did, like you should probably know about the TLB stuff as well. Like it's basically a cache for it and remember, like we'd had a little demos showing you why things were slow. Let's see, yeah, multi-level page tables, always good, trade-offs, stuff like that. File system and inode kind of expected that you did lab six and can reason about it, like just the inode structure itself, kind of what's stored there, how it works, things. Another question is priority scheduling, fair game. So priority scheduling, technically fair game. But like you had a scheduling question on the midterm and it was done well on the midterm. So given that, I assume you know scheduling. So I would not expect a large question on scheduling. If at all, there would be a short answer, but like I said, I kind of let the proverbial cat out of the bag that I kind of ran out of room for short answers, so your short answers are very, very short. So is locks implementation a great emphasis? Like knowing how to make a mute text from scratch or should the emphasis be actually using it? So the emphasis, yeah, more on actually using the locks to avoid data races and deadlocks. Like actually implementing it was more just to show how we would actually implement it if we had to and understand that, hey, you know, these atomic operations are done by the CPU for us, all the hardware, people figure that out for it. So as long as we use the right thing in our software, we're all good. But yeah, definitely be more, like if you look through all the exam questions, there's pretty much always one, that some type of data race, hey, fix it, then some type of deadlocks, hey, fix it, or like what the other thing I can do is, I don't know, maybe make it look like there is one and you have to say, no, there's not for reasons. But if you, yeah, I try and, I won't try and spoil too much. But yeah, maybe I should just start, stop talking, huh? All right, anything else to go over? Yeah, and your exams are in place, your exams, I mean, hopefully written all good, all ready for you on, where we Tuesday, can't believe we're the last day, but it is what it is, right? Yeah, Tuesday, bright and early. Oh, yeah, I'm gonna have to get up at like 4 a.m. But yeah, morning exam and you can enjoy, you guys can enjoy the rest of your day. My mind just begins at that point. Oops. Oh, all right, anything else we want to go over? Oh, apparently I need writing aids. And profs don't get to say when the exams are too. Nope, all exams are scheduled centrally. You guys actually knew when the exam was before me. So it appeared on Acorn before I got an email. So you knew of it before me and had just as much say over when and where it was as I did. Yeah, funny story, last semester, the exam was like in a church and like a community center and something all on Spadina on three different locations. And I was the only instructor for that course. So I literally had to run across Spadina for two and a half hours going to all the exam rooms. So I tried to complain about that one and said, hey, this is kind of ridiculous. I shouldn't have, can you at least schedule the exams like in the same building or on the same block? But no, I just ran across Spadina like a crazy person for two and a half hours. Yeah, one of them was Knox, was it Knox and it's like a community center by it too. I forget what it was called. But yeah, it was like Knox, one of the colleges and the community center or something like that in the basement and it was kind of gross in the basement. All right, anything else here? We can quickly go over stuff. So your exam pretty much format looks the same. The only thing is that your exam type is a closed book. So it's an A type. Yeah, I will still stick around to answer some discord questions. You've an exam on the 29th. I also have another exam on the 29th. So I will be at another exam on the 29th, but after that exam, which is also in the morning, I will be more or less around. Yeah, so after that exam, I don't really have to do that much, so that's good. So yeah, so your exam looks similar to like this, except after this page, there'll be a list of functions. So similar to the midterm, you're gonna have a list of functions. So like the midterm, you had a page of functions like wait, everything. Yeah, you'd wait, fork, what else? Like pthread create, pthread join, stuff like that. So on the final, that's the first page, but there's another page that has like the mutex lock, tri lock, center for, post condition variable functions, things like that. So you have the list of functions, and then you will also have a short answer that will be shorter than this. Don't know why I said that, but your short answer will be shorter. Then you will have a process question like this. Thankfully, in terms of not running the exam, I forgot what I wrote already, but you'll have a process question that will not be exactly like this, that probably if you look in the past a bit more, that's probably more reminiscent of that. This had to do with pipes and everything, but you had pipes on your midterm, so maybe I don't do it. Then let's see, then threads, so they'll probably be a threading question. Hopefully it doesn't have a mistake in it like this one. I've read over it and it had some time to percolate, so yours shouldn't have an error, but you'll have a threads question, right? That will probably have to do with data races and fun things like that. Then a locking question, so this one was deadlocks, resolving it, probably have something similar to that. You can bet it won't be exactly the same. Then center fours, condition variables, so you'll probably have center fours or condition variables. Both of them are good to know. Specify some type of order of something. Then let's see, what else we got? Then virtual memory. You can assume you'll have a virtual memory question. Let's see, page replacement. Good old clock algorithm. I don't know how many more, hopefully it's very obvious that there'll be a clock algorithm question. I would expect different numbers, but hey, it's the same thing, so hopefully that's okay. Just remember it to tick-tock and you're all good. Then file systems, you'll probably have a file system question. Again, probably not exactly like this one, but actually I completely forget what I did for the file system question, but I don't think it was, I don't know why I'm trying to think of the question for you. I don't know, I don't know. Trying to help too much, but your file system question as far as I recall is not that bad, and then, yeah, that's it. So those are, that's pretty much the format of your exam. All right, any other questions? Otherwise, we can end a bit early and then I'll just be around in Discord and things like that. Yeah, cat reveal and then a final exam reveal. Well, you will have this final exam revealed to you April 30th at 9 a.m. or whenever it is, so you will have a final. But yeah, other tips for the finals, same idea, like, I mean, the first page is probably going to be similar to this one, so you can go ahead and read the first page. So, at yours, it's double-sided, so there'll be a blank sheet at the back. If you need extra space, just write in the question that, hey, I wrote it on the last page because it'll all get scanned. General rule of thumb, if it doesn't have a QR code on it, we can't see it. And then, probably less relevant for our exam, since we're all in the same room, but if it's unclear, just state your assumptions, answer the question. Since we're all in the same room, if there's an error, I can just let you know, although we have some writing elsewhere. But likely, we'll just correct that. Hopefully, I wrote the exam okay, so we should be all right. And yeah, you can be brief, specific answers, clear and concise, then vague and wordy ones. So, general rule of thumb, my friend used to do this in undergrad too, if you didn't know the answer to a question, he would just word vomit all over it and like hope for partial marks. So, I don't like that, so you can like, if you have partial knowledge, you can say what you want as long as it's relevant. Irrelevant stuff gets nothing, and if you're wrong about your irrelevant information, I mean, it's like more or less minus a point for everything you've got wrong. So, if you just wrote irrelevant stuff and it's irrelevant and wrong, then yeah, probably shouldn't have wrote it. So, try and answer the question as best you can. So, like it says, no marks for just incorrect statements. So, do not be wrong. All right, any other last calls? Or shall we end half an hour early and then I'll just be around on Discord? And that is unlucky that you have exam on the 29th. But, oh well, and yeah, I'll more or less be around on the weekend too, in case for last-minute stuff. All right, any final calls on Discord? General questions, comments, concerns, other things. I think we covered pretty much everything. We didn't really do a process practice question here. We can do one quick. Let me just find one. Oh, this one had pipes. Hmm, huh. Okay, apparently I don't have them on me. But yeah, the UCLA one was also weird because the exam was like two hours or something like that. So, be mindful of that if you go look at the CS111 one. And yeah, CS111, it's not a first year course. UCLA is weird. If it starts with 100, it's a third or fourth year course. So, it's a third year course, just like this one. So, the UCLA people are not taking operating systems in first year. But yeah, sorry, I don't have one on me. But if you look at the midterms for other courses, you can probably find the process question and you should do that as reviewed too. All right, anything else? Last minute, righty. Well then, good luck on the final. I will be around. And yeah, I also, my partner gave me a nice coffee cup. So, here's to you. So, hopefully it's a joke cup. I'm not, it's not real. Hopefully. So yeah, with that, just remember, full and full year, we're all in this together. And good luck.