 There now there's sound oops all right So the only thing that weight does check for is if the child process I waited on was equal to the one I spawned so someone tried to quit the shell all it does is restart it so it tries to It creates a new child child process and then turns that into the shell otherwise checks for errors and Otherwise if it's an orphan just does nothing and then goes back into the weight So you can see that this does its two jobs. It launches every other process, which is just as SH and then it just waits over and over and over again, so So this is a implementation of a knit courtesy of MIT so With that fun detour of the way we can talk about old operating systems like Just because they're historical things I guess so back in the day when you only had a single CPU core and It wasn't very powerful Operating systems did something called unit programming, which just meant you can only one run one process at a time So you can't have two processes running in parallel or concurrent No matter what it's just a really dumb operating system You just tell the process you want to run it runs it until it's done And then whenever it's done then you can run another process and that's all you can do with it So if you've ever heard of DOS before that's what DOS does it can only run one thing at a time So anything nowadays is multi-programming that allows multiple processes So two processes cannot can then run concurrently or in parallel, which if you know English That sounds like they're the same turn once we get into threads. Well, you will Figure out that to computer scientists those terms actually mean something different so But the goal here is just to say that to modern operating systems They just try and run everything in parallel and concurrently as fast as humanly possible and otherwise try and get out of your way so if we talk about the mechanics of Actually switching a process the scheduler this is the thing that decides What processes to run at any given time which we will talk about in the next lectures? But we know to create a process the operating system has to at least load it into memory and then when it's waiting or in that ready state That's when the scheduler scheduler that we'll see later decides if it should run or not But first before we get into that we can first describe the mechanics of switching processes So to see where it fits well, this is the core scheduling loop that negates the decision So whatever it makes a decision it will pause the current running process Save its state so you can restore it later, which would be all those virtual registers So it would save all the registers to memory on a stack is probably the easiest thing to do So you can actually see this code in that kernel. That's what it does Then from the scheduler it will get the next process to run then after that it will load its process state and let it run So it would do the opposite of saving it state So if you just pushed a bunch of register values into memory Well, then to load it you have to pop them from memory in the opposite order And then you would restore all the registers for that process and then you can just let it have the CPU and start executing so When it comes to actually stopping a process and making that decision we have two options so the first is something called cooperative multitasking and That means the processes will tell the kernel or the operating system That it can pause it. So it would be like, okay, you can pause me now if you want to run something else it's completely opt-in and If a program just wants to use the CPU it could just never call that until it exits And it would get the CPU and never relinquish it The other option is what's actually used called true milky tasking and the kernel or offering system would retain control and pause processes So in order to maintain control the offering system can either give a process a certain time slice and then After it's a lot of time has passed Well, then it goes ahead takes over and then decides to switch to a different process if it wants to The other way to implement this is a kernel can wake up periodically using interrupts And then do the scheduling in that interrupt handler and then switch the processes from there So on CPUs there is timer interrupts you can program So you can just program it the curl before it launches it could program in hey Wake me up in 10 milliseconds and then start executing that program Then the kernel would get a timer interrupt and it can go ahead and switch to a different process Was that what those are you on? Okay, good. All right so that process of saving all the state and then loading another state that Process of swapping processes. Well, that's a lot of processes. It's called context switching so at minimum We have to save the current registers here to save all the values using the same CPU We're trying to save on so that's why they have to go into memory So on some hardware there is a lot of registers So like on x86 there are special registers for floating point vector instructions There are so many registers on your on your CPU that saving that state can actually be pretty slow And if you just have to push for every single register you have that could take a long time so there's going to be some hardware support for saving sets of registers at a time and Then you also use that with software because hey if a program doesn't use floating point registers There's no point in saving them and wasting that memory and wasting that time to save them So Context switching is like pure overhead and what does overhead mean overhead just means it's work We're doing that is not productive So the mechanics of switching processes and saving their state The goal of your operating system is to run processes not to switch processes you want switching to be as fast as possible and You want that time to be as negligible as possible and otherwise you want 99% of the time Your colonel should be running processes not saving so you want that to be as fast as possible So that's why we'd want to do things where like if a process doesn't use floating point registers We don't bother saving them because otherwise we're just wasting time that we would rather have for other things All right now we can talk about a new IPC call. So yeah and before this So I graded lab 1 and 2 or 0 and 1. I forgot to press a button on corkis So I'll try and do that after but spoiler alert most people got a hundred. So it was probably easy lab 2 Gonna be harder so lab 2 will not be impossible, but it's process management and You won't have to directly use pipes, but after this lecture you should be able to do it and In fact in lab 2 your process manager There's like a 20% part that you don't have to do but if you do for 20% you can become a sub reaper Which is fun. So that's the like extra thing for lab 2 So let's talk about some new IPC so I Showed you before that. There's like this bar character that represented a pipe Well, it actually corresponds to a system call. So there is a pipe system call that takes as an argument a array of two integers and It returns negative one on failure sets error. No all that fun stuff Otherwise it returns zero on success and then writes values to that array and it will write two values for that array Representing two file descriptors For a one-way communication channel, so it would create a file descriptor for the read end of the pipe so for this file descriptor you're allowed to read data from it and From this file descriptor you're allowed to write data to it and the rule is whatever you write to the right end of the pipe you can read from the read end of the pipe and The way to think of it is well It represents a buffer like just a some space that the kernel manages for you So you can pass information between processes using reading and write So you can think of it as just a kernel manage buffer Any data is that you write to it is you can read it from the other end of the pipe and This will find out it's useful Through an example, but first before you get into the example, which is where we'll spend most of the day in questions So there is an ampersand you can use in your shell as well, which Basically takes this process and then runs it in the background and returns immediately So it would do the same fork thing, but it would return So if you did something like sleep one ampersand it would start a sleep process It and then it would just let it go it would get reparented and then whenever it's done It would go ahead and return and then on your shell you would see the process ID whenever it's done There's another one So we saw the pipe character that created pipes between two processes that we'll get into more today But that while true fork, this is the sneaky bash version of it So this defines a function called colon that takes no arguments that Calls colon with a pipe to colon so they feed data to each other This is just a way that you can launch two processes and then it does an ampersand Shove that off into a background and then here it finishes the definition and then calls colon to call it again So this would call create two colons throw them in the background every single time So this will do that exponential at fork bomb explosion We saw before when we had while true equals fork But this is like the sneaky bash version of it. Don't run the command Only give this to like other undergrads that don't know what they're doing or something like that But you know not to run that All right, let's see our example. So I imagine we'll have a lot of questions and that is good So what are we going to do? so First in this program. I will have a main we can start arguing about what it does by reading main So main will declare a integer array of two Elements and we will call it fd's and then we will call pipe with my check function What does my check function do all just saves me from checking in negative one over and over again So it will check if the return value from the argument is negative one if it's not negative one It just returns because there's no error Otherwise it will have that same error handling code. We all know and love so That's just a shorthand just so I check for errors. So here I Do a pipe system call and if it's successful it will create two file descriptors So I already know file descriptor zero one and two are already open So it would probably open file descriptor three. That would be the same as fd's zero Which would be the read end of the pipe and Then knowing that they go up sequentially four would probably be files fd's one Which would be the right end of the pipe So then after that I do a fork After the at the time of the fork the two processes are exactly the same So the parent in the child process will have the same file descriptors open. So They'll both have file descriptor zero one two three and four open in both processes and file descriptors You can think of like pointers in the managed by the kernel. So There'll be exact copies and what their file descriptors point to are going to be the same thing But the file descriptors themselves are independent in each process So both processes will have all those file descriptors open and the only difference is going to be the return value from fork Which we immediately check So in the parent we create a string called howdy child We get the length by calling string length on it And then we do a right system call to the right end of the pipe and we give it that string And how many bytes to write and then after that all we do is check if there's any error and then in the parent It just closes our file descriptors and then terminates and that's it in The child we declare a buffer of that magical size And then we immediately called read from the read end of the pipe up to however many characters we have Check for errors, and then we would print off any information. We read So if I go ahead and run this I see the child read howdy child Any questions about that? Says yep Yeah, so the question is do I have to instantiate the pipe anywhere? So it's just the pipe system call sets the file descriptors for you So yeah after this pipe system call if it's successful It would set fd's at index one and fd's at index Sorry index zero and index one Yep. Yeah, so that's a good question. So what happens if the child reads before the parent writes? so Because we have this fork it could happen in any order So after the fork the child could actually start executing first in that case we would go into this else statement declare buffer and then call read and Reads a blocking system call so right now No process has written any information to the pipe So it would just sit and read and then wait for someone to call right on it So if it runs first it would just sit there wait for someone else to call right So That way this prop the child process will get put to sleep on read and then eventually the parent process will run and Then call right and then the kernel will know that hey This one's waiting on some data. I'll give it some data Any other questions on this one? Yeah Yeah, well the question is if the parent child goes first would the parent have to be in a different CPU Yeah, so The answer that is well the child could run first It would get into this read which is a system call Then it would go into the kernel and then the kernel knows it's waiting for something So even on a single CPU their current can be like, okay, that process can no longer run anymore The scheduler would be like, okay, what can I run and the answer would be the parent? So then it would load the parent state and then start executing the parent because it's waiting on it So yeah, you do not need two CPUs to do this So eventually it would wake up. Yep. Yeah, that's a good question. Well if the parent doesn't write at all so This is a question we can actually answer now. So what happens if I do something like that anyone want to? Give me their full explanation for full points Yeah, yep, so you think that so the child would Let's say the child goes first it gets in the read and sting They're waiting for something and then the parent executes doesn't do anything closes So the child's just kind of chilling and eventually read would time out it would get an error or something like that The answer to that is no. So read will block until it gets data So in this case, there's no timeout or anything with it So if there's no timeout what would happen to it. So it would still be alive on that read, right? What happens to its parent, huh? Yeah, it's an orphan, right? So the child process would be an orphan and It would just be an orphan, right? It would get reparented to something, but it's still doing read So it's just an orphan. That's just waiting for stuff. So in fact, let's make let's make sure that that's actually true So that is a warning. I don't need so if I read it now Like you said it should be an orphan, right? So I read it. It returns immediately. So that means the parents done So if I want to check if a process of that name is still running Which do we remember why I should type? anyone yeah PS yeah, I won't get its process ID though because I'm lazy So if I just do PID of pipes That will go through that proc directory. You could write this now, right? It just goes through the process directory find something with a matching name and then tells you its process ID So it's still running and I could in fact go look at it See hey, it's still running. It's sleeping Its parent process ID is one. So it got reparented. So you are correct. It was an orphan It's just gonna sit here forever So it's obviously not going to do anything useful. How do I get rid of it? Yeah Killed dash nine. Do I have to choose violence? Okay, just what happens if I just do a normal kill Normal kills should be fine in this case, right? I didn't write a signal handler that ignores or do anything It'll just exit. So I don't have to choose violence today, but I could if I wanted to And then if I check hey, it's not running anymore so Full marks orphan process All right, any other questions? Yeah, the question is could a bad actor just run a bunch of these programs and Clog up your own OS and the answer to that is you don't even have to be a bad actor You just have to be a bad programmer so This will yeah, this will just clog up. It'll just create a process. It'll just It'll offend it'll essentially be like a zombie process, but worse because it's actually could execute So it's actually maintaining a bunch of other state. It's maintaining memory and yeah So you could clog up a system by doing that In fact, this is why you are using your own virtual machine because if you clog up a system, it's your system And if you want to fix it, you just reboot it You know and you reboot a virtual machine, which is a lot less annoying than rebooting your actual machine All right, so another thing to know about this is Here I will do something tricky To illustrate another So remember that file descriptors are kind of like mallocking and stuff where if you open them You should probably close them. So in this case in the parent I will go ahead and close them and then in the child well if I look at this code The child never uses the right end of the pipe right never uses the right end of the pipe It just reads from the read end of the pipe So if I want to be a good programmer, I should close it immediately when I know I'm done with it So in this case, I know I'm done with it if I'm the child and I should close it immediately and Otherwise, I'll do the read from it and then close it when I'm done with that So in this case if I compile this and run it Oops bill. I get some output from the child. If I look at it It's not running. I didn't create an orphan Hmm Anyone want to guess what's happening there? So this is the right end of the pipe that I close Okay, you Yeah, there's a guess I closed one of the pipes that caused an exit status So if I look at it from this one, this didn't have an exit status at all I didn't create an orphan, but I saw this message So if I saw this message What does that mean about the read call? Did it finish and It finished and I don't see any information. So it probably returned zero zero bytes red What does zero bytes red from read mean? yeah, so negative one means there's an error and Zero means that no, it's not possible to get any output from it. If this was a file, it would mean end of file So in this case What zero means for pipes the kernel is actually very smart about pipes So what zero means if you are reading from a pipe means you have read everything you can possibly Read from that pipe and it is not possible to write anything more to that pipe So the kernel will keep track of how many processes have a file descriptor that refer to the right end of the pipe If there are bytes read because it's not possible for that pipe to get any more data any more So that would return it would print that and then just close the file descriptor So and that's only possible because we closed the right end here, right? Whenever I move that to the end It was technically possible because the process that was waiting on the pipe could had a Reference to the right end of the pipe still open and could technically still write to it even though it is in a read system call How would that be possible? so the main The main program would be in the read what happens if I send this process a signal could it start executing something else it could So if I send this process a signal it is theoretically possible it could start Running some other code and then it is also theoretically possible that process could write to the right end of the pipe and Generate some more data unlikely yeah technically possible Yeah, it's technically possible. So the kernel needs to be conservative, which is why we create an orphan process before They were still right in the pipe open. So read couldn't return All right any questions about that fun one Okay, let's uh Go to and more examine type question then So Here's we know all about forks now In fact, we could have reasoned about this in lecture four, but these are fun exam type questions So for each program shown below state whether it will produce the same output each time Or whether it may produce different outputs each time when we're in multiple times and explain why it behaves like this So explaining why it behaves like this is like why is that freaking out is Like how many processes does it make and what do they print out? So let's go ahead and read that code and Let me know your response All right, any initial guesses by just looking at this Yep, so if I run this program Why get each time I run it will I see something different? Yeah, so if I execute it and Then execute it again. Am I going to be guaranteed to get the same output same output same order same thing So here yeah, no, but what am I going to see here? Do you know the output? I'm going to see yeah four three two one yet four four Four twos so we're gonna have like 16 processes here something like that all right Let's just go through it. So everything starts at main so I will just a Process has to run main. Let's just make it up I like saying process a hundred So process a hundred starts executing main Creates a variable called I what is its value? for right Nothing tricky so far All right is I equal to zero No, so we will go into this loop So then we call fork What will fork do yeah create a new process and it will be a child so Here I will put up the parent child relationships up here So one oh one created one or sorry a hundred created one oh one So I will be super creative with my names So process a hundred created one oh one if it is an exact copy does it have an eye? Yes was the value of I Okay, how do I So now which of these processes are going to run child Anyone I think the parent Parent in this point you don't know right two processes your kernel decides so you've Right now. We have no idea. So let's just say a hundred runs for some reason So in process a hundred. What did it return from fork? 101 right so it got the process ID of fork returns the process ID of its child So it would have returned one oh one then created PID and then assigned one oh one to it So now I could go check this if statement. So is PID equal to one zero sorry No, so what's it going to do it would print off I what would it print it would print for and then exit zero It is now dead So it would print for Now I have no other choice. There's only one other process to run. It's 101 So what would it do? Well, it would return from fork it gets PID equal to what? Zero right it is the child So it gets the process ID equal to zero then it would check here is process ID equal to zero Yes, it is So it's going to do I minus minus so it's I is now no longer for whatever it is now three right Then it's going to make it to the end of the while loop and then this PID variable goes out of scope and it goes back up to Here so now in that process is I equal to zero No, so it would do the same thing it would fork again What once again create? 102 See we do not have to be imaginative with our child names So unlike real life, you know, we can be engineers and name things and everything's fine so 101 created 102 So in 102 does it have an I? Yeah, what's its value of I? three Why why is it three not four? Yeah, yeah, it copied the parent at the time of the fork whenever Process 101 called fork. I was equal to three. So in the new one I is equal to three so Now at this point, we don't know which of these processes will run first Well, that's for fun to say 102 runs first. So 102. What would it return from fork? Zero right so it creates PID it's equal to zero and then It would check this if statement is PID equal to zero Yes, it is so it's would do I minus minus so it's I goes from three to two So should I do this in the other one does it go from three to two as well? No, why? Yeah, they're separate processes right so one doesn't affect the other so in 101 I Is still equal to three so process 102 would change I to two and then PID would go out of scope and then it would go back up to this loop is I equal to zero No, so it would fork again It would create probably process 103 Wow, that's a big arrow So in process 103 does it have an I? Yes, what's its value of I? to Okay Which process between those three runs thumbs up? Yes, yeah anyone there's no specified order here. So which one do we like? 103 we like 103. All right. So 103 What does it return from fork? Zero so its PID is equal to zero it would go ahead Fall into here I minus minus so it would have to change I from two oops so one Then it goes back up to the top of the loop PID goes out of scope So now is I equal to zero? Not quite so it would go in and then fork again What does fork do? Oh good. We're creating process 104 or 104 so 104 does it have an I? Yeah What's its value of I? one All right, what executes next? Yes, again, don't know Let's say process 104 goes What does 104 return from fork? Zero So then it would check here. Whoops So it would check is PID equal to zero in This case it is so it would subtract one from I So I is now zero so It would fall out of the loop Pid would get out of scope and then we would check this statement here. So does I equal zero? Yeah, I equal zero now. So it wouldn't go into the body of this while loop It would go to the end at the end it returned zero So it just kind of dies right just exits doesn't print anything. It's just just exits so in the rest of the processes Well, let's pick 102. What does 102 return from fork? Yeah, 103 Because that was the child it created So it would return 103 then it would go into this else statement and then it would go ahead and print 2 which is the current value of I and then exit 101 while It's fork would return 102 So PID would equal 102 it goes ahead it would print out three and then exit and then Sooner or later 103 would execute Its PID would be equal to 104 from its return value of fork It would eventually print one and then exit so We will So without counting the original child process we create a total of four processes. So you wouldn't count process 100 because that's what started executing main but through that we created process 101 102 103 and 104 and Each of them except for 104 printed a number and they were all different So you will see one three two and one This time when we argued about it the order we saw was actually four two three one Is that the only order I have? No, what else is possible? Yeah Yeah, any permutation of them There's nothing to stop it from being in any order whatsoever Okay Great same as the previous a question except we just added this line So we just add weight PID so weight PID it's the same as Weight so wait for a child process to finish or terminate. Yep Yeah, you just think it's just one two three four all the time Yeah, same thing Yeah, one two three four all the time right so why is that well if we get back here the reason that 101 hundred could print four immediately like it could just start executing four Or sorry it could just start executing and then we got into this else branch And then it printed and then exited but if we have a weight PID here It did wait on PID so in this case 100 before prints anything would wait for process 101 to finish so Even if it ran first it would get to that point and then it would be waiting on process 101 so it wouldn't print anything until 101 is done then 101 Would before the print would do the same thing it needs to wait on 102 Then 102 needs to wait on 103 then 103 needs to wait on 104 Then 104 needs to wait on 105 and or sorry 103 needs to wait on 104 And then 104 eventually just dies without doing print at all right so as soon as 104 finishes then the wait from 103 finishes and it would print one and Then only after that process finishes so it would print one and then exit then after that process is done 103 would be done so in 102 Wait would succeed and then this process would print two Then same thing then we would get three then four and then it would always be in that order because of the wait Cool. All right, cool Yeah, that's it. So just remember pulling for you. We're all in this together