 All right, good afternoon. So we will be continuing our journey talking about processes today and especially how you manage processes. So now we have multiple processes potentially. And right now, we just kind of run them. They do whatever they want. They have that parent-child relationship. But we don't actually know how to use it or how to do anything useful with it. So just some more notes just to make it clear. So processes are assigned a process ID. Oh, yeah. Oh, screen again. OK, sorry. So processes, when they're created, are assigned a process ID, otherwise called a PID. Or it's just an integer if you want to think of it that way. Whenever they're created, and then it does not change from that point forward. So as soon as you create a process through something like fork, well, fork is the only thing you can do, it gets assigned a process ID when it's created. That is a copy of whoever created it. And that's the only time it gets assigned a new process ID. And it doesn't change throughout its whole execution. And it's unique for every single active process. So on most Linux systems, like older systems, the limit is 32,000. And 0 is reserved as being invalid. And 1 is kind of that special process that init process that gets created. So eventually, the kernel will recycle a PID after a process dies for a new process. So say if you have process ID 2 that exited, and that no longer represents an active process. Well, if you run out of process IDs, then the kernel would use 2 for a new process, assuming that 2 is already dead. So the limit for this on 64-bit machines is something like 4 million or something like that. But so the limits might vary, and you can change them if you want. But just remember that each process has its own address space or independent view of memory. And we can accomplish this through virtual memory, because each process thinks it has the entire memory space. And that's how the OS can actually make that illusion we saw before, where the same variable in two different processes had the same address and one changing it, and one didn't affect the other. It's because each process has its own address space, so its own view of memory. So if you've changed one, something on one, you don't change it on the other. So this is kind of bringing that state diagram back into it. This is how something like Firefox would create a process, or how we create a process with a fork. Eventually, you're going to launch your browser through some means, and it's going to be created, get a process ID like 1337, and then it would be in this created state where the kernel would have to load it into memory, load all the instructions, all that static information into memory, maybe set aside some virtual memory for things that request, like other variables and so on. And then it would go ahead, as soon as it's created, it would go into this waiting state where it hasn't executed a single instruction now. It's just able to execute instructions. So it's loaded, its process ID, or its virtual CPU is pointed at its entry, its one start instruction, and it's just waiting for the kernel to actually schedule it and actually run the instructions that are part of that program. So eventually, the OS is going to say, hey, Firefox, you can run. It would do that context switch and change it to running, and it would go get the CPU, run some code, and one of the things, if you're running it, you might click on, you know, you might open a new tab, which would cause Firefox to fork, because part of the architecture of Firefox is every tab is its own process, and that way, now we now know each process is independent. So that means each Firefox tab, just building off processes, are also independent. So that's a nice thing that if you crash a tab on Firefox or Chrome has the same architecture, it won't crash the rest of your browser, because the old architecture was, OK, you open a tab, it's in the same process. So if you have, you know, everyone has hundreds of tabs open at a time, if one of those crashes and your whole browser crashes, that'd be pretty bad. But if every tab of its own process and one tab crashes, well, you're all good, because your main one can just say, hey, that tab crashed, just reload it, do whatever you need to do. So as part of running, Firefox would call fork, and then fork would go ahead. And then as part of that, you'd get a system call, so the kernel has to actually do all the tasks associated with that, so it would throw that Firefox process into the waiting state where it's ready to run, but we're doing a system call, so we're going to just set it there because we're going to create a new process. And then a new process would pop up. Boom, our Firefox tab. It would get something like process ID 1338 or whatever the next available one is. And its parent would be whoever called fork on it. So in this case, its parent or, yeah, ppid or parent process ID is our original 1337. And then it would go through the same process, so it's in this created state. But most of the created state, instead of loading some stuff and initializing it, it will copy whatever forked it. So copy exactly where that is, exactly what it was doing, and which is this fork call and copy all of its contents of memory, all of its open files, everything like that. And then it would be set in the waiting queue again, and this would be as part of the return for fork. So after this, they're both waiting, and the only difference between them at this point, they're exact clones of each other except for this relationship. And now, as part of the system called the return value of that fork. So in the browser, the return value of fork would be like 1338. And in this newly created tab here, the return value of fork would just be zero because it's the child. So then at some point, the CPU is gonna choose maybe the main browser to run, maybe the tab. At this point, you don't know, it's up to the kernel to decide what to run, and we'll get into that later how you make that decision. But for argument's sake, for you programming and argue about your correctness, you have to assume either of them could run at this point. So Firefox goes ahead, runs for a while. The CPU would want a context which runs something else. So it gets stuck in that waiting queue again. Then maybe the tab runs for a little bit. It runs, executes some instructions. Then it gets, then the OS takes back control and says, okay, I need to run something else now. Maybe the browser runs a bit more. Then it gets thrown back. Then maybe the tab runs a bit more. And then maybe it calls something like exit. And then if it calls exit, it goes into this terminated state. And it's actually still around. And we'll see how to actually delete a process today. So it just goes into this terminated state where it can't run anymore. It's done executing. It can't execute one more instruction. But all the memory associated with this process is still sticking around and you can't actually delete that whole process from existence yet. What you have to do in order to delete them is do some type of process communication. So the rule in Unix and all kind of Linux-y, Unix-y operating systems is in order to clean up a process and all its resources, the parent has to acknowledge that it has terminated. So only when you acknowledge that process has terminated can you actually delete everything associated with it, including its process ID, and then go ahead and clean up. So it would have to do something called acknowledge that process ID 1338 has actually finished. And then as part of that, it can go back and waiting. And then the curl can go ahead and delete that tab so that process ID is now no longer valid. It wouldn't have an entry in that proc file system because now it can completely be deleted. And then Firefox would go on, run at some point and that's it. So we have to maintain this parent-child relationship. So previously we kind of made sure that our parent exited last using sleep. So that's what that sleep was before. So I didn't get any weird ordering but we don't need to do that. Yeah, so I can't see images on that thing. So that's a rip. So previously we saw that our parent exited last using sleep. So let's see what happens when our parent exits first and no longer exists. So let's see VM. So in our wait example here, let's make sure we can see it. So we have a fork and then after the fork we check the value returned. If it's negative one, it means an error happened and our computer's probably dying. And then if it's zero, it's the child. So it can just sleep for two seconds. Let's go ahead and just say printf child done sleeping. And now before we get into wait, let's just go ahead and in the parent, we'll just call this calling wait and we'll just exit the parent. So now we would likely expect to see whenever we run something we would expect to see, it would start at main fork and then there'd be two processes. One going into line 13 here and one that wants to go to line 17 here. So line 13 is asleep for two seconds which just puts that process to sleep. So likely my CPU is fast enough where this will happen within two seconds and then exit. So returning from main is the same as exiting. So now my parent process is dead and then after two seconds my child goes ahead and prints a message. So let's go ahead and do that. So if I do that, what was it called? It was called a wait example. So if I do that, I see calling wait and then I can actually type on my terminal again and then it kind of just interrupts me and throws the child done sleeping at me even though I can start typing again and that's because my shell here, it waits until the process it creates is done and then as soon as it's done, it lets you type again. So it doesn't know about my child. So my child's still running whenever my process dies. So as far as the terminal knows you're done running whatever you want to run but then later on after two seconds it goes ahead and prints something. So any questions about that? Okay, so we are going to get into a way to prevent this from happening and make sure that all our processes finish before the main parent process dies. So let's say a little bit more about so the parent process is responsible for the child only the direct parent process. So as part of the things you do when you terminate is you return, you know, you call exit with that number and that's called an exit status. So for example, whatever you call if you return zero from main that's the same as exit zero. So that means the exit status of that process when it terminates is zero. If there's an error typically it's just anything other than zero. So it'd be like one or something like that. So whatever you call exit in that child process or the parent process it so far has no one's read that exit status at all. So the minimum acknowledgement you can think of is just reading the child's exit status. So if you create a process and it finishes at minimum you should know whether what that returned if it exits successfully or if it had an error of some sort. But there's two situations because that assumes that, you know, the parent always, the child process always dies first but that doesn't have to be the case. So normally the child exits first and if the child exits first, well, like I said, you can't delete all of that information until you acknowledge it. So the state that that child is in from the time it exits before it gets acknowledged it's we'll have weird terminology here. It'll only get worse as this course goes on. So it's called a zombie process. So it's terminated, it's dead but it's still consuming resources. So that's where the name zombie comes from. So it's still consuming some resources on our system and we can't get rid of it yet. So that's why it's called zombie. And then the other name is if the parent exits first. So you create a child and then you perish or exit or die. They like using the term die, a little gruesome but if the parent dies and the child is now parentless. So now it is called an orphan, very, very literal. And that is also something you need to handle as part of the operating system because only the direct parent can acknowledge the child and now if the parent's dead, well, who's there to acknowledge the child? So we will discuss this through some more examples. So luckily this name is about the only non gruesome name we'll see. So that acknowledgement process I was talking about is called wait and you need to call wait on a child process. So it has the following API. It takes an address to store the status of the process and that is not just a number, it's coded and you actually have to use macros to get some meaning out of it. And then other things it does is it returns the process ID of whatever the child process is that is now exited that you've waited on and it can now be cleaned up. So like all of these C wrapper functions, it returns negative one if there's a failure. It returns zero for a non blocking call with no child changes and that might seem kind of like gibberish. All that means is by default, by default what wait does is it waits for a child to finish running. So if you call wait and nothing has finished running yet, it will sit there and you'll be in that block state where the kernel will not let your process run anymore until one of your child children have actually exited. So that's the blocking version. There's a non blocking version you might want to use in this course maybe not that you can just ask straight away, hey, do I have any children that are dead? And then it can respond instantly yes or no. So zero is the no. If you don't specify that option, it just means that it will block and wait for a child to finish. And then if it's greater than zero, you get the process ID of the child that had the change or the child that actually exited. So the wait status contains a bunch of information and one of the things is the exit code because calling exit is not the only way to end the process but for the purposes of this course, it will be exiting and maybe one other way we'll talk about but for now you can just assume that processes exit by calling exit or they get terminated by calling exit. So you need to use our man pages if you want to use any of the other macros and then wait will like I said wait for any child and if you want to wait for a specific process to die that is your direct children. So if you call this on something that's not your child, it will give you an error but if you want to wait on a specific process, so say you created eight children, if you wanted to wait on a specific one of them you could use wait PID which just takes another argument as with the process ID to wait for. So here's our simple wait example. It just starts with main and like we've been doing so far, it calls fork. So now after the fork, we have two processes. The only difference between them is the return value of fork. One will be zero, that's the child that's newly created and the parent will have the process ID of the child. So like I just showed in the child, instead of having that print statement, it just goes to sleep for two seconds and I showed if there's a print statement after that, it kind of messes up our terminal and makes it look ugly. And then here is actually how you use wait. So it will say calling wait, it will create a variable on the stack, just an integer for something called W status and that's the address that wait wants. So recall wait with the address of W status and it will write some value to there that we can actually read and it returns a process ID that we waited on. So hopefully it's the process we created so it should match that process ID from fork in the parent because we only created a single process here. So then we can use this macro here, this W if exited on the wait status. So that will just tell you zero or one whether or not that process has exited. And for now we can assume this will always be true for our purposes for now. And then if it's exited, you are allowed to use this W exit status macro that will give you the return value of that process, whatever it returned as part of exit. So in the parent, it would say wait, return for an exit process, whatever that process ID was and then whatever status it read. So let's go ahead and see that. So I'll just get rid of my print statements in this. So any questions before I run this thing? So if I run it, what should happen is nothing happens for like two seconds. I don't get control immediately from my terminal because my parent process, the one that the shell actually executed will go ahead and be calling wait and it will wait for my one child to die which will take two seconds, at least two seconds. And then after it returns from wait, it fills in that W status so I can actually start using it and get some information out of it. So if I do that and I run my wait example, now I'm actually waiting for two seconds and then it will finally return whenever my child is dead and then say wait return for an exit process gives me the process ID of the child and then that status, whatever it exited with. So any questions about that? Yep, yep. So if I have multiple children then I need to call wait for as many children as I have. Yep, so you can select a child with that wait PID one that I showed. Yeah, so there's this wait PID at the bottom. Sorry, there's another question, yeah. So the value of W status is something you're not supposed to read directly. You're supposed to access it through those macros. So it's supposed to be transparent so instead of using W status directly, you should be using these like this W if exited thing. And if you look at like man wait, it tells you what all these are. So you're not supposed to actually know what the values mean in there. So instead of using W status directly, I use W if exited W status and then if you read that it says, it'll return zero if it exited normally one, sorry, zero if it didn't exit and one if it exited. And then I use W exit status here and W exit status is whatever it returns. So if I do something like, so the usual convention is if it doesn't return zero, it exits. So in this case, I'll change it so that the child returns two and that would be thrown to exit. So my parent should be able to read that. So if I do that and I wait for it this time, then I see, hey, the status is different now. It's a two because my child returned a two. And usually this would maybe change the behavior program if I know whatever I launched failed. Maybe I display an error to the user like A, your process failed or something like that. Yep. So yeah, the question is, where in this code did the child exit? And the child exit, so the child goes into here because it's the child, it sleeps for two seconds and then it returns two. So a return and it's in main. So return from main is the same as calling exit. And that's just the way C does things for you. Yeah. Yep. Yeah, it just went all the way to zero. If I want, if I didn't want to return from there, I could exit three or something like that, which should do the exact same thing. So if I exit three, I didn't declare it, but whatever. Oh, it really, oh no, it's good. So I didn't declare it, but whatever, it still works. But I can also just call exit. Yep. In which one, like if the child just never exited? Yeah, if the child never exited, by default that weight doesn't have a timeout, it's just gonna wait as long as it takes. So if you want a timeout, you can do that yourself and that would be through that like non-blocking call. Like you could just, hey, are you done yet? Hey, are you done yet? And you can not like maintain control, but by default it's gonna wait there forever. Yeah, so that's a good question. So the question is what happens if I call weight without a child process? So let's just say instead of fork, I just go pit equals down to three. I just make up something. Whoops, I need to define it still. So if you call weight without a process, it gives you an error. So here, whenever I ran it, this by default, your shell will actually tell you the exit status of whatever you just ran if it exits abnormally for some reason. So when I ran my process, it exited with this code 10. And if I go look at my code, please say I wrapped it properly. That here, I check weight, I get weight PID. And in this case, this is probably garbage and it didn't exit properly. So it went into this else statement and just returned and just it got an error from weight. So it just hit that exit with that each child is just 10. So it died, yeah. Yeah, so the question is what's preventing the parent from exiting earlier than child and the answer is nothing and we'll see that. So if the parent exit first, that's that orphan case we have. So this is the case where everything is nice and proper. So let's go back. Okay, so this is our normal case where we actually properly wait for our children. We don't die first, everything is well and good. So let's go into what happens when that is not the case. So the first one is that zombie case where maybe that child process is done but the parent has not called weight on it yet. So it's just in that zombie state where it's still consuming some resources. So that means that child process is done, it's terminated but it hasn't been acknowledged yet. No one called weight on it and the process might have an error. That child might indicate an error but nothing ever reads through it. So it's like that tree falling in the forest kind of thing. They kind of plug your ears. So there's some things the operating system can do. So one of the things we'll see later in the course is the operating system kind of generates, interrupts two processes. So one thing it can do is poke the process and say, hey, your child died. You should probably do something about this. But of course, because it's your program and you can do whatever you want, you're free to be like, yeah, sure, whatever and carry on. So that's just a slight nudge where you don't have to handle it at all. So that's like the most basic thing. So that form of interrupts for processes are called signals. And by default, there's like some default handlers. The default handler for your child dying is to ignore it. Yeah, just the weight call. Yeah, so weight is the acknowledgement. So it and weight will wait for your child to actually exit. Yep, yep. Yeah, so the acknowledgement is returning from that weight and that process ID you get, that's what you acknowledged. The question is, can you wait for a process that is not your child? And the answer to that is no. You get an error from the weight call, just like you would get an error if you tried to wait and you didn't have any children. So that's one of the error scenarios. So in this zombie scenario, the operating system has to keep that zombie process until it's acknowledged, like something has to read its exit status at the very minimum. And if the parent ignores it, well, that's the zombie parent, that's the zombie process and it needs to be waited to be reparented and we'll see what that means in a second. But first, let's see that, what a zombie process looks like. Zombie. Okay, so this example starts off kind of the same. Here I'll make it a bit bigger. So it starts off kind of the same, we fork. So now we have two processes and they're exact clones of each other, but so far nothing really interesting has happened. So one will have process ID zero, the other will have the process ID of the child. So in the child we go ahead, all it does is sit here, sit here, wait for two seconds and then eventually it's going to fall through here. So I didn't show this earlier, fall through here and then after two seconds it's going to return zero. So it's going to call exit and it's going to be done. Then in the parent process, what we're going to do, we're going to set up some temporary variable. To use for return value and then all it does is sleep for one second. So likely when it wakes back up, the child should still be alive and running because it just sleeps for two seconds and I wake up after one second. So I wrote a little thing that as part of your doing for lab one, so you're looking at that status file in proc and there's a bunch of information about it. One thing is its name that you're using. Another thing that's in there is its state. So in its state, you can read off what its state is if it's running, if it's blocked, if it's whatever and we can go ahead read its state. So we'll read its state, print it out and if we get some error from that, we'll just return what that error is, hopefully it works and then the parent's going to wait for an additional two seconds. So by this point, whenever it wakes up from this sleep, it would have been running for three seconds and our child's dead after two seconds. So our child should be dead at this point. But so it's dead but it won't clean up any of its resources because we're the parent and we were bad parents. It's been dead for like a whole second, which is an attorney. And instead of acknowledging it, we'll just go ahead and poke its dead body and see what its process state is just to show you that, you know, zombie states are a natural thing and I'm just not making it up and all it does after that is it reads its state and then it just returns. So it never acknowledged the child whatsoever, it's just kind of in limbo. So if I go ahead and do that, so a zombie example. So I go ahead, so I see its state, here it's sleeping because I woke up after a second and then I go to sleep for two more seconds, it's dead and then I can see that, hey, reading from that file, it's in the zombie state and the colonel actually tells you it's in that zombie state. So it means it's terminated but no one's acknowledged it yet and it's just kind of chilling there. Yep, yeah, so that's a good question. So we can see if it, the question is, hey, if that, you know, if the parent exits, does the OS finally clean up the resources associated with that? So let's go ahead and print out, so let's just print out the process ID of the child so we can go ahead and poke around in that proc file system. So if we do that, okay, so my child is 41031, it was sleeping and now it's a zombie. Well, if it's still a zombie, it should have an entry in that proc file system because that just read it. So let's see if it's still a zombie and if it still exists. So it was 41031. So if I do that, it says no such file directory because it's got cleaned up by something, which is a bit of a mystery because I didn't acknowledge it. So if the rule is someone has to acknowledge it to be cleaned up, the natural question is, well, who did that? So that process, so that zombie process, whenever that parent exited, it became an orphan, right? Its parent is now dead and it was also kind of dead, but no one acknowledged it. So it needs a new parent and because it still needs a process to acknowledge it, that hasn't gone away, that's like an ironclad rule, that will always be a thing. So what your kernel does is it does something called re-parenting. So if the parent is dead, it will go through a process of re-parenting. So by default, it will re-parent that process back to init, which is process ID one. So it will just be like, hey, init, you should acknowledge this thing and that's one of the things init does. So now we know the two things init is responsible for. So init is responsible for creating processes and also acknowledging all the dead zombies that come in or everything that gets re-parented to it. So you can kind of think of it as like the orphanage if you want, as like what, if you go through this terminology. So it kind of re-parents it and now init's now responsible for acknowledging the child and at this point, if init doesn't acknowledge the child, then you're just going to have wasted resources on your system and they're never gonna be cleaned up. So if you have a really bad init, then you're gonna run out of memory on your machine faster. So hopefully someone good has written a init for you. So let's see, let's just create an orphan see what happens. So in this example, I call fork again and let's assume that there's no errors. So in the child, I just say, hey, who's my parent, which will be whatever just created it. And then after that, I sleep for two seconds and then I'll say, hey, who's my parent now? And after two seconds, the original parent sleeps for one second so it should be dead. So it should have went through this re-parenting process and that system called just tells you who your parent is. So if I go ahead and do that, so I say it says, oh, child parent process ID is 41124, whatever. And then after two more seconds, I can see my parent's dead because I got control back on my terminal. And now it says the child's parent process ID after sleep is 701, which is not a init and not one, which is kind of weird. But now we know, we can kind of figure out, well, what the hell is 701? So let's just go ahead and see, whoops, 701 status. Oh, that's a great file, sorry. So 701, we'll go through all this crap. And it's something called system D, which I said was a init, usually what a init is. So this is actually system D, but this is like the user version of system D. So it's responsible for cleaning up your processes. So this is what some init systems can do. And by default, things will get reparented to a init, but one thing you can do on Linux and other operating systems is you can be, you can essentially opt in to being the orphanage and saying, hey, if any of my grandchildren has to get reparented, instead of going to a init, go to me instead and don't keep going up. So you can opt into this and it's called being a subreaper, which is a very great name. So init is actually called a reaper as well, if you wanna get into the terminology. So you can opt into being a subreaper, which means that, hey, I get to kill all the children, which is so fun, right? Yeah, terminology real bad. Okay, so, but if you go ahead and run that example, so that will work on macOS or something like that. And on my Mac, like if I just run it directly, it gets reparented to process ID one. So Mac follows the same rules, but uses a different init system. Yeah. So in this case, in this case I kind of had a, so you can reparent a zombie or just a running process. So in this example, well, the child that had to get reparented was still running. So it was just a plain orphan process. And then the other example I had like the first example, where my child died first and I didn't acknowledge it, well, before the parent process died, it was already a zombie. So it was already a zombie and then the parent died. So it became simultaneously an orphan zombie. So it was both at the same time. So it was a zombie whose parent is dead. So that you can have both, they're not mutually exclusive. And then what would happen that zombie, that orphan zombie would get reparented to init likely or something else, and then it would get acknowledged and then cleaned up right away. So in this example, when it got reparented for the orphan example, it was already still running. Let me go back to it. So in this orphan example, when it got reparented, it was still running because it was still doing that sleep thing. So I was actually still running, so I printed out who my parent was, but when you get reparented, you could still be running, you could be a zombie, doesn't matter. All right, any more questions? Okay, if not, we see how well we understand things and we go back to reading something a bit more difficult. Oops, so close that. So let's go ahead and try and read this program. So, and we'll try and argue about what it does. So in this process, or yeah, in this program, what's it do? So in main, it goes in this for loop, which starts at one, goes to equal to four. So through this for loop, I will be equal to one, two, three, and four. Hopefully everyone understands that, right? They'll see everything still makes sense at this point. So it will call new process with I equal one, two, three, and four. So what do we do in new process? So a new process, it will fork, uh-oh. So this is a fork within a for loop. So it will fork and then if PID is greater than zero, it will just return. So thankfully, in the parent, nothing that interesting happens. So in the parent, it will fork and then immediately return. So it just creates a process, returns. That's all it does. So in here, in the parent, it will just go through this for loop and it will make four children. So, and then new process creates a children, child returns, creates a child returns. So hopefully that's okay. And then the more interesting case is what happens in the child. So it also checks if it's negative of one, just the error check, and my computer would probably be dying if that was true. So I go ahead and assume that that's true. And then now I'm in the child process. So each child will go ahead and print what's it ID is, which is whatever, it's just a function parameter. It's I and because there are exact copies at the time of the fork, well, first time through this, ID equals to one, it forks at that time of the fork ID is one and the parent would return and then the child, it would also have ID as one. So it would come out here and for the rest of that child, ID is always going to be one because it's a copy at the time of the fork. So it should print off process one and then this just prints off 10 times. So process one, zero, one, two, all the way to nine and then eventually it exits and it takes a second to print each one. Now when I call it with argument ID two, well, that happens another fork and because fork copies at the time of the fork, now the new process ID is equal to two because that's when it got created. So it would get ID two, do the same thing, come into here, go into that for loop. So that process should print process two and then zero to nine. And then similarly, the same thing would happen with three and four, hopefully. So any questions about that before I run that? So what should I see? So I created four processes. I should see, hopefully, I should see process one, two, three and four and they should all do like print 10 times. So let's go ahead and see just what happens. Whoops. So if I go ahead and do that, I see that, hey, it prints a lot of stuff and it's not necessarily in any order because I created four children and I just kind of let them go at it and see whatever happens happens. So up to the, I don't know what order they print. All I know is within a process, it still makes sense because it's still executing instructions sequentially, more or less. So I'll see process one, I'll see process one print eight and then nine always, but I can't depend on any ordering between two processes. So let's just go ahead and verify that everything prints. So here we can just see what our luck was. So we created process one first, then two, then three, but when we executed it, well, process three printed zero first and then two and then one. And then before process four, it even got to print anything. Process three woke back up and print out one. And then process four finally came in, print out one and then eventually we go through all the numbers. So every process will do the same thing in order between itself but between all the processes, every time we execute it, we're going to get probably different orders. So this time we got three, two, four, one, one, four, you know, execute again, three, four, one, four, three, two. You've no idea what the order is. It's always going to be different. Yeah. Yeah. So the question is, why are they kind of ending it all the same time? And the answer to that is, let me get, is because I have this sleep because I'm essentially making them fall asleep for a second each and then letting them all battle it out. So I can go ahead and just remove sleep. So likely that runs so fast that I might just see one process print everything it has. So if I go ahead and do that, yeah. Yeah. So I see essentially process one here got kind of the short end of the stick. It printed nine things, oddly not zero. And then process two printed, you know, everything all in one big chunk together. So you can actually see why it kind of switches. So it got really unlucky. Process one started printed off zero and then the kernel was like, no, no, no, no. It's process is to turn. And then it got chosen and it went all the way until it finished and then only process one went. And then process three went all the way to completion process four all the way to completion. So likely printing something is really, really fast and you get a fair amount of time whenever it chooses to switch to you that likely you'll just print everything. But if we run it over and over again, we'll see that, you know, not necessarily the case. So here three got interrupted after it printed zero and then one and then got interrupted before it got restarted again. But this is all some order that you can't really control if you just create everything and just say, hey, go for it. All right, any other questions before we wrap up? Nope, good, sweet. All right, just remember, pulling for you, we're all in this together.