 Hello. Hello. Hello. All right. One thirty. A lively room. I guess nobody wants to learn anything. They just want to try programming. Okay. I'll assume everyone's online. That's where everyone is. Let's make sure we're running on the good stuff. Look at that. Yep. Stream good. Okay. Cool. All right. How many are done with assignment? Pretty good. Not bad. Cool. Okay. So let's get to the final piece of the puzzle and to give an example of one of the things that we have that we want to deal with. So we did all the way back on Monday. So let's run S trace. We can see it. Okay. So on Wednesday we were programming a similar style of server that was doing a different style of protocol. If we check that out it did something like the client would say give me underscore file underscore and we would say here you go underscore content. So let's see. And we were on, if I remember correctly, we were listening on port 8080 on IPv6. So I have on the left the server from S tracing the server so I can run this myself to see all the sys calls. And then on the right I am netcadding to 127001 port 8080. And that did nothing. The dash V. So I'm going to connect it. Connection refused. Oh, I thought we fixed this. Oh, I see what's happening. Look at this. This is not 8080. This is just 80. It's because this was me experimenting with old code. So if I get that handy-vandy line to compile, assemble, we would valid suffix for GE 55, go to line 55. Okay. Jump if equal found underscore. Did we leave this in a bad state? I thought this was working at the end. Was it not? It was. I think he didn't save here. I promise. Because I think you fixed the GE at the end of last week. Maybe. Okay. Well. Okay. Comparing with 5F, 5F was 0x5F is equal to the underscore character. GE. Thank you. Now let's run the server. There we go. IPv6. So now we're actually executing the correct version. Let's connect reading. And I'll need to remember gimme underscore file. I said gimme underscore slash flag was a client. Okay. Let's copy this. We don't want to do that again. All right. And so we already did so far. So we read that in from the socket on the other side. Oh, that's right. We just did a, um, we found the bite that was after the flag. That's right. And we were echoing that back. Okay. So we left this in a broken state. So we found our underscore. Now what do we want to do? Cause we want to eventually pass that to open to call open. So what was our point in trying to find where this underscore is? So we have this input from the client, gimme underscore and then a file. So the point of finding the first underscore was the start of this file. So one bite, one address after that should be the start of the file. So let's see. I think R13 is here. So if I increment R13 again, we should have for certain the start of the thing. And now I just want to open that. Can you all read this in the back? You're going to be bigger. I'll make it bigger. You're nodding your head like, yes, you can see, but I always find that it's better. Okay. We want to call open, which is R, uh, move into RAX2. Uh, we need to pass the file name, uh, into RDI. So that's R13 is the file name and RSI are the flags. Uh, let's see. I was just doing this the other day. Okay. I believe zero. We'll find this in a second, but, uh, I lost my thread. Okay. RDI, RSI, uh, we'll move zero into there. I believe that's a read check based on. Okay. Uh, an assist call. So let's see if that opens it up. Okay. Compiled it correctly. Let's connect. Okay. So let's check. So it read that request in now it's saying open slash flag underscore client underscore one slash N. Okay. Over it only was correct. So why is it saying that no such file or directory exists? I go here. There should be a slash flag. Yeah. That file does exist. Right. So I'm reading that error. Cause I'm reading a different file. Right. I'm reading a file that actually has a new line character in it. Right. And that's because the operating system and C programs in C strings, how does you know when you've reached the end of the file? So if you're writing a string length character, how do you know when you reach? Sorry. Not the end of the file. How do you know when you've reached the end of a string? The null terminator specifically a null bite. And there is no null bite here. It, well, it must exist actually after this slash N, but. It's continuing reading here. So what do we actually want? The operating system, what file to open slash flag, which means we want to change this underscore here to what? A zero and null bite. And if we do that, then our, our operating system will say, oh, open slash FLAG null bite. Great. So we want to open the file called slash flag. And we already wrote code to find the start of an underscore. If I was, you know, really doing some software development stuff, I'd probably move this into a function and figure out how to call it. But let's say I'm kind of lazy. I don't want to do that. Maybe you can relate. So, okay. So let's call this be, this is find first underscore and oh, yeah. So find first underscore. So that's the start actually. So I did a lot of work to find this first underscore to find the start of the file. Did I need to do that? What's another way I could have found the start of the file? Forward slash. What was that? Forward slash. I could look for a forward slash, although I guess we'd have to look at the spec that we came up with last week and it doesn't say anything about there necessarily being a forward slash. But what does the spec that we have here say? How many characters is it from the start of the request to the start of the file? Six. Is that ever going to change? No, because this is the protocol, right? Any valid request. Now you have to think, okay, do I want to make a very robust system that can handle any kind of weirdness and then I can detect if there's an underscore or not or whatever. But I can just cheat a little bit. I don't know if I want to tell you this, but it's fine. So yeah, if we just add six to the start there, that puts us directly at the start of the file. So this was in R13. So if I add to R13.6, let's see. And the comment I'd want to say is so 0123456. So this should now point to the start of the file. And I'm going to comment this out. I'm just going to delete it. Okay. And this should do the same thing. So it still didn't work, but it did. We did properly increment to here so that now get that back. So now R13 points to the start of the R13 points to start of file. And now we want to find out where the last underscore is. I need another register. Somebody give me a register. That's not RCX because that didn't work. RSI. We use RSI in these calls. Let's see. I have the calling convention. Oh, I had the calling convention. Was it R9? Okay. R9 will point to end of. So I'm going to change these R13s to R9. Why am I not using R13 here? And we need it later. We're going to pass that to open to open from there. So we need that pointer to the start. We're going to do is go to the end and replace that underscore with a zero. Okay. So now if everything is good, R9 should point to the second underscore. See this is why comments are bad because I just moved this code around and then this comment. Okay. Second underscore and null out second underscore. So how do I know out that bite? So I want to write into memory. So I'm going to move what zero where into what? Louder. The address of R9. The address of R9. Yeah. So I want, how do I do that? What does this mean? Replace everything in the R9 register with zero. Is that what we want to have happen? No. What do we want to have happen? Oh, let's get there next. But how do we write to memory? Yeah. Brackets around R9 to say that we want there. And then they answer that question. If you're the assembler, how many bytes of zero do I want to write out here? Do I want to write out one, two, four, or eight? Huh? Yeah. Well, no, nibbles too small. Actually. Yeah. We want to write one bite. Right. By telling, and there's no way for it to tell here about how big because we're using the constant zero. So we need to tell the assembler. We want to write out a bite of zero to where R9 points to. So wherever that memory region is, which we've moved that pointer forward until we got to the last underscore. And then we're going to overwrite that with zero. And now this open file should open up the file that we want. Is that correct? Ever on board? Yeah. All right. Assembled and built. I'm going to add this in here. Oh, why is that? Ah, segmentation fault. What'd you all do? Okay. Looking through this. Accept, read, seg fault. Oh, no. So what do I do here? Just, uh... S trace. I did S trace. Oh, this is already still in use. This is in use. This is detached. Okay. I'm very confident that this shouldn't work. Let's see. So connected. All right. Segmentation fault. Excellent. So now we just give up. Yes. Change to IP. We weren't having that problem before. So I think that should be fine because we were... Everything there was fine. So what we're going to do first is look at the code. Try to understand. So I think I see. So let's figure out the file. We use R13 for the start of the file. Move the offset above into R13. Move 6 into R13, or add 6 to R13 to increment it. Okay. Where did I set R9? R9 should point to the start of our file string first. Where did I set R9? Not at all. So whatever's in R9 is what in there. So it just started looking for memory through an underscore. Probably there was a zero in R9 as my guess, and that's why we had a psych fault. So I'm going to first initialize R9 with R13's value. So close. Okay. What happened here? Say it louder. Yeah, we did... If I remember correctly, let's go back here. Yeah, we found out where the underscore was, and then we added one more to it at the end to get to the character after the underscore. Because that's what we wanted to do to get to the start of this string. But we don't want to do that anymore. Cleaning up correctly. That's where I should be. Okay. If this worked correctly, okay. So were we correct in what file we're trying to open right now? Yeah. What is the return value of open though? Negative one. Is that what we want? No. Why is it negative one? Yeah, if we look at the permissions on flag, only root can read flag, that makes sense because think about if you were doing all those levels and the whole time you could just read the flag directly. That'd be kind of silly. So this is why you can't read flag directly. Let's give it... Let's see. Let's give it its own code. How about that? So gimme.slashserver.s openserver.s Read only. All right. So we're successfully opening. The whole point of this exercise though is to read out that file. So let's give ourselves another big file read buff. And we want to then read that file. So I'll need to store the cyscall, the return value somewhere. I need another register. R15. Have we used R15 yet? Okay. We have not. So R15 is file FD. Okay. Then we're going to call read. Go to our handy dandy cyscall table. Read is zero. So move zero into RAX. The FD. So move into... This is backwards. Zero into RDI. R15 into RDI. Go read. This is the FD. RSI is the buffer. I had just created this down here at the bottom. File underscore read into... What's it? RDX is the count, which is 1024. Okay. This should then read. And where's it going to be after I call read? So where is this data going to be? In where? File underscore read. Yeah. Just in this memory location, right? There'll just be bytes here of whatever was in the file. No stuff instruction N. On line 86. Go to line 86. It's a classic Emacs problem. So I opened it correctly. I'm reading from 5, 1024, getting 1024 bytes there. And now I wanted to... So what I wanted to write was the string, here you go, slash N, and then the contents. So the server response is here. Okay. Good. That is... Oh, there we go. That would be here you go. Slash N. And then we need to output the file content. So now I want to write to client FD, the file read. So whatever we just read in, how do I know how many bytes we read in from the file? Is it sort of RDX when? Read is file descriptor buff size. So the return value here is exactly that. Be how many bytes were read in? Why can't I just hard code in 1024 bytes? Just say write out from this buffer 1024 bytes. Yeah, there may not be that many bytes in the file. There may be only 10. And then we're saying print out 1024. So it'd be the 10 bytes of the file and a bunch of junk, right? And we need things to be precise and to be correct. So I need one more register. What is it? R14 now. All right. R14 looks good. Okay. R14, store bytes read into R14. So now we're going to do, we want to do another right sys call this time. Move RDI R12. Modeling this off of what's happening up here. Move into RSI offset. Where do I want to write from? Where did I read the bytes from? File underscore read up here. So offset file read. The buff. And move into RDX the size which we, our handy dandy R14, which we call bytes read. And with a sys call. And let's see what happens. Hey, look at that. Here you go. Why is it gimme underscore? Oh no, this is going so great. I put 20 in here. This is not correct. You can see it's adding extra stuff. This actually is the, if we look in here, this is the server response. So there's these bytes and then a null byte and then the buff that we got from the server. So that's why that's being output as part of what we sent. So we want to do, I don't know the string length of this. I'll stop my head. That's why I will use my friend Mr. Python. 12 characters. Here you go. New line. And then boom, the file. Let's see. Let's get hello world into test. Echo hello world into test. Oh, that was the same one though. I wanted to show a different example. Okay. Test. The address is not in use. Okay. I need to close these stupid things. This is driving me crazy. Okay. Before we exit, now let's close the client FD. Close the, what I call the first. Yeah. The socket, the FD. Okay. That's in RVX. Okay. Close is very simple. Close in the FD. So move. Move into RX three. Move into RDI. The client FD. I have no idea what did I call you Mr. client FD. R12. Call. Move RX three. Okay. Move into RDI. And this is, I hate that that's there. Let's move this here. Sort into RVX. Close four. Close three. Exit. Just starting to use still. Do we agree that it worked? What's the problem with this as a server? Is it a good server? Why? I wrote this. Are you telling me I'm writing bad servers? It's a trick question. No, but seriously, why is it, why is it not a good server? That's because we created this protocol. It's a great protocol. The protocol is not the problem. Yeah. You could access any file on the server that doesn't have permissions. Yeah. We can access any file on the server, but, you know, hey, that's okay. We're, that's the point, I guess, of this, this. What was that? Yeah. Maybe it's a honeypot to see what people are doing. What else? Do you think there's somebody at Google that starts their web server every time somebody makes a request? No. No? Yeah. And a lot of overhead, and it's like insane, right? We have a server. The server should operate indefinitely, continuing to accept connections. So where were we? The accept system call is the one that, the accept system call is the one that tells the operating system, hey, I want new connections from a socket. We agree? Yeah. So after I'm done processing this, so after I write out the response, I close the client FD, why don't I just go back to start accept, right? In a loop. And that way I can make as many requests as people want, and it'll just be the happiest web server, not web server, fake web server that ever did exist. All right. So I can make a request. It succeeded. I can make another request. I want to get other files that it definitely exists. The other one was test. Boom. And I don't even have to do this. It just keeps going. Isn't this awesome? Wouldn't I run out of file descriptors? Theoretically no, because I should be closing everything that I open, every go around, right? The only file descriptor that should be open was our first socket that we created on three. But you can even just look here and see, hey, this is getting up to seven. That's kind of bad. That means that we've definitely done something wrong. So if we, we closed client FD, but how many opens did we have? Then client FD came from accept. That's the other side of the client. What was the other thing that we opened? The file we want to read. Yeah, this one. This one in R15. So the file FD. So we also want to, and probably do this before. Yo. So I mean I should look it up, and it says Linux systems limit the number of file descriptors that anyone processed may open to 1,024. Yep. We're so bad about having seven open. Oh, it's nothing would be bad about seven. Actually, why don't we show that? That's a great question. I can't remember batch syntax. It's... Oh, that's right. For IN, seek, was it one? That's right, because seek is a command. Do, was it 10,000 was the limit you said? So 1,024 per process is what it says here. Under Oracle Health 10. All right, let's see. So if that fails, just 10 exit? Yeah. At some point it's going to fail. Oh, wait. I know. It says this condition is not a problem on Solaris machines 86, 64 and Spark. Those are completely different operating systems, or Solaris and Spark are different operating systems. Eventually, so eventually we won't be able to reopen any more files. So at a certain point this should fail and not give us the correct file back. Oh, it's an inherent limitation of any operating systems. So the idea is, remember how we had that table of like, that the operating system keeps track of the file descriptor and what actual file it is? That, oh, this is weird. File size, open files. Oh, this is going to take us a long time. But while I can talk while it's doing it. Because if you think about it, the operating system has to keep some data open for those files. So in order to prevent a user space program from taking down the entire operating system by just continuing to open files and it has to store all this data on all these files, it limits the number of max files you can have open. But as you can see, that's a setting that you can change at the operating system level to let it be unlimited. There's a lot of things like this in file systems. Like if you have the ability, because the whole point of an operating system is you don't want my process, like the server, to take down any other process or anybody else's services. So you limit what things can do so they can't do this. And it's a bummer that that size was so large. I thought we'd get there very quickly and this is going to take forever. Yeah. Oh, yeah. So what's interesting is, you know, so I'm wondering why we've completely blown past 1024. It's because it's configured. The Pone College server is configured differently. Like let me... Is this not x84? I mean x86? It is, I guess, even my Mac file. Oh, yeah. So the Mac default limit is 256. So each system is configured differently. So you can configure it. The administrator can configure these to be higher or lower. Well, we've also blown past that number as well. This is on my local machine. The 256 is my machine right here. Not the server that we're using. So, all right. We'll let this run. But anyways, to fix that, we don't want that. We want to close the file FD. File FD was in R15. Move into RDI R15. That would be fun. Okay. But now we've got this. So this is great. So now we have a server, right? Everything good? Just say like, yep, it's cool. But another aspect of a server that we definitely... Oh, I want to compile this. That we definitely want to think about and worry about is, what about concurrent connections? If Google is serving your HTTP request, does that mean that they can't serve anybody else on the entire world? Connection refused. Yes, I know. I just want to start this up. Another example. Try to fix this. Address and use... socket. Yeah, there you go. You can use setSockOpt to set the SO reuseAdder socket option which explicitly allows a process to bind to a port which remains in time wait. It only allows a single process to be bound. This is both a simplest and a most effective. So I'm doing what I should have done is look up how to fix this and then do it so that that way we don't have this problem in the future. Okay, so SO reuseAdder setSockOpt. Let's check how do you do this. Other score. Okay, this is where we can set... There we go. Manipulate options. I wonder if we can look for that. No. Okay. So this means we need to after we call this before we bind we're going to set on that file descriptor we're going to set this option. Of course you shouldn't take code from the person who's asking the questions because I usually have the bug in it. To do both of them. Let's not do that. I can make my point. Let me make sure we're good. So I don't want to do a thousand connections. So I actually did like ten thousand connections so we saw our system is pretty robust. It can handle a lot of connections like ten, twenty-four connections. That's way faster it feels like. And we can see that the file descriptor numbers aren't increasing anymore. So that's great. If I connect to it on two different so this connection has succeeded. This is actually connected to port 8080 but this one has not. Why is that? What's my program doing right now? We have the trace right here. So it just called accept that returned to file descriptor four which is this one because I did that one first. And now what's it doing? From where? What specifically? Waiting for what to be read. Yeah so we called, we jumped back. We were waiting. We called accept. We get a connection coming in. The operating system says good. Here you go. File descriptor four the very first thing we do is read from file descriptor four and what if they send us nothing? This read call will wait forever. But there's actually another request that came in that's ready. It has its request. But until we go back to accept we're not going to be able to know to talk to that server. Or sorry that client. We don't know that there's another connection. So the problem is we can accept connections one after the other but every time they connect to us we're going to wait until we fully process that request before going on to the next one. And this has massive problems as we can see because this one request we'll see. It's waited too long. Anyways the point being so what we'd really like to do is have a request come in and then be able to immediately handle another request while that other one's processing. And that gets us to where we're talking about today is multi-processing. So we went through all these steps in depth and in glory detail of calling socket, bind, listen, accept and what we really want to be able to do and there's many different ways of doing concurrency and all this stuff right now we'll look at multi-processing which is saying asking the operating system and think about like maybe taking biology. Yeah what's the process when a cell splits into two cells? I thought that's what it was I was gonna say I was gonna have you say it first that way if it's wrong it's on you. Yeah cell mitosis right it splits into two different copies of a cell and they can each do that this is exactly what this what we want to do is we want to split ourselves into two different processes and have one go back and accept more requests while the other handles that client request. And there's a very handy dandy nice function for this called fork. So fork is the sys call and again the operating system deals with talking to the file system talking to the network also the creation of a process so a similar type of thing fork will actually is done by the operating system which is why it must be a system call and so the idea is now that you've split in two how do you know which one is which because the code is executing the exact same code the same memory everything is happening simultaneously so you need some way to know which is which so the terminology is like child and parent so that's the the terminology here and actually this relationship forms a tree structure most of the time so the new process is referred to as the child process the process that originally called fork is called the parent process so after the accept we get a new sys call in a new connection in then we call fork the operating system literally does the cell mitosis splits us into two and then says both of you start executing but we need to know are we the child or the parent maybe we want to watch the child and watch it grow up and see if it crashes and deal with it I don't know so the idea is the only thing that is different between these two processes that are running because they literally have the same code the same data everything the same open file descriptors there's literally no way to know which one you are except the return value of fork so if fork is successful if it's not successful then you're only one process because no fork happening but if it's successful the PID so the process ID of the child process is returned to the parent in the REX register and it's zero in the child and this is how you can know who's inside who's doing what so at this point socket bind listen accept boom one process we call fork now it splits into two and so the child will be on the right why is the child on the right how do I know the child is on the right because their turn value is zero on the far right also there's nothing before it visually nothing happened in the child process before that because it's all come from the same place whereas in the parent the value 43 will be returned from fork and literally just like we saw like the all of this metadata is copied essentially from the parent to the child so all the file descriptors are open are the same the user IDs are the same the thing that's different is the PID so the process ID here it's 43 and the parent process ID is 42 so that's metadata that the OS stores about that so then in the parent we can then close the the client file descriptor because we don't want to deal with it what do we as the parent want to do except new connections that's our whole job except connections and create children to deal with the mess it's kind of like a classic parent I guess so we close it here but because even though it's closed here in the parent the important thing to remember and you'll get into this when you study operating systems about how this is actually done so if you think about it this is insane like every time you're making a connection this whole other process is treated with exactly the same memory but they're separate so if you change one it doesn't affect the other exactly the same here if I close file descriptor 4 it's only changed in the parent whichever process did that just like you can't change memory of other things but it's actually done in a very performant way so it's very cool about how operating systems make this happen so it goes away in the parent but still exists in the child so that the child can then do that the child can close file descriptor 3 the original socket that we listened on with absolutely no ill effect because it doesn't affect anything and then they can both work on them and the cool thing is after the fork these are all happening at the same time so if you have a single core system the operating system will decide what to schedule and how much of a thing to do whereas if you have a multi-core system this can literally be happening simultaneously then the parent would jump back and call accept and the parent could then be processing it while the child does everything so seems pretty cool okay I really don't want to deal with this that sock stuff so I'm gonna ignore it for now but if I need to do that we'll do that what how do I do that I don't know that's fine but I believe you that would work okay so we kind of already have this right so we have our accept we have our start accept so we want to accept and then who deals with so we accept do we want to read in the request first and then fork no we just want to immediately fork let the child deal with all the dirty work and the parent just jumps back to accept fork doesn't take any arguments which is also nice what is the sys call number 57 okay now boom at this point I now have spun up two separate processes the parent and the child so I'm the parent what do I want to do or how do I tell yes so what do you want to do with our X return value on success the PID of the child process is returned in the parent and zero is returned in the child so if we want to check before the child what should we compare against zero and we can do a jump if equals to some label called child and otherwise we can jump to let's say parent and child we know is actually just down here because we've already written it it's all doing dealing with all the stuff parent actually well I'll just do it here but it's fine we can do that let's see in the parent I want to close the client FD that was sent that means I will need to move this up here to get that in a register and I've written so many closes today you'd think I'd be able to do it I'll stop my head but I cannot move rax3 move the client FD which is R12 DI R12 jump back to start accept so all the parent does is close whatever was returned in the accept jump back and just boom boom boom the child we wanted to be if we're following an example we want to close the original socket move into rax3 move into RDI FD is in RBX RBX alright let's watch this see I can make two connections that both succeed I can't do them simultaneously but here you go that seems not good oh is this because of the limit did I fork on myself okay that was weird am I hitting a max number of processes can I have on the dojo 24 then why I thought it was a lot lower max user processes unlimited okay so this either means that so I'm forking correctly I guess one idea would be if my child is yeah okay how can I show this because we're in a very screwed up state where we have no let's kill this not quite yet okay PSAUX shows you all of the every process that's running on your system we'll do that at the top and type it through wc so we have 30 different roughly processes running so run the server check I'm at 32 not so bad because I'm running strace now I connect amazing okay but first thing I do is look at okay let's look at our parent so the parent closes the file descriptor and then jumps to start accept seems pretty simple not a lot that can go wrong here we saw that it kind of worked now child on the child socket we can then close read figure out the file start the underscore open the file call read store that write it out output close the file descriptor close the client descriptor and then what do we do so what happens after we close the client client fd jump where so after this there's a jump to start accept which will go the way up to the start call accept which will fail I think in return negative one and then it will fork again and then it will keep going so I think we're just spinning up a ton it's called a fork bomb when you just keep forking and call fork over and over and over again so we want to definitely not do that so these are defunct which means they can be cleaned up actually the z I believe is for zombie it's going to be a zombie process just in case you're curious okay but I can make tons of requests we definitely fixed that it just can't clean them up fast enough so you can see that I am it is creating a process and then you can actually see the downside now of doing multiprocessing in this way of spinning up a new process for every request it's actually kind of an expensive process and you have limits on the amount of these processes but we can fundamentally handle OS like fixes these but anyways we can handle but anyways these are zombies that's fine but we can handle concurrent connections and on a good enough server these will be fine questions on this so some other techniques are you don't actually usually it's like a worker pool so you wouldn't just spin up a process for every request you would have a specific set of like 20 30 or whatever processes that are ready to go and then you tell them that they have jobs to process and they don't go away they just stay open a long time show me that that's kidding like we're gonna look at something now okay so one thing we didn't show is so S trace has an option on a fork trace the child process as they are created so we can pass dash F so that we'll actually see the child as well so let's see so we call accept the accept got a four fork this and then we close close accept read open read write read wait what okay fork why is it calling accept again oh it's a different process I see okay this is the parent which makes sense because we closed for then called accept excellent and we read it write it out and then exit it okay great so we have some stuff to clean up here this bad file descriptor because we already closed that but that's okay okay cool so I was getting confused you have to pay attention to these process IDs these will tell you which one is which whatever they're doing okay more questions no questions what are you doing why are you saying it doesn't have any questions have you seen this it looks insane it looks even funnier this way maybe but in the camera okay now it's good I was bonkers well the camera doesn't have any questions does any of you have any questions any questions on twitch I'll get like two minutes so then finish the assignment and then we'll start new stuff on Wednesday yay for finishing assignments wow that was really really terrible