 Welcome back to 353. So today we get to talk about IPC or inter process communication. One of the most important aspects to actually using an operating system that is for some reason not given its own chapter in the textbook. So we will get to have some fun with that today. So IPC basically that's whenever you transfer bytes between two or more processes. So you've done IPC even as soon as you've done Hello World that is technically IPC you are sending Hello World to the terminal so it can go ahead and actually display it to you. You've probably read or write files that's also technically another form of IPC where two processes are communicating but they're not even active at the same time. So one process writes a file another process reads a file that's technically transferring bytes between two processes. And we also kind of already seen the read and write system calls so that is how we do our inter process communication. So we'll just start off by getting right into a code example. So a simple process could just write everything it reads. So we should know the standard file descriptors. So file descriptor zero standard in file descriptor one is standard out file descriptor two is standard error. And knowing that we can go ahead and write a little program. So hopefully can we all see that in the back. Good. All right. God not. So we can write a little program that actually uses the read and write system calls. So here first line this doesn't spawn any new processes or anything like that. So just a single process. So I declare a buffer of some magic size 4096. It's a power of two. Why I chose this side we will discover later turns out the operating system really likes that number. Then here I have a variable to actually capture the return value of a read system call. So it returns says SST. That just means it signed because if it's negative one that means we have an error. So here I do a read system call. So I read from file descriptor zero. And then you have to give it the address of the location to write the bytes to and then how many bytes to actually write up to. So here I give it the address of that buffer and then say you can write up to 4096 bytes. And this while loop will just run over and over again as long as read return some bytes. So as long as it read some bytes it will keep on executing this while loop. So what I do is read will return the number of bytes read. So it's not going to fill that entire buffer. It might only I don't know write 10 bytes to it 12 bytes. Who knows depends really what we get in as our input. And then capture it in bytes read. And then I do a write system call to file descriptor one. So that is standard output. Give it the address of the buffer and I just write out the number of bytes I have read in. So I read in some bytes and I directly shove them back out. So read from file descriptor zero write to file descriptor one. Looks like a pretty silly program. And here write you know like printf and everything will return the number of bytes written. Here I actually check. So I see the number of bytes written. See if it's negative one. If it's negative one there's an error. So I'll save that error no inside a variable. And then do p error. And why I do p error is p error might also set this error no variable. So it might itself has an error. So I want to know the first error that's happened. I don't really care about the second. So print an error message and then I'll return that error no from main so I can kind of figure out what it's doing. And in this case returning from main same as calling exit. So here I write a little assert here. So I just check if you haven't seen asserts. Good thing to write. So you can program defensively. So if you ever make an assumption about your code you should probably write an assert statement. And what it will do is check this condition. And if it will is false it will immediately terminate your program there and then tell you you know the line number that has happened at and a bunch. So you can actually figure out what the hell is going on instead of trying to debug things randomly based off your wrong assumption. All right. So another thing here the only way to get out of this while loop is if read returns a value that is zero or below. So if it returns negative one it means there's an error. Otherwise I should see that there are zero bytes read. And what we'll learn the way that the read system call works. It will return zero bytes read whenever it's not possible to get any more data. So for example if that file descriptor was like a file that means it has reached the end of the file. There's no special end of file character on Linux or anything like that if you've ever been taught that. Not a thing on Linux. The read system call just says hey there's no more information left there's no more bytes to read. So looks like a fairly silly program. Right. Doesn't really do much. Yep. So we don't actually know what file descriptor zero represents. So we'll walk through it. Well I'll run it and we can see what happens. So let's just run it. So I called it read write example. So if I run it nothing happens. Right. Because whatever I type should be in standard input. Right. So right now the code what it's doing it's doing a read system call. But there's currently nothing to read. So that's like a blocking system call. It's waiting for some information to actually come in. So if I go ahead and type something. So say I type hello. Still not doing anything waiting for that read system call. And then if I wanted to say that I'm done writing a line what should I hit. Probably enter. Right. So if I hit enter. Well then that read system call would return. In this case it would probably return hello and then a new line. So it would it would fill up this buffer with hello and a new line and probably say it read six bytes. So it would go in here and then we would do a write to file descriptor one using that buffer and six bytes. So that's why I immediately get back what I typed in. So anything I type in every single time it will print the same thing again. So hi says hi you see whatever says it seems pretty useless. Right. Or does. But have we used a program that actually is implemented like this before. Any guesses as to what this actually is. Yep. Microsoft Word not quite like a Linux utility we've used. Yeah. Yeah. It kind of looks like cat. Guess what this is cat. So oh also I just type here over and over again. How do I how the hell do I get out of this. If I wanted to get control back over my terminal and launch some other process or do something what do I have to hit. Yeah. Control C. I could hit control C. Any other things I could hit. Yeah. Control D. Yeah. So there's we'll learn what both of them do today. So first control D. So control D will tell your terminal you are done writing. So if I hit control D that means I'm no longer going to write anything which means there's going to be no more input on standard in and then that means this read system call would return zero in this program and then it would fall out of this while loop. There wouldn't be any error because I didn't screw up and then it would pass this assert. So number of bytes right is zero and then it quits. I can get control over my terminal again. So questions about that. Yep. Yeah. In this case since I just typed it in my terminal file descriptor zero and one of my terminal. Yeah. So yeah that's the next step. So if I've used cat I could do like cat file name right. And then I get the contents of the file name. But here I mean I can't write that because if I write anything else I don't really like take any arguments or anything like that. So this nicely slides into our next example. So here I'll just write pretty much the same program. So here I'll scroll down. So this part of the program right here that just reads and writes from file descriptor reads from file descriptor zero and writes to file descriptor one. I have not changed a single character. It is exactly the same. So I'm going to make this operate on files. So how do I do that without even changing that. I like I still use file descriptor zero. So it turns out what I can do is first you know I'll take an argument now in my C program. So if there's more than two arguments I'll just fill. I'll just assume that there's exactly two arguments. So first one by convention is the name of the program. And then the second argument is going to be the file name. So there is this closed system call that takes a file descriptor. So remember file descriptors are unique for a process. So whenever this process started by convention it had three file descriptors open zero one and two. And past that processes are independent. So that process could go ahead and change what its file descriptors are if it really wanted to. So here I close file descriptor zero. So now file descriptor zero is now no longer valid. If I tried to read from file descriptor zero at this point I would get an error because it doesn't actually represent anything. So next system call I can make is a system called called open. And that is how you open a file. And what it returns is a file descriptor. And then when you do system calls on that file descriptor well it operates on this file. So I open the file that I give as an argument. Whoops shouldn't have done that. And then here you can give it some flags. I'll just say that I am opening it for reading only. And then you get a new file descriptor back out of it. And Linux has very specific rules how it assigns file descriptors. So it will always assign the lowest number it can. So in this case since I closed file descriptor zero zero is now no longer valid. So file descriptor started zero. So if I open a new file and get a new file descriptor back I'm going to get zero because that's the lowest number that's not used. If for some reason I did not close file descriptor zero and I did an open here I'd probably get back file descriptor three. So you can think of file descriptors also kind of as pointers. So in this case I'm saying file descriptor zero actually points to that file. So if I do a read and write system call on it I'm operating on that file. And then pass that I just check for errors. So now if I run this I can be meta and just open on its own source file if I want. And without changing anything hey guess what I get the contents of that file because now whenever it's reading from file descriptor zero file descriptor zero now represents that file. I didn't change file descriptor one so it still goes to my terminal. Yep. Yeah so by default the convention is whatever you start your process you'll have file descriptor zero one and two open and they'll be valid. You don't know what they actually point to. So yeah so usually for a lifetime so far they always refer to our terminal but they could actually represent files if you wanted to. Nothing could change you don't really have to change anything. Alright any questions about this fun program? Because yeah guess what we if I just do this and I just change the name and say hey instead of that it's cat it does the same thing. This actually is cat. You can go through the system calls if you want to see that they're the same they're the same. Yep file descriptor two so you just want me to change like this. So if I change this like I'm not going to see anything different because if I'm running things in my terminal file descriptor two and one both represent my terminal so I actually can't really tell which one it's coming from but here if I run it it's just still to my terminal but it's actually to a different to separate them because even with S-trace so S-trace outputs the standard error and the normal stuff goes to standard out and you can separate them if you want to see which came from where so that's one thing you can do with them. Alright yeah so in this case if the file was larger than what can fit my buffer it'll just do multiple read system calls until it's eventually done. So I'll read like 4096 write it out read another 4096 write it out so yeah file could be as big as I want. Yep so the first argument here so that's just the file name so in this case just the C convention is it puts all the arguments I type out in my shell inside this character array here so this would be the first string in the array and this would be the second string in the array so I'm just opening this string that's just hello example dot C and yeah another question does this include a new line at the start of the file so the file doesn't start with a new line but would end in a new line but yeah it would capture the new line and read everything so read the entire contents of the file. Okay so here's we can play around with it more today so here's just for reference so read that reads data from a file descriptor right now that file descriptor could represent your terminal could represent a real file and there's no such thing of it as an end-of-file character read will just return zero whenever it is not possible for any new output to happen and that's it so if you are trying to read on a closed file descriptor you might get zero you might get an error you should probably check for errors save error no all that stuff so right is the other side of the coin just writes data to that file descriptor returns a number of bytes written shouldn't assume it's always successful same things and this is a type of inner process communication even when we are doing hello world or even typing into my terminal so that standard input I was reading from another process might must be writing to that and the other process that's writing to it is your terminal so whenever I was typing hello while my terminal's job was to take my keyboard press and then translate my keyboard presses into ASCII characters and then write them to a file descriptor that my process is then going to read from so that is how we got to communicate between my terminal and that program I just wrote so these standard file descriptors really really powerful so even like I showed when I disclosed it open a new file I didn't have to change the core part of my program and suddenly it could just use a file I didn't have to change anything in fact you can actually do this with just your shell so I will show you some little tricks so so you can actually manipulate your shell if you want so we'll run the program we had before that was just reading from file descriptor zero and writing for file descriptor one it wasn't trying to open a file or anything like that so it turns out part of what your shell will do is it will replace the standard file descriptors with files for you if you want it to so whoops not that so if I type a left arrow and then say a file name what that will do is your shell will before it actually executes this program it will open this file as file descriptor zero and that's it so now whenever I run this program instead of me typing well file descriptor zero is now a file so if I do that I see the contents of a file so turns out if you actually know how to use your shell you don't actually have to write support for opening in opening files in your programs at all you can just use your shell and just replace the standard file descriptors if you want and there's also if there's a left arrow there's probably a right arrow so a right arrow will replace will replace standard output so if I wanted to I could just say let's say let's just call it copy.c so now my program is going to read all the contents from open example.c and then write them out to copy.c so if I do that oh I don't get any output in my terminal because well I'm just using file descriptor zero and one and if I now have it just use cat look at copy.c hey it's a copy of that file so turns out you don't actually need that CP command to copy files you can just use cat if you want and just change the input in the output so turns out CP is kind of redundant which is fun if you do this stuff so any questions about that? CP might do a bit of things that make copying files bigger so it'll probably use bigger buffers and things like that but at the end of the day your end result is going to be a copy of the file so this will do the same thing CP might actually just be a bit faster but other than that does the same thing yep so if I have more than 4096 characters then they'll just read several times yep yep yep yep yep yep yeah so in this example so it's just a question about what this copy.c is so if I have the right arrow that means my shell will open copy.c and then before it actually runs that process it will make that file descriptor one yep so it's just convention so the question is why does it even have a standard error so it's just another file descriptor to write to that it's supposed to just be you're only supposed to write it in the event that you have errors so yeah this p error function as well so this p error actually writes the standard error not standard out for example but it would just write to file descriptor two instead all right so now we can actually figure out so here's that on the slide just for reference so yeah someone told me to use control C to end a process has everyone used control C before at some point all right anyone want to explain to me what the hell it is or what's happening yep yes okay so probably didn't hear that so let's go into what happens when you press control C so when you press control C what is actually happening is you are sending something called a signal to that process and hopefully people have had like a low-level course before then where you had like an interrupt handler everyone remember that term so interrupt handlers are like the hardware name for that that's like the actual what happens signals are basically interrupt handlers but for processes so they behave exactly the same way so when you press control C what happens is your shell is going to send a signal to that process and a signal just gets some type of number to figure out what you actually sent to it so it basically just sends a interrupt to your process and that interrupt you just get a number to figure out what it is and then you get to handle it by default you can actually install your own handlers so there's going to be some default handlers for you if you do not write one yourself and by default the interrupt handler that runs when you hit control C will just call exit and terminate your program and the default handler actually exits with the error code 128 plus whatever the number was from the signal handler so if I go back knowing that I can actually figure out what signal number actually gets sent so here if I run read right example and then I hit control C well here this part of my terminal tells you the exit status of the last process so in this case it's a hundred and thirty so what's a hundred and thirty minus 128 too so it means whenever I hit control C it sent a signal to that process and the signal number was they just chose two for it so the default signal handler actually ran and well this is also why we have exit statuses so you can actually figure out what the hell is going on whoops oh I believe I accidentally hit something I shouldn't have you okay sorry about that I hit the wrong button so you can actually write your own signal handlers you want with the system called called SIG action and well we can write our own program that does that and it will definitely confuse your friends that have not taken this course so here's my signal example so I'll write the same program I had before that read and write from standard error or from standard in and then printed out just wrote to standard out so here all I'm going to do is register two signals so here is a number we don't know so SIG in that signal to it's supposed to be short for signal interrupted so it's supposed to just represent your program getting interrupted by a user and they just picked two and I will register another one for funsies that we'll figure out later so this is how you actually just register your signal handler so this basically tells the kernel hey when my process actually gets a signal instead of doing your default thing that will actually exit my process please run this code instead so it's a bunch of code basically I'm just telling it to run this function called handle signal and this handle signal is my interrupt handler code so it takes as an argument the signal number and then here I'm going to be an absolute jerk and I'm just going to print I'm ignoring you whatever so now if I run this something fun happens so I do this still works but now if I press control C something odd will happen it will say it ignores but then my process dies and this is why you should program defensively because I actually got an error from my read system call and that error says interrupted system call oh jeez that sounds like something I can actually recover from and I didn't know that would happen so if I didn't program defensively my program would just crash and I would had no idea why and I would not have been able to debug it at all but since I did that I know system calls can get interrupted annoyingly and the remedy to that is you just call it again you just restart it so I can rewrite the program that looks a lot uglier but actually handles that case so here in my while loop I'll check that for an error from read and then you'll have to look up what interrupted system call refers to in terms of error no and if you look it up it turns out it's this error so E interrupt so short for interrupted system call I guess so I just say hey if I got an interrupt system call that's not a real error I'll just continue I'll just do it I'll just kind of restart that read system call otherwise I will just break and content and then it's a real error and I'll print it out so now if I do this and then I hit control C I'm stuck so if you gave this to someone like 105 or like in a beginning C course and you could really annoy your friends that haven't taken this course yet right actually I probably annoy you right now if I had you guys run this anyone know how to recover from this yeah yeah so what you can do is try and send it there is a signal you can send that says hey please exit and please stop and that signal is just called sig term so it's like please nicely shut down so if I want there is this little command called pid of so that will tell you the process ID of anything that matches the name after you've done lab one you could actually write this yourself if you really wanted to just go through the proc file system looks tries to match the name so in this case the process ID of what is running here and being an absolute jerk is whatever 15,000 something like that so this is where our terms get more fun so for some reason don't ask me why you would think the system called to like send a signal is probably called like send signal or something like that yeah screw that it's called kill so kill there's a little program they wrote that basically just does the kill system call so I can say kill and then this process ID so by default kill will send that sig term to that process and ask it to actually stop if I go back and look at it says ignoring signal 15 so turns out that's just a suggestion to and turns out in this program I ignored it so now I'm stuck anyone know what I can do for this yeah yeah I could hit control D in this case yeah without control D control D is a cheater yeah yeah I can hit the power button and then boom it's done so that is I guess a solution yeah yeah I could I could run this as a knit probably wouldn't help me finish or close it so yeah so so far this thing is an absolute jerk so curl developers are smart and there is a way to actually kill something and mean it so if we go back let's see so there are some signals there that are they're kind of non-standard but on Linux they're always going to be fairly consistent so there's a sig int so that's control C whenever you interrupt from keyboard there's sig sig v which is what happens when we access null memory and stuff like that turns out it's just a signal so for fun you could just ignore it in your program if you sag fault and just like try and keep on trucking that's something you can do then there's this terminate which is you know nicely terminate and then you might see this fun one here there's one that looks really serious it's called sig kill so it's kill sig kill so it's like double kill uber dead something like that so if instead I wanted to I could kill and then you can actually use this kill command and tell it what signal to send to that process so if I do dash nine that means I will send that sig kill to that process and if I actually do that hey guess what it's now over with so that sig kill it is a special signal and you they really mean it so you cannot install your own signal handler for sig kill the kernel will not let you do that because of course actually registering a signal handler that's a system call so the kernel if you try and register nine it'll just be like yeah no I'm going to kill you don't worry about it so nine that will force any process to terminate immediately more or less so if you really really really want to kill something and you have its process ID you can just kill dash nine whatever it's going to die yep yep yep so so if you install a like sig fault handler then if you say fault if you were smart you could just recover from that if you wanted use you know make sure fix up your memory so that whenever it resumes so after the interrupt handler it'll resume executing your code so you could try and use it to fix up your seg fault and then keep on going but you can't your signal handler runs in your process so you can change whatever you want in your process so if you need to allocate more memory you can do that if you want but yeah you can do lots of fun things using this um right any other fun things we want to do I like today's lecture and yeah this is where our terminology that I mean it gets pretty gruesome now right we can now kill our orphans and stuff like that so uh let's see actually so let's see so yeah so here is my examples just so you have it and this was us sending a signal to a process so we just figured out its process ID with pid of by default it sends like the nice please quit so the reason to have that too is well if your process needs to actually like save some information to disk or something sometimes just killing it while it's in the middle of something is really rude and it might corrupt some files or something like that if it was in the middle of reading a file so the reason to have sig term is your process should do any cleanup it needs to do like finish writing out to files and everything it needs to do so it can actually exit successfully without corrupting stuff so that's why you have it so if you just randomly go and like kill dash nine a bunch of stuff bad things could happen but again that's why I had you guys install a virtual machine go wild with it if you really want you're probably corrupt something at some point um but you know whatever um yeah then suggesting to kill my shell so we can do that at the end because I'll probably end the lecture and yeah so the only caveat to kill dash nine not working is if that process is in in un interruptable sleep then that means the kernel won't even interrupt it in which case you can't get rid of it and it's really annoying sometimes that happens uh usually it's quite rare but it can happen so most of the operations are something called like non-blocking that we're used to having so non-blocking that returns immediately and then you check if something happens so so far read and write and wait we're blocking system calls like we had to wait for them to complete whatever the operation was but if you want you can turn them into non-blocking calls if you want so if you want to turn wait into a non-blocking call you can wait PID and then there's like options if you look at the documentation from it one of the options is no hang so then that way I have non-blocking calls so I can figure out if I like want to do other useful things I could just pull the application or pull that process over and over again to see if it has any changes so here is some silly example where I fork and then in the child process I just sleep for two seconds and then in the parent I'm just going to it's not going to do anything useful all I'm going to do is a non-blocking version of wait so I'll just count how many times I ask wait over and over and over again so here I'll say calling wait attempt count and then I'll do wait PID so I'll wait specifically on my child and then fill in that W status and then give it no hang that says that turns it into a non-blocking system call and then other than that I just check for errors and then when eventually it returns a value that is valid I will just print off whatever it told me so if I run that see pretty wasteful right what might I want to do to make that less wasteful yeah yeah if I wanted to right I could just put a sleep in here but then oops not that attempt one attempt two so that's better but then you probably can carry this in that low level course so I'm essentially trading a pulling wasting time pulling for response time because here I could be up to two or one second late as soon as the actual event happens so I might actually miss something for a while so again it's a trade-off what I can do is remember when I told you that the kernel will poke your process to say that like hey one of your children have terminated so please be good well that poke was actually a signal so there is a signal and the number will be sig child so it will send you a signal and say hey why have your children have terminated so instead of pulling over and over again or blocking with a weight if I wanted to I could just register a signal handler for sig child and then here in my parent I'm just going to simulate I'm doing something else something useful so I'll just go to sleep for 9,099 seconds and then in the signal handler for the parent whenever my child terminates it should actually just call weight do the no hang version of it and then say it exits successfully and then here in this case I actually exit the whole process because well I don't want to wait 999 seconds so if I run that says time to go to sleep wait two seconds and then I get that sig child whenever or signal whenever my sig or whenever my child terminates and then I go ahead and I call weight in my signal handler and it's all good I successfully waited I was a good parent and all of that fun stuff so I did not create a zombie or anything like that so any questions about the whirlwind of today's fun of for some reason killing stuff I don't know why they called it that still to this day all right any other fun things so yeah for some oh yep which one's better having an orphan or a zombie so it's okay to have an orphan if you intend for your that child process to outlast you that's fine you just should probably never have a zombie process so so you all so if you intend to have an orphan and an orphan happen okay wow that sounds weird in any other course right so if you intend to have an orphan and an orphan happens that's fine that's a perfectly valid thing to do if you don't intend for it probably bad and you should probably never have a zombie right zombies are always pretty bad uh a less if it's like Halloween time but yeah a bit too cold all right any other fun stuff we want to try today now that we went through that whole whirlwind yep yep and then I can enter into an uninterruptible sleep so in an in in interruptible sleep is only for like system calls that are like waiting on some type of resource or whatever you can't it's yeah you can't voluntarily do that yeah otherwise yeah that would be bad all right so well what's a really important process id anyone remember yeah one's pretty important yeah okay do it yeah yeah I could do that so what would happen what would happen if I did that so turns out this is the only exception to the rule oh nothing happens because the colonel won't let you do something so stupid so we can s trace it oh it gives me oh it doesn't like me using sudo with that let's see if it would work with it yeah operation not permitted I mean it just doesn't want me to kill one because that would be stupid um yeah I could kill anything else um yeah I could write a loop that just kills everything so right your process viewer just goes over every single process on your system so if I wanted to I could just send it a signal to kill it immediately and you could just write a program to end every other program so uh you probably not make some friends with that and they probably never use it again but you might be able to get away with it once um yeah other dumb things you could do so here's a very mean example if you want so I could fork and then in the trial I can register that sag fault handler and be like here I'll just say this is the greatest program in the world no sag fault could ever happen here and then well I'll just be a jerk so in the parent I'll just sleep for three seconds and then send it a sag fault and then hey guess what if I run it greatest program in the world no sag faults here and then I sag fault so if you really want to make your friends life a living hell it's like randomly just like create a program that just randomly sends sag faults to different processes and have them figure it out um yeah you can do whatever you want uh let's see so there's probably some important processes so yeah any other questions before we start doing something stupid yeah where's what oh sorry so the questions where's eggs were I sag faulted is so I just registered a signal handler to happen for sag faults I just prints I sag faulted but if I didn't exit here it would just continue executing so just print I sag faulted and then just keep on going yeah yeah in this case the parent process is just sending a signal to the child so in this case the child just prints off I sag faulted and then exits but I could do whatever I want in the child um so yeah we can end this so let's browse here what's important so yeah any other thing before we see if this actually works all right uh vs code looks fairly important yeah yeah that'll be fine right yeah let's see if I can kill this process so if I want so one of the one of the names in the proc is self and that's your own process ID so to figure out what process ID this is I can look here oh whoops I hit cat uh parent process ID is this so if I want I could just kill dash nine this and this process is what I'm currently writing into so if I do that oh that goes away and now well I have another shell man you just kill random stuff so remember there's like that system d for the user that manages all the stuff about your user and responsible for doing all that so if I decided to find that where's a system d so if I just killed dash nine let's see that one yeah I meant it okay wrong one let's see if this ends the lecture no I hit the wrong one oh whatever I just killed random stuff I probably royally screwed up my vm now so with that uh just remember I'm pulling for you we're all in this together and pray for my computer yeah