 Yep, all right. Welcome back to operating systems. Yeah, the exam content is exactly the same for 353 so Here is the first session for final exam review So I told Satow to start grading lab 6 to so if you have finished it already Then you can get your grade for it and you don't have to worry about it So you can know your grade ahead of time. So he is periodically marking it. So pay attention to announcements so Any questions? We're here for review anything in particular. You want me to go over otherwise. We'll just randomly go over exams and you can stop me Yeah all right So let's randomly start at Let's see So here is the last semester three five three exam three five three exactly the same course As your one it just lasted for a week longer. So Here is the final exam. So let's breeze through it and see what we want to do So looking at the topics here. Is there any topic we're particularly interested in? Locking we all like locking the hardest part. All right votes for locking all right So locking consider the following code snippet So we can see that so we have three threads Thread one that grabs mutex Mutex one and then mutex two and then thread two that grabs mutex two the mutex three and then lab or Not lab thread three that grabs mutex three then mutex one So the question says assume we only have three threads. We create three independent mutexes. So all mutex One mutex two mutex three are all initialized for us and each thread executes its own different function above So thread one executes thread one thread two executes thread two thread three executes thread three So any questions with what's given there? So Let's see what the first question is Identify and explain the deadlock condition that can occur in this program So I was nice and told you that it there is a deadlock So can anyone explain what a deadlock condition is here? So each thread just grabs The first mutex and then we get context switched. So in that case Let's see if I can write better thread one would lock M1 Thread two would lock M2 and then thread three would lock M3 whoops not M2 and three Now in this case if every thread just grabbed it the first lock Well can thread one make any progress right now? No, because thread one it's going to try to acquire Mutex two which is currently held by thread two. So it can't make any progress Thread two is going to acquire mutex three Which is currently held by thread three so it can't make any progress and then thread three is going to acquire Mutex or try to acquire mutex one which it can't because it's held by thread one So no threads can make progress in this scenario. Therefore. We have a deadlock Questions comments concerns about that seems like a deadlock to me. All right, so Four points to explain that so that is the correct explanation Six points Propose a solution to avoid deadlock in this code modify the code or describe your proposed solution and Briefly explain how it addresses the identified deadlock condition. So how would we fix this deadlock? Yep in the second statement for all of them So just try What else do we need to do in the try Yeah, try in a while and in the while loop. We would have to do that unlock yield lock thing, right? Yeah, so in this case if we try to get M2 then we would if we fail we would unlock M1 Yield or something? Whoops, I don't know I Can write C not English So lock and one and then we could have that a while loop and We could have that in every thread so we get rid of anyone remember what that condition for deadlocking is called Holden weight. Yes, so that eliminates hold and weight because While I'm holding a mutex if I don't get it I give up the one I was holding So I do not have that deadlock condition. So that is one Is there another solution to this that only requires me to swap two lines around? Yeah Yeah, just swap these two like that All right, if I swap those two lines so instead of locking Three and then locking one. What if instead I locked M1 and then locked M3 Do I have a deadlock in that case? No, why do I not have a deadlock in that case? Sorry Yeah thread now thread one got the M1 lock then thread three wouldn't be able to acquire Lock three without also holding lock one So remember what that's called? That we've broken this case Sorry, so Thread one whoops, let's see if we can come up with the name for this. So thread one in the deadlock condition was waiting on thread Two then thread two was waiting on thread three and then thread three was waiting on thread one Can we come up with a clever name for that not a race condition? circular weight Circular weight was another condition for deadlocking where I don't acquire the threads in the same order if I acquire the threads in the same order in All of my threads then I have a set order and I do not have a deadlock because I will not have a circular weight So that is also a valid answer is just to always acquire them in the same order So you could get six points real quick if you just swap two lines and you explained it All right, see locking is not that bad. That was like what? Okay, it looks like 18 minutes. Okay, maybe it's not that bad All right. Yep. Yep full marks for the tri ones. Just a lot more writing, but it's fine still valid solution Okay, oh, do we want to do center for us? Okay, so let's see if I can make this Okay, I'll use you to cover up some answers Okay, that didn't work. All right, that's scuffed All right, so Says consider that looks okay, whatever so it says consider the following code we have Define that says total threads is eight We have a center for that we can initialize and then a run We have this initialize everything function and the comment says we want only thread zero to execute this and That green line was not there originally But you would have to have an if condition there. So I'll just give it to you and then here it says we want all cores to Execute this initialized thread function only after initialize everything finishes So a question says assume we have n threads all set up to start executing run and each thread Receives a unique thread ID in addition that initialized center for Function executes before any threads get created. So it's safe to initialize the center for Then the question asks using that single center for Sam Enforce the synchronization constraint constraint specified in the comments You may add code directly above and see or pseudo code be sure to give the center for an initial value Try to make a solution that supports any number of threads assume that thread zero already always exists and for a maximum of seven points you can use that total threads in your solution and Assume it's the number of threads executing the run function So using a center for how would I enforce those conditions? So I will give you a few minutes to think about that Okay, where should I start? Yeah, you could start with the initial value so What do you want the initial value to be? guess one So sometimes thinking about the initial value is Usually the second step first step. It's usually easier to figure out where to put the weight So in this case what should be waiting? Yeah, the other threads that are not zero. So if I have this is if condition and then let's say I have an Else so I could put the same weight in the else and Now I should probably think about then initial value. So if I want to prevent Any thread that is not thread zero from executing then what should the initial value be? Zero. Yeah, zero. So No Sorry So the initial value is zero actually can I just erase it? Nope, okay So the initial value is zero. So now in this case if I am not thread Zero then I wait in this case. What's going to happen? So thread zero will execute initialize everything and then Break out of its if and then do initialize thread and then all the other threads are going to be waiting So next I should probably place my post. Where should I place a post? Yeah Yeah, after I initialize everything right here. I can do a SEM Post right. Is that all I need to do? Yeah post also here Two threads can wait on a SEM4 So SEM4s are remember SEM4s are atomic. So it'll decrement only if it can So should be okay, right? So here first we can argue What happens if we don't have this post? So if I have eight threads say well all let's say all Thread one thread two all the way up to thread seven We'll be sitting here at the weight and the only thread that could execute is thread One or thread zero It would execute initialize everything and then post so my center for value would change from a zero to a one and then One of those threads will be able to successfully pass the weight because it can decrement it now from a One to a zero and then if I just left it as it is right now Only thread zero and then one random thread can go ahead and execute this initialized thread the other Six threads will be stuck So that's why I had to add a SEM post here So if I add a SEM post here then what will happen is as soon as one thread of Thread one all the way to seven makes it past the weight it will post to increment it back from a zero to a one and then Another thread will wake up and make it past the weight and then it will post then another one can execute And then it will post another one then another one. It's like that DJ Khaled song. Is that the one? Another one. Yeah, all right, so if I Didn't want to put a post there if that was confusing What is something else I could have done that would have made? You know the seven threads make it through the weight So what about if I just did like Had this post But instead it was like for one to numb threads So thread zero after it's done just post seven times That would have also worked But that solution well that was this one that was for a maximum of seven points So you got minus two if you wanted to use that so that essentially Doesn't work with any number of threads. You have to essentially Anytime you update the number of threads you have to recompile everything The first solution with just a post after it doesn't matter how many threads will wake them all up All right any questions about that one All right Part two. All right So consider the following solution without center fours So we have a run Then we have if thread ID is zero Initialize everything otherwise instead of the center for anything. We just have a good old-fashioned yield So now the question says assume that we have cooperative user threads aka like lab for for you is This a valid solution that Actually adheres to those conditions Explain why or why not? Yeah Yeah, we want to initialize everything first before an initialized thread comes in So yeah, the only condition is here is that initialize everything should execute before any thread runs initialized thread So assuming this is like your lab four if you yield what happens goes to the next one Yeah, it goes to the next one. There's like a FIFO queue you had right so it just goes to the back so Assuming you had a FIFO queue with this work so initially all threads are somewhere in the queue in any order and then You get a turn and then if you get to turn you just yield so you go the back of the line So eventually as long as you go to the back of the line Eventually thread zero will come up before any other thread Wakes up again and in that case thread zero would call initialize everything and then the others would call Initialized thread after they wake up Or after they get recued again. Yep. So whenever they get recued, they'll resume back from the yield, right? So the contacts which back and they'll resume from the yield and then they'll just Fault oops, and then they'll just fall through and then execute initialized thread. So It seems like a valid solution, right? If you had lab four so assuming like this question says assuming you have a FIFO queue and It's essentially your lab four So for you it's lab four. It was lab three before So Everyone would give up their turn No other thread would get recued ahead of thread zero and then initialize everything would run first Next part says assume we have cooperative Kernel threads now running on multiple cores. Does the solution work now? So let's yeah, so if it their kernel threads they could run in parallel and It could even be the situation where Thread yield doesn't even do anything. So say you had let's just start off with core zero core one and core two Say this was executing thread zero. This was executing thread one. This was executing thread two Say you we were down to the atom time scale or something like that and core two executed and thread two executed so it would go to this yield and While since the kernel decides scheduling decisions Thread two could yield and it could be like oh well that core is not doing anything else anyways So just continue executing it Don't worry about it and then thread two would go ahead and then just call initialize thread and It would be broken and while they're running in parallel So you don't know what's going to happen and because their kernel threads they can be scheduled independently You don't know what the scheduling algorithm is So even if it couldn't run in parallel that this would still not be a solution because a real kernel can just ignore your yield It's just a suggestion. So it ultimately decides the scheduling so Question says no, this doesn't work since the kernel threads. They might also run in parallel So you can't ensure any kind of order All right any questions about that All right, what we want to do next So what are our choices? so Choices are one of those topics or one of those topics and we look at a different exam file systems All right, that is a good choice So file systems For the following questions assume your file system has a block size of 4096 Each pointer to a block is four bytes and i-nodes are a hundred and twenty-eight bytes each So first question says what is the minimum amount of space in bytes? You need in a file system like ext2 aka your lab six to store a hundred files Or sorry a thousand files assume that each file is 96 bytes in size Include the size of the i-nodes in your calculation You may skip the final calculation if you don't have a calculator. So each file needs an i-node. So each file Needs a hundred and twenty-eight bytes for an i-node and then even though a file only consumes 96 bytes it still gets an entire block, right? So since it gets an entire Tire block and only one block then it would need this many bytes per file So we need 128 plus 4096 which is what? 4224 Times a thousand files so Our final answer is that number that I'm not going to try and say So questions about the first part All right second part says for the previous question How many bytes are lost due to internal? Fragmentation you may say zero if that's the case You know 4096 minus 96 so that's per file and then times a thousand so that should be 4,000 times a thousand which would be four million Do-do-do-do-do All right, so questions about that one. Yep. Yep Yeah, full marks if you write down the right thing So for this next question Why might copying a directory containing a single Large file be faster than copying a directory with many small files Even if the total size of the small files is less than the size of the large file My inspiration behind this was well I was copying files on my computer and then I got to a bunch of small files And then you know the windows progress it just went and then oh wait The class should be able to explain this now. So I wrote a question So why would that be the case? It would still need them. Yeah, you still need to make a copy of the data like actual copying files But you're on the right track. So for a single large file. It's one. I knowed right and then All of its data would be in a bunch of IO blocks that are full essentially, right? They would all be full except for the last one So like if you have a single large file, it would probably look like, you know, I knowed And then that one I knowed if we just had its blocks and Didn't look at the structure. It would be pointing to like block zero all the way up to I don't know block What's large? Block one million. I don't know. Maybe that's too large. Let's say ten thousand block nine hundred ninety Block nine thousand nine hundred ninety nine So that's what a single large file would look like, right? so Block zero to block nine thousand nine hundred ninety eight would all be full and this one You know might have a little bit of internal fragmentation at the end, but It's total internal fragmentation is going to be negligible If there were a bunch of small files well for each small file It would be an I knowed To a block and this block Could only have I don't know like a few bytes in it. So like ten bytes hundred bytes whatever but The file system still has to read the entire block. It can't just read part of a block in so if we had a bunch of small files Say we had a lot of smart files. So say we had Wow, that's not a good brace So say we had ten thousand of these So if we had ten thousand of them, we have to read the same number of blocks as that large file but the contents of it this could only be a few bytes so This could be like ten bytes and I'd have to read the same number of blocks for way less data. I'm only copying ten bytes of block That's actually useful and for each of those. I also have to copy an I node so most of my copying is going to be copying I nodes and just accessing empty blocks all the time and usually when Windows shows you File copying process it only cares about the contents and the size of the I node outweighs the contents even And that doesn't even count. Yeah Yeah, so the I know it has a size on that knows what's valid, but whatever you're reading From a disk you can only read blocks at a time. It doesn't matter, right with the SSD Stuff you can only read blocks or pages at a time So even though that it only has a few bytes valid Given by the size you still have to read the whole thing in you wouldn't have to copy all of it They still have to read it in from disk So that is also why once you hit small files Performance goes down because it only keeps track of copying the contents Hey, the I nodes could be bigger than the contents and they're not even counted Yep So so the question is if it's a regular file it's super super small Can we have the same? Optimization as we had with the sim link where we store the contents like on the I node Yeah, so in that case the reason that optimization does not exist for regular files is because well remember they're also Mapped to virtual memory pages So they have to correspond to something if the contents were stored on the I node itself And you did like that n-map magic thing then suddenly If you tried to map its contents of the page that page would also contain like the whole I node and All the neighboring I nodes so then a process could just change I nodes which would not be good So even for regular files It does that so that it's on a page and it can map it to a page in memory and then Carry on from there Yes said optimization is only for sim links because you can't really access its contents like just the kernel reads it anyways, so it doesn't matter and Also sim links generally don't change and all sim links are generally smaller than 60 bytes anyways So that's why they do that optimization all right Oh, let's see so Next question assuming an I node has 12 direct blocks. Whoops and one indirect block What is the maximum file size that can be supported? You may skip the final calculation if you don't have a calculator so I will write the important things so The block size Was equal to 4096 Which is 2 to the 12? and then the pointer size Was equal to 4 bytes Or 2 to the 2 So if my I node has 12 direct blocks and one indirect blocks What is the maximum file size in kilobytes that could be supported? So how many blocks could I point to with an I node that just has 12 direct pointers and one indirect so Let's start off with first if I only had 12 direct pointers. How big could the file be? Yeah, 12 times the block size. So in this case, it would be 12 times They would be pointing to a block So 12 times the block size, which if we want to just keep it in kilobytes it would just be 12 times 4 or 48 kilobytes Okay, webbo if I just had a single indirect block, how big could the file be? So the first idea behind this is like with page tables. So it's like How many pointers Could I fit on an indirect block? 2 to the 12 or Sorry Yeah, so 2 to the 12 So it's block size divided by pointer sizes. How many pointers I could fit on a single block Which would be 2 to the 10, right the number of pointers per block So if I only have a single indirect block then I can point up to 2 or 2 to the 10 things so I could point to 12 plus that 2 to the 10 Which is equal to this is 1024 so in total I could point to 1036 blocks and if each of them is four kilobytes then That is the maximum size of the file In that case, it would be what is that and times four So that would be 4,144 Kilobytes All right, any questions about that one Yeah Each block is four kilobytes. So this is aka four kilobytes But if you just wrote it in bytes Probably fine Yeah, probably I think It was in bold. So I think it was like a minor deduction if it wasn't in kilobytes I forget Yeah, if you just did something like as long as it you knew what you were doing it So if you did like Even wrote 12 plus 2 to the 10 times 2 to the 12 All divided by 2 to the 10. That's fine So the multi-level page table this says just one indirect block so That it kind of implies single indirect Maybe I should have said single indirect But the ones you're talking about it would be explicitly like double indirect and then triple indirect I guess this should have probably stated Single, but if you just made up double or something for that I guess I would have to give you full marks because I didn't You could argue that one Okay, what do is there more? Oh, yeah, okay, so Let's get rid of No Okay So how many directories need to be accessed when attempting to read a file located at Home user uToronto ec blah blah blah labs grade dot txt assume grade dot txt is a regular file So how many directories do I need to access to actually get to that file? And what are their names? Yeah, yes Yeah, so Root And then this this this This right So that's five so Yeah, five So This question is mostly so you knew that the root directory was a natural directory. The other answer I mostly got was like four so Next question You think you should create a copy on write file system But someone who hasn't taken this class Says you don't need to because you can already do the same thing with hard links Explain the differences between file one and file two being hard links to the same inode And the concept of copy on write can copy on write be achieved using hard links. Yeah, so basically That probably would have been full mark So if you just said that if you have two hard links to the same inode Well, if you change the file through one or the other it changes them for both They're not independent at that point. So you wouldn't get the same effect as copy on write what you would want Because they share the same data what you would want is to oh Jesus What you would want What are you Stop it So what you would want is for Two inodes to share the same blocks if they are the same and then if one tries to modify it Then you create an independent copy of it and you could do that if you want for the file system So you could make like that copy command Essentially do copy on write instead of copying the contents and there are our file systems that do that But they would have different inodes and then internally the file system would have to keep track of the pages Exactly like in memory Yep, yep Yep, okay. I think that was it for file systems. All right anything we want to do real quick in the last six minutes Okay, that's a blank page. Let's see Threads we want to do threads as quick as we can Sure, okay So here's our program So we declare a bunch of threads we get a bunch of id's Then in the main thread We create What in this case we create eight threads So we create each threads each with their own id and then we join them and we print a global sum Global sum is this variable And then there is another global variable called array And then in each thread Well, it seems to Start us off at a unique Index that doesn't overlap with any other threads. So like thread zero would get I don't know Let's say Index is zero to Zero to ten Not including ten and then the next thread would get like ten to twenty And then the next thread would get 20 to 30 So on and so forth So real quick Is it necessary to protect access with the array variable? So is there a data race with this array variable here? So anyone off the top of their heads remember Definition of a data race. Yeah possible for two concurrent actions With at the same location or with the same variable with at least one of them being a right so Even if they overlapped In what parts of the array they are accessing If we have the array on the right hand there, is that a read or a write? It's just a read So local sum is being written to in red, but array is just being read So do we have a data race with array? No, because it's just being read. All right Next question is do we have a data race with local sum? Yes No Yeah, no, right. So local sum Is a local variable in each thread so each thread has its own independent stack So therefore There's no concurrent accesses to the same location because each thread gets its own unique location So no data race with local sum Is there a data race with global sum? Yes, why? Yeah, so here This is a right and they all are concurrently writing and well, guess what they're also reading and writing So all of them have access to the same location. It's the same variable. So all of them will be reading and writing There's no protection. They could all be running concurrently and therefore we have a data race with global sum So what is an easy way to fix this? Lock and unlock so create a mutex And then where would I lock? Just before the global sum because I would want to make My critical section as small as possible and then I need an unlock Right after right Boom fixed I think that was all the questions Oh, then last one really really quick if we change all the threads to detach threads Is that a problem? So now I detach them And if they're detached then I can no longer join them. So I don't have this So is that a problem? So it's not a problem Yeah, so in this case it would be a problem because I probably care about the global sum What could happen if I just detached everything is Before all the other threads run the main thread just prints the global sum Global sum is zero so it would print zero and then Return from main which is the same as exit and now the process is done So could do that before anything runs or it could you know have a thread one thread updated and not all of them Recipe for disaster here Because we want each thread to be done before we print the global sum So we need joinable threads in this case or center fours or something like that All right, so with that remember fooling for you