 All right, welcome everyone to a wonderful quiz morning So today, we'll just do quiz to review should be fairly straightforward Also, I did a review of my lectures apparently. I have a verbal tick where I say right like a lot No one's noticed that just me Okay, well if I say right like too many times just flag me down tell me to stop Because I had a prof with like a verbal tick like that, but his was okay, and it was so distracting that I Counted how many times he said it during a lecture. It was like 300. I didn't learn anything that lecture So I don't want to be that that bad or distracting But let's go on With quiz to review. So let's ask a forking question start the morning off with a bang So consider the main this code. It's fork fork fork So if you have a question like this, this will probably be beyond the scope of the quiz because it probably takes too long to write But we should be able to do this. So what's one reasonable set of outputs? assume the initial process has PID 2 Again piece just a number to identify a process and then other questions could be like what order are the outputs in? How many times does that print statement? Even get executed and what are the relationships between all the processes? So a went off the top of their heads. How many processes are there at the end of this? Yeah, hey Yep, anyone disagree and all their relationships the four and the parent like the parent has four children Yeah, and then have they so let's even break this down give outputs to things and just Break this down as much as possible because this is somewhat involved. So let's go ahead and switch over So our question was we had main. I'll skip a few I'll skip some steps second equals fork. No, and I have my print statement print If we know what's gonna print Okay, so what's some reasonable thing that could happen? Well Initially, we said we'll have process ID to and It will start executing main. So it's a process. It already exists our terminal launched it or something and Then did exec Vee and we know what exec Vee does and all that fun stuff. It's good to know So on the first call to fork We'll have PID 2 and It is going to create a child So after the call to fork, we're going to have a new process So this will move here and then We'll have a new process probably called process 3 Which will also be at the same point right after the fork and the only way to distinguish them is the return value of fork Which is stored in first so in PID 2 first would be equal to We'll just give a number. So Let's say we create process ID 3 that probably makes sense. So in the parent first is going to be what value? To because that's the process ID of the child. So first would be equal to 2 and in the child What would first be equal to? Zero because it's the child. So now we have both process 2 and 3 that could execute next which one executes first. Yeah. Oh, yes, sorry That was a brain fart three like that. Yeah, sorry It would get the process ID of the child which in this case is three. So thank you. All right So now we have two processes right after the first call to fork What order do they execute then? We don't know that's the fun of this course, right? We don't know anything So let's say process 2 goes ahead and executes and it executes another fork So process 2 executes fork and it would create a brand-new process So process 2 moves on to the next line and It would create a new process probably process ID 4 And it would be a direct child So let's draw a line from there. Actually I need more room So in PID 4 What's first going to be equal to? Yeah, first would be 3 Because in process ID 2 Before the call to fork it has a variable called first and when you call fork. It's an exact copy at that point First already exists. So we were just get a copy of it so first would be Equal to 3 still because it's a copy and then the only difference between them should be the value of second So in let's say in process ID 2 which created it. What's the value of second? 4 anyone disagree? All right in process ID 4. What's the value of second? 0 that's it and now we have process 4 that's just sitting Sitting after the second call to fork so let's just be boring and Worry about another call to That process ID 2 makes So process ID 2 let's go ahead and have it call Fork again, so it's going to create another new process So at this point it would probably create process ID 5 What's the value of first? 3 yeah, so it's a clone first equals 3. What's the value of second? or again clone right before the call to fork and The only way to differentiate them should be Third so what's the third in process ID 5? 0 what's third in process ID 2? 5 and then at that point process ID could go ahead and we'd see something like You would see a print of first equal three second Equal four third Equal five and then that process would be done so Thankfully we can go ahead and delete it so we would erase it and Then also process ID 5 exists at that point so it could go ahead and print its statement if we Were so lucky with our scheduler So we can go ahead and delete five and show its print line So it would print first Equals three second Equals four and then third equals zero So that's so let's put a little check mark check mark check mark. So those are done All right, so let's just argue about process ID for for that's going to come in So it's going to call fork and it needs to do its third. So when process ID 4 calls this fork it will create a new process that will be its child Actually, I'll put on the other side and give it a bit more room So it would probably spawn process ID 6 Something like that. What's first equal to? What second equal to and then What would third be equal to and then in process ID for what third be equal to? Six so now at this point Either could go ahead and print because it's all done now so we'd have another statement where This would be for four first equals three second equals zero third Equals six and then we could also have first equals three second Equals zero third equals zero So that means this is done. This is done. So we just have process ID three now So process ID three is About to call its second fork. So if it calls fork It would probably create process ID. What we up to now seven So what would the value of first be for process ID seven? Zero because it's a clone and then what would the value of second be? Zero and what would the value of second be in process ID three? Cool, so now at this point we will have Process three here and then process seven here So argue about process ID three first. So if it comes it would create a child Probably called process ID eight What would the value of first be? Zero this should be getting fairly boring now Seven and then what's the value of third and then what's the value of third in process ID three? Eight and Then they can both finish and execute now and then we just have to argue about seven So seven would come probably create something called process Nine at this point What would first be? What would second be? What would third be? Okay, and then what would third be in process seven? Nine so if we do all that let's see We have first Equals zero second equals seven and third equals Eight so that's from process three and process seven first equals zero second equals zero third Equals nine there's that one For PID eight first is zero second is equal to seven third is Equal to zero and then for process ID nine first is zero second is zero third Zero and then there's a question Can you remind us why the last variable takes on the value of the child PID? So at the time of the fork exact copy and then the only difference is right after the fork the return value of Fork so these are all the print statements We'd see that's like a reasonable thing and this is what our tree would look like so yeah Yeah, yeah, so the process ID would be different, but they'd be consistent along the processes So but the process IDs could be different I just picked you'd get random process IDs depending on what your OS wants to do and then You can't determine the order of the print statements to because you have no idea So all the IDs might be different all the print statements might be in different orders But they would be somewhat consistent with respect to whatever this tree looks like and our trees always gonna have the same shape So whatever the original process is has three direct children which makes sense because there's three fork calls and then the first child from the parent made two and then The other child made another two and then that one made one So this is what our tree would look like every single time So any questions about that? So what could I do to if I want to enforce some weird order between processes? What's one thing I could do I? Could just wait so if I stuck weights in here you would actually be able to somewhat get an order So should know what weight does it just waits for that process to essentially call exit and die Okay, so that is the forking question. So we're all good. That's probably harder than anything on the quiz So that's good. So if you understand that like it's just getting used to fork fork just exact copy at the time of the fork and then the only difference is the value of the process ID So if this didn't hammer at home going like eight times, I mean it should be okay All right, what about a threading question? So let's say We have something like this. We have a global variable int i and we have three threads So before a thread executes we have i equals to zero and then two threads execute i plus plus One thread executes i minus minus. You don't have any locks whatsoever What are all the possible values of i? All right, anyone want to tell me all the possible values of i? Yeah, pretty much anything from zero to one Okay, I get yeah, okay, so we think negative one No, we gotta know okay. No negative one zero at the end. Okay. We got zero One two three So we think it could be zero one and two Okay, well, let's see. So if we have this we have three threads So they only have one line each. There's no order. You don't know what's going on What's i minus minus while it's actually like a reed of i and then You know it goes to register and then we increment it and then we write out i So each of the these threads would be doing essentially the same thing read i Ink write i Minus minus is a little bit different because it would do a deck instead so I mean What value should it be if you know everything was atomic and everything was all great One okay, so we should be able to argue about what happens with in the case of one so in one thread one could Just read zero ink I Ink it so now it's one and then it would write out I so it would write out i Equals to one so now globally i equals i equals one then thread two could execute It would read a one Ink it to two and then we'd write out i Then i would be equals to two and then Thread three would come along read two deck it to one and Then write i And now i is equal to one That would be the case if every thread just goes to completion It's like all atomic if it was like magically protected by a lock. That's what we would expect All right, so how would we possibly get the value two? Sounds sounds reasonable, so let's see So you said Thread three starts executing Yeah, so it would read a one then it would decrement it to a Zero and then now gets preemptive so thread two starts executing And it would then read a one So two Yeah, so three so within a thread, you know, they have to execute in order So three just execute the decrement and has to execute right because within a thread you still have to do things in order So it would write out What's it so it would write out a zero so now i is equal to zero and Then thread two would execute and it would write out a two and now i is equal to two and then everything's finished So two is possible So let's see So We have a one and a two so those are both possible What about? What could happen to get a zero? Where's my big eraser? Whoa, that's such So what would I do to get a zero can just you to ink to a one and Then so now i is globally whoops equal to one. Whoa, that's a big eraser And then thread two Could go ahead right. Whoops, right. I so now I is equal to one again and now In thread three we would read one Deck so now we have a zero and then we would write out I So now I is equal to zero Okay, so now we think it could also be a Zero All right, so now we're done So negative one's possible. How would negative one be possible? Yeah, so all three threads could so I equals zero so we could have thread one execute read Zero it could do an ink to one Context switch thread to read zero inks that to one and Then thread three would read a zero Decrement it to a negative one and then all we need is for thread One and two to write doesn't matter what order at this point. So it would write I so now I equals one Thread two would execute right I So I equals one again, and then thread two would Or sorry thread three would execute right I and now I is equal to negative one But no matter what because we have three threads There's going to be three rights to the global variable. We just don't know what they're going to be So our possible values negative one zero one and two Anything else it could be yeah We kind of covered the the worst and best case the most positive it could be is a two Which is essentially we ignore the minus minus and then the most negative it could be as a negative one Which is ignoring the two plus pluses So but it could be literally any of those values depending on how unlucky we get So that's why data races are fun Right any questions about that? That's a data race. Yep. You probably could there There would be a distribution, but it would be specific to probably your kernel version and your actual CPU On the same machine probably But as soon as you move machines, it might be completely different or you know the next gen Intel releases your next-gen processors. They change something internally. It's all different or they change the kernel for some reason Well, they change it all the time So you get update your kernel your thing doesn't behave anymore, and that's why like some of these bugs just You don't see them for like seven years because it just works and then someone changes something that should be unrelated and Then all your programs are now broken So that's fun Okay, so that was threading forks That's pretty much the hardest parts and we'll just go through the rest of the stuff Because the only other real hard thing was like the condition variable stuff But let's quickly go through everything else and I'll take other questions So we saw way back. This is like lecture four. These are just all the summary slides how to create Processes so every process has a parent-child relationship. You only create new processes with fork that's the only option you have and then Right before the fork the processes are going to be exactly the same after the fork They're going to also be exactly the same except for the value returned by fork and the child It's always going to be zero and in the parent. It's going to be the process ID of the child There's no exceptions there. That's what's going to happen Unless fork fails in which case your computer is probably dying So don't worry about it and then there are different processes You're up to the scheduler You can't determine any order unless of course you wait which would wait for it one of the processes to actually exit and At least on Linux, you what are you only allowed to wait on? Your direct child you can't wait on anything else It's only your direct child on like super old unixes. You can wait on other on like grandchildren But they quickly decided that was a very poor idea And you can't do that anymore. So on Linux. It's only your direct child So we yeah, so the question is what happens if I call wait pit on something? That's not my child And the answer let's see man wait So it sounds like something would bad would happen blah blah blah so the value of options that's options that that Yeah, I wonder why people don't like reading documentation Air there we go. So it would probably be an error some of the errors are the file descriptor is Non-blocking process that refers to it is not terminated. So you're doing a non-blocking call then there's each child So the calling process does not have any unweighted for children. So you also get an error if you try and wait without children You'll get an error if It is not a child or does not exist So you'll just get an error You get an interrupted and then you search Yikes, all right. Well, those are all the errors you can get So if you try and wait on something that's not your direct child, you'll get an error And I'll just be like noop But of course if you don't check for errors You'll might think it actually worked and of course it didn't and it's gonna be bad But yeah, you have to scroll pretty far to find out what bad could happen All right any more questions For children all good. All right, so we then we're responsible for managing processes This is where we actually learned about weight and learned about the state processor. We're in there are zombie processes and orphan Processes this is probably beyond the scope of the quiz for you guys because I don't think section one Has gone into this in that much detail yet but Zombie process is just a process that's exited that hasn't been waited on yet it can't free its resources because you need to call weight on it and the orphan process is just You can't tell what order processes are going to exit in so if the parent dies first Then the child would still need a parent so it gets reparented likely it would go all the way up to process ID one Unless your Linux and you do weird stuff, but for quiz to the focus is just on Weight if that is even the focus is probably not even gonna be that big of a part, but you should know what weight is Yeah, so there's two versions of weight There's weight like the default weight which waits for any of your children to exit and the The first one Yeah, so if you call weight it you'll get the first child that exits and you can also do weight PID to wait on a specific process But weight would just return once when the first child dies. So in this case Yeah, if I wanted to wait for everything. Oh, okay So this might appear initially to be somewhat Correct, so is this correct? Right, so weight only returns for like for one child dying or sorry on the first child so if I call weight three times will wait for three processes to die and if we go back to Yeah, so in this case if I have three weight calls at the end they would all execute after all these get set So the only thing that can actually do three weight calls is PID to So so PID to can do three weight calls and wait on three children But the rest of them if I don't check for errors and I do this I mean that'll happily execute and finish and everything because The first parent is going to wait for everything and be fine and the other ones are going to wait for its children But eventually if it only has two children and as three weight calls the third one's just going to air But we just don't check errors. So this is just gonna wait for everything, but it's like Totally not obvious Yeah, yeah, so So yeah, the comment was could I just put weight in a while loop and just go on and on until I don't have any children anymore Well, that's exactly what a knit does Right. You just made process ID one because not only is it just going to go in a while loop while it's all out of children If you get re-parented stuff If you just wait on all your children that doesn't mean you're done. You might get new children later So a knit at the end of it Wonder if I can see it if you look at a knit it essentially will have a loop after it makes some processes It'll have a loop at the bottom that says well true weight Yeah But that so if you keep track of the number of children you have in a variable Wherever you fork you're going to copy that variable So you could do something but you'd have to change that variable be like if I have it if I'm the child I have to reset that to zero and then keep track So you you can do that, but you have to be careful because it's a copy whenever it happens So if you're the child you have to reset yourself to zero every time. No, you just call what if you Want you can see how many children you have by just I Think you can there's a system call that will tell you how many children you have But if you are a knit you might get new children But otherwise you but you should know how many children you have anyways Yeah, yeah, I mean you're the only one that creates children. So you should be able to keep track of it Okay, any other questions All right, then we saw IPC, which isn't gonna be covered anyways But that was like reading rights lots of fun with file descriptors. That was a good fun signals there might be a little bit about signals but not really they might One of the actually, why am I telling you what's actually on the quiz? Not that important very very minor For question go over this All right, then we saw threads We explored them to something we already know processes So they're just essentially the same thing. The only difference is what address space they're in threads are all in the same address space Then processes are completely separate. So threads are lighter weight share memory by default That's generally the big thing and the process can have multiple threads, but whenever it starts it just has one Yep. Yeah, so if you have kernel threads and say you have like eight kernel threads running in your process and you get a signal There's like Various things that could happen you could get a signal you could have to Handle the signal on every single thread or what the kernel does is like oh well one signal one interrupt And it'll just interrupt a random thread Yeah, so user thread the kernel doesn't know about it. So I can your lab Was it lab three? We're using signals. There's only one kernel thread. You know what the it's gonna come in on that Yeah, there's only one that's the thing that will handle it Which is why in lab three if you disable interrupts or if you disable signals, you won't have concurrency Right because you only have one kernel thread The curl doesn't really care about what's going on and if you say I don't want to listen to signals it just You'll ignore them Okay Then we saw The difference yep, so are the labs on the quiz? Not no So like the concepts of like threads and stuff and like what they would do Perfectly valid Implementation Yeah, you won't have to write any code You'll I mean you'd have to read code and you might have to suggest changes or be like Hey, this is broken. This is what you should do Okay, so that was yeah user threads kernel threads Funnily enough actually why the hell am I saying what's on it? This isn't terribly important, but now we have synchronization issues, which are important So then we got critical sections. This was just using mutexes. So we saw yep Yeah, so that's another thing. So what happens if you have a bunch of threads in the process and it forks So whatever a fork happens it clones the calling thread and the calling thread only So the new process will have a single thread in it. That's a clone of the exact thread that called fork but that's also a headache because Say I have eight threads and one calls fork and I want like a consistent state at my fork if I have Seven other threads monkeying around with memory. I have no idea what state that's going to be in So there's actually like a p thread at fork where you can control what happens So you can say hey all threads like join or do something So I know what memory looks like at the time of the fork, but that's like super super advanced Aside from that the only thing that would be reasonable in this course is to ask you of that fact Like at fork what happens it clones a calling thread Yeah, yeah, because whenever you make a new process. It's just one thread Yeah, it would just clone the process because it doesn't know about them So it would be in whatever state that happens in which would if it's only a single thread. It would actually be consistent though, right? I said right again. God damn it Okay, so you could You could fork your lab 3 1 and it would generally work so To choose so yeah, we saw mutexes. That was the main way of walking ensuring mutual exclusion they were this most straightforward lock and Yeah, they weren't too interesting, but we saw how they prevented data races We need some hardware support to implement that that was generally just for your labs And then some curl support you saw you did your own wake-up and signal So implementing that not a problem, but essentially the condition variables how you would use them probably is a problem And then We saw a read write lock the other section did not go over read write locks. So they're just kind of my performance thing Then we use centifors And they just contain a value. It's like an unsigned value and you can either Increase it or decrease it and the rule with decreasing it is it will never go negative So if it's zero it's going to sit there block wait for some other thread to increment it So then it can safely decrement it and that's it. So that was mostly for Ensuring some type of order between threads But then we also Explored that big producer consumer example, which was great stuff and then we saw a condition variable So they're generally clearer for complex signaling conditions And then we also saw like locking granularity matters of like the extent of your locks when we had that parallelization Example and we locked the whole thing it worked, but it was slow And then we saw we had deadlocks and bad things happen when we had deadlocks Yeah, so any final Questions, so I'll be on discord Ashley and I will be on discord during it format True to false true multiple choice and then to short answer. They're going to be much longer this time though So be very careful Just spend time if you have the extra time just do the question again like one minute true and false Should be fairly straightforward and then ten minutes for each multiple choice Probably similar to that threading one that was pretty good the working one was pretty good then some short answer You know, how would I do this and then 15 minutes for a final short answer and then That's pretty much it. Um, we can go ahead and What lecture was that that had good stuff? Yeah, so the producer consumer example, so oh No Yeah, does Yeah, yeah, it's like the condition variable stuff Do we kind of remember this consumer code? No Yeah Some try weight. Oh, so that that's essentially tries to decrement the center for so that this is not meant for synchronization That's just meant so I can record how many times a loop needs to execute So that's like an internal thing just to simulate producer consumer So the only code we actually need to care about is like whoops is that So this was the condition where We did not we wanted to wait on an empty slot so if everything is filled we want to wait on an empty slot and then In the consumer that empties the slots it would signal anything that's waiting for an empty slot because now We change the condition. There's now at least one empty slot so it could wake up the producer which is essentially will Wait put itself in queue if all the slots are filled And then wake up So any questions on this because I think we had a question before where we screwed this up a little bit Where we replaced though like wow with an if or something like that and was that bad? Yeah, yeah, it might have a spurious wake up, which I think is For the context of this course assume that doesn't happen if there's a third thread. Yeah, but you might wake up too and Something woke you up and then before you reacquired the lock another thread got the lock and then did the thing and now the conditions false again So you would want to put yourself back to sleep and recheck it And if you have an if you would assume that it's true, and then you're screwed at that point This is condition variable. So the center for here. I only use for I only use for keeping track of the number of things to produce and consume um Yeah, so let's see any other last-minute questions For the condition variable So for condition variables So essentially you only have to care about the weight and the signal So wait you have to call it while you have the lock and then it essentially will put yourself in the condition variable You can think of as just a cue so it will put you in the queue and then put yourself to sleep and unlock the lock and then Whenever something signals you the first thing internally that would happen is you would grab the lock so you would have the lock before and after the wake call and Signal will just wake up one thread broadcast will wake up every thread that's in that queue That's pretty much it. Yep Yeah, that's one One way of thinking about it, but it also has to re-acquire the lock at the end of it, too Yeah Yeah, yep, yep, yep, no we deleted those Because corkis was like hey You get zero Yeah, so the way corkis does is if it's a multi answer one It takes like the total points for that answer divides it over the number of correct answers So it like varies wildly depending on the question and if you select an answer that's wrong It's negative whatever that point is and if you select it, right? it's you get that so if there's only one right answer and You select one right and one wrong you get zero if there are you know four answers and Five total possibilities and it's worth four marks each one of those is worth one and You can like it's like totally out of whack, right? so we just got got rid of them and Yeah, right now there's only one correct answer and a lot of choices like 16 Well Well, you you pick one of the 16. Yeah, it's the right answer Yeah They're They're like separate questions Like each multiple choices a question. I guess we have to wrap up But don't if you follow the examples today you should be okay. All right, so good luck. I'm pulling for you