 All right, welcome back to operating systems So there's someone in here earlier doing maintenance and then they read your messages that you're talking about killing children They left immediately. So I'm pretty sure Hopefully I'm not in trouble for that one. Whoo. All right. So today This lecture should be fairly chill. We just have one little project to do This will also help this is like the last lecture that will help you with lab two as well So if you do this, you should start lab two like start lab two immediately Otherwise because if you have a little problem, trust me if you leave it to the last minute You are going to be screwed because you probably need a good night's rest to figure There we go. All right So we want to create a new process that launches the command line Argument that we give it so it should be the name of a program that we want to run Then we want to send a string to it Just testing and then a new line Then we also want to receive any data. It writes to standard output So we want to give it some data through its standard input and then we want to read its standard output at the end of it So we are going to communicate two ways with it So we saw it exec Vee exec Vee is kind of a pain in the butt because it has that big array and It's kind of annoying to deal with so we can use this helper this little helper function called exec LP so what exec LP does is it takes a File and just the file name and it will look in all the default directories for you So you don't have to specify a giant path So and it also doesn't take a big array of C strings It just takes a variable argument around amount of C strings that we can end with null So again like exec Vee behaves exactly the same Returns negative one if there's an error and if it's successful doesn't return because we start executing a different program So this is just a little more convenient and the default directories I said it searches for that executable. Well, that's using a path environment variable So if you've ever heard of that, it's just like the default search directories for executables So some final API is dupe in dupe to so what do these do? so they will Duplicate create a new file descriptor that points to whatever that old file descriptor argument is so you can duplicate a file Descriptor so now two numbers refer to the same thing So in the first version, it just returns a new file descriptor Which would be the lowest number that is available For dupe to that new file descriptor is what you want the new file descriptor to be So if that is already open then it will go ahead and close it for you before it replaces it So it will do it atomically which might not mean that much to you right now But once we get into threads it will So it both will close that file descriptor if it's open if it's not it won't do anything and then make new FD referred to the same thing. So that means if I did like dupe to Four and what zero then whatever file descriptor four points to it will make file descriptor Zero also points to the same thing and it would close file descriptor zero if it was open before that So it gives you a way to kind of direct which number is which yep Yeah, so the question is will it close for that example and no so old FD will still remain open after this If you want to close it later, you can Yep, so the file descriptors are just numbers that the kernel understands So if you just make up a number, it's probably not going to refer to a valid file descriptor So if I just start if you launch a process and then you start writing file descriptor five You just pick a number of the blue. It's good. The kernel doesn't know what file descriptor five. It's not valid for your process So by default you can only depend on zero one and two being open But like yeah file descriptors are just numbers so you can tell the kernel Hey, I want to do something as file descriptor five and they can be like yeah, that doesn't exist Sorry, so you would just get an error if you did that All right That's it for the lesson today. We're just going to do a coding example for the rest of the class and The there's just a skeleton here We're going to go through the code today And then the intention is that the code isn't there for you so you can practice and redo this again So you can get some practice doing it So remember our goals. So we want to create a new process. Well, that's fairly easy Then we're going to have to send a string testing to it and receive any data rights Which probably means we need to use our good friend pipes so Here is the setup so I have this check error function that we all Hopefully know and love you were allowed to use this. It was perfectly fine. That will just check any standard C style system call check for errors for you and exit if there is an error and do all that fun stuff So in the main I'm going to check if there are two arguments because I'm expecting a name to run as the second argument Then after that, I'll just declare some space for an in pipe FD and then out pipe FD and then I will fork and then in the parent while I'll pass of those arrays and Also the process ID of the child and then I'll just write a little function for the child that takes those and then The name of the program to run from the arguments So right now in the parent does absolutely nothing and in the child it does exec LP So if I go ahead and run it with something like this What should happen? Executive people fail and return minus one. Okay, so first I'll just type you name It's a program. It's a valid program. It prints out Linux. So if I do something like oops, so if I do something like this Subprocess you name well happen. Yep Nothing same thing Why? Yeah, so this sub process Creates a child process and then that child process just becomes you name Because that's the argument. I give it. It looks up that you name Program is part of this file argument and by default we have to give it its own name as an argument and it just runs that So because it's our child and inherits our standard in standard out everything like that So it would just the child process would run you name and just print out to the terminal Right anyone disagree with that argc So these are just the command line arguments that you get from your running your program So in this case if I run this program like this this argc would be two because there's two arguments and This would be argv zero and this would be args argv one and argc Is just the number of arguments there are like it's the size of this array So this build slash like that whole thing would be the first argument That's how I ran the program if I was in the other directory Like if I was in the build directory, I could run it like this and it would just change the name of it But I don't use the name doesn't really matter Right. Oops Okay, yep What is your name? So your name is just a program that prints out the name of the kernel by default in lab Zero row you had to do like your name dash Are to get the kernel version you're currently running and that should look familiar to people So your name just is a program that will return some information about your kernel that is currently running So on Linux basically it would just look at proc and be like hey What version is currently running all that fun stuff? So that works any questions about that Cool. All right. So if I want to capture its output What should I do? so I Want to whatever it writes out to standard out. I want to be able to read it in my in the parent process here Yep, no, I want to read it in the parent process. Yep Yeah, I can create a pipe and make that process right to it And then I can read from it in the parent process, right? Everyone agree with that all right, so I Will use out if out pipe FD for that. So check error the system call was just pipe and I will create out pipe FD and then pipe whoops pie So I create a pipe and I do it here, right? So if I fork I should have you know file descriptor three equals to out pipe FD zero Which is the read end of the pipe And similarly for four It would probably be something like this, right? Did that yesterday? Hopefully not terribly surprising So if I fork both processes now have access to all those file descriptors, right? Quick question. What would happen if I did something like that? Yeah, you're gonna say the same thing too. Yeah, so if I do that Well, they would fork those file descriptors They both just have the valid like zero one two open at the time of the fork So that's when there's exact copies of each other and then after the fork They would both create a different pipe So the parent would create a pipe and the child would create a pipe and there's no way to share those file descriptors so the only way to way to share file descriptors through These style of pipes that don't have names or anything like that You can only share these file descriptors by inheriting them by forking That's the only way you can get a essentially a reference to the read or the right end of the pipe So I initialized Out pipe FD and then I have the child process here. So I Want to capture the Output of the child process, right? So what should I do here? Sorry? Right to right to the pipe So I should write To what the right end of the pipe What would I be writing the name the output of exec LP? What's the output of exec LP? So remember what exec LP does it? Starts running a different program right process doesn't change starts writing a different program. So before I exec LP from it is That program even going to be running probably not yep, we should fork again like here So it replaces this current process So that process when it runs When it's outputting to standard out, it's just like writing to file descriptor one, right? Yeah, so I need to change where it's going to write to so I need to change essentially Like when it does exec LP, it will inherit all the file descriptors in the process are going to be the same It just starts running something else. So if I want to monkey with the file descriptors What should I do? Yeah, close other way around Yeah, so old FD for both of these is the one that gets copied And then dupe will return a new file descriptor that also points to the same thing and Then dupe to that new file descriptor will point to the same thing at whatever number you pick so you can pick whatever number you want So in that case Okay, that died cool All right in that case we want to use Duke to so it says even whoops So we want to use Duke to and we even get a fun little message here So duplicate FD to FD to closing FD to and making it open the same file Again file descriptor doesn't actually have to represent a real file so We want to Essentially make it right to the right end of the pipe right for its output. So that is out pipe FD Anyone off the top their head zero or one one right so one is the right end of the pipe and what do I want to replace? File descriptor one right So if I do that and I run it I get unused parameters if I run this now, what am I going to see? Nothing hopefully right Everyone agree with that nothing why because now when that process is running you name It would just be writing to file descriptor one file descriptor one is the right end of the pipe Which we never read from so it just kind of goes off Eventually it will close all the file descriptors and then be done Right, so other things we should do to clean up. So what file descriptors do we have open at this point? so hopefully we have Here I'll write them in separate lines to So what else is open so zero? Well that standard in One we just replaced it with the right end of pipe to a standard error Yeah, three the read end of the pipe. Yep, that's still open. Well, so it's likely still open Yeah, probably four as well. So So if I'm being nice and following conventions Should I close some file descriptors here? What should I close? close for Type of D Alright anything else I should close Yeah, I need the read open either right The convention is that when that process starts executing only file descriptor zero one and two should be open So I may as well close three and four. So I made one essentially point to something else so that's fine and After that, I don't need three and four. So they don't have to be open. So right Here the only things that are open before the exec LP is Zero one and two which is great, right? So I want to read data from that process. How should I read data from that process? From the parent so I'm just using exec LP instead of VE because remember exec VE Needed like slash user slash bin slash thing. I don't want to type that I don't have to make the user input that every time. So exec LP Will search the default directories for executables for you So instead of user slash bin slash ls or you name or whatever I can just say you name and it'll find it for me Yeah, yeah, so in this case the child process gets replaced by you name whenever if my arguments you name, right? I could make it Cat or something like that in which case it kind of complains at me Yeah From here, I just read from So I should read from file descriptor one here some other steps. What are my other steps? Yep, should I not read from the read end of the pipe who wants to read from the read end of the pipe? Yeah Huh? You're good. Okay. So what's the read end of the pipe? zero right So the child process Whatever it executes, you know would write to file descriptor one, which is the right end of the pipe I should be able to read from the read end of the pipe and Communicate with that process, right? Yep. So right now Yeah, so right now. What are the file descriptors open in the parent? Yeah, so right now its file descriptors are like Essentially like this right so I didn't in the parent. I didn't monkey with file descriptor one Right, so these are the file descriptors are open in the parent, right? So if I want to read well, I Will go ahead declare some space of a magical number and then read from it Check error by All right. Yep What was which one? So in in pipe FD, I haven't screwed with yet So I just declared some space for it and gave it as an argument just as a reminder to myself. So we're just like Using our tasks. We're just breaking it down one step at a time So right now I'm just trying to get the output from the program because remember our other task is to send it some Input so that's why that one's there So where'd we go? So in the parent We read some data right looks about right anyone argue with that one Oh, yeah, well, so the comment should I close a file descriptor? I'm not using like immediately Yeah, why am I not using for which is the F index one, right? So I may as well just close that Yeah, I may as well just close that okay, so that looks pretty good, right any disagreements with that All right. Well, let's see. Let's do a little print F then so got percent sign dash Don't need a new line. Oh if I do something like that, it should just Print off the string I receive, right? So now I captured it. I got Linux, right? So I'm communicating with it at least in one direction Everyone agree with that every looks kind of cool. Yep. Sorry. Oh, yeah So the percent sign dot star s Means that this is not a C string So the dot star means just take an additional argument The first argument should be the number of characters in this string Sorry Yeah, bytes read is the number of characters because I'm just doing a read system call that returns a number of bytes actually read so I know the only that number bytes is valid and It is not a C string I'm not guaranteed it to be a C string or anything like that because it's just from standard output so It doesn't end in a null byte. So I have to tell it how many bytes there are Yeah, if it was a C string, I could just use percent sign s, but it's not yep Would it also sorry in the child? What would happen? Yeah, so that's exactly right. So like in you name you name could be printing do in printf to See the output Linux, right? But it just writes the file descriptor one So we would capture that output to if it use printf, which it probably did Like in the child if we do a printf like here. Well, let's let's see. We can just print it off. Hey, hey compile So in this case it does not Wait, did I not compile it? So It should Did I save? Oh, I might not have saved Yeah No, so this should go to file descriptor zero or one. Sorry, which is still the right end of the pipe So this should actually print Ah Okay, that's weird I would have to look at that later But that's a good question because we should see it because we just wrote to it So that's really weird Okay Yeah, yeah, so the question is can I change the the file descriptor that exec LP actually uses and the answer that is no because When that executable runs it just would do a system called a file descriptor one So I can't change what file descriptor uses but I can change what file descriptor one points to Yeah, so it would make here after the dupe to file descriptor one also points to the right end of the pipe So it doesn't get closed. Yeah, unless something to do real bad happen So I might have to figure that out later because that seems really weird But anyways, if I do that I still communicate I should probably still close another file descriptor So after I read I should probably just close that Other I don't need the read end of the pipe anymore. So I should probably close that So that looks pretty good. So at this point I got a one-way communication channel Oh All right, so yep, so I'm using Duke to because I want to replace File descriptor one with the right end of the pipe. So when exec LP happens, right? This will just run whatever that program does That program is this going to output the standard out which would be file descriptor one I can't change that it uses file descriptor one without rewriting that program But I can change what file descriptor one points to which is why I use Duke to and I tell it Hey, I want to change file descriptor one Yep, so exec LP will essentially Reset the process control block, but keep all the file descriptors open So it would just load that program into memory and then start executing that program as long as it can find it Yeah, yeah, so I made File descriptor one point to the right end of the pipe Before I did exec so that if it uses the standard out It goes to the pipe instead of to the terminal So the print F isn't working because it should be Writing to the right end of the pipe. So I should be able so I wouldn't see it in my terminal But I should see it when I read from it It is what they comment of so I probably need to do two read calls or something like that But I should see it first, which is weird. Yep Yeah, so the question is why do I close file descriptor three and four in the child process into that is Because I just want to follow the convention by convention. It should only have those three standard file descriptors open and it's Considered rude to have additional file descriptors open Because it like Yeah, because you don't expect them to be open You don't know what they're supposed to represent or anything like that. They're like unspecified. So Why would they be open? Yep Yeah, yeah, so it replaces the current running process. So like the process ID of the child isn't going to change at all but it's going to End what it was running do the exec LP call Load that program in the memory and then start executing that program Yeah, if we did s trace on it, we'd see that called the exec LP start executing something else and then you can give So s trace might stop at that point But I think there's an option where you can say hey follow the new process or follow it after the exec as well So there's an option to do that, but s trace will follow it. Yeah Okay, so other half of the problem we want to send data to this process so I Can create a new pipe call it in And I essentially want to do this the other way, right? So I want to send the string Testing to it. So if I want to send the string testing to it Well, let's see in my parent That is where I would want to send it and I should probably just write to it first. So if I want to Send the string testing I could just declare it testing and Then do a write system call. So Sist right What should I be writing to the right end of the pipe which is that index? one So I want to write this message and it's the length of the string length message So that should write some data to my child process, right? So I might need to wait in the child process for the parent to execute first You want me to do a wait here? Yeah reads blocking, right? but right now actually Yeah, right now our file descriptors look a bit different because there's now two pipe of There's now two pipes, right? So it looks kind of like this In in In and then this is the out So if I go ahead and run this right now and say I'm supposed to be writing the string testing to it So if I run like cat Well, if I write this string testing to cat it should also output that string testing, right? So I should receive the output if I go ahead and run this now Yikes sits forever Whoops So what's cat doing? So it's waiting for its input, which means it's what yeah. Yeah. So right now cat Was reading from stand from file descriptor zero, right? In cat. What's file descriptor zero? Yeah standard in its which is by default my terminal So if I do this cat's actually waiting for me to type something Which is not what I want. I want to send it information. So what do I need to do? Duke to again, right? Yep. So Now I can get rid of all this So I want to do to Probably in use the in pipe. Is this right? No, right. I want to replace file descriptor zero and I should probably replace this because it's currently the right end of the in pipe, which I don't want I want the read end of the in pipe so then I've replaced its Zero file descriptor with the read end of the pipe So it should be able to read any information I give to it And I should also be nice and then close these file descriptors oops So I'll just do the lazy thing and replace it so there So now my child process cleans up all the additional file descriptors after it replaces them So if I go ahead and run that now God testing Am I done? I have to close the file descriptors in the parent. Yeah, I probably should Here I should probably also check for errors So I should close the file descriptors in the parent. What should I close? What am I not using immediately? Yeah in pipe FD zero it never uses the read end of that pipe and Also, it never uses the right end of the out pipe, right? So we should clean those up immediately and then we should close the other ones as soon as we know We don't need them. So after the right system call. We don't need this one and after the Read system call we do not we already close it. So we're smart. Yep Do I double which one do I double close? Oh, sorry out pipe one. Yes, so I can delete that. There we go So yeah, I get an error from clothes, right? Yep So right now I'm running cat not you name So yeah, hey we can try that so You name if you just run it doesn't take any input, right? And it just outputs Linux so Well, guess what we're sending a string testing to it So now if I do sub process you name and run it Hey, it's still just outputs Linux because I send it data But it never reads from file descriptor zero So it just kind of ignores it and then when the process ends Curl goes ahead and cleans up all the stuff for us because it's nice like that So am I done this looks pretty done? But we have to ask ourselves one question What is that question? Are we a good parent? Sorry? Yeah, did we ever wait on our poor child? No, right. We should probably do that. Where should I do that? So here. Let's just make up some stuff So I need to wait and then I will check for errors So I'll just wait on my child here, right? And we should always write some assert statement. So I'll just assume that W if exited is true and I'll also assume that The W exit status of W status is equal to zero Kind of indicate that the program exited no problem So does that look good? All right, no complaints. Let's I run this now I should get the same thing where I read this testing again, right help my program is not working. Yeah Should I do it after the right? So should I do it after the right after the read? So let's see. Should I do it after the right few hands? Should I do it after the read? More hands There's slightly more hands. All right. Well, let's do it After the read So we'll do it as the last thing. Hey, that works Cool, but what did it have worked if I moved it up one? So What is the? Thing preventing weight from actually returning Yeah, the read The read in the child is waiting, right? So what is signifying to the child that is not going to get any more data? Yeah, the close of what? Yeah, the close of in pipe of D1 right here that signifies That's closing the right end of the pipe that the child's reading from right and Because the child cleaned it up. Well, it doesn't have the right end of the pipe open And then if I don't have the right end of the pipe open the kernel knows that No problem We can close this. So if I miss this Even if I put this way down here What's gonna happen? So I didn't close the right end of the pipe So if I run it What am I expecting to happen here? Yeah, it should wait forever again, right? So it got the string testing because I sent it to it But now remember cat just reads over and over and over again So it's still alive and it's still waiting for some more data because it could read some more data if it really wanted to Right cool so any other questions with this so what we do we created a child process we Execute it we turned it into something else and we communicated with it So we could send data to it and receive data from it, which is kind of cool if you've ever used the Python sub process That's a really useful tool in order to call other programs. I use it all the time in fact That's what's grading your assignments But and this is essentially how it works so you can ask Python Hey change file descriptor standard in to be a pipe So I can read its output or give it or give it some input and then you can play with processes like this So it will do these things for you, which is kind of neat Any other questions? All right, if not, I'll be here. It's Friday. So we can kind of chill but Again for this lab to start it. All right. Just remember phone for you. We're on this