 Okay. All right. Welcome back. We're starting to thin out, which isn't great. So I guess people aren't really using the discord because I posted a question as a follow-up to the last lecture, and I got one reaction and no one responded to me, which is kind of sad. So yesterday, we talked about transforming one process into another to execute a different program, and that's the only way to execute a different program, which doesn't really make sense at this point, because we don't even know how to create new processes. So today, we'll be talking about how to actually create new processes and why it's like what it is, and we're going to start getting into the nitty gritty. So we'll have a lot of code examples today that we can play with, so feel free to ask questions about that, because so far everyone's just written a program and then started at main and start executing. Well, as of today, that fairy tale has now ended. So today is going to be, might hurt your brains a little bit, but hopefully we'll get through it. So first question is like, well, why did I kind of like split off having to execute in a different step? If I'm creating a new process anyway, shouldn't I just create it all in one step, create a new process, tell it what executable to run? You could do that. That's one kind of kernel design you could make, and that's exactly what Windows does. So Windows just has one system called to create a new process and load something like LS and then execute that, and then it's all done. But Unix decomposes that process into two parts just to make it super flexible and we'll see why that flexibility has a benefit and it also has a nice implementation benefit. So in Unix land, instead of creating a new process and then telling it what program to run, you just clone the currently running process exactly as it is. So the mechanics of this is whatever process is running, say hello world, LS, something like that, your kernel will pause the current running process and copy it completely. So copy all the information in the process control block like exactly where it's executing, all the variables, files that has open, so on and so forth, and then it reuses all that information and we'll start executing now two processes that are running the same code. And the only way to distinguish between them at that point, since they're exact clones of each other is something called this parent-child relationship. So one creates another. So you can distinguish who created who and then that's the only way you can actually distinguish between the two processes and we'll see that again and again today. Then if you have two processes running, it's up to the kernel to decide which to run. So they could be executing at different parts of the code and you have no idea. And we'll see examples of that today. So don't worry just yet if this is kind of confusing. So if you look at, because everything has this parent-child relationship, you'll see it will form a tree of what process spawned another process. So if you go ahead and type something like, if you look at all the processes running on your virtual machine or macOS and you actually follow all the relationships, it'll follow a tree, something like this. So in Firefox, right, so we see Firefox that gets executed from something called a GNOME shell, which is like your whole desktop shell. So that would create Firefox and then within Firefox, every tab is a different process. So it would have to clone itself a bunch of times and then start executing that tab stuff. So this is just kind of how your process tree looks. You'd have your terminal emulator here, which would run your shell, which would run some command and htop is the command. It's a bit nicer than top to go ahead and see all your running processes. So you can see that if we run htop, right, you'll get something like this that will show a tree. And then the only thing that really matters here is every process just has a unique process ID. So this column all the way on the left. And that's how you identify different processes. So that's just for reference there. You'll notice that we had that kind of process life cycle before that said, you know, when things are ready, running, blocked and so on. So as part of that htop output, it will tell you the current state of the process, but the Linux terminology annoyingly is a bit different. So instead of saying something is in either running or in the ready state or waiting, it just links just lump them together and puts them in a running or a runnable state. And you don't know if it's actually executing or if it's just ready to execute. And then if you see S, it will be in interruptible sleep, which is just a category of block. So the kernel could wake it up if it wanted to. And then in interruptible sleep, which is again blocked, but the kernel won't be able to wake it up because it's waiting for a file or something like that. And then there's these two states that we can ignore for now. But the stop state is, you know, your terminal can actually stop a process and then you can manually decide to restart it if you wanted to. So it's another way just to stop processes from running altogether. So you'll notice at the beginning because we have this parent-child relationship, there must be something that starts off, some ultimate parent process that starts our machine. And on Linux, right, or Unix, the boot process is pretty simple. As soon as the kernel boots up to start all of user mode, all the kernel does is load, is create a single process and then load whatever is called init in there. So it'll just look up a hard-coded path called sbin init. And then that's all the kernel does. So as soon as the kernel's booted up your machine and it's initialized and everything, it creates one single process and then that process executes whatever is in sbin init and then it's responsible for launching everything else on your machine. So everything is rooted there. So if you wanted to, you could replace, you know, we could do something really stupid and replace sbin init with like hello world, in which case if you just have hello world, it doesn't create any new processes or anything. So when you exit out of the initial process, your kernel just crashes because, well, it just stops because that's the only program that's running. So as soon as the init stops, your computer stops. So on Linux, init's something called system D. There's other old styles of init systems. Soon we'll see what actually goes into what the responsibilities of init is aside from creating a bunch of different processes and we'll see that next lecture. And then some operating systems, you know, create an idle process to just keep track that just runs at the lowest priority, just so you can see it in kind of the process manager, Linux does not do that. Okay, and before we get into this, kind of the reason why our hello world even worked is because of something called the standard file descriptors. So like the first argument of a program, these are just kind of a standard convention that everyone uses and why hello world worked. So anything that executes from the command line has these standard file descriptors. So zero is always standard in, which is the standard input. One is always standard out and two is always standard error. So when I did that right system call, I use number one because that is standard output and that will show on your terminal, right? So if you're a terminal emulator, you have to translate, right? You have to do something more important. You have to translate the key presses into bytes that go to standard in. So that's part of the terminal emulators job. And then you also have to display bytes that come in from standard out or standard error. So you can actually see the characters on your screen. And what we'll see later is you can kind of, you may redirect file descriptors between processes. This is kind of like the nice Unixy thing where instead of one just going to the terminal, I could instead have one be a file and then whenever you execute your file, all the output just goes to the file and you don't have to write any like file handler code. Any of the, right, we've opened files before and like written to them. Well, if you just have everything that goes to standard output, you can just direct it to the file through your shell and you don't have to write any additional code whatsoever. So there's this whole kind of PROC subsystem in Linux. So there's a special directory called PROC and in there you can look up all information associated with the process. So this is the way the kernel exposes information to you. So we'll kind of use it to kind of explore things and see how things tick. So in there there's like an FD directory that will tell you what all the file descriptors are and if you execute on your terminal emulator you might get something like that. So let's go ahead and try that. So on here I can just LS PROC. So self refers to this own itself. So it would be the shell and I can see all the file descriptors here. So my shell actually has a lot of file descriptors open and they seem to be dealing with Chrome. But you can see the standard ones are all this weird pseudo terminal that we don't have to worry about yet. But you can go ahead and play around in the PROC file system if you really want. And then as part of that too, you can actually see what programs have what files open. So it's a nice kind of debugging thing. So you can use this command called LS of to see what processes have certain files open. So for example, you can go ahead and try using LS of on the C standard library and you can see every single process on your machine that's using the C standard library which spoiler alert is pretty much every single process. All right, so little aside for kind of programming and this course I guess you'll probably have to heavily use man pages. So you just use the command man and then whatever the function is but there's a little caveat here in that sometimes it doesn't give you exactly what you want. So we saw exact VE last lecture today we'll see a command called fork which you can have a lot of fun with that term and then we'll see weight in the next lecture. But if you look at the, so we'll be using fork today and if you go ahead and do a man fork you get something really weird. You get this AUK extension module which doesn't look like a C API at all and it gives you this weird load fork and it doesn't give you any information about any C APIs. So if you want to look up information you actually have to use the number. So you have to do man to fork and then that will give you the actual documentation you want. So create a child process and that tells you what header file to use, what the signature is and then the description. But don't worry, we'll be using it and kind of breaking stuff. So we're gonna look at fork and it is the only way to create a new process and like I said before it copies the currently running one so it's ABI or API is pretty simple but also not simple. So all it does is it takes no arguments and just returns a value and it returns a process ID type which is just a number and then because it's a C API you can see kind of what values to expect. So if fork returns negative one there's a failure and if fork fails which is like the only way to create processes on your machine your machine's probably crashing so you probably can't handle it anyways. And then the only way to distinguish between so fork makes a copy of the two processes and then the only way to distinguish them is the value returned by fork. So in the child process it will return zero and in the parent process it will return a value greater than zero so it will turn some ID number and that ID is the ID of the new process that was just created so it's a clone which is a clone of yourself. So after that there's two processes running and the only way to distinguish between them is that value. They have the same variables they have the same everything but after that point of the fork because each process is isolated changes won't affect the other one and we'll kind of see that. And why it does that is because copying a process is actually much cheaper than creating a new one from scratch and if you want you can create two processes and then have them each do like half the work or something and then suddenly your kernel can actually if you have more than one CPU your kernel could run them in parallel and remember we had parallel versus concurrency yesterday. Yeah. So the parent process is the one that initially called fork that's running. So one process will be running and then call fork and then it will create a new process that's a copy of it. So the parent is who originally called fork and after it calls fork it will keep on executing and then there'll be a new copy of it that will also be executing. Yeah. But then there's gonna be two processes running. Right. Okay. Let's just see the example. All right. So here's our example. Also was there a question in the back first? Yeah. So copy on right is something we'll see later but for now it's like an optimization and we'll see why that optimization is good. Yeah. So PID underscore T is it you can think of it as just being an in so it's just a number. It's just, you know they called it something different in case they want to change it or grow it later but you can think of it as just an in. All right. So this is good and this is going to be weird. So, right. Let's start off with what we know. Execution starts at main and then the first thing we do is call fork. So at this point after the fork there's actually going to be two running processes that are at this exact point in the code. So now there's at this point two processes, right and they're both executing and we don't have control over which one executes that's all up to the kernel at this point but one is going to have a PID greater than zero which is the original process and then the other one will have a PID equal to zero which is the new one, right or you could call them parent child. Oops. So if I run this, right I have to consider two processes running after the fork. So the original process where the PID is greater than zero it will go into this if statement and execute so it should print off parent returned whatever the return value was which will be something greater than zero and then it will print off its process ID and this is just a function that is a system call that gets your process ID and then it will also print off its parent's parent ID. So whatever created this process so that's where the extra P is from. I have a sleep there just because reasons that you will explore in the next lecture but then, and then so our original process it will go into this if statement one more second, okay the original process go into this if statement and then exit it, right because it won't fall into else if or else and then it will return zero and then our original process will die. Yeah, so we'll see. So in it's like the grand-grandparent but we'll see if this one is started by a net or what's this started. Okay and then before I execute this again so now our parent process should just print off the three lines there and then call exit and now we have to consider the other running process which is the child which will have a process ID of zero. So in here it would go ahead it would try and execute this if statement it wouldn't succeed because process ID is zero it's not greater than zero it would fall into this it would fall into this else if statement and then print information about itself so the child return process ID this would have to be zero and then we just print off its process ID and then its parent's process ID which hopefully if I haven't lied to you is the same ID as the parent. So let's go ahead and start the brain busting. So if I do run that, oh that's perfect output. Okay, so it's a bit weird the output's a bit weird because it's not in order, right because after the fork we have two processes running and the great thing about this which is the complication of this entire course is you don't know what's gonna execute after that, right? There's two processes and the kernel gets to decide what runs. So the order of these print statements will not be the same within a single process they'll be the same so you'll always get child return, child PID the child parent PID for the child running and the parent you'll always get in the same order but between the two processes you have no idea what the order is gonna be you are completely, the kernel figures that out, right what to run at what time. So if we look at this we get the parent return PID to 2,987 and then we can see that the child's process ID is exactly that number. So that makes sense, right? And then the child's parent process ID so whoever created it its number is 2,983 which is the parent's PID. So that makes sense and then there was your question of what is the parent's parent ID? So it's not one in this case it is 1,477 and if we wanna figure out what that is we can go into that PROC file system, right? So we can go ahead and look at so PROC and then process ID number so we know that process ID and then we can go ahead and read the status and if you look at those files it's just a bunch of columns but I know there's one called name and you can see its name is ZSH which is my shell. My shell created so my shell is my parent so that kind of hopefully solves a mystery from the last lecture so your shell which is bashed for some of you ZHC for me would have cloned itself to create another copy of itself and then it would have done that exec vehicle to become whatever the command is you typed. So it would do a fork followed by an exec and that's the common combination of how to create a new process and initialize it and start it running something from scratch. Okay, any questions? Yep, nope. Yeah, in fact you should pretty much always get this input which is the nice annoying thing about this is these concurrency issues, right? Because you don't know what can run they're very hard to debug because I guarantee if I run this probably 99 times out of 100 I'll get the same output as this, right? Running, running, running but the first time I ran it I got one of the child messages first. So these are the types of bugs that can exist for like seven years if the order actually matters on the behavior being correct. All right, any questions about this? There's two processes running here. Yeah. Yeah, so the question are these two processes running in parallel and the answer to that is it depends on the kernel. So if your kernel is well-written, right? They'll be running in parallel because I have multiple cores on this machine and I want parallels faster than concurrency, right? The more parallelism I have the better. So it will try its best to run them in parallel but you as a user you have no idea you have no control because it's all up to the scheduler, right? The kernel decides what runs and when. Yeah. No, so every time it calls the fork right after the fork it's if it doesn't have an error which you, if you see an error something really bad's happened so we can for now assume errors don't happen. After the fork there's two processes and the only way to distinguish them is the value of that is the return value. So yeah, so one will be zero otherwise it'll be executing the exact same stuff, right? If you couldn't distinguish between them then you can't do anything different you'd just be running the same thing, yep. Oh, the shell running. So this is the only way to create new processes, right? And execve is the only way to load a new process. So if I wanted to how the shell basically works is so your shell could execute this fork call and then in here say this is your normal shell it's still executing and then this would be a copy of the shell and then it could do an exact call here and then transform that clone process into a new one and start executing for example LS. So that's exactly what your shell does and we might have a lab involving that plus everyone loves answering or asking these questions and not bring system courses. Yep, yeah like I could, we could take our example and do an execve call here if we want, right? There's two running processes I can do whatever I want with it. Yep, the parent, so these ones? Yeah, so the return PID is just the result whatever got returned from the fork call and the PID is the result of this get PID called which is its own PID. So it's what was returned from fork versus what my parent ID is. So fork never returns your own parent ID if you are the original process the return value is the PID of the child, right? So that's why the parent returned ID is, you know, will always be equal to the child's process ID here. All right, let's do a little bit more mind-bending stuff. In X equals, so if I am telling the truth and it's the exact copy of it, then in here I can go ahead print f X is, let's say, and let's distinguish between them. So parent X is that and then in here I will do child X is that. So if they're exact copies they should both have a variable called X should be equal to one, right? So they both have a variable called X is equal to one. Okay, that makes sense. And then also if I go ahead and change it in one if they're completely isolated and their exact copies of each other down to all the memory bytes one shouldn't affect the other. So say in the parent I'll just change X equals to two. So if I do that I get in the parent I get X is two and in the child it's still one because it doesn't affect it. Then you may be like, oh, well what if it wrote to that before the child print so the child saw the old value or something. Well, I'll make sure that that happens here. I'll even sleep the child for a second. So it creates a new process. The parent changes the variable and then the child assumes it gets created. It will wait a second to make sure that the parent definitely writes to that variable first. So if I do that it's the same thing and I see my output way later. So in the parent X is two and the child X is one. So they're complete copies and they're complete independent copies and we'll discover kind of the magic behind this later. Yep, I like that question. All right, can I print the address of both of them? So if they're exactly the same that should mean their addresses are exactly the same, right? So you want me to print them off before fork or after fork? So right before you want me to print it off here? Okay, parent would start address of X. So that compiles. So I have the address of X is blah, blah, blah. That doesn't help because I haven't done a child. So I'll show it in the child. So let's see if both of the addresses are the same. So if we look at this, let's make it bigger. So in the parent, the address of X is blah, blah, blah, blah, E4 in the child, the address is blah, blah, blah, blah, E4, they're exactly the same. So when I say they're exact clones of each other, they're exact clones of each other. Yeah, so if you declare X after the fork then it wouldn't copy it, right? Because you created it after the fork. So if I create Y here, after the fork, there's two new processes. The parent wouldn't go into this loop and it wouldn't create Y after this. Yeah, so if I do that, it'll kind of behave the same except after the fork, they're both clones of each other. So they both already have X. Then they would both, whenever they actually execute the next line, they would both create a Y. So they would look the same, but they wouldn't be a copy. But they would do the same thing. That one would, you know, they would get, they would probably get the same address. If it was something more exotic than just being like a variable on a stack, they might get different addresses, but they would get the same address. Yeah. Oh, sleep is just to put a process to sleep, say don't execute me for like a second. So that's just a way to make one execute before the other. And I guess a nice thing about sleep is sleep is like usually how, why, when you actually look at your CPU usage, it's not always 100% because a lot of processes on your machine, if you're watching a video or something, you only need to draw one frame every like 60th of a second. So you don't need your CPU wasting power the entire time doing nothing. As soon as you draw that frame, you can go back to sleep and you can relinquish the CPU. Yeah. They have the same address, but it's not changing. When you change it to parent, you make the x2, but in child, the x is the same x as in parent. Yeah. It's still one. Because they're, so as soon as we do the fork, they're exact copies of each other and then they execute independently. So if you're the operating system, you have to make sure that they execute independently. So if that address was like a physical address on the machine, that would make no sense, right? Because I'm changing it and then reading it later and it's the old value. So these are all virtual addresses. And when we see virtual, we'll see how this illusion happens because they look exactly the same down to the addresses. And the only way for that to be possible is if you had virtual memory. Yeah. Yeah. So as part of the clone, remember the operating system? We haven't got into it yet, but it has to map where a virtual address is in physical memory. So one of the naive things you can do if you just copy everything is you just copy all of its virtual addresses, copy all of that memory into some new memory and then just map all the virtual addresses so they line up to the copy so they look exactly the same. But they're different physical blocks of memory. Yeah. These are all so user processes can only use virtual memory. Yeah. Yeah, this far. All memory addresses you've seen printed to you, they're all virtual memory addresses, even if you've never known it. I can guarantee you it's not going to be the same address. Unless, right? Because even that address starts at like FFFF if you started byte 0, how big is that? I don't have like four terabytes of memory or something. That address is huge, right? Any other questions or fun things we want to do with this one before it gets even messier? Yeah. So my friend mentioned if you declare an int y later, you said the address will be the same. Yeah, it will likely be the same. Yeah, we can try it if you want. So the way they declare a variable sort of following the same way, I thought it's all random. So it will be randomly placed between two executions if you run that same program again. One of the things that Colonel will do is like randomize addresses. So if you ran it again, it would get different addresses. But at the point of the fork, that randomization has already happened, then I clone everything. So everything is in the exact same state there. So they'll probably get the exact same address because they're both executing independently, right? So let's just double check. I'm like 99.9%. Sure, they'll get the same address. Whoops. So they got the same address. My camera died. Cool. They got the same address because right after the fork, that variable doesn't exist. But they're exact clones of each other and they would do the same thing to allocate it because they're the exact same program, right? Any other questions? Yeah. Yes. Yeah, so that's part of the guarantee of your Colonel that whenever you fork, you have an exact copy and it doesn't affect the other one. Yeah. Sorry, is X. So the process for cloning is whatever is executing main. Yeah, I mean you could do whatever here, right? I could call fork at whatever point in your program. I could output some stuff to a file, do whatever, and then call fork. And then it's just going to create an exact copy of that. And if I was writing to a file and then wrote a bit more to the file and I forked in between those steps, then suddenly, since it's an exact clone, you'd have two processes writing to that file. Yeah. If you don't have the memory to create a fork, that means you've probably run out of memory on your machine and it's about to crash. So yeah, because this is the only way to create new processes, if you can't fork anymore, you can't launch new executables. You can't really do anything. So if fork fails, it's like if malloc fails, well, malloc failing is less bad than fork failing. All right, any more questions about this? Because the next one is going to be, oh, even better. Because there's a, yep. Yeah, so this is called fork example. It's a C file. It has a main. So when you make it right, it makes an executable called fork example that for simplification purposes, you can think of it start executing at main. And then to actually create the process that runs that fork example, we saw my shell would have cloned itself because it's my parent in here. And then it would have exec VEED the fork example file. And then it would have started executing. And then whatever that fork example execution made it to the fork, it would have created two copies of itself at that point. And it would have been exactly the same. So yeah, so that's kind of disposed of mystery of your shell. Like your shell would have done a fork, kept itself the same to keep on running. And then the child, it would transform the child into whatever process you typed. Fork example, LS, whatever. All right, we ready for some more fun? So here is a better process in operating system courses. Love asking this on exams and stuff like that. So because this is the only way to create processes and it's good to know processes. So I have a program called multiple fork example. We'll start reading main. So thankfully, that's the same, even though the rest of your world has kind of went upside down. But program execution, you can still think of happening at main. So inside main here, I just have a for loop that runs four times i equals 1 to i equals 4. And then it calls this function new process. If it returns some error code, I'll just say zero is no errors. And then if it is an error of some variety, I'll just catch it and then return. And return from main, that's the same as exit pretty much and that kills this process. So all it does, so main will go ahead and call this function called new process four times. Now inside new process, first thing it does is fork. So it will create two new processes. So it will create one new process, which would be one will be a copy of itself. And to distinguish them, the only way I can do that is with PID. So let's take the first call. So ID, the first argument there is equal to 1. I would have forked. There are two exact clones of each other now. So that ID in both executions is still ID equals 1. And now the original one has process ID greater than 0. The newly created process has process ID equal to 0. So thankfully for you, it is the original process. So this is the parent. So remember, two processes executing now. The parent would go ahead and fall into that if branch and then return 0, which means no error. So the original process just does a fork and then returns. And then the other one, so the new chop. So the else if there's an else if there's an error. If this ever happens, I would be shocked. And then in the newly created process, it would fall through there and then start executing. So what it would execute is there's another for loop that goes from 0 all the way to 9. And it would say what process there is. So it would print off the ID and then print off whatever the value of x is. And this u sleep is just to put itself just wait a millisecond so that we get more interesting output. So everything will just wait a millisecond. And then the newly created process here called exit. And so that should end that newly created process. So at the end of this, the two processes, the original process would have just immediately, after the fork would have immediately returned from this function. And then the new child process would have made it down here and would have exited. And then it would have been dead. So then this happens, right? Then for i equals 2 in this for loop, it would create another process. And the same thing would happen. The original one would immediately return. They would give back here i equals 3, i equals 4. And then as soon as that's done, it would exit from main. So the original process would then exit and be dead. And then all the child processes should do the exact same thing. So if we do this, our original process is the only thing calling fork. It should make four fork calls. So it should create four new processes. So if we run that, again, we have four running processes that we newly created. And we have no idea what order they're going to run in because it's up to the kernel. Just each one of them is going to print in order. In this case, it goes 1, 4, 3, 2, 1, 4, 3, 1, 4, 2, 3. And then just prints them off, but it's all in jumbled order. And we have no idea. All right. Any questions about that little example? Does that make sense? So there are the original process, and then it creates four other processes that all print off independently. So if you're the kernel, if I only have two CPUs cores and four things are running, then I have to do some things concurrently. I try and do as much in parallel as I can, but some will be concurrent. After the fork, the parent process is immediately terminated. So it returns 0 from new process, so it just returns here and goes back here. Yeah, it's the same parent that has multiple children. So each time it goes through, it creates a new child. And then for each fork, that's here. The first time through for i equals 1 in the first loop in main, id would be set to 1. And then after the fork, exact copy. So in that new child, id is 1, which is why we see id 1. And then the next time, the original parent process would have returned, and then that i would have been reset to 2. Id would have been 2. It would have forked exact copy at the point of the fork, so the new child would have id equals 2. This one? All right. This is where the fun begins. You want to return 0 from here? All right. I'll make this a bit easier, because this will quickly go at a hand. Let's reduce that to 3. So this is a good question for your exam, because your brain's going to hurt now. This is going to be great. So if we run multiple fork example with that, it's still outputting a bunch of stuff. It's still running. We seem to have a bit of an issue here, because so process, it goes 3, 2, 3, 2, 3, 2, 1 doesn't even happen. And it's actually still kind of crashing. And there it goes. So here we just got a bunch of random output. We actually created a lot of processes, but we can see that. Let's see how many ones we got. So process 3 printed 0 here. Hopefully I only made one process 3. But if we go through all the output here, there's a process 3 with 0. So we've made two process 3s. What? And then we go through, oh dear, there's a lot. And then we go through. So what actually happened there? Because that looks like a mess. So if we return, let's go over it slowly. So let's see what happens. So if we go in here, we create a new process i, or sorry, we call a new process with i, i equals 1. We go in here in new process, id equals 1. We fork. So there's two exact copies. And now the original one does what it does before. It returns. And then the new lead created process comes in here, prints off, you know, process 1. Yeah, prints off process 1, and then returns 0. And now the newly created process comes up here and returns. Because it's the exact copy, if it hits return, it would have done exactly what the original parent did. So the child also returns from new process, comes through, goes into the loop. And now there are now two processes that are about to execute new process with i equals to 2. So now that happens. And now they create two new processes. And then you kind of grow exponentially. So that's why I put an exit there, because I wanted to be sure to kill the new process immediately so it didn't go out of control. Yeah, OK, one more time. So we're executing our original process. We start at main. i equals 1. i equals 1, so we call new process with i equals 1. So here is our original id equals 1. Then at this point, we have two processes. So we have pid greater than 0, which is the original. So id is 1. And then we have our new child process where pid equals 0, id equals 1. And these are two processes executing at this point in time. So this one would go into this if and return. So I'll move this. So this one has returned. It would go here. It would go here. And then there wouldn't be an error. It would go through. And then it would now change this i to 2. And then it would essentially call this with that argument equals 2. So I'll leave my original process there. So the new child process here with pid equals 0, it won't go into this if statement. It won't go into this. It will print off process 1 and then all of its numbers. And then in return 0, it would execute it. So move this. So it would hit this return statement. Return 0. And it would return to exactly where this, the original one returned. Then it would go here because it's the exact copy. And then go through. So x was 1 before. Now here it will be here. And then i equals 2 for it as well. And then they both call a new process with id equals 2. So even more fun thing. So see what would happen if we just did while 1. If you want to really screw up your system, you could do that. So first time through this loop, I don't even check the return value. So I don't care. So first time through this, it would create one new process. Then they start executing the exact same code. Then they both go through the loop again, create two processes. So now there's four. Then they both go through the loop again, create a new process, 8, 16. So if I execute this, which is a good way to end the lecture because I will brick my machine, I'll go that. And then it just disappeared. That's not good. And then if, oh god, it's really slow. Oh dear. Why did I do this? So the number of tasks there is the number of processes running on your machine. It should be around like 150. It's now at 2,000, something like that. And all they're doing is creating more and more processes. And I guess it's because I'm on battery. So when I tried this earlier, I made it to 32,000. So that's some fun thing you can do to your friends. That's called a fork bomb. You can actually write a little shell script that looks like nothing that does that and bricks someone's computer. But if you're the kernel, you have to be smart and prevent that. So that's a good place to leave on because my computer's now bricked.