 All right, good afternoon, I guess. Yeah afternoon. So Hopefully everyone is done with at least setting up lab one so Okay, come on. We're starting little countdowns already done so Hopefully you've already Set up lab one if you haven't set up lab one yet. I suggest that you do that the record for the Practical session to get someone running on an old version of Windows 10 was like 10 Windows reboots So especially if you're in on a older version of Windows 10 You should if you haven't done so already try and set up the lab environment and make sure it works otherwise if you're struggling set up the lab environment and it's due on a Monday and it's a weekend and The rest of the teaching staff tries to have like a little bit of a life then you might be screwed So don't do that So today we're going to talk about more practice or process practice get used to it We'll do some more fun examples today. Some of you might have noted There's only a few slides today So most of it's code examples and we'll go ahead run some stuff Make sure we really do this at the end. We'll do some like sample exam questions that they even had last year and Hopefully we'll understand it a bit better So yeah first note of advice on I'm good try and move the midterm to Tuesday the day before on February 28th So there seems to only be one major conflict with that otherwise most of you have already have an exam on March 1st, but The schedule I got says you guys are free on that week. So I guess that's not true So I'll try and do that try and move it to a better room That doesn't have the crappy side table thing because those are awful. So we'll see where I get at with that So if that presents a bigger problem, let me know so far it seems that might be better And I'll probably just move my office hours to Tuesday too to get the advance for that So the last thing I didn't mention in the last lecture when we were talking about signals and then being user interrupts Well, I should talk quickly about like hardware interrupts like on your actual CPU that we won't encounter but you should know about so On a risk 5 CPU, this is the model for most other CPUs or there's something similar to that There's actually three different terms for interrupts and different types So there is an interrupt which is something that's triggered by external hardware that might be generated by like your keyboard Or a network device or something like that and that's like some external source generates that and then you have to handle and We know that the kernel is the thing that interacts with the hardware So the kernel would have to interact with that and the nice thing about interrupts as opposed to polling is you know If we need to respond quickly we respond as quickly as possible if we use interrupts Then there's something that kind of behaves like interrupts called exceptions and they're not like C++ exceptions They're hardware exceptions. So they're generally kind of like interrupts, but they're triggered by an instruction So if you try and do like a division instruction one of the exceptions you might have might generate is if you try and divide by zero or something like that if you try and Access some illegal memory corresponding to that instruction and the default handler is going to be the kernel where Whatever process made that instruction gets suspended the kernel would get the exception and then choose what to do with it and one thing it can do is optionally Kind of translate it into signal and let the user process handle that So you might get an illegal memory access It would generate an exception on the CPU the kernel would get that and then maybe it sends a sig fault to your process and you can actually handle a sig fault if you want and Actually try and recover from it or do whatever that's one of the options you have Or you could do like we saw in the last lecture where you just kind of ignore sig faults But likely that's not going to get you anywhere because it would just try the instruction again, and it would probably fail again So those are the two reasons so an interrupt So that's something from hardware or an exception which corresponds to an instruction and then there is a trap So a trap is essentially the interrupt handler So a trap is just when you transfer control because of either a exception or an interrupt and it's the code that runs or the handler so a trap would Either those could generate a trap where the kernel run some code to handle that so In this case a system call would be it's an instruction So it would generate an exception and then that handler would be in the kernel and you're kind of requesting it So it could be considered something called a requested trap. So I want the kernel to run some code for me So that's a little bit terminology for the actual hardware So today it's going to We'll introduce a new API. So this will complicate our life with processes and make sure you really understand them So the new API is called pipe and it is just a simple system call That takes it might look a little bit weird But it just takes an integer array of size two so it just wants an array of two integers and then Returns an int and that it is basically either zero where it created two file descriptors or Negative one where it didn't create file descriptors and then it's gonna set error. No like we know in love so What pipe does is it will write values to that integer array for two file descriptors And it essentially forms a one-way communication channel using two file descriptors so You get two file descriptors out of it. So pipe fd The array at index zero would be the read end of the pipe so you're allowed to do system calls read system calls to that end of the pipe and There is these one index and that is the right end of the pipe and the way the pipe works is Whatever you write to the right end of the pipe will be read from the red end of the pipe Which seems kind of useless if it's one process but this is one way to do inter process communication and You can think of the pipe as like a kernel managed buffer that the kernel manages So two processes are able to talk to each other It handles, you know, setting aside memory for that where it lives all that stuff under the hood Where you don't have to worry about it and your interface is you write to one end of it And then you can read from the other end of it and you'll get the data sequentially out of that out of the other end so oh Yeah, first I'll do a quick aside So we kind of saw that you know, we can fork and create a new process and just let it run So you can actually use your shell to create a new process and not wait on it So have it return immediately and that's using that ampersand sign So if you use ampersand at the end of the command on your shell your shell starts that process and just returns instantly and kind of Let's it run in the background and it will tell you when it's done. So if you do like sleep One with an ampersand. So let's go and do that quick So if I do sleep one with an ampersand it returns immediately and tells me that process ID that just created and then Let's me type on the terminal again, and then eventually when it's done. It's going to say hey, it's done and You know, it's done. I mean that's pretty much it so What you can do with that is we can Kind of create a fork so that pipe character will create a pipe between two processes where the standard out of one It goes to the standard in of the other So one writes to the right end of the pipe Which would be whatever is on the left hand side and then the other process reads from the read end and it gets it replaces standard in so Remember we talked about a fork bomb before where you just kind of exponentially create more and more processes well in bash you can Create a command that will fool your you know, maybe your friend Maybe you're not friend into essentially fork bombing themselves and that's what this does So this looks just a bit weird and it's meant to be something you like kind of copy and paste That's why there's this warning do not run it so what this actually does is Defines a function called just colon that takes no arguments and what colon does is Create a another version of itself and then pipe it to another version of itself So every call to colon creates two more processes that just communicate to each other But they don't have to and then this ampersand sign throws them off in the background and then Semi colon just means I want to do another command again and then this semi colon just calls the function once So this would call the function once which creates two processes and then it would recursively call itself over and over again so that process would create two more processes and each of those would create two and That's a way to fork bomb your friends without doing just like the explicit while true fork in a C program So that's I feel like that needs to be like in every operating system course to understand that but you know Do not run that command so Let's get into Kind of the main example for today, so we're going to look at the pipes.c. It's in the examples repository So let's go ahead and see that So in pipes, let's look at that We're just going to write a little helper function here because we're going to make a bunch of other system calls That basically just checks it's the return. So we'll just always give this the return value of Whatever our system call wrapper is so it will check if it's not negative one that means there's no error So we're all good. So it'll just return Otherwise it will capture that error. No Print whatever the error is make sure that p error doesn't overwrite error No, and then just exit with the status, you know equal to that error code So just in case anything goes wrong So here's our main function. Do I need to make that bigger? Probably? Okay, so here's our main function So it creates an array of two integers and then calls pipe. Whoa, that's huge then calls pipe with that array and Make sure that it doesn't have an error. So if I get to this point, it means I have two valid file descriptors So I have fd zero which is oops the read end of the pipe and then I have fd's one which is the right end of the pipe So I'm gonna have that I'm gonna have two file descriptors if by default I already know what numbers these are I could probably guess my standard file descriptors as we all should know zero one two So the rule is it would use the next lowest available number So this is probably file descriptor three and this is probably file descriptor oops for So Whenever we fork again review, it's Exact copy two processes exactly the same the only difference is going to be the return value of fork So at the point of the fork There are now two extra file descriptors open So both processes will have all those file descriptors So the parent and the child will have filed descriptor three and four which are the read end and the right end of the pipe and The only difference is going to be the return value of fork here The PID will be greater than zero if it's the parent and you'll have the process ID of your child and the child It will be zero. So hopefully that is clear and I don't see questions about that Then we're going to check make sure that fork actually worked properly and then if it did We'll do something kind of tricky. So in the parent. We'll just create a string called. Hello. Howdy child I don't know why we're like Texan today, but apparently we are and then Just compute its length. So we'll get its length and then we're going to write some bytes So we'll write some bytes to the right end of the pipe So we'll write to FDs one and then the buffer will be Pointing to that string, which is just a bunch of ASCII characters And then we're going to tell it to write however many bytes are in that string and that's it And then we'll just go ahead and check that our right system call actually work correctly And if it didn't, you know, the process would exit, but hopefully it works and Then in the child the child will do something completely different So the parent all it does is write a string to the right end of the pipe and that's it So I'll scroll down quick to show that at the end of this If else all it does is close the file descriptors representing the pipe and then return zero So we'll just exit zero So all the parent does is right Now all the child is going to do it's going to create a buffer And that's, you know, only in the child and it would do a read system call A read, okay, stop that Okay, it'll do a read system call to the read end of the pipe and give it the buffer and However many bytes are in the buffer. It's that magic 4096 which we'll see why that is later and then What it will do is check if there's any error and then if not It will print F and this looks like a bit of a weird string format. You might be used to just ampersand S So this I didn't send a C string. I didn't send an all byte or anything Although I guess I could have but if you don't send an all byte what this will do is say hey The period is just a I want a width and then the star means I will give the width as an argument So I won't use a C string at all. So I'll tell it. I want you to you know Output a string of bytes red length. So read that many bytes and my string is just going to be the buffer So doesn't end in an all character because I don't know. I just didn't feel like it And then that's it. So Anyone want to tell me what I see when I actually run this program or easier first question How many processes get made one so I start executing main and it forks once and creates One more process. So while it's running, there's two processes the original one and the newly created one So what should I see as output here? Does the parent output anything? So does the parent output anything No, it shouldn't because it's just writing to the right end of the pipe Which isn't our terminal so we shouldn't see anything and then will the child print anything It should print out child read and then what should it read? Yeah, it should read Howdy child if this pipe works how it probably should work and that's actually a form of inter process communication Yep, so the parent wrote to the buffer right here Whoops, so it did a right call to FDs one and FDs one was The right end of the pipe Yeah, so in the else so the parent won't go into the else right so From the fork The parent will get a process ID greater than zero and the child will get a process ID less than zero So say we create, you know Say we create. I don't know process 101 Then the parent would get 101 as the value of that PID and the child would get PID equal to zero because it's the child and then we have two things executing at that point So the parents only going to go into this if branch and execute this code And it wouldn't go into the else because it went into the if So we're just writing that string So that string is pointing to a bunch of bytes because it's C So we're just telling it to write that string to the right end of the pipe So we're writing that string out and then we don't know what we're reading So we just create a buffer of some bytes that the kernel can go ahead and fill that information in with data So you can kind of think the curl is doing the opposite thing So for a write system call the kernel has to read the bytes we have there and then for a Read system call the kernel has to write the bytes to that buffer So what happens to the buffer is actually the opposite name of the system call Okay, so let's go ahead and so if we do that yeah Hey, it actually works So the child all it does is read and it gets information from the parent, right? It doesn't Read some string that string only exists in the parent because it's in that it's in that if branch and The buffer only exists in the child So I'm using a pipe and the fact that fork copies everything including all the file descriptors To be able to essentially share a file descriptor So I create a pipe I forked and I said hey parent you write to it Child you read from it and then since those are two different processes. They get some communication. Yeah So there's a so the question is how do we know the rights going to happen before the read? That Okay, so for that So yeah, so we do have to argue about both but Read is going to block so what could happen after we fork We don't know what's going to run first if the child runs first It would create the buffer and then do a read system call before we write anything and that's okay because read is blocking So read will sit there and the kernel won't return until there's actually something to read So it'll sit there if it happens to go first. It's fine. It doesn't matter because Read is yeah, what we call a blocking system call where it's not going to return until it actually gets some data Back out of it or that file descriptor gets closed or whatever Was that the other question too? Yeah, that aren't what sorry Yeah, so the question is can you use a pipe to communicate between? Things that aren't related essentially and the answer to that is no so I'm only be you're only able to share this file Descriptor because of fork copying everything otherwise you can't just create a file descriptor that just point There's no way to get a pipe back unless you have a name for it or something like that So there are other forms of IPC that essentially take a name that you can other processes don't have to be related But pipes they have to be related because they only get shared through fork. Yeah Yeah, so if you create the pipe like after the fork Then you can't share because each process would have created its own pipe So this I can only share because I created a pipe before I forked and then they both got copies of it or I guess the newly created one guy copy of it the parent always had it any other questions about that. Yeah So that's playing D which was the recommended thing for the lab setup So if you do the recommended thing you'll get arguments like that. Okay, so let's do something fun that will That will test our knowledge of you know orphans and zombies and all that So this is kind of related to that question of like, how do I know that right happens before the read? Okay, well One thing we might have a question about it was like hey What if the parent? Doesn't write at all What do we think is going to happen if I do something like this? So the parent doesn't write to that buffer and the child just wants to read from it Anyone hazard a guess as to what would happen here? Yeah. Yeah, so So what will happen? So let's argue about the parent so in the parent same thing It's going to set up the pipe then fork. So now it creates a new process and now In the parent it does nothing and then it would go ahead close the ends of the pipe and then Return zero which is the same as exit. So now the original parent process is now kaput. It is Terminated and we have essentially created an orphan. So our child is going to be an orphan It's going to get reparented to something. Is it going to still exist or is it going to die as well? So what what happens to our poor orphan process when it gets reparented? Is it still running or does it die or what happens? so system so the guess is system D or knit will kill it so System D or knit would call weight on it. So would that necessarily kill it? No, so it should System D would inherit it But it wouldn't kill it itself unless it was like blood thirsty for some reason because it's just a program Someone could have written it that way, but by default this kind of waits Okay, well, I guess let's just run it and see whoops So if I do that returns immediately because my shell only cares about the parent So I create an orphan. I don't even know what process ID is created But I can go ahead and run a command that will get me the process ID of everything with a matching name And if you've done lab one you could probably have written this program yourself So it's called PID of and then you give it a name and it will tell you if that program still exists So if I do PID of pipes. Oh crap It's still alive. So my child is still actually running. It's not a zombie or anything Well, I actually don't know if it's a zombie. So I can go ahead and check Proc 9 and 2 State Yeah, okay, so I see that it's there and sleeping so what is actually happening is Something strange So it's still alive and it's kind of just waiting. What is probably sleeping doing? Anyone has her to guess as to what it's actually sleeping on? Yeah, yeah, so it's actually just stuck on this read call So we can even verify that to be like hey printf If we do that, let's anyone want to tell me how to violently murdered this process because I don't want running anymore Yeah All right kill dash nine. I don't I'm sick of it All right, so now it's gone so I cleaned it up and then I'll go ahead and run pipes again So I see that I see before read. I don't see after read So it's still in that read call it is reparented to whatever System D in this case and it's sitting there blocking on read so why that is and why Some of you already asked in lab once like why the hell do I have to close my file descriptors? Won't the offering system clean up for me? Well in this case the offering system is not terribly smart So what it will do is for a pipe Read will return zero, which means there's no way to get data into the pipe only if Nothing can write to that pipe Which something can write to that pipe if they have a file descriptor open to it So my process that created the pipe Doesn't have the right end open to it because well one At the end this else statement closes both ends of the pipes and then it exits But it would have got cleaned up anyways when it exited But unfortunately in the child the child also has the right end of the pipe open and the read end of the pipe Open even though it never uses the right end of the pipe So because it has the right end of the pipe the kernel just assumes that maybe it could write data to itself So read I'm not going to return zero because it's possible Although maybe unlikely that it will actually get some data written to it. So We can actually clean up our child if we do something like this so if we close the Right end of the pipe When we're done or when we're done with it and then close the other one After we're done reading from it, and then I will just move this up to the parent so we don't double close ourselves Even though it doesn't really matter So if I go ahead and do this now, I won't create that problem. So let me kill this So that's my child. That's just kind of still waiting on something and I cleaned it up So if I go ahead and do this now I see hey I Created is on I might have created an orphan in this case I don't know what ran first or whatnot But because I actually closed the right end of the pipe and the kernel now knows it's not possible for no process Has the right end of the pipe open so read will actually return zero and then I print out Hey, I read zero bytes. So child red just shows nothing so this is a One very important reason why it is like really really important to close your file descriptors when you're done because that's how it works So any questions about that Okay, so yep So in this case Yeah, if the child is not possible to get any data into the pipe then read Would just return immediately with zero bytes red indicating that that it's not possible to get any more data So you're done So now we know why there's no like end-of-file character because it could represent different things So read returning zero mean just means it's not possible to get any output Or read anything more so that could be end-of-file That could be that no one can write to the right end of the pipe So I can't read anything so you can see why they made it really abstract because there's multiple reasons Why you can't read anything anymore Yep Yeah, so this is only one way so anything you write will come out the read end So if you want two-way communication, you need two pipes So I could make this that it you know That I create two pipes up here and Then in the child I write to one of the pipes and the parent reads from the other end and Vice versa for the other process. So that's how you get your two-way communication. It's just two pipes Okay, any other questions before we move on? Yep. So in this case So the question is if I didn't close the fd's in the parent would it still run like that and In this case it doesn't matter because when your process ends it will close all the file descriptors So if I just didn't have these lines Well in the parent all it does is just return zero which would call exit Which would close everything because that process is now dead So I actually didn't need to have them there. It's just kind of like good A good habit to get into just close it as soon as you don't need it anymore And then I would that's as soon as I don't need any more So if I did close them before the fork well Fork is a copy at the fork if I close them before the fork then they're closed in both of them So the other one wouldn't ever see it Okay, so Let's go back So yeah, so that's some basic IPC. We'll see more of it as the course goes on But that's like really basic ones and most of the mechanisms will still be the same read and write thing It's just how you get that file descriptor essentially is the only thing that changes So we saw kind of you could redirect some file descriptors for communication as we saw before with like that open stuff And then saw some signals. So this is recapping on last one too So signals were like interrupts for user processes and the kernel has to handle things more complicated There's like three different kinds of interrupts. So just remember, you know exceptions related to instruction interrupt from hardware And a trap is the actual code that runs So let's look at some finals from last semester. So one of the things People like doing is making sure you can understand and follow what processes could be running when So it says for each program below state whether produces the same output each time it is run Or whether produces different outputs each time it is run. So basically if it if it's constrained to be at a set order or if it's not and then explain why the program behaves like this So here's our example I'll leave that up for a little bit and let you think of that and then we'll go ahead and you know argue about it together All right, anyone have any guesses as to what this might do Yeah Yep, okay, so we'll go through it. Yeah Yeah, so Answer we have a bunch of print four three two one, but don't know what order. Yeah So fork remember it's a copy where both of them after the fork look exactly the same, but they're completely independent So in this case two processes do not affect each other ever Unless you kind of opt into it Yeah Okay, so one possible solution is print four four times Okay, so let's go through and argue about see if it's an order or what So here's kind of what it looks like So initially I'll give processes PIDs. So I'll say the original one just for argument's sake is PID 100 So PID 100 it's going to come it's the only program or it's the only thing executing and it's going to start main and Start executing it's going to create int i equals four and then After that line that would be allocated on the stack or something like that Well, it would be and then it would go into that while loop So the while loop is now while I does not equal zero and here all right in as well in PID 100 I equals four Since that's the main thing I care about so I'm going to go into the loop because I does not equal zero and then I'm going to fork So in the fork, I'm going to have create a new child and I'll write out What the two things are? So in the parent Return value of fork is probably going to be a new parent. I'll just use the next number just for argument's sake and then in PID 101 Well, it's an exact copy at the time of the fork that includes the value of I So in 101 I is currently four because that's what it was at the time of the fork and PID is going to be equal to zero because it is the child and now they are both right there Right after the fork. So We're both good right now. There are two processes right after the fork same. I value same everything At this point, we don't know what's going to run first. So we have to argue about it So if the parent runs first, which is PID 100 What's going to happen is it will check This if statement does not is not true So it would come in the else and then print for and then exit and now it's done. So this would have print For and now it is Kaput But that didn't need to happen because there's nothing preventing the other one from running first So it could print for first and then went kaput or in process 101 We would have come in here decremented I and it's independent. So it would have changed only the value of its I so now in that process I Equals three and it doesn't affect the other one one. Well, there's independent because of the fork They should be independent and two. Well, the other one could have Could have printed for even before that happened So any questions about that part? So just remember they independent so What's going to happen here is in this case? It would have went into the if statement decremented I which we did and then go back up into the while loop I Is not equal to zero then it would fork again So in this time through fork Forks going to essentially overwrite that process ID that process ID, so it would create a new process probably called process ID 102 and Exact copy at the time of the fork. So in that process What was the parents value of I? Three. So what's the child's value of I? Three and then in this case we'd be overriding the the PID So in process ID 101 that fork would have overwritten it So PID is now 102 and in 102 PID would have been equal to zero So any questions about that? So now they're both right here right after the fork and Then yep. Yep Yeah, because that would have been out well in this case it would have Reallocate so PID would have been dead by the time it exits this loop and we've got reallocated when it gets back to that loop So but it would be the same address But if you want to think of it kind of more theoretically Well, I should have I guess erased that PID equal to zero in 101 When I went back in the loop and then create a new PID because it would have allocated at the beginning of the loop So yeah, good point. I kind of skipped it step So in this case, we still know what's going to run first in this case We could process ID 101 Could execute first and then it would go into the else statement Go ahead and then print three and then it's dead So in this case was there anything to prevent Three from happening before four No, because what could have happened is well process ID When they forked and created process ID 101 I could have just executed 101 and let it run it were created 102 and then printed three and exited So there's I there's no ordering between them. They could have happened at any time so In this case, what's going to happen in process ID 102 is it's going to hit this if branch Hit I minus minus so it would decrement I So I is now equal to two and you could see how this could be a real mess If that I wasn't actually independent for each process We wouldn't even get four every single time It would just depend if that was actually shared between all of them. It just depends who gets to it first So you'd have no way of even knowing like that outputs of the processes would all be completely different So Whoops, so process ID 102 Go ahead it decremented and it would go back to the beginning of the while loop So I'll do the right thing and just say when it gets to the end of the loop It would have just removed that PID and then it would allocate a new one So now I have a fork again And I'm going to create a new process called PID I'm going to assume I'm going to call it 103 And what's its value of I? hopefully Anyone value of I? Two so exact copy at the time of the fork right now Process ID 102 called fork. It's I was equal to two So it process ID 103 would have I equals the two and then the only difference is the value of PID So this one would be 103 and 103 since that's the new one its PID is equal to what zero So If 102 decided to execute oops, sorry, I'll move this first So they're both there So now if 102 decided to execute first It would go PID is not equal to negative or not equal to zero So it would go into else and then it would go in this graveyard down here Where it would go ahead? It would have printed To and now it's dead as well So now it's dead as well So now the last process remaining is process 103 so what it would have done is it would have checked when this loop I is equal to one So it would have decremented I then Went back up to the top of the loop remove PID and in this case I is not equal to zero So we go ahead work again Creating process ID 104 Which is the exact copy so I would be equal to one in this case and The new PID in 101 would be 104 and 104 PID would be equal to zero In this case same thing is going to happen Well three would print One and then die and Then in 104 Well, what 104 is going to do? whoops So 104 is going to check the process ID. It's equal to zero. So it would go Or it would yeah process ID is equal to zero. So it would decrement I I is equal to zero now Then it would go back to the top of the while loop check the condition now I is zero So it wouldn't execute so it would just die and not print anything So we would get one two three four printed in no particular order So any questions about that? It's like Fairly complicated lot things are going on but the principle is a copy exact time of the fork. Yep So in this case there's only one kind of hierarchy of processes So my original process 100 So only the child creates a different process because here the parent the original parent immediately prints and exits So 101 would have created a child called Sorry a hundred would have created a child called 101 and then 101 is the one that created 102 And 102 created 103 And then what 103 created 104 So the PID is zero is that's the PID that the child sees the newly created process sees so In this case if I come in and start executing with process 100 if this is the only process running it's going to go ahead Get some space for four and Then call fork and then fork creates a new process so Fork would create process whoops, that's not fork creates process 101 and Then the way fork works the only way to differentiate them is the return value of fork. So in process 101 That variable would be equal to 101 because that's the process ID of the newly created process So that's your child and then in process 101 PID equals zero because it's the child process And that's the only way to tell which one's which Any questions about that? So what you can do or we can follow up on the discord too is you should understand also this one So same question except now there's a wait PID before before the print so Oh can't see okay same Whoops it wrong one again. All right, so it's the same thing except there's like a wait PID instead of the print So we can take it up in discord But just so there's a record of it. This should always print in the same order one two three four so You can take it up or ask questions if that is unclear. So just remember pulling for you we're on this together