 So it might be weeks where we cancel some office hours because I need TAs to do grading or other things. We get awesome. And all the stuff to get you bootstrapped and started and digging through the source is there. And today, what we should be able to get up there is at least the rest of the assignment in terms of what else you have to do. And then, hopefully, we'll have the forums up so you can start submitting answers. There are also, for each assignment this year, I've learned my lesson last year, there are going to be, and there are already, for Assignment Zero, very clear collaboration guidelines. So please look at those and make sure that the way that you're going about doing the assignment is in accordance with our expectations for how you will complete it. And I know people would like to have access to the slides. That's something that we'll get to. My priority right now is the assignment, but the slides will be posted online. And the videos are doing them to put them up. Yeah, one look. They're pretty much the same. There may be, it depends on if I decide to change some things. I haven't made too many changes to the beginning stuff, but there were things down the road that I was kind of unhappy with. So hopefully, I'll get ahead of myself at some point and start to. But yeah, the slides for last year are up. People have asked how to print them. Another feature that we haven't implemented yet, but we'll try to get to that as well. Any other questions about sort of logistical things? OK, so last time we talked about fork, we talked about file handles. So questions about the stuff that we covered on Wednesday. This, I would expect you guys to remember, it was only two days ago. This is a game I started playing with myself. I put my water bottle down somewhere, and then I see, five minutes later, I can find it again. Because I usually want it, and I don't know where it is. So today, we're going to finish talking about fork, and then we'll start on exec if we have time. So file handles. Who remembers file handles? Who remembers what a file handle is? Who wants to help us out with some review? So file handles established three levels of indirection between a process and disk blocks. So a process wants to store some state in a stable way, and disks are designed to do that. Disks have blocks of data that they can hold. What is the process of getting from the process to blocks on disk? Let me start over here. AJ, what's our first step? Want to help him out? File descriptor. So what is a file descriptor in a UNIX process? Anybody remember what it is? What is it actually? You call open, and what do you get handed back? An int. So a file descriptor is an index into my file table, and that points to a file handle. So there's our first level of indirection. So clearly, the next level starts with the file handle. What is that file handle point to? To our file object. And then what is that file object in the kernel actually refer to, eventually? It represents a file. Are you going to take a block? Yeah, like the actual blocks on disk. So this is our three levels of indirection. I map a file descriptor to a file handle. I map that file handle to a file object, and then I map that file object finally down to the actual disk blocks, assuming this file is on disk somewhere that store the data for this file. OK? So I wish I remember what was on the slide. So the file descriptor is an int. It's part of the process. It's what processes receive from open, and they pass this to other file system calls. So this is just an index into my process file table. And the file handle is this object. Now this is now a kernel object. So the file handle is an object maintained inside the kernel that contains some state about the file. It contains an offset, permissions, and then potentially some other information. And the file object is another kernel object that contains everything else. Or you can think of it as containing all the other information about the file. So why did we do this? Why not just have some file descriptor or some representation of a file that maps directly down to a box on disk? Why did I establish all of these levels of indirection, or the extra level of indirection? Yeah, because I want to share it differently. So what this allows me to do is to say, file descriptors are private. The file table is private to each process. This is going to make a little bit more sense today when we talk about fork, and when we start talking about exact, because fork in particular manipulates the open and closed or fundamentally what manipulate the file table. But fork has some semantics related to the file table that are important. And the reason we did this is to set up some interesting semantics with fork that allow processes to communicate in a nice way. But what we've done here is we split pieces of information between different objects so that they can be shared differently. Process creation. Where do processes come from? Unfortunately, it's on the slide, but that's OK. It's good to have some easy questions, Brian. It's on the slide. What's the system called I use to create a new process? I use fork. Yes, there we go. Fork, right? Fork, what I forgot to hide on the other side. Alissa, after fork, we refer to the caller as the what? Parent and the terrible slide, lots of bugs. So fork creates a new process that is related in what way to the process that called fork. It's a clone. It's a copy. And in this class, we're going to think of the forked process as being a complete copy. Although we pointed out that there are versions of fork that allow you to do copy of a variety of different types of things. So it creates a copy of the calling process. And we refer to the caller as the parent and the newly created process as the child. And the parent and child on Enix systems have a special relationship that allows the parent to do certain things and gives the child a way to communicate with the parent. So again, so this is what we covered last time. Questions about this before we keep going. I see some confused faces in the audience. Maybe that's just a 9, 10 AM sort of look and not confusion. OK, any questions? All right, so let's review what happens in fork, right? So I have this process here that has a couple of threads and address space. And here's my file table, right? I've got my file descriptors, which point to file handles, which refer to file objects, right? After, so fork copies one thread. Remember, we talked a little bit about the difficulties between fork and threads, right? So fork is potentially going to copy the thread that called fork. It makes a complete copy of the address space. So we're going to come back to this in a few minutes, right? So all the memory that the parent is using, right? The parent's address space, all the memory the parent has access to, has to be copied for the child, right? And this is, some people may be able to identify a problem with this immediately, OK? It copies the file table, right? So after fork, the child has the same files open as the parent. But what's interesting about this, and one of the reasons that we created this level of indirection here, is the file table, the file descriptors in the child point to these same file handles as the parents. So what you can see here is that there is now this shared state. It's difficult to see, because these are really very unappealingly horizontal lines. But what this is designed to show is that the parent and child are now sharing the same file handle objects. So what does this mean, right? What state is in the file handle object? This is another one of those relatively easy questions that's on the slide. I'll pick somebody from the back of the room over here. Kevin? Well, so what's, yeah, it's very small from back here, actually. So this question is more difficult for the people in the back of the room. And what state was in the file handle? Jim? Sure. Jeremy? The current position. So the offset into the file, right? So this is interesting, and maybe it's something that people aren't familiar with. But when you use, when you do reads and writes, the kernel and unix maintains the position into the file. So if I start, if I open a file, the file position is usually initialized to zero. Then I read 512 bytes, then the file position is now at 512. So I've read another 512 bytes, I get 512 to 1024, right? But what's happening now is that that state is going to be shared between the parent and the child. So if the child does a read and modifies the offset, the parent is going to see that modification, right? So if the parent reads 512 bytes, it gets from zero to 512. The child then reads 512 bytes, it gets from 512 to 1024, right? So there's this piece of shared state, right? And there's a reason for this, right? Which we'll cover in a minute. All right. So fork and exec, which we make it to today, have these really kind of nice, have a nice duality, right? From the perspective of a process. Because fork is a funny system call, right? Most function calls that you make as a programmer go off and do some work and then they return, right? But fork is weird because fork returns twice, right? It's just the semantics of fork because after fork returns, there are two copies of the process that was called in and has to return to, right? So when the child starts, so the child's entry point, the child's birth into life, essentially, is at the exact moment that the parent called it. So the child starts executing. Again, it's like you've cloned, right? And in some systems, it's called clone. You clone the parent process and it wakes up with the exactly same memory, the exactly same state that the parent had, right? Except there's one important difference, which I think is up on the slide. So fork returns twice, but it doesn't return the same thing twice, right? And that's kind of important because if you really had an identical copy, then it would be very difficult because frequently what I use fork for is to run another program. So we're going to talk about how that looks in a minute, right? So if I was trying to use fork to run another program and then I'm going to wait for that to complete and then I'm going to do something else, right? This is what your shell does over and over again, right? It calls fork, runs a program that you passed in with the arguments you gave it, waits for it to complete, and then draws the prompt again, right? That's how a shell works, basically. I mean, clearly there's a little bit more to it, right? But that's the simplest possible shell you can write, right? So fork returns two different things. So the child, it returns zero, right? To the parent, it returns the process ID of the new process that was created, right? So this is kind of important, and it means that you can, you see frequently blocks of code like this, right? So I've called fork, right? Now it's something you have to wrap your head around. Now there are two processes executing this piece of code. One process is going to see return code zero and say I'm the child. The other one is going to see something else and realize that it's a parent, right? Frequently what you see is if return code is not equal to zero, do something, right? And if return code is equal to zero, wait on the process ID that I just created, right? So again, all the contents of memory are identical. Same file is open with the same positions, right? Because remember, I didn't copy the file handle objects, they're actually being shared, right? So the child has pointers to the same file handle objects that the parent had opened, right? And again, changes that the child makes will be reflected. Yeah. What if there is, so fork creates one child at a time, right? There are no twins here, right? This is a, you only get to create one process. What's your name? Amit. Yeah. Guru. Yeah, well what process, what do you think gives out PIDs? The kernel, yeah. I mean the kernel is the process or the entity that distributes process ID, right? And one of the things you guys will have to do for assignment two is to figure out how to allocate PIDs, right? Because there are some interesting semantics on most systems associated with PIDs, yeah. So, right, there are, and welcome to multi-threaded programming, there's no guarantees about the ordering in which these threads are running, right? We'll come back to this when we talk about scheduling. But they are on a multi-core, I mean, on a single core system they are fundamentally running sequentially, right? Because there really is no real parallelism, right? But unless they coordinate their activities, there's no guarantees about the order in which they will run. On a multi-core system they could be running at the same time, right? Yeah, well, yeah, no, no. So, once the child is cloned first, right? So fork enters the kernel, it does all sorts of work, right? It creates a new process for the child and then it returns twice, right? So, when it returns to the parent, because the idea is when it returns to the parent, I should be able to access that PID, right? If I hadn't set up the process yet, if I hadn't even allocated the PID, I wouldn't be able to return the PID to the parent, right? These are good questions, yeah. Why? What's that? So, yeah, I mean, this is a good question. So, why return zero to the child? Jeremy, I'm purposely ignoring you. Yeah, what's your name? Dan. Yeah, I mean, zero is not a valid PID, right? If I returned, so I could return the parent's process ID, right? What would be wrong with that? You don't know either PID, so there's no, like, you'd have two integers coming back and no one would know whether it was the parent or child. The parent wouldn't be able to tell, is this my parent's PID or my child's PID and the child wouldn't be, like, that would be very weird, right? So, zero is just the special value that's used as a signal to indicate that I am the child process, right? Any other thing would cause confusion, right? I guess you could return, like, a negative value that was, these are the semantics of fork, right? That have been passed down on stone tablets through the generations. But anyway, there's kind of a reasonable reason for that, yeah, true. Yeah, so zero, okay, so that's a good question. No, no, no, if fork fails, so actually if fork fails, then actually, I think if fork fails, then it returns a negative value, right? Yeah, or something to indicate to the, the other thing to keep in mind, and I just wanna point this out when you guys are trying to map, because if you guys have done C programming, you probably use things like, what's the Erano or whatever, right? Erano is not a kernel construct, right? So, most of what you're used to using in C, if you programmed in C, are the C standard library functions that wrap system calls, right? So, the semantics, and you guys have to keep this in mind when you're doing assignment two, because the semantics of your system calls have to match the kernel system calls, right? Not the C library, right? So for example, Erano, people think, well, how do I set Erano? The kernel doesn't set Erano, right? Erano is set by the C library, right? The C library takes the return from the system call, stores that in this global variable called Erano, and then returns something else, right? From the wrapper function that it wrapped around, right? So there's a difference between fork, frequently, which is the C standard library fork, and sys fork, which is the actual system call, right? And if you, once you guys build your kernel, there's some code in there that actually generates those wrappers for the C library, right? So you guys, I mean, when you guys start writing, running the test for assignment two, you guys will be compiling against a version of the C library that's in your tree that's built specifically for your kernel. So if you, and if you add system calls, which you can, if you want to, I don't know why you would, but if you add system calls to your kernel, you need to make sure the C library knows about them and knows how to call them, right? We'll get back to talking about how the processes and the kernel communicate in a couple lectures. All right, great questions. Let's go on, right? So one of the reasons that we have this funny semantics with file handles, right, is to support pipes. And pipes are this programming, are really this programming methodology that's been designed, you know, like old school UNIX hackers and even some new school UNIX hackers take pride in their ability to build up these really gnarly programs by stringing together like little UNIX utilities, right, with pipes in between, right? So you can see these things where it's like, I can compute 600 digits of pi using like grep, cat, LS, I don't know, whatever, like I'm making this up, but you see people doing these things where you can find these huge long shell pipelines that actually do something pretty helpful, right? So pipes create this, the pipe system call creates an anonymous object that essentially allows the parent and child to pass data through the kernel as if it was a file, right? But there's no actual file that's used, right? Instead, data that gets written into a pipe gets buffered in the kernel up to a certain degree. And then when the, when another process reads from the pipe, they get that data, right? So it's kind of a producer-consumer buffer, right? And again, I mean, the reason this is useful is because we can use this to compose these streams of programs. I wish I should have an example of that, actually, that's too bad. Maybe I'll put one up on Piazza, all right? So this is one way that we can accomplish sort of some nice IPC between parent and child processes, right? So the way this is done is using both pipe and fork, okay? So, and I have a graphic for this, so if this doesn't make any sense, we'll walk through it in more detail. So let's say I wanna communicate with my child through a pipe. So before I call fork, I create this pipe object. Then I call fork, and then when I create the pipe object, I have file handles pointing to both the read and write ends of the pipe. That's not very useful, right? It just allows me to read and write data from me to myself, which is not very helpful, okay? But after fork, what happens is that the child closes one end of the pipe and I close another end and now we have this buffer, right? So again, let's see here, let me go through here and then we'll go back to the example, right? So parent is about to call fork, it creates this pipe object, right? So here's my producer consumer buffer inside the kernel. I have a file descriptor pointing to the, this is the read end and this is the write end, right? And again, if I don't do anything special with this, this is not very interesting because I can read and write data to myself, okay? Now I call fork, right? So now my child has those same file handles, right? So it has a file handle that points to the read end and a file handle that points, sorry, a file descriptor that points to the write end, right, because we're sharing this file handle, right? Now what happens is that I close the read end and the child closes the write end. So I can now write into this pipe, right? I have the write end of the pipe and the child has the read end of the pipe and now we can communicate through this anonymous share buffer, right? Let me go back to the example and then we'll walk through this again, right? Yeah, so here's an example of how to do this, yeah. You could do two pipes, yeah, in general, like the whole idea of pipe lining is usually thought to be like a left to right linear thing, right? But yeah, if you wanted bi-directional buffer communication between processes, you could certainly use multiple pipes and you could do all sorts of whack things with pipes, just weird stuff, right? So here's the C-like pseudocode that gets this to work, right? So I had this pipe ends array that's gonna hold my two file descriptors. I initialize it by calling pipe, that might call fork. So my, this is, who is here? Who executes this code? The child, right? Because this is return, this is return zero, okay? So the child closes the right end of the pipe and by right I mean W-R-I-T, like not, I don't know what the right end, the right end might be, the right end might be the left end and the read end might be the right end. Not that it doesn't make any sense, clearly. I know why this has been confusing me actually, at the right end, like right, the writer end, maybe that's what we should call it, that still sounds wrong, okay? Anyway, so the child closes the end that is used to write data into the pipe and then can now read data from the pipe and the parent closes the data that's used, the end that's used to read data from the pipe and can now write to the pipe, right? And so the parent can now read it's beautiful new child process, right? Questions about fork, yeah? One end is closed, can't I open it like just the same way that I'm doing it right now? Can't I use the same pipe to again, now interchange the open and the close end? Instead of creating a new pipe for the reverse? Yeah, that's a good question. I don't know how that works. I think pipes are a directional abstraction, I wish I knew the answer to this, I think they're a directional abstraction in the kernel. So a pipe has, so I can't push data through pipes in two directions, right? I put data in one end and it goes to the other end, right? If I put data in, there's no way to, there's no way to write into the read end of the pipe, right? That would cause an act, that would cause a problem. God, all right, so again, we'll go through this again, right, because this is kind of clever. So I create my pipe, I have file handles pointed to both ends, I clone my child, it has file handles pointed to both ends, and we both close the end that we don't need, and now we have this nice communication, right? Yeah? Can you go back to this? Oh, right here? One more. Yeah, that's what I'm saying. Yeah, I think the pipe is a directional abstraction. Oh, yeah, no. So you're saying if I... In the other two, if the other two arrows vanished. Okay, so let's see. Oh no, okay. If the other two arrows vanished, yeah, okay, fair enough, fair enough. Okay, right. The pipe is a one-way abstraction, but it can be initialized to point either direction, right? I just don't know what would happen if I tried to open two file handles to both ends, I don't think that works, right? So you're right, if I close the other ends, right, and I can't show this on the slide very easily, but the ends that would be left would be this end, right? And this end, so I could pass data back from the child, right? But I don't know what the semantics are if I try to pass data both directions to the pipe, right? So once you initialize, you cannot initialize. That's what I hope is true, right? But that's... I'll look that up, that's a very interesting question. I hadn't thought about that before, yeah, good question, all right. Okay, so let's go back to fork, right? We talked about one of the things that has to be copied for each child process is all of the state in the parent, including the parent's address space, right? So after fork, the parent and child are identical, but they should not share memory, right? Remember, one of the main things the operating system is trying to do is to isolate processes from each other, and that starts when I call fork, right? I do have something that's shared, I have those file handles, right? But if the child says, you know what, parent, I am done with you. It can just close all of its file handles and reopen them and it has its own file handles with its own offsets, even to the same files, right? So if I wanted to, after fork, let's say I didn't wanna communicate with my parent, I could be just like, sorry, man, I'm closing that pipe and you can write into it so your heart's content and at some point it's just gonna fill up and the rights are gonna fail and I'm on my own, I'm independent, my own person, my own process, so yeah, so there's semantics associated, but again, the child is in control of that relationship. The child can completely sever all ties with the parent and initialize its own file handles to its own files and everything, right? And sometimes in certain cases that's the first thing that certain types of fork do, right? So there's certain types of fork that don't copy the file table, right? Because the child doesn't wanna share file handles with the parent, right? But one of the things I do need to copy is the memory, right? Because that should not be shared, right? Threads are a different thing, right? And we can talk, we'll talk a little bit, I think in a few lectures about multithreading and running the multiple threads in the same address space. But fork is creating a new process, right? A new process is a completely separate container for threads and memory and other things and so it has its own copies of these things, right? And the other problem, this has been up on the side for a few minutes, so I won't try to surprise anybody with it with fork, and the semantics of fork is frequently, so, I mean, this type of code you'll see sometimes, right? And there are certain processes that fork, there are certain applications that fork new processes to act as clones of themselves, right? So who can give me an example of a process like that? We've talked about one in the past. It's an example of a process that would call fork to create more copies of itself, yeah. So tabs would probably be done with threads, right? So we'll come back to that. That's a good idea, it's a good example of a multithreaded process, right? But what about, what about a, because remember, it's not necessarily easy to communicate between processes. So I need an application where individual instances of that application can essentially act pretty much independently from each other, right? But what's a case where I might need multiple instances of an application to run? What's an application that might spawn off a bunch of copies of itself? A web server, right? Because a web server process, and newer web servers frequently do this with threads instead of processes, right? But for a period of time, Linux support for multithreading was done in user space and we'll get to that later, right? But frequently old web servers would spawn off like 10 copies of themselves because the work of a web server is pretty much independent, right? They didn't really need to talk to each other, right? They would wait, they would get a request on some port, right? There was maybe something that had to distribute the request initially, but then all the request processing is completely independent, right? I go to disk, I find the file, I render the file if I'm doing some sort of dynamic web crap and then I blast it out over the port and I'm done, right? So requests come in and I can just hand the request to each separate process and after that there's very little communication that's required between those processes, right? With tabs in a web server, right? There's frequently, there's more state that's shared, right? You might be able to do tabs with processes, that's a great question, I don't think about it more, but I think that that's probably done with threads, yeah? Yeah, so they're frequently like kernel type services, like a lot of different type of servers, right? That expect to handle multiple requests, achieved concurrency by calling in fork and just creating new copies of themselves, right? And usually they had to have some limited coordination between each other, right? But it's a question of how much coordination is necessary, right? And if it's not much, then you can get away with doing fork, right? Yeah, yep, yeah. I mean, as far as the file table is concerned, yeah. I mean, if the parent wants to, it can just close all of its files after it calls fork and reopen and then we'll have its own file handle objects. The only time those pointers are copied is by fork, right? So this is kind of an important, I wonder if I can back up here and, yeah, let's get this right before we go on. Where is my diagram with fork in it? Nope, I went back way too far. All right, oh wow, I forgot that these things, okay. Okay, so this is kind of important, right? And I wish I had a better description of this, but this is what things look like after fork, right? If the child wants file handles pointing to these files that are independent of the parent, it can close the file handles it has and open slides.html and file.svg again. And then what it will look like, it'll have its own file handles, right? Those file handles will still point to these same file objects, but now the offsets won't be shared, right? So if you had a case where you said, I want my child to have the same files open as the parent, but I don't want it to share file offsets and file handles, what I would do is I would go through my file table and I would just basically figure out which file that handle was pointing to, close it and reopen it, right? And then I would have the same files open, but I wouldn't share offset with the parent, right? Reading and writing for multiple files at the same time for multiple processes is a little wiki, right? It requires some coordination, right? Because again, I'm writing in the file, you're writing in the file, and who knows? So that's not necessarily always something that processes want, right? Coordinating access to files is hard because if I write at a particular portion and then somebody else writes there, then we're gonna overwrite each other's changes, right? Yeah, I don't think so. No, no, no, they can't be, right? Because the child, because let's say, let's say I have these files open and I have that index saved in a local variable that's on my stack, it has to point to the same file, right? Otherwise I'm not the same process, right? So the file table has to be copied exactly, right? You can't change the indexes. Because if you change the indexes, again, like the parent has file handle one open and it points to slides at each TML. If I made that two in the child, then the child's gonna do a read on file handle one and the kernel's gonna say there's no such, like that file descriptor's invalid, right? So yeah, the handle objects have to be identical. That's part of the state I'm copying, yeah. No, it doesn't have to. I always forget what the semantics are. In general, concurrent IO to multiple files is not a good idea, period. But I think that, can you ask that on the forum? Because there is an answer to this question and I forget it every year. And I will remember it because it's important because you guys, this is one of the things you guys have to get right for assignment two, right? You need to figure out what the semantics are. I think you might be right. It might be possible that changes to the offset are done atomically, right? So we're gonna talk about synchronization more in the next few weeks, right? But yeah, so if, here's what has to be true, right? So if the parent and child both write 512 bytes to the file at the same time, right? One of those writes goes from zero to 512. The other goes from 512 to 1024, right? So when I'm finished, the offset should be 1024, right? Which ordering they go in? I don't know if that's well-defined, right? But they shouldn't, like, if they both write 512 at the same time, the offset shouldn't end up as 512, right? It should, so the offset should be changed atomically, right, and the right should not overlap. That's a good point. And again, but please post that in the forum because I wanna make sure that I, I get the right answer to this. Okay, let me talk a little bit about fork overhead and then we'll be done for today. So, so there's, and again, so sometimes I'm making a copy of myself, right? And in that case, fork is great, right? But frequently what I'm doing is I'm making, frequently what I'm doing is I'm trying to create a new process, right? So fork is what creates new processes, right? But all the processes on your system aren't forks of in it, they're not clones of in it, right? That would be a very boring system, right? That system wouldn't be usable, right? So on Monday, we're gonna talk about exec and how processes change. But frequently the first thing a process does after calling fork is calls exec. And exec wipes away the whole address space. Exec basically is like that, you know, the pre-Madonna dressing room person, right? Like they just come in there and they totally, whatever was in there before, it's gonna be totally exactly according to the instructions that are in the binary that I call exec, right? So now this is irritating for the operating system, right? Cause I did all this work, right? On fork, right? To copy all this memory potentially. And then the first thing that happened is the process came in and blew away all my changes, right? So what do I do about this? So there are two solutions to this problem, right? One solution is I can use something called copy unwrite. And I wanna point out that these solutions fall into sort of interesting categories, right? So fork and exec and the Unix system call interface has been stable for a long, long time, right? There's been changes to inhibitions, but in general, it's a pretty narrow interface and it's been standardized. But I think it's kind of a neat idea, right? You implement fork and exec and then you figure out how to get them to perform well. So they didn't create a new system called it's like fork and exec, they just figured out how to do this. So one of the things they did is they optimized the existing semantics. So there's a nice memory optimization that we'll talk about later when we talk about memory management that avoids the copy that I would have to do after calling fork in many cases, right? So it removes a lot of the work while allowing the process and child to share memory safely, right? So I won't give away all the secrets of how this works, but the idea is that I don't have to copy memory, but changes that the parent or child make to memory are not reflected in the other process, right? So they are not sharing memory, but I don't have to copy all the memory, right? So this is a copy on right, kind of gives you a clue to what exactly is happening, right? And then there is one addition to the system call interface which is this thing called v fork. And v fork is a different system call and what it does is it says if you call v fork and the first thing you do after calling v fork is not call exec, I'll fail. Like I'm just gonna terminate your process, right? So v fork is only to be used in this design pattern where I call fork and I immediately call exec to do something else, right? If I was running a web server and I used v fork to create new copies of myself, it would die, right? Because v fork doesn't do all this work, right? It doesn't copy the address. All right, so let's see here. And then, yeah, so there's a new system call also called clone, which I think I mentioned before, which essentially gives a lot more control to the calling process over what gets copied, right? Pieces of memory can be shared, signal handlers can be shared, things like this, and we won't get into this in detail, right? But this is another very interesting thing, right? Which says that clone allows the child to start at a different place, right? So the child doesn't even have to start executing at the same place that the parent, right? All right, so when you start forking processes, right? Fork establishes essentially this parent-child relationship between the process and then when the child calls fork, then child has children, right? And what you end up seeing is this kind of tree of life type of thing. And if you run this Unix utility called psTree, which may be installed in your virtual box or maybe just be able to install it yourself, you can see that, you know, here's the process tree on a web server I was using to run last year's website. So here's init, right? Remember, init is the parent of everybody, right? And it is the boss, or God, or whatever you want to say it. And then, you know, so here's an Apache process, right? Which gave birth to another Apache process. And I think these are actually a condensed version of saying that there are many, many other Apache processes which respond by these, right? Oh, these might indicate that there are, actually, you know what? Sorry, that's not true. So this notation here indicates that these processes have multiple threads, right? So this is Apache running in a configuration where it both creates several processes, but those processes also create their own threads, right? So there's a way to configure Apache so that it uses a mixture of multi-processing and multi-threading, yeah, Jim. Good question, you could try it. Try it in your virtual box and see what happens, right? I don't know if, in it, handle signals. But it's possible if you send it kill nine, it'll just crash the machine or shut down or something like that. So it probably doesn't handle just kill, right? You probably have to send it kill nine, yeah? No, no, it doesn't, right? So that's a great question, right? So all of these processes here may have been, it's not necessarily true because we'll talk about what happens when a child's parent exits, right? But these potentially could have been created by in it, right? But they are not in it, right? So you can see that these are various system services, SSH, and so here was my interactive session, right? This is me, SSHD, SSHD, SSHD bash PS tree, right? So here's PS tree, which I just ran, and it's in its own tree, right? So this shows you, this is a nice way of visualizing everything that's running on the system and how it's related to itself. So what is, so has anyone ever tried running and compiling this program? It's kind of a fun program. What does this code do? Yeah, yeah, yeah. Yeah, this will just exponentially spawn new processes, right? It's worse than threads, right? If it was threads, actually you might, your machine might survive it, but processes usually not, right? So this is like, yeah, this is my favorite. Like what is the, like more, more. Okay, so I think we're at a good point, right? So we're done with the fork. Monday we will talk about exec and then finish the system called Stuff Next Week. So keep your eye on the website for new parts of assignment zero, which should be done today, and have a great weekend. I'll see you on Monday.