 All right, so we left last lecture talking about processes and creating them, and we got into a little bit of a mess. So just to make things crystal clear, yep. In the future, maybe for this lab, no. If you get, if the tester script gets you 40 or 40, the TAs are just going to run that. So you'll, you can fix it. But yeah, so I mean it won't be checked for memory leaks, but if you have some, you probably want to fix them. Yeah. Sorry. Ashfin releases lab two, I have no idea. He should. I'm going to talk to him after this, so I'll double check. All right. So to be crystal clear, and we can talk a bit about one of the issues I see like everyone doing in the lab since everyone seems to be just posting on Piazza about Seigfalt, which is like over a hundred posts, I think like a hundred of them are about Seigfalt. So we'll talk about that since a lot of people don't know why they're getting Seigfalt. But just to be crystal clear from last lecture, right? So processes are assigned a process ID or PID whenever they're creation, and then it does not change throughout their entire execution as long as they are alive. And a process ID is just a number and it's unique for every active process and active is a very important word there because on Linux systems, right, it has some maximum any number on your system has some type of maximum on Linux for some for whatever reason limit is 32,000. And then the number zero is reserved as a placeholder value. And then since there's only that many processes, you might actually run through all of those numbers so the kernel would have to recycle a process ID. So if you have a process ID that's like number 1000 that it goes, it executes, it exits, and then it dies. And we're going to have lots of fun terminology today. So as soon as it dies, that process ID is gone, but later if you create another process ID, it might also get that same process ID 1000. Yeah. No, so active is as long as it is alive as long as it hasn't exited yet. It's active. Doesn't matter if it's running. It doesn't matter what state it's in. Yeah, active just means it's alive. The kernel has to do something with it. And a valid something with it is just to put to sleep forever if it really wanted to. And then remember what we saw when we looked at processes just to give a name to it. So every process operates in its own address space. And that's its own independent view of memory. Because again, rare processes have this isolation. Everyone is independent. Each process thinks it has all of the memory available. So yeah, so all the memory available to a specific process is its address space, and it is unique. So we saw when we cloned, right, when we forked that one process, we got two clones that were exactly the same down to the memory. And then afterwards, any change we made to one didn't affect the other. They were two completely different address spaces, which means they are two completely independent views of memory. Under the hood, the kernel could redirect them to physical memory, which is what it would do, which we'll see later. So here's just to illustrate what happens when you are running processes on your machine, just to go back to the state diagram we saw originally. So if you're running Firefox or Chrome or any other web browser, something is going to create it, probably your shell or your desktop or whatever. It will get created, get assigned a process ID. In this case, it's 1337. And then the kernel is going to have to do some things. It's going to have to load it into memory. It's going to have to initialize the registers, give the program counter, whatever the entry address is, and kind of do any initialization stuff it needs to do in this created state. Once it's been created and it's able to execute, it will transition to this waiting state, so it still hasn't executed yet. It's just set up that if the kernel decides in its glory that it should run, it is allowed to move it from waiting to running where it can actually execute on the CPU. And throughout this example, this is what would happen on a single CPU. So only one thing can run at a time. And once in this waiting state, the kernel is doing something. So the kernel would decide what to run. And then if it decides on this process to run, it would transition to the running state where it would execute code as normal. And then say one of the things it would do is create a tab. Now, all your web browsers, when they create a tab, they're going to create a new process so that if that tab crashes, it doesn't crash the entire browser. So if this creates a new tab and therefore creates a new process, what system call does it need to make here? Anyone? Fork, right? It's the only way to create a new process on Unix. There's another one called clone, but it does the same thing as fork. So because it makes a system call, this process would go back to the waiting state because the kernel is now executing and would handle that fork system call. So within handling that fork system call, it would create another process for the, another process ID for the new process. In this case, it would also be in the created state, but we are instead cloning the original process. So most of the things are all in memory. We might have to copy all the memory that, yeah, we may have to copy the entire contents of memory into its own private memory under the hood, but that would all be done in the created step. And then after that, it initializes it. They're both running the exact same code. They're two different processes though. And the only difference is the return value from fork, which I updated the examples. It says return PID instead of just PID because that didn't work last time. Yeah. So what did we learn before? How to replace a process. Exec VE. So if you want to create a new process from scratch on Unix, it's two steps. If I want, like what your shell does, it will fork itself and then there's another copy and then it could immediately exec the other one and then it would start running LS or whatever. Yep. So threads we'll see next week. Basic, the long and short of it is threads operate within a process. The only thing they have private about them is a stack. Yeah. Threads are just kind of like virtual CPU, everything. Processes are virtual everything. Yep. But that's next week. Yeah. No, that's good. Yep. So it needs to create a new process for it, right? Unless you are transforming yourself and unless you just don't want to use a shell anymore, the shell could transform itself into Firefox in which case it gets replaced, right? Yeah. Yeah. So like if I'm in a terminal here, right, we can kind of explain what happens in our terminal now. If I just type LS, right? What's currently executing what I'm typing into is bash. I think it's bat or ZSH in this case. Some type of shell. In order to execute this command and we'll see more today. As soon as I press enter, right? At a system call level, bash is going to fork itself. Sounds terrible. Going to fork itself and then the new child process, right? We're going to keep the original process running. That's our shell because it didn't disappear. The new child process, which would get assigned process ID one after the fork. It could, we could just immediately exec it and then exec VE LS like we saw in lecture four. And then it would just execute and then execute LS and we can still continue. Yeah. So like, so what you're saying is like, hey, if my shell, you know, takes a whole gigabyte or something and I copy it, isn't that like slow as hell? Yeah. There's a trick for that. So it turns out that the way why UNIX splits this whole thing up is that as soon as we get into the mechanics, so the things it copies, it can be really smart about. Copying is super cheap and we'll see why, but we haven't discussed memory management yet, but you're right. If you have to copy a gigabyte, wouldn't that be really slow? We'll see when we actually do some memory management, what tricks we can use for that? Cause it is insanely fast. Yeah. Their own address space. So they have their own address space or their own, you know, virtual memory. So they think they have all the memory, they can access any address they want. As most of us have done in lab one, if you access memory you're not supposed to, you get a segfault, right? So yeah, in this case, the Firefox tab would come up, be created, then for now we can think maybe it has to copy a whole gigabyte, which would be slow, but it'll take some time, it'll finish. Yeah. Yeah, same address space. Yeah. Yeah, so if you try and access, if after the fork happens, if you try and access the same memory location, you don't know what physical memory it actually maps to. So the kernel's job is to make it so that even if you access the same memory location in both processes, it needs to make them completely independent so they have to affect different pieces of memory. So that's why even when we forked, right, we saw the address was the same and then we changed the variable in one and it didn't change the other one. Right. Okay, so at this point, we have two processes we actually care about. The new Firefox tab, you know, the kernel would create it, do the cheap copy and we'll see why that's cheap. But then it would be in the waiting state, so both processes would be waiting and now the scheduler that we'll get into later will decide what to run. And then it will just pick one of these, you have no idea, and then graduate it from waiting to running and in doing so, what's the name of the mechanism to, you know, to put one process to sleep and then start another one called if we remember from last lecture. Sorry. No. So concurrency is just being able to pause one task and then start another one and make progress. Yeah. Wait. Wait? Wait. No, we haven't seen wait yet. Yeah. Context switch? Yeah, you have to context switch, right? So whatever process would have been running before, I would have saved, at least it's like program counter or any other registers it needs. I would save them so I could restore them later and then as part of this, the kernel would have to restore whatever process it wants to run and then set the registers to those values so it would set, you know, the program counter to wherever that process left off before and then the kernel could just decide to have it execute and give it the CPU. So say arbitrary decision, the kernel just decides the main Firefox process should run again. It'll run, execute some code, then eventually the kernel will decide that's enough of you, put it in this waiting state so we're gonna context switch again. So process 1,337 gets context switched out so it gets put to sleep and then it would context switch to the other process. So 1,338, it would run for a while and then it would go to waiting and this is again on a single CPU. So this is an example of concurrency, right? I'm switching between two tasks, making a bit of progress and switching, going back and forth. Then it could decide main process can run again, put it back to sleep, put it in the waiting queue, run the tab again and then the tab could call exit and be terminated. Now at this point, the kernel can't clean up all the resources associated with that because it sets at least an exit code and you need to read it and how we read it is something that, the generic term for it is called acknowledging that process. So we're going to see that next. So as soon as the tab process dies, right? And this lecture is going to have lots of violent terminology, so get ready. As soon as it dies, then the Firefox browser has to acknowledge it so like at least read its error code just in case there's an error or something with it. And then as soon as it gets acknowledged, then it does it through a system call that we will see. It will go in the waiting state and then the kernel can go ahead and remove that tab process and clean up any other resources associated with it. So then it would be waiting and then it can go just run again, create another tab, do whatever it's got to do. So one question we might have here is we know that there is a strict parent child relationship in terms of processes. So last time when we had our two processes, we made sure that the parent process exited last so we put a sleep at the end of it and that's why we did it so we didn't get any weird scenarios, but today we get to see those weird scenarios. So in which case if you have a parent and child relationship and then the parent exits first so it no longer exists, what anyone want to guess what should happen to the child and like take real life examples like very, very literally. So the child is still, with the analogy, the child is still a child. So it's not allowed to do stuff by itself. Yep, stops working that generally happens with children but not in this case. Yeah, so something needs to adopt the child, right? The child does not have a parent anymore and something needs to adopt it. So it's like this whole thing is gonna get super literal and like super gruesome very shortly. So yeah, the parent process is responsible for the child. So again, the operating system set like when a process needs to exit, it has to go through that exit system call and then the operating system is gonna set, keep track of whatever that exit status is somewhere. So at that point it can't remove its process control block, it's also hold on to that value and then the minimum acknowledgement the parent has to do is read that child's exit status and we're going to see how to do that next. But there's two situations in which the terminology gets really weird. So if the child exits first, so that's what happens normally, well it doesn't just disappear, it keeps that process control block kind of around in memory because it needs to read that exit status but that process is now called exit, right? So it can't execute anymore. So it has both the properties of being kind of alive and dead, hence the term zombie. So it's kind of alive but we can't execute it anymore, it's just waiting to be someone to acknowledge it and then read its exit code so the kernel can actually delete that information. And then super literally if the parent exits first, so it's now gone, then the child doesn't have a parent process so it needs to be reparented and that child process is called a orphan process. So this is going to get fun when we start killing them. So the mechanics of acknowledging your child process is called a wait system call and it has this following API which is why you need to know pointers. So its first argument is a address of an integer that stores where the status is so it will de-reference that address and then write the value to wherever you allocated that. So you have to do this and it's just a way to return, it's like a C way to return multiple return values at once. So that's one of the, you can think of it as a return value if you want where it writes the information and then the actual return value is the process ID of the child process that is now dead. So if the wait call returns negative one, that's a failure, it sets an error number that you can read. We'll ignore zero for now, this is just for reference, we'll see it tomorrow and then if it's greater than zero, that's the process ID of the process that is now dead. Yeah, yep. So one created the other one though, right? So that's the only distinction. They're separate processes but one still has to manage the other one. Yeah, and by manage like at minimum it's a wait but we'll see later that doesn't have to be and we'll see what happens to it. Yeah, so we'll see. So we can go back to the last lecture because we kind of ran out of time. Today I booked in some more time so that we can go back to that and see but we'll show an example where it creates four children. So it just needs to have an address that points to an integer that's valid that can write information to. So just essentially just as part of wait it'll de-reference that address and write, you can think of it as a second return value there. So it's just a cheesy way to get around only having one return value. All right, so it contains a bunch of information including the exit code so it, sorry one second. So it looks like it's just a number, it's an integer but you can't actually read the integer because it's gibberish. The exit code if it's there is like the last eight bytes or sorry last eight bits and it just kind of sets flags throughout it and you need to use macros to figure out if certain flags are set and we'll see examples of that but just awarding do not use the number directly. Yeah, yeah, because otherwise it'll be a zombie and if it's a zombie it's wasting a process ID like it's wasting resources it'll stay around forever. You could but UNIX does not let you do that because it just keeps it around so someone reads it and it will ensure that something reads it. Yeah, yeah. What good does reading the exit code provide us? So it lets you know that you can actually see what happened to the process, right? So do you ever need to maybe not but like your shell, right? The general convention, everything's a convention is zero means everything went well and then if it's returned something that's not a zero you should alert the user to that somehow. Like even your shell when you execute something that returns an error, you'll see it on your shell, right? So your shell would have read the exit code and then displayed it to you. Yeah, so you can only read your direct processes exit code and that's just the rule. So we'll see kind of what happens with orphaned. There's like a centralized orphanage. But if you're a parent, right? Then someone created you. So you are a child of something so that has to wait on you. Yeah, so it will wait until a process that process is gone until a process is exited. Wait does it not create? Yes, wait's a system call. Yeah. Yeah, so it will block and then sit there and won't execute until a child dies and then it will go back then it'll return from wait. We'll see the code example. So let's just go ahead and look at the wait example. So here's what it would look like. Everyone's favorite thing we're gonna start off. Right, as soon as we execute this we can assume it's running in a process, a new process and then execution starts at main like we're all used to. But again, first thing it's gonna do is fork. So now there's two copies executing at the same time as soon as it is at this point. And the only difference is the value of PID. So in the child, PID is going to equal zero. And in the parent, the PID is going to equal, the returned PID is going to equal the process ID of the child. Right, yeah. Oh, so sleep is kind of a way for processes to yield CPU. So sleep too, just means put myself to sleep for two seconds like I don't wanna execute for at least two seconds. And then after that, the kernel will wake you up again and let you execute. So after the fork, first we check if there is an error, if it returns negative one, there's an error, our computer is probably crashing so you can probably safely ignore it. Then we have to keep track of what would happen in both of our processes now. So let's start with the child. So in the child, it would go into this if branch, sleep for two seconds, which just puts it, doesn't let it run for at least two seconds. And then eventually the kernel will execute it again after that two-second window and it would return from sleep. And then we would fall outside of this if statement, hit return, which would be a return from main, which would call exit with the value zero and then this process would be done. Then in the parent, on the other hand, it's going to immediately print F calling weight and then it's going to allocate some memory here on the stack for that exit status. So again, hopefully we all know this. So that just allocates some memory on the stack and then we give the address of W weight status to weight. So that's an address of some allocated memory. If you really want to and you were like, if you wanted to be, for some reason, use the heap, you could have malloc four bytes if you really wanted to, but you don't need to. So you call weight, then at this point, the kernel should just wait until that process dies. The child process dies. It should take two seconds in this case and then it will return a weight. The return value from weight would be weight PID that should say what the process ID is that died. Yeah. Yeah, so it's blocked. Yep. Yeah, so as soon as I call weight, the calling process is now blocked, at least with this version of it. We'll see another version of it later, but for now it is blocked until the child dies. Yeah. Yes. So the weight call acknowledges it, puts it in that status thing and then you can choose not to read it if you want, but it's given it to you at that point. Yeah. Yeah. Weight PID? Oh, this? It's just a stupid type depth. You can just replace it on your head by an ant. Okay. Replace it by an unsigned ant. So if it has multiple children, weight will return after the first one dies. Okay. We can go ahead. If we have time at the end, we'll create more children and see what happens. Yeah. So in this case, it would be interruptible sleep as just like super technical detail because you could still kill it if you really wanted to, but that will not be on any exam question or anything like that. Yeah. First child that exits, which it can die in many gruesome ways. Yeah. Yeah. Yes. Yeah. Yeah. So in this case, we're just assuming for now all processes exit instead of other ways they can die. So as part of this weight call, when it returns, it's going to write some value to W status, which we don't know the format of it. And it's going to return this weight PID since we don't know the format of it. We have to pass it to these functions. So this macro will just check if there's like a bit somewhere that says if that process is exited. So this just checks that bit and we'll return true if the bit set, which means it's exited or otherwise false. For the purposes of this course for now, we'll just assume everything exits. So if it exits, which I said for the purposes of this course, it should always exit. We should print this out, right? Weight return for an exited process gives the PID that weight return. So it should be the process ID of the child and then whatever status it read, which should be whatever it returned from main, right? So let's go ahead and exit. So if we execute that again, what we should expect is immediately the parent says calling weight and then it should then it calls weight and it should wait around for about two seconds and then show that it actually weight return and what process died. So if we run that call weight two seconds later, we get that print F that says weight return for an exited status. Holy crap. The process ID of the child that died is five, four, four, one and its status was zero. Yep. Waits two seconds. So it waits for at least two seconds. So because of the sleep call because it's waiting for the child to die. God, that sounds awful. So it will wake at least two seconds and then that child calls exit and then it would go ahead and wake itself up because that child's now resting. Yeah. So we'll see that next lecture. Like in the case where it returned zero. Yeah, we'll see that next lecture. It returned 5,441. So a child with a change means a child has died. Okay. Yeah. Okay, so just to see that we're actually, reading the right return value, we could go ahead and in the child here just to see that we're actually reading the code. I could return three, which means something like catastrophic happened in the child. So if I do this weight example now, it should update our status, which it does. So we read the value three. We knew in this case, if we were more sophisticated, we know our child had some problems. Maybe this was like a copy command or something you tried to do, I don't know why you'd do that, but you'd be like, oh, well, it didn't work. So yeah. Yep. Yeah, whatever process is waiting just is blocked, but it's otherwise just a normal process. It's just kind of waiting for a child to die. A parent needs to wait it. Yeah, so in this case, the child would have called return three, which would have called exit three. And then that entry is still around. I could have read it. We'll see in the next one. So it still is occupying all the resources associated with that process. And then as soon as the weight completes in the parent and it's read that exit status, the curl can go ahead and delete that process now. Yes. And then the child process can disappear. Yeah. Any other questions or things you want me to do with this weight example? Okay. So in that case, let's see a zombie example. So this will hopefully clear that up in terms of cleaning up resources. So a zombie process is a process that has called exit. So it is functionally dead, but it's still wasting resources. Again, hence the term zombie. So it's terminated, but it hasn't been acknowledged yet. No one has called weight on it. It's still waiting around for that. And while you do that again, process might have an error in it. You'd at least need to acknowledge it, to at least have a chance of handling it as opposed to just ignoring it. Otherwise, if it didn't have this design, everything would just kind of silently fail and you'd have no idea what is going on. But another mechanism that we'll see next time kind of goes with the whole interrupt thing. So instead of calling weight, you could let the kernel interrupt you and kind of prod you to, hey, a child has died, please acknowledge them. But it's also just a suggestion, you're free to ignore it. And this is like the most basic form of IPC that we'll see next lecture and it's called a signal. But we'll see that in a later lecture. Again, why you would need to clean up zombie processes is because it's at least wasting a process ID, right? If you only have 32,000 of them and then you start stacking up zombies and you have 32,000 zombies, suddenly you run out of process IDs because they're all deemed active. And then if you try to, I don't know, open a new tab in your web browser, your kernel will be like, yeah, you're out of process IDs and then fork would fail, which would not be great. So let's see, yeah, let's see a example of a zombie real quick. So the zombie example set up kind of the same. So again, whenever I execute this, it'll be executing a new process. It will be starting execution from the beginning of main for all intents and purposes. And this is the world we liked before. Again, let's just ignore the exit code. And then we're gonna do the same thing. So in the child, we just sleep for at least two seconds. And then after we sleep for two seconds, it would return, set the exit status to zero. And then the child is now a zombie and the parent needs to acknowledge it. So just to see what actually happens. Oh, yep, the child exited first. So it was a zombie until we acknowledged it. It was a zombie for a very brief period of time. So this time we're going to, so this is the same situation. Our parents are going to last for at least three seconds in this case, because I have a sleep one followed by sleep two. So we're also going to outlive our child. But in this case, after one second, while it's still running, we're going to query its status. And then after two seconds after that, so three seconds total, we'll query its status again to see because we haven't acknowledged it yet, right? It still should be, it should actually print out like a zombie or whatever it is. Yeah. Yeah, so like if you do an exec here. Yeah, so if you're doing an exec here, remember, exec just transforms the current process. It maintains all of its. Yeah, it transforms it. Yeah, so it's still a child of the original process. You're just transforming that current process, but it maintains the same relationship. Process IDs, once they're set and they're active, they don't change. Yep. So the parent would know what children it has because it can only create children. So you have to keep track of it. So it's every time you return from fork is how many children you created. So the parent process should keep track of its children because if it's being a good parent, it should acknowledge them when they die. But you can of course just, you can write bad programs, right? You can just ignore it if you want. And then if you die and your children are still around, they're orphans and then it'll give it to someone hopefully more responsible than you. And then otherwise, if the children die first and you don't acknowledge them, you just have a bunch of zombies around, right? Yeah. Well, your process has to deal with zombies and then because the only way you get rid of zombies is calling weight on them, right? But the kernel, when you have a zombie, it could be an orphan zombie, which is great. And then that would get reparented to, again, someone hopefully more responsible than you and then you would call weight on it. So let's go ahead and look at this. So if we execute this, again, only the only messages printed here are from the parent. So after a second, I read the state of the child and it says it's in a sleeping state because it put itself to sleep. That's one of the blocked states, right? And then after we read it two seconds later, which is three seconds total, it's now dead. We know it is called exit. And then if we look at its status, it is a zombie, right? So that shows it's a zombie, right? It can't execute anymore, it called exit, but it's wasting some resources. Its process ID is still valid, still has entries. It won't go away and be cleaned up until we call weight on it. And again, since this is a user program, right? If I just was wholly irresponsible and like never executed and just slept for like 10 seconds after that, right? This process would be a zombie for an additional 10 seconds and just waste resources during that time. All right, yep. So the process is in charge of its children. If a child exits, it becomes a zombie. And then if you want to delete it, you have to call weight on it. Yeah, so only the parent can call weight on its children. It's only direct. All right, any other questions? Yep, so you should call weight because it'll just be a zombie and waste resources. And otherwise it's not doing anything. If you called, if you created it and it exited, you probably created it for some reason. You probably want it to be notified that it's done. Like maybe it did some useful work for you. And you probably, yeah, actually want to wait for it. Yep, so the kernel will reparent you. It won't do anything about the zombie. Yes, who's hopefully more responsible than you. Yeah. Then the child will get reparented. The child will get reparented, yeah. But at that point, if it exited before, it would be a zombie still. So it'd be an orphan zombie. And then once it gets reparented to something, it could clean up, the kernel could clean up all those resources if the new parent calls weight on it. Yeah, but the centralized orphanage is also just a program. So you could do whatever you want. You could not do it if you really wanted. Yeah, so just in Firefox, just the main process each new tab is a new process and only the original process creates them. Yeah, it's just like Firefox and it manages all the tabs. So it would know a tab crash, right? If it calls weight and then one of the processes just dies unexpectedly and returns. They would prompt in Firefox, it would probably return, you know, not zero. In which case it would be like, God, something happened with your tab. Yeah, so if you call weight with no children, that's an error condition and it'll return negative one and set an error code that says you don't have any children. Yeah, so the default weight call, right? Like this call, wait, example. So this call doesn't take a process ID, it'll wait till the first child dies if it has multiple processes from the kernel. From the kernel, it's the return value. Okay. Because the kernel manages it at the end of the day, right? Yeah, so as part of the weight, right? When you call weight, it knows all of your children and then if any of them dies, right? In this case, this is, if any of them dies, it will return from weight and then the return value will be the process ID that died. Yeah. You can only have one parent, like not in like a strict, like one child, yeah, you can have that, but they're unrelated. So you only deal with your own direct children. So you're completely different. It'll just wait on whatever that parent is on its direct children. Yeah. So. Yeah. I can't, wrong class. Dies. Uh-huh. So we'll just run that experiment. We'll see what happens to an orphan because right now it's just, you wait on your direct children. All right, yeah, let's just get into orphans, sweet. All right, so an orphan process needs a new parent, again, super literal. So the child process has lost its parent, needs something to acknowledge it. The operating system will re-parent the child. For the purposes of this course, it will always reach parent a orphan to init, which is that special process ID one. So now we, so init on your system, now you know how to write init. It only has two jobs. So remember when you boot up your kernel, the kernel just creates one special process called process ID one and execs whatever executable init is. And now we know init has two jobs. Its first job is to create all the processes on the machine or at least be, you know, the great grandparent of all the processes of the machine. And then the second thing it has to do is be that orphanage. So if you look at the simplest example of init, it will go ahead, create some processes and then at the end of init, it will just put itself in an infant loop that just calls wait over and over and over again. So that is just init. And then to your point, if anything can be init, it's just a program. I could write an init that just never called wait. Right, and then I'll just stack up zombies until I run out of process IDs and then my computer will crash. But it might take a while. All right, so let's look at what happens with our orphans. So this one is smaller. So again, orphan is the parent dies first. So let's make sure the parent dies first. So again, main starts, it will fork and then there'll be two processes, right? The child will be process ID zero. The parent will be process ID greater than zero. So again, let's assume that there's no error. We'll skip that. Then in the child, it's going to use this system call to print off its parents process ID, which hopefully is the original running process at this point. Then it's gonna sleep for two seconds and then print out what its parents process ID is now. And then it's going to return zero and exit. And then in the parent, all it does is sleep for one second and then it returns from main, which would call exit. So in this case, if we run this, we get our original process ID, just trust me that that's the original process ID. And then after it wakes up from sleep, we can see it changes to a weird process ID, which is not one. And this is like a weird Linux specific thing. If we, we can go ahead and check what that process actually is by looking at that proc file system. So in here, I could look at proc 3431, state, and then just see what the name is. So of course, what 3431? Sorry, it is called status, not state. So it is something called system D, which is like the normal init process. This is like a weird Linux specific thing. System D is, in general, it's the init system, but there's a special kind of pseudo init for individual users. So this is my users init. So this is, you know, this is my own init things that's responsible for starting all the processes associated with my user, instead of just everything on the machine. So eventually init would have to call this, create it, so on and so forth. Holy crap, that took a long time. We did not, we ran out of time. Crap. Yeah, you should know what zombies and orphans are. I'll try and carve out more time later.