 Welcome back to 353. Thank you for joining me on this cold blistery day. So last lecture, we created processes and then we just kind of let them run wild. So today we get to control them a little bit and we get to do some process management and also more directly related to what is going on on Linux and what you will see. So the Linux terminology is a bit different in that state diagram that I showed you. So when we look at the process of state using that PROC file system, so PROC PID which is just that number, status, if you grep for state and you might see a bench of different stuff. So the states you will see are, they might be R which means running and runnable. They're combined into one. So in the previous lecture for us the generic names are running and waiting but Linux just kind of groups them all together in one because, well, it switches between them very, very fast. So it looks like they're all running at the same time so constantly ping-ponging forth between running and waiting might be a bit confusing and it takes that block state and separates it into two. So one is called sleep. That means interruptable sleep so it is blocked. It's being blocked by the kernels and being prevented to run but if you really wanted to, the kernel could actually poke it and then force it to run if it actually needed to. The next one is D, it's called interruptable sleep and that means no matter what that process cannot start executing because it's actually waiting on something like some network connection or something like that and you actually can't get rid of it. There is a state called stop which we'll see in the next lecture how to actually force a process to stop and then you can manually resume them if you want to and then there's this fun state at the end there called a zombie state. I did not make that up but this is where our Google searches start getting weird. So now there's like just random Google searches with children and zombie or can my child be a zombie? What happens if my children turns into a zombie? Weird stuff like that and it only gets worse from here. So one of the questions we got asked yesterday I think is like, well, if fork is the only way to make a progress or process, how does the first one get made? And well, the Linux kernel, its job whenever it's done initializing itself and booting up, it only has one job and it launches one process and then that process is responsible for creating literally every other user process on your system. So the special process that the kernel creates when it's done booting is called init and it will look for specifically a executable in that path. So usually it's espin slash init. It's just an elf program. So it will run that process and then as far as the kernel is concerned for launching processes by itself, it's done. So it launches one process. It gets a special process ID. Its special process ID is one because it is the first one and it runs as long as your computer is actually running and it is responsible for launching everything else or it launches more processes and then that process launches other ones and then so on and so forth. So that process ID one must always be active. If that process is not active, then the kernel thinks you are shutting down and it will reboot your computer. It thinks its job is done and you don't actually need to run anything. So specifically on Linux init, you could actually write your own init if you really wanted to, but the one that's most commonly used on Linux is something called system D. There's a whole bunch of other options. It might be the case of some religious wars of which one is the best or which one you should use, but most operating systems, well, every operating system is gonna have their own version of it. MacOS has their own one, Windows has their own one and as an aside, some operating systems might create an idle process like Windows does that and it just lets the scheduler run something to keep track of how long your CPU is actually idle for. So that's other things that you could do. So on your virtual machine, you can actually see your current process tree and see all of the relationships between them. So what you might see is, well, at the top, init has to be the parent of everything because that is the process that exists. So init might launch some processes and remember, each of these lines is like a direct parent child relationship. So init would be the parent to something called journal D and that's just like a log manager and then maybe it's a parent to another process called Udev and that helps you kind of be able to talk to hardware. Maybe there's like a system D version for your user so that process is responsible for launching processes for your specific user, for like me, it would be John and then maybe you have a GUI installed, well, that there's a process that is actually responsible for drawing the GUI and everything like that. If you did the default options on your virtual machine, it might be something like Nome Shell and then if you go, you click on the Firefox icon, well, the only way to create a new process is by forking. So it would have to create Firefox, it would be a child of that user interface process and then Firefox, the way it's architected is that each tab is a new process. So you would see Firefox in this case, I have three tabs open and that's a choice Firefox made because, well, if one of the tabs crashes, since they're all in different processes and they're independent, if say this process crashes here or it's seg faults or something like that happens, well, guess what, this main process, which is probably drawing the main window and everything will still be active and they'll just draw, oh, your tab has crashed, too bad for you. And other things it could do, if you launched like a terminal, well, maybe it's this known terminal server and then as a direct parent of that would be your shell. So that's what you're actually typing into and then you might be running something called htop and what is htop? Well, that's how you can see your process tree and you're going to write a kind of scuffed version of this for lab one to kind of look at all your processes, but if you want to explore with htop, that is something you can do. So if you run htop, oh, that is not showing for you. Is VS code such a pain? All right, now we can see it. All right, so here it'll actually list off all of the processes each on its own line, tell you it's process ID and I can make that a bit bigger. And then it kind of draws a line from parent to child. So you can see at the top process ID one is S been a knit and then it launched a whole bunch of other processes. So like network manager, something you diss cups, modem manager, just a whole bunch of stuff. And then here we can see, hey, that's the one of the shells I'm using. So, hey, that's where that is. And you can go have fun and explore if there is anything good on here. I don't think there's anything terribly fun. So yeah, there's me logged in over SSH. So you can see it spawns a bunch of processes. So it spawns this one, which eventually spawns ESH, which in this case spawns bash. So that's weird. So that probably has to do visual studio. And then you can see all the stuff, visual studio stuff. So here's the htop I'm currently using. You can see all the processes running on your machine and there's a few others that we'll get into later. But that is just a tool that if you're curious of exploring around on your system, we can actually kind of read it now. By the end of the course, you'll be able to read all of those lines, but for now we can ignore a fair bit of them. Oh, yep. Why does it jump between numbers? So it's going to assign process IDs consecutive, but one process might spawn create a new process and then the kernel switches to run a different process and then that creates a new process and then it would switch back and then that creates a new process. So it would jump by one. So it keeps on switching back and forth. So they're consecutive when they get made. And also it might reuse process IDs as well. So the process ID is unique as long as that process is alive. So as soon as it's dead, it can reuse it if it wants. So some of those might be recycled and some maybe because it switches back and forth a lot. Yep. There's a lot of processes there. Does that mean that most of them are all stopped? Yeah, so there's a lot of processes there and it had the state there. They were all pretty much asleep. So they were all waiting for nothing. If they were all running, then my CPU would be at 100% all the time and my laptop would probably die from battery before I got rid of the lecture or finished the lecture. Yep. So the question is, can I use execve to force a different program than init? So there's really no point of doing that because you can, one of the kernel configuration things you can use, you can tell the kernel what to launch initially. So like one of the debugging things is, well, init's complicated. So if you wanted to, you could just tell your kernel to initially just launch bash like your shell and then it'll just launch your shell. And then as soon as you exit out your shell, your computer's done, it reboots. So you can replace, you can tell the kernel what to use for init. You can write your own if you want. And in fact, you can do that with your virtual machine because it's a virtual machine and who cares if you screw it up. So we won't quite get to that in that course but through this course, we'll actually understand how to write init because we don't know quite what it does yet. So like I said, yeah, processes, they're assigned a process ID whenever they're created, doesn't change as long as it's alive. So most Unix systems, PIDs go up to a maximum of like 32,000 and then zero is reserved. It's invalid and that's also the result we get from fork if we are the child process. So it has some type of meaning that is reserved. So no process can actually get the process ID of zero, makes sense otherwise if forked return zero from in both parent and child, that would be really, really more confusing than it already is. And like I said before from that question earlier, kernel can recycle the process IDs if that process dies for a new process and remember each process, they're completely independent, they have their own, what we call address space, basically their own independent view of memory or in other words, they each have their own copy of virtual memory. So we have to maintain that parent-child relationship and sometimes processes end and what about, this is also where your Google searches get weird, what about if your parent no longer exists, then what do you do? Oh, well, the kernel always has to maintain that relationship so if your parent terminates or it dies or it goes away, well, you need a new parent. Like I said, it's like very literal with this course. So previously I had to sleep in there and I didn't tell you what it was for. The reason I had to sleep in there after the prince is so that parent lasted longer than the child so I didn't have any weird situations where the parent just disappeared before I can actually explain something because well, what would happen if the process, parent process exit first and no longer exist? Well, you need a new parent, so how does that work? Well, first we have to go over your responsibilities so the parent process is responsible for the child. Again, think of this like real literally. So if you were a parent and you have a child, guess what, you should at least raise that child, you're responsible for it, until at least they're like 18 and then they can do whatever they want. Same idea here, so if you create a process, you essentially are the ones responsible for it. So the operating system, whenever you terminate a process and it exits, one of the things it does is save that exit status and when a process terminates. But even though a process is terminated, it can't remove that process control block because well, something would probably need to read that so it can see, hey, did this process exit successfully? Did it exit with an error? Did it exit at all? So a parent has to acknowledge the child if it actually terminates. So the minimum acknowledgement we have to do is the parent should read the child's exit status to see what it actually is. But there's actually two situations we might get into between what happens to the parent and what happens to the child. So in one case, if a parent creates a child, well, you have two choices. Either the child process could exit first or the parent. If the child process exits first, that's what we call a zombie process. So that child process is terminated, it's dead, it can't execute anymore, but we can't remove its process control block because it needs to be acknowledged by the parent and the parent has to read whatever that exit status is. So that's why it's called a zombie process because it's still kind of alive, we can't get rid of it, it exists, but it's also kind of dead, it can't execute. So the name they gave it very creatively is just a zombie process. So the other one is also not a creative name. If the parent exits first and the child is still executing and still being able to run, then that child process is now called an orphan process. So its parent no longer exists. So yeah, so they're independent, but the parent should at least know when the child process has exited and like you should see, hey, did it finish successfully? Did it finish with an error? Did it finish at all? That's one of the things you need to know if you created a process. So you just need to know whether or not it terminated and there's quite a few reasons for it too. So the operating system, right, you have to kind of acknowledge that a process has died so that you know that, I don't know, say process 100 died, and you want to make sure there is no situation where process 100 just gets randomly replaced by another process that happens to have the same ID. So if you acknowledge it, you say, okay, the process 100 I knew about is dead, I acknowledge it and then the operating system can go ahead and delete that process ID 100 and then it can reuse it and then you don't think the new process 100 is your child. Yep, so if the child exits first, that means we haven't acknowledged it yet. We'll see an example too of why it's a zombie process, but we just need to acknowledge that it has terminated and before we actually acknowledge it, it's just called a zombie process because the kernel can't get rid of it yet. All right, so yeah, I said acknowledge kind of in a general term, so the actual system call to acknowledge is called wait, so that is what being a good responsible parent process is all about on Unix systems is you need to call wait on it. So wait has the following API. It has a status argument where it's the address to store the wait status of the process, which will tell you how it terminated and its exit code for now will just assume that they just called the system call exit, but within the next lecture, we'll figure out another way a process can terminate and what it does is it returns the process ID of the child process. So like all the C wrapper system calls, negative one means it failed. We'll return zero for a non-blocking call we'll see in the next lecture. That basically means that it will return immediately and then tell you whether or not your child is still alive, not morbid at all. And then if it returns a value greater than zero, that is the process ID of the child that has actually terminated and you can now read its status. So that wait status, that status variable contains a bunch of information including the exit code. You can use man wait to figure out how to query it. We'll see some examples and that's probably the easiest way to do that. If you want to wait on a specific process, there is a system called called wait PID that we will use in our lab and then you can wait on a specific one. And then, oh, whoops, I saw a question discord. Do grandparents inherit orphan processes? The answer to that is they can, we'll see what actually happens in the example. It's more fun just to figure it out. So here is a wait example. So let's look at this program and argue about it. So everyone can see it, okay, in the back ish. All right, this projector, not the greatest, but I guess we'll do. Here, I'll make it one bigger just in case. So first thing we do is fork and then remember after the fork, we have two processes now. The only difference is the value of, the return value of fork. So if fork, quick, recap. If fork returns zero, which process am I? The parent or the child? Child, right? And in the parent, what gets returned from fork? Yeah, the process ID of the child. So here I check that I don't have an error. If I have an error, well, we found out when we can have an error from fork is when we fork bomb ourselves. So if we didn't do that, hopefully we won't have an error. Then here, if PID equals zero, so this will only be executed in the child process. So all it does is sleep for two seconds and then that child process would then return zero, which is the same as exit. So basically the child process just goes to sleep for two seconds and then exit. So it should last exactly, well, about two seconds. The fun case is what happens in the parent. So the parent process, it will print out that it is calling weight. It will create this int for that weight status in order to capture that value. And then it will do the system call weight and then give it the address of that weight status so that system call can go ahead and write a value to that variable. Then we capture the return value in weight PID and for now we don't even check for errors. We don't do anything. We assume we just have an if statement here that this is one of the macros you can use. So one of the bits in that weights status variable will tell you whether or not that process has called exit or if it terminated in some other way. Right now, we just know that it can call exit. So we'll just, if it doesn't, we'll just return some error code. Each child is like some error code that something bad happened to your child. If it has exited, well, we're gonna say weight return for a process. We're gonna print out the process ID that we actually waited on. So it should be that our child that terminated and then its status, which is going to hopefully be whatever it returned from main. So if we go ahead and run that, you should see calling weight and weight is what we call blocking system call. So weight won't actually return until our child has terminated. So because our child took two seconds, it will wait for two seconds. And then after it's done, it will say weight return for process ID, oh yeah, so that number is so high because of you. So thanks for fork bombing me. Yeah, I did not restart my virtual machine. And then we got the status as zero. So any questions about that little example? Yep. So weight returns the process ID of the child here. Yeah, so that's a good question. So what happens for weight if there's multiple children? So it'll return whatever the first child terminates and then the process ID, it'll tell you the process ID of that child. So it waits until a child terminates and then it returns that process ID? Yes, yeah, weight waits until the first child terminates and then returns that process ID. In this case, I only have one child. Yep. So the rule for this, the strict parent-child relationship, you're only allowed to wait on your direct children. So it has to be your children, your child that you create. Yeah, so weight's a system call, so the kernel knows the parent-child relationship. So if I tried to use weight PID where I wait on a specific process ID, I could try and wait on process ID one for an example, but that's the parent of everything and it'll be like, okay, that's not your child, sorry, we'd get an error. Yep. So this W status, so the system call just writes to that. It writes a whole bunch of information there that we don't actually know how it's formatted, so that's why we use all these macros. So it'll have some bits that say whether it exited, it'll have the exit status will be stored somewhere. It has a whole bunch of different rules for how to extract information out of it, but just writes a bunch of bits to it. And we have to use the macros, yep. Yeah, question is why is it using just an int instead of a struct or something like that? They can get away with it, ints are easier than structs. Also remember whole thing, if you create a struct then you're screwed if you have to ever change it again. So here they're allowed to just change the actual values that get returned and change the macros if you want because it gets all, well actually they couldn't change the macros, so they just decided it, I don't know why. Yep. Oh, so what status being zero means? So that status is the exit status of that child process. So if I wanted to change it, let's see here. So in here this is what happens in my child process, right? And if I just instead did a return one here, that's the same, returning from main is the same as calling exit. So in this case it would call exit one, right? So if I do this, that is the exit status. So if I run it again after recompiling it, now it says, hey, my child returned one. And usually one means that my child had some error and I should probably do something about it. Or I should expect that something if failed whenever I ran that process. Yep. Oh, so the question is, how does the parent exit? So the parent would call wait, wait would finally return, then it prints this and then it would fall through and then reach just return zero and that's exit zero. So then, oh, so the return each child is just, if the child did not exit, then I just return, so this is also like returning from main, I just return a non zero value from main so I exit with that number as the error code and that's just a magic number that means something messed up with my child. So it's just, yeah, some magic number that it's an error code that means something. Yep. So it waits only for the child to terminate. So if you want to wait for the process to be ready to transfer data, that's another system call. Wait is only for waiting for it to terminate. Question, once a process returns a value, does it get terminated immediately? So a process gets terminated whatever it calls exit. So if it calls exit, then it's terminated and whatever value it gives to exit, that's the exit status that gets set. So where is the zombie time in this example? The zombie time would be pretty much non zero in this because the parent's waiting for it to die, waiting for the child to, okay, waiting for the child to terminate, let's have nicer words. So it's waiting for the child to terminate and then as soon as the child terminates, it gets waited on and cleaned up pretty much immediately. So it's like pretty much zero in this case. Well, the case where it's not zero, yeah. So if they die, yeah, so you'll kind of implement this in one of the labs too. So as soon as they die, they'll check if anything is waiting on them, like the kernel will do that check and then if there's something waiting on it, it'll immediately have that process return from wait and keep on going. It doesn't actually need a queue or anything like that because only one process can wait on it, only it's parent, right? So it doesn't need a queue or anything complicated like that. All right, everyone, any more questions for this or shall we create a zombie or an orphan? I forget what's next. All right, let's create that and have some more questions with creating more gruesome things. So yeah, let's do zombie. So zombie process, remember that's waiting for its parent to read its exit status. So process is terminated, hasn't been acknowledged, no one has called wait on it yet. Like I said, process might have an error in it where it never reads the child, like the parent process might have an error where it never reads the child's exit status. And in that case, it's going to be a zombie for a long time. The operating system has some mechanisms that we'll see in the next lecture that can interrupt that process and kind of poke it and be like, hey, your child's terminated, please acknowledge it. But that's just a suggestion and then that process is free to just ignore it. You can be a bad parent just like in real life and this poking of a process is called a signal. We'll be able to see that in the next lecture. But the reason that it's called a zombie, again, like I said before, the operating system has to keep that zombie process around until it's acknowledged. If the parent ignores it, then that zombie process just needs to wait to essentially be an orphan and then get reparented to hopefully a more responsible parent. Yep. Yeah, so it can only keep the exit status and the process ID that's finite can clean up everything else. But yeah, that process ID has to stay around. So some, that process control block has to exist. Maybe it, like it probably frees all the memory and everything that has to do with the process and clean up a bunch of it, but it can't remove it completely. Yep. Yeah, so in this case, we're just assuming the parent just flat out ignores it. So if the parent just flat out ignores it, as long as the parent exists, it will be a zombie process. There's nothing we can do about it. We have to wait till it gets a better parent. And yeah, so an orphan is a process that needs a new parent. So that's when the child process lost its parent process. The child process still needs a process to acknowledge its exit and the operating system, what it will do is reparent it and there is a designated process to reparent things to and it is called init. So by default, all of the child processes get reparent or the orphan processes get reparented to init. And the idea behind that is init is supposed to be the responsible one, init has a very important job and init should be written well. So one of the jobs of init is to just acknowledge a whole bunch of orphan zombie children. Yeah, so in this case, to be a zombie, you've already terminated, right? So you've already terminated. So all that's being left around is your process ID and then your exit status probably. So you as a child, you're already done, you can't do anything, right? You're terminated. So in general, waiting around and still being alive is gonna change the fact that you have an irresponsible parent. So you always have to have a parent, the kernel will make sure you always have a parent and if your parent dies, it reparents you, probably to init, yep, yep. Yeah, so when I fork bomb myself, I didn't acknowledge anything, so I would have created a bunch of zombies, but in fact, I didn't create any zombies because none of them ever called exit because they all just repeatedly went through that infinite loop. So none of them called exit, so none of them were ever zombies. But if they were, well, they'd be zombies and then eventually if some other processes died, they'd get reparented to init and then init cleans them up for me. Yeah, yep, we did. So returning from main is the same as exit, means the same thing. Well, it eventually does the same thing, yeah. Yeah. How would each process know that its children have already terminated without probably waiting for a person from a past to come back? So the parent would have to call wait. But if you called wait, it can no longer execute its own stuff, right? Oh, yeah, so that's a good question. Oh, well, being a responsible parent is a lot of work. Like I don't want to just have to call wait and then wait for my child to exit. We'll see some ways that, so this wait waited until the child blocked. There's a version of wait that just asks really quick, do I have a terminated child and they'll return immediately yes or no. So there is a version of that and once we get into topics of threads, you can do stuff in threads and all that fun stuff. But for now, we'll see that in the next lecture, that non-blocking version. So let's, yeah, let's create an orphan. So that's something that's, so yeah, that's something that's unique only to this class. So when you tell your friends what you did the day, you know what to say. Yeah, so I create an orphan today. All right, so same kind of setup. We're gonna fork immediately. Let's ignore our negative one. So now in the child, I will print off. In the child, what is the child's parent process ID, which should be the original process, then I sleep for two seconds and then I'm going to print off my parent's process ID after I wake up through sleep. So the child process will immediately print what its parent is, go to sleep for two seconds and then print what its parent is again. And then in the parent process, it's just going to sleep for one second. So that means that the child, whenever it does this print here, its parent should still be alive because it lasts for a second. And then after one second, the parent should be dead or terminated. And then one second after that, the child should wake up and then print what its process ID is. So if I run that, well, then I see a whole bunch of fun things. So I see that the child's parent process ID is, well, the one I started with, and then it was also a bit weird if you watched after a second, I got my terminal back, like I could type on my terminal again. Well, that's because your shell, it's responsible. So it waits for this orphan example to finish, which only takes a second. And as far as your shell is concerned, it just waits for its direct children or its direct child that just created to terminate. After that, it assumes you want to type and actually start launching a different program. So I got my shell back and then my child process, well, it can still use my terminal. So it printed its second message and then I saw that, hey, it indeed got reparented and it got reparented to process ID one, which is a knit. So you don't have to take my word for it. It clearly got reparented to a knit. And hopefully a knit was actually responsible and cleaned it up when it terminated. Yep. Yeah, so the question is, in Bash when you use ampersand, what happens? So that is still a child of the shell, but the shell just lets you type while that's running in the background, but still a direct child. And it will know when it exits too, right? When it terminates, it says, oh, your background task, PID, whatever the hell exited, here's its exit status. So now you could actually write your own shell because it doesn't really do that much. Yep. Yeah, yeah. So in this case, sleep too. So the kernel puts that process to sleep for at least two seconds, I think it's defined as. So it gets into that kind of block state where it just kind of runs. Or it just, the kernel can schedule something else to run. Yep. Five seconds in the U.S., what do you just say? Yep, so if I change this to B3, so the parent lasts longer than the child, then in this case, I should see the same process ID both times, right? Because the parent is still alive, it doesn't have to get reparented, so we can just check that real quick. So parent process ID is that, after sleep wakes up. So in that case, the parent lasts longer than the child. In which case, I would have made an orphan for, or I made, sorry, I would have made a zombie for a second, right, because it's sitting around. Is that, no? If an individual was to be malicious, for example, could they use this to create like a hidden process because that process, all the process they could create would mean parent is back yet? Like, having a knit as your parent doesn't give you any special powers or anything. But for example, shell was to close. Yep. So there are options for the shell. So if the shell closes, one option you can have is have your processes reparented to a knit, actually, and sometimes you want that. Like you just log in, you want a lot of process that runs for a long time. Maybe it's doing something important and you log out of your shell, you don't want it to die. So that's one thing you can actually do. So your shell will let you do that. I forget what the default is. Yep. So, yeah, if I have them at sleep for the same amount of time, essentially it's just gonna be random then. Like the one second is just a suggestion, so it'll be close, it'll probably be more or less random. All right, yep. I know it's still long, but I remember you say at some point that the maximum process you can have is like 30K or something. Yeah, so the maximum process ID of 32,000, that's a default for most systems. For mine it's bigger. Because I think for you it's bigger. I think they changed the defaults, but I might have changed this too because I know what you guys do. But it's a configurable thing, you can just set it to whatever you want. Yep, so there's a slight difference. So the terminal is actually drawing this stuff to the screen like drawing all the characters and everything. The shell is actually taking the characters like the keyboard presses you write and then starting more programs and everything like that. So it doesn't have to draw user interface or anything. So you could actually write your own shell if you wanted to. Like it just kind of, the shell is just like reading in this string and then while it reads in that string and then it creates a process out of that. So the shell would fork itself and then exec that. So close the shell is like when I log out of this shell, like if I hit control D or something and actually log out or close this terminal. If I close this terminal, it will then close the shell because it's a direct parent or a direct child of it. So I can kill both of them at once. But there's a slight difference to it. Basically a terminal draws the user interface and deals with like the actual physical keyboard inputs and the shell just reads like bytes. All right, let's create a zombie. So same type of example. So we immediately fork and then in the child we go to sleep for two seconds. So now in the parent, I go to sleep for one second and I wrote some code that will go into that proc file system and actually read the state of the child to see what its actual state is. So after one second, it should still be alive, probably sleeping because it's supposed to be sleeping for two seconds and I'm looking at status after one second. And then after I check, I sleep for two more seconds. So that's a total of three seconds. I should have been running at this point. So my child should be dead, so it's dead after two. So I print the child process state again and then both of them eventually terminate. So if I run this, I'll see that in my parent, child process state is sleeping because it's still alive. And then after it, it's a zombie, which is an actual state name in Linux because that was just read from the proc file system. I didn't name it myself. So terminology is not my fault. So any questions about the zombie? Yep, after the second one? So if I just call wait here, wait and all, whatever. I have to define and declared. Yeah, so here, whoops, that is code you shouldn't look. So yeah, here I wait right after the child. So here shouldn't actually change anything because I asked for its status before I actually checked it. So I wait after it's a zombie. So here, sleep, two seconds later it's a zombie. So it's still a zombie and then I call wait on it and then it would be completely removed from everything after that point. So after the wait. So if I went ahead and move this, say I called print state on it again after waiting on it, well then that process doesn't exist because after wait it just cleans up everything, right? So now if I do this, I actually have never done this before. So let's hope it's okay. So sleeping and then, yeah, and then zombie and then I actually don't have, like I just wrote that myself. So if it didn't find the directory, usually students were just running a macOS which PROC doesn't exist. So that's why I have that error message. So this unknown macOS question mark actually just means it couldn't find it because that PROC entry would be removed because I called wait on it, it's cleaned up and everything. So yeah, probably should change that error message as assuming that you actually use Linux. All right, any other questions about that? Yep. Sorry, print state is, did you write that yourself about those things? Print state I wrote, yep. And for lab one, whenever you're starting to like, you're gonna be looking at that file and then returning like the name of it. So if you want, you can look at my function for inspiration if you want. So print state is actually going, you're gonna print the name. So print state is actually more complicated than what you'll have to do. So if you wanna look for inspiration, go ahead. Yep. Yeah, yeah. So for tomorrow for the practical session, T's are just there to help you only for lab zero. So if your VM had some problem setting up or anything like that, you can go to that. And if you're already set up and you did your crowd mark and you did all that stuff, then you're good, you don't have to go to the labs. Labs are mostly for help. They're the same day that labs are due. So pretty much last minute, oh, help me. All right, so with that, just remember, pulling for you, we're all in this together.