 Alrighty, so let's get into it. So process management, even more fun stuff. So after we fork how do we follow up on the fork and actually manage our processes? So first dive into that execve example because that's another important thing to know how to replace this process with another process So here is some code probably easier to show that Again, we have main We're going to print f some message. Just should hopefully always print and then we'll set up Array of char stars or an array of C strings and the first argument is going to be ls then null So the definition is that it knows when the array is over when the last element is null So we have arg v and then we have np which stands for the environment variables Since I don't care about them. I'll set them as null and then this is the system call That well, this is a raptor to a system call, but the system call is called the same thing This is called execve and it will take as the first argument the path of The program you want to replace this process So if this is successful the kernel will go ahead load that program into memory Figure out where it wants to start executing and start executing it if it's successful And it would give it these arguments and this environment variable So we have two options if this is successful It will actually just run ls and do ls things. It just runs ls. This process gets replaced So this process would have the same PID or PID, but now it's running something else So if it's successful if this calls successful nothing else will execute because this process got replaced Now if there is an error, so it's defined that if you look at the documentation if this returns negative one It means there's an error and it sets this global variable. Well, if it's there's an error. I'll just store it in this Variable because it actually this P error will also interact with error. No and may change it So I want to report the first error that happens. So I'll use this to print an error message It's a wrapper that will give you a human readable Error no so you can't really read what was going on and then the Process will exit with whatever the error number was that we got and then in the case And in either case this line will not print because either exact VE worked and this process got replaced Or there is an error and it reached this return statement and exited for main So there's no way this line will execute and you can see if I run it It says I'm going to become another process and that does the same thing ls does so it shows me the contents of the current directory So that is how you run another process Sorry, how you run another program on any Unix system So you can tell now that hey when your shell wants to execute your process Well, it would fork itself and then likely in the child. It would just exact VE your process That's why we see even that hello world example when we s traced it. The first line was actually exact VE So that was not from us. That was from your shell actually executing it. So that's where that came from Okay, so yep So the arguments of exact VE the first argument here is like the full path to the program UI execute on links It's going to be like an L file, right and then This argv is essentially the argv you get from main right Yeah, so you'll notice that this ls name like doesn't actually correspond. I could have named this whatever I want like Actually, let's go back So it still works the same It's just a convention that the first argument in main is supposed to be the name That the user type to run your program It doesn't have to correspond with the name of the actual program or vice versa or anything like that in fact Was it ls help Yeah, so usually when you type help for something it says usage Ls whatever we'll say if I want to do ls Help I'm giving it an argument. So if I want to actually run it. I could do something like that So you can tell what your shell is doing it splitting what you type into Different C strings and then passing it to argv to actually execute your program. So if I do something like this This will be fun Well now it's printing out the help message for ls because I gave it the help argument and that program's gonna read it and do something with it and funnily enough You'll see where it actually uses that convention. You see usage so in part of the uses Usage message it's supposed to tell you what you type to actually run the program The convention is the first argument supposed to represent what you actually typed So it doesn't have to match up with the program name at all in fact look at the name right so Yep, no, so these are all the command line arguments to your To your program. So like if my main, you know had in our Z and Rv whatever So they line up exactly with those arguments to main so when ls runs it would run its main and In this case it would get two arguments the first one would be ls the second one would be help Yep, the third argument environment variables, which we don't really get into in this course, but It's just it's essentially like you can think of there like global variables for processes That the operating system kind of keeps track of Okay, so let us blast through some content today and then get into more fun examples where we break stuff So you might notice whenever you use Linux the terminology I showed you before is a bit different than what you'll actually see on Linux So some of you have already played around with the proc file system So if you go read the status file in lab one you're using name another thing you could look at is the state and again Everything the proc directory that has a number Represents a process. There's actually a special process called or a name in that directory called self Which refers to whatever process is accessing the proc directory So if you want to figure out what your process ideas you could read proc self status or something like that and that's basically what I get PID does So yeah, so on Linux It represents running or runnable as the same thing which in the state diagram is pretty much the same as running or waiting Which also meant ready so links doesn't really differentiate between the two and then there are two types of block states There is interruptible sleep and un interruptible sleep Interruptible sleep means you can it's doing something But you can wake it up and kind of prod it to do something else if you really want the details of that get a bit Well, we won't go quite into those details in the course Just know you could prod it and it could react and do something Uninterruptible sleep is like it's waiting for IO or it's waiting for network and something that even if you poke it and Try and say hey do something it will not wake up and it will wait for that operation to continue You can't get rid of this process in any way shape or form and it's kind of annoying Then there is a stopped state that is will get into signals Hopefully in the next lecture, but you can actually tell a process to stop executing and you can control whether or not it executes or not So that's what that represents then you might see the z state or z state for zombie That's a fun name So keep that in mind because we'll be talking about zombies today So you might also ask the question Well, if we have parent and child relationship for every single process on our system, how does it ever start? So that is a good question the entire user space on Linux starts with the kernel as A hard-coded thing it launches process ID one and process ID one is responsible for Launching every other process on your system So as part of booting up and when the kernel is done initializing all your hardware and everything like that It will start one and only one process that will be called process ID one and as long as that process is running Well Then your computer is running if that process is not running your computer is no longer running So it's responsible for launching every single other process on your system It might not launch it directly, but it is going to be the ultimate Grandparent if you want to think of it that way so it is called a knit and again Responsible for every single launching every other single process on your machine It can delegate that work to some of its children, but no matter what that's going to be your ultimate ancestor Specifically the software on Linux is called system D But there's lots of other options if you wanted to yourself you could write your own in it And that would be fun So you could write it in it that just launches the shell if you only want shell access and then you only have one shell Things like that you have as many options as you want on some operating systems There might be like an idle process that the scheduler can run as long along with a knit to represent like your CPU Is not doing anything you might see that on windows where there's like an idle tab in task manager So this is what a typical process tree would look like since we have parent-child relationships There is that relationship between every single process So it would look something like this with a knit at the very top So the direct children of a knit might be some system services We don't really care about but they're called like journal D Udev D which kind of deals with hardware Then it might launch system D a user version to represent your logged in user and manage all the processes for your individual user Then you know we might open the terminal So that would be another process. So your terminal would be a process And within it it would have your shell So your shell would be a another process that your terminal launches And then you would launch something from your shell like h top which we'll see and then This gnome shell. Well, this is your whole user interface the process that represents your whole user interface So if you launched a web browser, it would look like this. It would just be a direct parent And then your web browser might have many sub processes in it So it might have many children sub process which looks a bit weird, but in fact All the browsers are architected this way where every tab is actually a different process Because since every process is isolated if one of the tabs crashes, it doesn't bring down your entire browser It just destroys that one tab. So chrome will follow that architecture firefox will follow that architecture Um, and that's a handy thing for processes. Yep So the terminal is what you type into so it's like the window that you see So it's responsible for translating your key presses into bytes And then translating things that come from like standard out into characters. You can actually see so it's doing all the graphical stuff and the shell is just Taking bite what you write in and then executing Programs and stuff. Yep So they are independent. So we'll see that today. That'll be fun. Yep So when you log into your virtual machine if you install the desktop environment specifically the gnome one The whole user interface like your desktop is called gnome shell So that represent Sorry Yeah, it's like your primary interface for that. So everything you see will be gnome shell. Yep All right, so how you can see the process tree on your virtual machine you can use htop There's if you press f5 it will translate what all the processes you see into a tree So we can see that real quick So if I type htop And expanded a little bit It'll give me some information about my cpu usage up there and then You can see that f5 Uh-oh my key doesn't work Oh, okay. So now you can see it's a tree view. So at the top There is a knit and then you can see it has lots of direct children all of these That do a lot of things And it goes on and on and then you can see hey, here's my ssh session. It's a process I can see that this is me logged into it. There's some words I can't understand But eventually ssh launches zsh, which is my shell and then here's bash. So it's running bash That might be a different session, but you can go through This is an actual tree of all the processes running on your machine And you can go ahead and explore that you can see what vs code is doing There's vs codes terminal. So it is It launches the shell as well and here's the command that I am currently running So you can see where it lives in the hierarchy So if you want to experiment and go around with that, that's lots of fun to do But everything has a parent. Yep How does what work? Sorry The numbering Oh, so the question is how does the pid numbering work? So In practice, it's pretty much sequential Like it just assigns one than two than three than four than five them And then it won't reuse them until it comes back again. That's generally how it works, but Theoretically it can just assign any free number when that process starts And the only rule is it will stay that number across its whole lifetime After it's done, it can reuse that number But typically you'll just see it go up sequentially. Yep So the question is about virtual memory. We'll get into virtual memory later. Don't worry about virtual memory for now Yeah, that's a whole another can of worms. We'll get into All right. Oh, yeah. Hey, there's a slide for it So processes are assigned a process id on creation and it does not change for the entire lifetime of that process So as long as it's active, it's that process id won't change So most link systems it goes to a maximum of like 32 000 and then zero is reserved for invalid That's how the child knew what the child was so zero is just not used The first magical process is one zero mean zero is just invalid And eventually the kernel will recycle them after the process dies for a new process Maybe you never reach it to 32 000 and it doesn't recycle them I think now the limit is even larger than that so and the mac limit is different than that as well So you might see different behaviors based off the kernel But they'll always follow the golden rule of you assign a process id that's unique to a process while it's running Stays the same for as long as that process lives and then when it dies you can go ahead and clean it up And each process has its own address space. You might hear that term that basically just means It has its own virtual memory. So whenever you say a process is address space It's the same thing as its virtual memory So its view of memory that it has which is completely independent and it thinks that it's full control over memory all right, so Previously you might have noticed in my example. I had a little sleep to make sure that the parent exited last You might have answered ask the question that what happens if the process exits first And no longer exists. So you create a child and then Your child's doing stuff and then you die what happens to your child Anyone guess Okay, so one of the things we think the child also dies. Hmm. Is that true in real life? No, so yep, any so Yeah, so the question is why would you die before your child dies? So you die by just calling exit and that's the end of the process So your child could go do some calculation some big long thing and you could just do nothing. Yeah Yeah, so the child should still go on Yeah, the child essentially becomes an orphan And again, this is where your google searches get even weird weirder because you might google how to kill an orphan Whoo, that's not good So let's get into the orphan processes. So The parent like I said before is responsible for the child and what does that mean in terms that sounds like a thing But it doesn't mean anything quite yet in terms of operating systems Well, what that means is that the operating system like whenever you exit a process You set an exit status, which is that number you return from exit So as long as your process is exited the kernel has to keep track of at least that number in memory So it could get rid of all the memory that process uses But would still need to keep its process control block around because Someone might have to read what happened to that process, right? So it has to be acknowledgement So the minimum acknowledgement you have to do is read that processes exit status So there actually is a step before Or between when the process exits and before the operating system can completely remove it and that's that acknowledgement. So if a process exits Most of its resources can get cleaned up like memory, but its process control block will still be around its process id will still be around And in that process control block will be its exit status and the parent is responsible for reading that So there is a system called to read that then after the parent reads it then And only then the kernel can clean up all the resources that have to do with that process So there's two situations because typically your processes will not Both end at the exact same time So the first one we just kind of said the pro if the parent exits first That is The remaining process is called an orphan process So it means that that child process no longer has a parent. So it is an orphan Very like the terminology is not terribly great, but I guess it's effective and then the second one is If the parent's still running and the child dies first That child process is called a zombie process And why is it called a zombie? Well because it's in like this halfway state between alive and dead So it would be alive if it was executing, but if it exited it's not executing anymore It's terminated But the operating system can't completely get rid of it Its process id is still around and it's waiting to be acknowledged by its parent So that's why it's called a zombie because it's neither alive nor dead. It's kind of in between like you know a zombie So what's that acknowledgement look like? Oh, yep Yeah Yeah, so in the zombie state the pcb will Remain around but that process will be terminated And in this state the orphan process is still running, but we don't know essentially who its new parent is going to be Yep, so it needs to be acknowledged by its parent still so So Yeah, so the question is why is it need to be acknowledged because if you didn't acknowledge it actually ended How will you ever tell that that process has finished? Right? So it might be the case where you might think that okay I'll just constantly pull its process id over and over again to see if it's still running Well, if there's not some acknowledgement what might happen is your child exits And you look at it its process id exists. You think it's still running And under your feet The operating system goes ahead because that doesn't exist anymore creates a new process That got the same id because it's no longer in use and then suddenly you think it's still alive But it's a completely different process So you wouldn't be able to keep track of it without something like this So if the parent exits The parent process id might get cleaned up because it might get acknowledged by its parent Yeah, so you these grandparents and all sorts of stuff. So we'll see an example So How you do this acknowledgement? It's another system call and it is called weight So weight has the following api So weight takes a status which is an address to store the weight status of the process So it will go ahead and write some information to that It is basically an integer and it will return the process id Of the child process, which is now Dead So if it's if it returns negative one, it means there was a failure You don't have any children or something like that If it returns zero Well, this call will actually block and weight by default for a child to die If it is not already dead So there is an option to have what's called a non-blocking call So it would return immediately and just tell you whether or not that you have a child that needs to be acknowledged And if there is a child It will return greater than zero to tell you the process id of the child that you just acknowledged so that The information it writes to the address you give it Has a bunch of information including the exit code So you can type man weight Find all the macros because you're going to need a macro to query each individual status So it'll be things like hey did that process exit? Was it killed some other way which For now we just think that processes can exit But soon enough we'll see other ways for a process to exit And then if you want to wait on a specific child process There's a system called called weight pid so you can wait for a specific process to die And it has to be your child So this is what the example looks like so Let's go over this case because now yeah, it has fork. So we have main Okay, main starts executing. We have a single process And then immediately it forks It returns a process id For the parent it's going to get the process id of the child for the child what process id is it going to get zero so Here we just check if there's an error otherwise if we are the child process We'll go to sleep for two seconds and Next in the parent we will call weight. We'll just have a print statement that calls weight define a Integer on the stack And then call weight with the address of that integer so weight would write information To that so we can read w status So we immediately call weight And weight should not return until my child exits and here my child should Sleep for two seconds and then fall outside of this else And then return and then that process would exit so the child process should just exit after two seconds so Now in the parent I wait so this should just block until my My child actually exits which should take two seconds And then it should return the weight pid Of the child that exited while I only have one child in this case So if it returns it's hopefully the one I created And then I use this macro here. So there's a w if exited So that will tell you if the process has exited and if the process has exited You are allowed to use this macro to read its exit status. So I just print weight return for an exited process what its process id was and then what its status was So if I go back to it So if I do that I just see calling weight waits for about two seconds That says weight return for a process gives a process id of the child that is now dead and it stars. Yep Yeah, so w status is just it wants to it Just writes a bunch of information to an integer like four byte And it's formatted in kind of a weird way So it'll write information to it. So you give it the address of the integer you want it to write to so it will change this value and then To decode the information from it they they have a few macros that check Which bits are which and it will tell you things. So that's why you have to use the macros. You shouldn't use it directly So yeah Yeah, so weight without any arguments will return as soon as the first child dies Or as soon as the first child terminates Yeah, so if I created two children This would weight would return whenever the first one exits Just the first one. So if I want to acknowledge both of them, I would have to call weight twice Yep. Yep. So the kernel Is smart it can do it offend essentially using events because That process will have a system call and then it can just wake it up immediately It doesn't have to pull the the kernel wouldn't have to pull for this So if you do want to pull like I said, there's a version of weight You can say no block and it will essentially do the pulling for you if you want that Yeah, weight doesn't pull. Yep. Yeah, that's a good question. What happens if I don't have any children? What does weight return? We can go ahead and just try that So weight I'll let's do it again So I have to wait for my child to die and then I called weight again So this will print the latest up to date one. So I got negative one, which means I got an error message an error code So If I actually read into the error code and whatever it was It's going to be an error code that says hey, you don't have any children What why are you calling weight the kernel knows how many children you have So if I call weight, I create one child and I wait twice Well, the first one's going to work and then the second one's going to be like you don't have any children What are you doing? Yep Returning zero and exiting zero. Yeah So Returning from main that number you have is the same as calling exit with that number It's the same thing. Yep. So javascript async Uses threads and does things. It's basically threading for people that don't want to deal with threads We're gonna have to deal with threads later. So we can get into how async would kind of work, but Basically threads you can't see All right, so anything else for that hopefully that's yep so the macro functions, so It's just that this w status number It has a lot of information on it that records everything about how that process terminated So you just kind of have to look up the documentation to what they are for now all we need to know that This is essentially the only way we think a process can die right now is if it calls exit So that's what this macro is for and then if you read the documentation for w exit status It'll be like this is only valid if This is true if w if exit is true then I can read the exit status So it's just one of those things you're going to have to read some documentation for but for now exit status is Good enough and yeah, so we're going to have lots of weight examples. So weight will Wait for One of your children whatever the first child is that terminates weight will return at that point and then It acknowledges your child. So Whatever it returns from weight and that I got that process ID Let's see So it calls weight in which case weight is going to wait for the first child to terminate in this case This process only has a single child and whatever weight returns that process ID can now be recycled. So if I tried to like Go here and look at Look up in proc or whatever that process ID wouldn't exist because I waited on it. I acknowledged it It's done. So at this point completely deleted from existence If I looked up that process ID before the weight it would have already existed and we'll actually do a quick example on that So let's quickly blast through some other examples real quick. So let's explain what a zombie process is So a zombie process is a process that is just waiting for its parent to read its exit status So it's kind of like that in between a live and dead thing I talked about So that means that child process is terminated. It called exit. It's not running anymore. It can't run anymore But it hasn't been acknowledged yet. It would have still have a process control block Another way to think about it is that process may have had an error in it or something like that So you want to definitely make sure that hey, you know what happened to it I need to read its status so I can figure out what problem it had Um Another thing that can happen when we get into it in the next lecture is the operating system Can essentially generate its own interrupt to the parent process that say hey Your child is now dead. So that goes with your pulling thing. So there's another way to essentially have the system Generate an interrupt to say hey one of your child Is now no longer with us. Please acknowledge it. Um, but this is just a suggestion And you are free to be a Very bad parent and you can just ignore it if you really want Like you're controlling what calls you make The operating system can tell you you should call wait and you can just ignore it and What the operating system does well, it still has to keep it around until it's acknowledged and then If the parent acknowledges it then The zombie process just needs to wait any ways in order to be reparented Eventually it will become an orphan as well and we'll get into that but first Let's see the zombie example So in the zombie example So in the zombie example, we fork We're going to do the same thing where we create a child process. So checks if there's an error. Hopefully there is not an error In the child we're going to do the same thing where we sleep for two seconds and when then we just let it Then we just let it return So in the parent we're going to do something a bit different So in the parent we're going to sleep for one second And then read the child's process state, which it will still be alive after a second because it takes two seconds But it's sleeping So I just wrote this function that will just read its state so we can read its state and Print its state and then check if there's any errors. Hopefully there will not be any Then the parent is going to sleep for two more seconds So it's going to wake up from here after three seconds And then at that time we know our our child is now dead So we'll read it again. And if we read it again, we should read that it is in a zombie state because We're the parent. We never called wait. So you can see that hey It was sleeping after a second and then After two seconds it's now a zombie. So that is an actual state. I didn't lie to you. It says zombie So that means So that means its process control block still exists. I didn't call wait on it Yep So in This state so the question was hey if in between those two things someone else got the child's process ID, what would happen? so technically The child's process ID. It's still Active because it's still a zombie. So no one else would get its process ID. It still has its process ID It wouldn't get cleaned up until after it's acknowledged and then it gets cleaned up. Yeah Yeah, yeah print state is just something I wrote But it's going to open that proc file system look at status and just print the state line Okay So the funner one is the orphan processes So an orphan process It doesn't have a parent anymore The kernel is very strict about the parent child relationship Every process needs to have a parent. So if your parent is now dead, you need a new one so Because that child process would still exit at some point and still needs to be acknowledged So this reparenting process while the default parent of every single process on your machine is a knit So by default if the parent dies it will get reparented to a knit And that is why I don't think it happened this lecture But sometimes when I ran that example, we saw that the child's process parent process ID was one Well, that was because it happened that the parent died first and it got reparented to a knit so a knit is like you can think of it as like the The Orphanage or something like that process ID is one is the orphanage if anything needs a new parent by default It goes up to them. There is a feature in linux to designate another process as an orphanage But it has a fairly gruesome name It's gruesome name is called a sub reaper because it is meant to reap the zombies and kill the zombies and technically a knit Orphanage is a nice way to put it technically a knit is called a reaper like the grim reaper. So Those are the naming conventions that they have decided don't blame me So here is an example of an orphan process. So same thing. I'm going to fork immediately create a child In the child, I will just read my parents process ID And then wait two seconds and then read my parents process ID so so my Yeah, my child reads its parent sleeps for two seconds read its reads its parent again And then finishes and then in the parent it will just sleep for a second So for the first one its parent should still be alive and then in the next one. It should get reparented So if I go ahead and run that I'll see that. Hey, I have my parent and then My parent dies and it actually returns to my terminal because guess what when you type commands in your terminal Your terminal only cares about the process you type so when the parent's dead you get control Back and you can type again. So that's why the terminal looks like that But I can see here that after the sleep I can see what it got reparented to it got reparented to one like I said, so you can see this and That's an orphan process So now it is no longer an orphan it got reparented to a knit and now it has a parent again Everything is right with the world Any questions on that? Yeah, well in this case the child print something sleeps for two seconds and then prints the parent again And the parent just sleeps for a second and then exits so Yeah, the question is if the parent ran first, does it matter so the the sleep Computers are really fast sleep for a second is like an eternity so No matter what ran first or Whatever, so if the child ran first Well, it would do the print line immediately if the parent ran first it would have slept for a second anyways And then your kernel would say oh, I could run the child and it would run that print line and then go to sleep so so so there might be a situation if The child doesn't run for a whole second, but that will ultimately never happen on any computer because they're like a second is They can do so much execution so in There is like a one in a trillion possibility, but in reality, that's not going to happen. Yep. Yep Yeah, so So I got the first print statement. Whoops Here right again So I got the first print statement there and then I can see my terminal came back because After a second the parent died and your shell only cares about what you executed Yeah Yeah, so This build orphan example was the thing that called main so in the context of the program It's the parent So after a second it dies and then I can get my shell back and type on my shell because As far as my my shell doesn't know that the process I ran Also created processes, right? So it just cares about what you typed after a second. I can type back on my terminal But that other process I got reparented to a knit is still running and whenever it's doing print f It's going to go to the same terminal. Yep Yeah, so your question is is it possible that the I launch a child and it starts messing with my terminal After the parent Yeah, so there's a question that if I have two children that were created at two different times Will they Interfer with my terminal? I guess at so The answer to that is Kind of so we'll get into that in the next lecture because we're going to talk about IPC in the next lecture So we'll cover that in the next lecture Yeah, yeah, so you say I just increased my time there and then run a bunch Yeah, so if I run a bunch here uh I get my terminal back after a second and I can print another one print another one And then I should start seeing some return values from all the children. So yeah, that's what's happening So I ran it three times. I got my three back Yeah Yeah, so your terminal output for input it gets a bit trickier But for output, they'll just all use the same terminal that they were launched on even if they get reparented Yep Yeah, that's a good question. So will in it automatically call waitpid So that is actually the Second and only other mandatory job for a net. So it launches processes and then it calls wait so we'll if you look at the source code for we'll look at the source code for a educational operating system But if you look at it's a knit implementation It forks and then exec v ease a shell And then it goes into while true Wait That and then it just waits for things to get reparented acknowledges them. It's just an infinite weight loop at the end So that's like so now you can implement a knit. You know the two things. It actually does now All right, any other questions? All right, cool. Well, let's see More fork stuff then real quick so So someone asked a well How would this maybe be useful? So If we wanted to we could write a process Like this that spawns Lots of child processes that hopefully do something So in this case I have a four loop that goes one all the way to four and each time through it It's going to call this new process Function and in the new process function It can fork and then see if pid is greater than zero That means it is the parent and it could just return immediately and it would have created a child and then here I can say oh check for errors and otherwise I could have a process that loops zero to ten and then This represents it actually doing some useful work. So if I go ahead and run something like this Well, I should be creating four processes and each of those four processes are doing ten things And if I have more than one core on my machine, I'll probably see them interspersed And they'll be all working at the same time. So if I run that it's really fast I see that Between them they all they all do zero Then they all do one Then they all do two then they all do three then they all do four and not necessarily in the same order every time So for three it did two one three four then it did one two four three And that's all up to the kernel one to actually schedule them but you could do something like this to split your task into like 10 things and In this case you can answer questions like how many children do I have? How many children did I create there? here let's Four right, so I created four children What didn't I do was I was I a responsible parent? Absolutely not So this program I should probably improve it a little bit and anyways that's time So just remember pulling for you. We're all in this together