 All right, welcome back to operating systems. So lab one and slash zero I actually hit the button now so you can't actually see your grades They were available before but I forgot corkis has like a three-step process to like unhide your grades So hopefully you have those now Most of you got a hundred So lab one probably too easy. So lab two is posted. So that's a bit harder so lab two split off into a Another harder section that's worth 20% where you get to become a sub reaper for an additional 20% So if you don't want to do that it's worth 80 if you do then yeah, you get that extra hundred So let's have some more practice today with processes So also based off the lab zero feedback a lot of you are very ambitious and want to write your own operating system Unfortunately, that is not where this course is aimed at but where this course is aimed at There is a teaching operating system written that's used for MIT graduate courses that at the end of this course You can completely read and understand everything that's going on with it. So it's pretty basic. You'll actually learn everything behind it The goal of this course is to like help you help everyone be a better programmer not those that just want to do operating system So the goal of this course you should be able to read this But you should be able to write better programs at the end of this course So we're more at the system call layer. This is like a lot of kernel code. It's a full operating system You can read it you can there's instructions in the repository if you want to run it in the VM So it will also run as a VM. So it will run as a VM in your VM So like there's VM inception if you want to write it and it's basically a Reimplementation of Unix v6 which is really really old, but it's still a functional thing It's done in C and any the minimal assembly it has to do is all done in risk 5 So if we go ahead and we could read some of it if we want so like for instance This is the init Process from that. So this is what they compiled to create a knit and we should be able to read it all There's just some magic so they open a file called console Which is a magical file that represents a serial console that you can read and write bytes to so you probably hopefully Maybe have done that in computer organization where there's some magical memory address You could write a byte to and read a byte from and it would communicate over a wire or something like that So that's what this terminal would represent or this console would represent and then was it what it does is it? Duplicates that file descriptor that it opens twice So dupe just copies that file descriptor and creates a new file descriptor So this would create a file descriptor one that points to that same console This would create file descriptor two that points to that same console So now this process has file descriptors zero one and two because it's a knit that Convention of having those open doesn't exist because you're a knit you have nothing you have to start that convention So it starts the convention and then it has a for loop here That's prints off what it's doing Remember one of the responsibilities of a knit is it has to create other processes. So this one It's a very dominant. It will only start a shell So it says prints F and then starting shell then forks And then it checks if there's an error if there's an error it bells out and your operating system doesn't exist Otherwise it checks if it is the child process. It execs sh, which is the same thing as our exec VE So it tries to replace the child process Load that sh program and start executing that So if it's successful the child process gets transformed into sh Otherwise it they know there's an error So just get print off it failed and then bail out and then and it's dead and then your program or your operating system is dead Otherwise they have this for loop that says that's just wait and before we had a pointer to To keep track of our W status. They don't care about reading the exit status. So this Pass a null pointer in there. So it won't update anything, but they just call essentially while true weight The only exception here is that they check if the process that just terminated is equal to the shell I created well, then it wants to restart this shell So we'll break out of this and then launch create a new process and launch and exec sh again Otherwise if it returns an error it bails out and then otherwise it was a parentless process We just cleaned it up. It was a zombie So we just cleaned it up not a zombie got rid of it all good And that's literally all and it does in this operating system. So like I said, it doesn't have to be that complicated This is what a knit does so With that detour we can talk about old operating system since we're on that topic So for old DOS if you've ever heard of DOS So you guys are probably way too young to ever hear of DOS, but that was back in the day when CPUs were like a mega hurt at best and couldn't really do much. So There's this old-style programming called unit programming and that meant only one process ran at any one given time Can imagine how life how boring life was back then so you can only do one thing at a time So if you wanted to play a game you launched your game You couldn't do anything else until you're done with your game and then you could open your word processor Something after you close your game. So alt-tabbing when I when your boss comes in doesn't work So that was the system back in the day very very simple You don't have to do any special scheduling or anything like that process runs. You let it run till it's done It's done you run the next process So multi-programming is what we have today. You can have multiple processes and they can run in Parallel or concurrently which if you just understand English you're like what the hell those are the same words Why do you say or once we get into threads? We will figure out that computer scientists like to overload words So those two things actually mean slightly different concepts So we'll get into that when we get into threads, but most operating systems They want to run everything in parallel concurrently use all your CPU cores go as fast as possible Use your hardware to its maximum capability So the part once you have multiple processes you have to decide what to run at any given time And that is the job of the scheduler that will be going through for the next few lectures So remember when we create a process and we load a new one the operating system at least has to load it into memory Then it gets put into that waiting state and then the scheduler which we'll see in the next two lectures Decides when it gets to run, but first we have to Focus in on the mechanics of actually switching processes that we've kind of touched on before So the core scheduling loop is going to look something like this It's going to pause the current running process and then save its state so you can restore it later That would save all of its registers right because we're representing each process is having its own virtual registers So its state would at least include the registers that it currently has Then we would ask the scheduler. What is the next process? We're supposed to run again Well, I answer that question later Then after that we load the next processes state and then let that run So we would just do the reverse of saving the state. We would essentially set all the register values Yep So this is on a single CPU core that can only run one thing at a time, but you could have multiple CPU cores running different processes But yeah, this is just for a single core. This is what would be happening on a single core so Also, once you have multiple processes, you can schedule them in different ways So one ill-advised way is to let them kind of schedule themselves and that is called cooperative multitasking So a process uses a system call to tell the operating system explicitly Okay, you can pause me now if you want to run another process Well, that's probably a bad idea in general because if someone just wrote a program that did never did that system call It kind of goes back to that old unit programming way where that program would just run until it's done and The operating system would have no control over it. So you might hear this term Basically, that's only Possible if you are writing every single program on your machine and you want to be very precise about one thing switch You could do something like cooperative multitasking on like very small embedded machines or things you have control over But in general operating systems need to do something called true multitasking where the operating system or the kernel Maintains control and pauses processes. You don't get a say about it So for true multitasking is two different ways. It could do it The kernel could give each process a certain time slice and then just give you the CPU for that amount of time Once your time is done, then it can switch to a different process Another thing can do which is what pretty much everyone actually does is you can wake up periodically using interrupts to do scheduling so if you have in if you're in kernel mode and you have control over the CPU you can set a timer interrupt and You can say hey CPU after I don't know like a hundred milliseconds Please send an interrupt to me. So I the kernel can handle it and in part of handling it I will pause that process and take the CPU away from it. So that's the way kernels actually do it They just set the timer interrupt they get control back and your process doesn't get any say in the matter So that mechanic of like saving a state and loading a state that Swapping process is called context switching. You're gonna have to know this term that term for this course We'll probably say it all the time So at minimum as part of a context switch, we have to save all the current registers Save all the and we're saving all the same values using the same CPU. We're trying to save Them on so we have to save them to memory, which is going to be of course fairly slow and Also on some CPUs on like modern x86 CPUs the number of registers you have is like Insane so there's special registers for floats or special registers for vector instructions There's so many registers on your actual machine whether or not you know about them So sometimes that is slow if you just save literally every single register on your machine if it's a very complicated CPU That could be really slow So Because that's really slow and your program might not actually use it You don't really want to save everything So if the kernel can keep track of your process and say for example Your process never uses floats so it never uses any float registers Well, then that software can be smart and be like okay. This process doesn't use floats I'm not going to save the registers because they don't use them. It doesn't matter So why would it do something like that? Well? Context switching is pure overhead and what this overhead mean it means your kernel or Operating system is not doing something you would like it to do. So it's just wasting time anytime you spend just Physically saving some state and loading some state that is time that is not spent Running applications, which is all you really want your operating system to do is just run applications So the longer the context switch weights the more unhappy you will be because your hardware will be wasted, right? If it's spends like 50% of its time just saving state and not running your program Then you're going to be fairly mad and probably switch operating systems Want to be as fast as possible typically I mean like 99% of the time your kernel should be just executing applications so kernel needs to be Really good curl needs to be smart save as little as possible without destroying anything So now we can get into some more fun IPC stuff So we're going to learn another IPC mechanism called a pipe. We saw that before When I showed you that bar character. Well, that is actually a system call and it's actually called pipe So it looks like this the system call takes as an argument an integer array of two ints and Just returns an int and it only returns whether or not it failed. So if pipe returns negative one Means it failed it sets error. No, it does something. We all know it does and if it returns zero It means it was a success and it is going to write values to this array What values is it going to write? Well that pipes going to represent a two-way communication channel and you will get two file descriptors so In that array the file descriptor at index zero is going to represent the read end of the pipe Which means you are allowed to do read system calls on that end of the on that file descriptor The file descriptor at index one is the right end of the pipe and you are allowed to write data to that and To that end of that pipe through that file descriptor and the rule is Whatever you write to the right end of the pipe if you try and read it you'll read it from the read end of the pipe so You might think that's a bit weird, but you can think of it as a kernel managed buffer and We'll see that that's actually useful for IPC. Yep so a file descriptor is just a number and The kernel uses it to represent something that you can read and write bytes to So you know what file descriptors because Like open returns a file descriptor and it just returns a number that represents what file descriptor that is Right you used it in lab one Yep, so for this Pipe so the questions how does the process determine what file descriptors it's using? so By default we have the like zero one and two and you're You're expecting those to always be open aside from that the valid file descriptors are whatever you create so you can create it through an open system called a open a natural file like a real file or Through this pipe, which doesn't represent a real file But things we can read and write bytes from right so but you control them and your file descriptors are independent of the process and We'll actually use this and see that Because yeah, it might not be obvious just by me saying it doing is always better All right, so yep. Yeah question is why is it just take a Array instead of just two individual in pointer arguments and That's because that that would take two arguments. This is one. This is clearly more efficient, right? Yeah, it they could have done that it doesn't really matter They just decided to do this because they thought was easier But yeah, yours would probably be more flexible if you didn't want them beside each other for whatever reason All right, so quick another aside, so using that ampersand in your shell We can actually explain that now So if you use ampersand at the end of your command your shell starts that process in the background and returns immediately So it just creates a child process and then it gets reparented because the parent immediately dies Or sorry, it doesn't get reparented. So it just starts it in the background and as a child of your shell And just lets it run until it's done and it will tell you whenever it's done kind of asynchronously So it will output the PID whenever you create it and let you know when it's actually finished so with that and this Character will create a pipe between two processes we saw before so we can actually explain this fun fork bomb Remember that was like while true fork. So this is a valid But you can apparently name bash functions a colon So this defines a function called colon taking no arguments and what it does is Call colon pipes it to another colon just to glue two processes together and then does an ampersand Run that in the background. So every time this comes through it creates two processes So this ends the definition and then it calls colon once So each of these will create two process and then create it and then we'll get that exponential explosion And you know, you'll probably have to reboot your system. So do not run this command. Yep So a colon is the name of the function. We're defining so we define a function called colon and Why because if you give this to fourth year, they'll if you call it fork bomb or something like that They might not run it, but if you just call it colon, this looks kind of like it might be fun to run this, right? Yeah, no Yeah, yeah, but they're not gonna generate any input Yeah Yeah Yeah, it's just a way to glue them together so that this ampersand affects it launches two processes So every time I call this I launch two processes. Yeah Yeah, so this is basically while true fork So this is while true fork, but it's like insanely difficult to read and you can probably like Not to say you should do this But you could trick it if I gave this to you while you were taking 105 and told you to run this You'd probably do it, right? The depends how much some of you trust me Maybe you would never trust me anymore after this course, but you might have might before you've known me All right, let's just see the exit. So let's see the pipe example first cuz I'll be like the whole lecture. Yep Have I seen the effect of that? Yeah, so that's a configurable thing your kernel is Smart enough to detect that depending on how new it is and your security Whatever you have so if you try do not run this on any university computers But they're smart enough to know things like this will happen They'll probably limit you to like not that many processes Which is one of the reasons we're using a virtual machine because we can break it We don't really care like if you run this and you go a crap You don't have to physically restart your machine like you guys made me do You can just restart your virtual machine, right? No harm. No foul. So that's why we like virtual machines in this course Yeah The fork. Yeah, so this the last colon there that says like if this was called fork bomb This would just be called fork bomb. It's just runs it once, right? So it this sets it off. So this is like this like defines the function and then this calls the function Yeah, yeah, yeah, so if I rename this to fork bomb I would have to rename these to fork bombs and then it's really long too and If you see fork bomb like four times You probably wouldn't run it see four colons. Yeah Some of you might yeah, most people don't know that colon's a valid function name for whatever reason All right, but let's go into the fun fork example. We can break our brains with today. So Here is our main We're going to Declare a integer array of two ints called fd's then we will call pipe Now I wrap everything in this check function. What does this check function do? Just make sure that it returns something other than negative one if it returns negative one means there's an error So I'm just going to have some error handling code here because I don't want to write this like 10,000 times So way we see check I'm just checking for an error code. We'll just assume everything works So I call pipe So what that will do if it is successful and I will assume it is it will create two file descriptors So we'll have fd's at index zero and then fd's at index one so Because this will create two new file descriptors and we know zero one and two are already open We can probably argue about what the actual number will be because we know how the kernel works So what number file descriptor will this be? likely three and this represents the read end of the pipe This one would probably be file descriptor four and it is the right end of the pipe So Right after we create it we fork So we create a new process It is a complete clone at the time of the fork. So at the time of the fork this process has Five file descriptors open zero one two three four five or sorry zero one stops at four So the child process is also going to have all those file descriptors open so you can think of the file descriptors as Just the number the kernel knows what they actually point to but the kernel kind of treats them like pointers So for both processes both processes have file descriptor four And that's independent for each process But in each process file descriptor four points to the same thing. So they both point to the right end of the pipe So what do we do? Well, let's see that we can actually transfer some data between these two processes So we check the PID if it is greater than zero. We are the og parent So we just declare a string called howdy child. I don't know why I'm Texan today, but I am Take its string length and then we will write to fds one which is the right end of the pipe And we'll just give it the address that starts the string Tell it to write the length of the string and then we will check for an error After the parent's done it would close both file descriptors return zero Returning from main is the same as calling exit. So if you want this could be exit zero if it makes it easier for you to read Otherwise if we are the child process we declare a character buffer of this magical size We do a read system call from the read end of the pipe Give it the buffer say the maximum size we have Number bytes read and then we will just print off the message we receive from it So if I go ahead and compile this I'm in the wrong directory I'm in that one Whoops Okay, so if I run that I get child read how would howdy child so That came from the child and the child That just read it didn't write or anything that string just magically came from the parent So the parent Wrote that string to the pipe You can think of it as just being a buffer that's managed like memory That's managed by the kernel that it protects and makes safe that so you can only access it through a pipe So that would be managed by the kernel and then the other process comes back Reads from that pipe and then it reads whatever we the other process wrote to it So it's communicating across processes Cool Any questions about that? Yep So yeah, there's a question Do I have two files then so The name file descriptor is unfortunate because they don't actually represent files So remember file descriptors just represent something you can read and write bytes to through system calls They don't necessarily represent files. They can but it's not necessary in this in this In this case, this doesn't represent a real file So a pipe is basically just some memory managed by the kernel so That fulfills like what I can do with a file descriptor because I can Read information to it and then the other one can read information from it But yeah, that's a good question. But just because they're called file descriptors doesn't mean they're files All right, any other questions? Yep Yeah, so that's a question So, uh, the question is if they have virtual memory, how can one read the other so Remember these processes are completely independent Just their file descriptors point to certain things And that memory this when I write in this process howdy child It doesn't actually exist in like it does exist in this processes memory But after it writes it it writes it to the kernel and then the kernel is managing that information So after it writes the kernel like copies that memory And puts it somewhere in a safe kernel space In some kernel memory so that another process can access it So they're still independent, but I wrote that information to the kernel and it can do magical stuff with it So I wrote Howdy child to that and then if I modified it after the right call I mean the child process would never see it because that would be in my virtual memory But if I write it I write it to the kernel and then it's written to the kernel the kernel gets to manage it And then in the other process I read it that it reads it into that processes memory. Yep Yeah, so the question is can I just do it the other way, right? If I just have a right in the child and a read in the parent Yeah, so if you had it the other way that also works. That's fine So what makes it one way is that whatever I write to it like whatever I write to it pops up in the read like I can't I can't write to the other end of the pipe. So I can't write anything to fd's at index zero so That way they're communicating one way with each other if I wanted like One process to send information to another process and then that to send it back like simultaneously I need two pipes so I could switch it we're like One writes and the other reads all the time, but I can't intermix them together without having two pipes Yeah, I could have one read then write then write then read, but then I'm like Yeah, it's just kind of weird Yeah, that's true. They can only send messages in one direction at a time. Yeah, but if you Had some protocol and you only sent messages in one way at a time you could do that But in general you just make two pipes and then you don't have to worry about like Making sure you're reading when you should be and writing when you should be That's a good question. So how did I make sure the right happened before the read? Anyone tell me did I have to do that? So I argued Huh Anyone why don't I have to do that? Yeah the read blocks Yep Yeah, so here If the child happens to execute first Before the parent writes anything It would hit this read and reads a blocking system call. So the kernel knows that Hey, this read represents a read of a pipe Someone could write data to it. No one has written to it yet I'm going to wait and only return from this read whenever someone has written data from it So this process will just go to beddie by time go to sleep and wait for another process to call write So this would go to sleep It state would get saved and then the kernel would decide another process to run even on a single cpu And then it would be like, oh, yeah, I should probably run this process There's probably nothing else to do it would eventually call write And then it calls write and then the other one can return from read Yep, why couldn't we switch them? Oh, sorry. I deleted Wait, so what do you mean by switch them? So they're just two integers, right? But they are two integers that mean something very specific if you talk to the kernel about them So if I tried to hear if I tried to do a write of fd zero, which is file descriptor three The kernel is going to give us an error that's like you can't write to a read only file descriptor So the kernel manages it so Because they're just integers, right You could make them up to if you want like I could try and write to file descriptor 10 But the kernel manages that it knows 10 doesn't mean anything right Okay Oh, yep So the question is does that mean I can't write twice for a pipe? And you can write as many times as you want to a pipe in this case The child process wouldn't see it because it only returns from read once But in general if you wrote a application that was like communicating constantly It would have to constantly call read over and over again Yeah, but you just call usually you just call read and get all the data that's available for it Yeah Yeah, so that is another good question So the question is well If the parent goes through before the child does anything and it closes these file descriptors Won't that mess up the child? Anyone else answer that one? Yeah, so remember all the processes are independent so in the parent Both the parent and the child have like file descriptor three and four open And they point to the same thing so In the parent file descriptor three points to the right end of the pipe to the kernel and To the kernel the child its file descriptor three also points to that same read end of the pipe So if I close file descriptor three in the parent it only closes file descriptor three for that process Not the other one In fact, let's break something. Yeah So it closed Is the kernel saying that you are done with that file descriptor you don't need it anymore So that means if you close a file descriptor Like you can't access that anymore. You can't read to that. You can't write to it or anything like that Right, we'll see why that's important in a second. So let's See Well, people have asked some good questions. So what happens? I'll ask you a question. What happens if I do that? So I commented out the right in the parent For full marks who wants to tell me everything that is going on here Okay, something's blocked forever. Yeah, uh-huh Yes Okay, and for full points What is that child process? Oh, I heard the magic word so An orphan right so the parent goes away The child is going to be stuck on this read call and then its parent goes away What happens to it? It becomes an orphan. Where do orphans go? Killed though they go They go to the reaper which is a knit which is process ID one So If I go ahead and compile this And run it I should have created an orphan, right? How do I how do I check if I created an orphan? PS well, but if I just want the process ID of whatever this is called There I'll just there's pit of pipes So you could actually write this now after doing lab one, right? All it does is take a name and then prints off Like looks in proc Sees any processes that match that name and output their process ID You could write this now. So I see that hey Process ID this is still running that is called pipes if I go ahead and Whoops, not you If I go ahead and look at it. Well, it's my process. It's sleeping Its parent process ID is one. So it did get Sent to a knit This is kind of chill in there. How do I get rid of it? Kill dash nine Someone woke up and chose violence today yikes So I could just normal kill it and it should work because I didn't ignore it or do something bad But I could have killed dash nine it if I really hated it So That's fun So what if instead? So let's really get our file descriptor knowledge here. So Remember what I said about file descriptors where like you should close them if you're not going to use them anymore So in the child Well, when the child starts executing it has the right end of the pipe open, right? So And does the child ever use the right end of the pipe fd1 Yeah, does the child yeah, does the child ever use it? No, what should I do when I'm not going to use it? Close it Close fd1 And then after I do my right I can go ahead and close fd0. So If I go ahead and I program correctly, we're going to notice something magical Whoops, so if I run pipes again Whoops, oh, whoops. Sorry. I'll keep this commented out So the parent isn't writing anything No, something magical the child has returned It's not running Well, it printed this child read line. So what must have happened? Yeah, it read no bytes So if read return zero that means what? And the file or in more generic terms it means it's not possible to get any more input So the kernel is very smart So the kernel knows it keeps track of all the file descriptors for all the processes So the kernel is very smart And if no process has the right end of the pipe open It knows it is not possible for any data to go into that pipe So it is not possible for any data to go into that pipe and you have read it all already That's the same thing as end of file or no more output is possible So in this case, it's not going to sit in read indefinitely Because eventually the parent is going to close the right end of the pipe And in the child before I call read I close the right end of the pipe So when read returns, there is no right end of the pipe open And I am free. I get an end of file character or no more input possible I read nothing. I close. I do not create an orphan. I do things properly Any questions about that? Yeah No, so file descriptors are just valid for a process So they're independent in a process and whatever they represent They might represent the same thing as another process Yeah, so in this case Right right after the fork Both processes have all the file descriptors open So the child has file descriptor three and so is the parent In this case Right, they all file descriptors open. Yep So yeah, the question is well When it slept forever So it would sleep forever in this case So if it slept forever can't the operating system be smart enough to know it will sleep forever so Because the only process that has the right end of the pipe is the process that is currently reading, right? So you might think it is impossible for it to Somehow write data Not true So even though it is doing a right What happens if I send a signal to it? It does whatever the handler does, right? If I wrote a silly handler, I could write I could technically write a handler that wrote to the right end of the pipe And because that is technically possible Although otherwise insane The kernel has to be super conservative and say because that is a possibility There's no way I can tell with a hundred percent certainty that will never happen Then I can't just end it I have to let it read because maybe in a hundred years That happens or something like that, right? So the kernel has to make sure that it does things correct So even if there's a point one percent chance that something weird will happen It has to account for that Which is why this course has a bunch of education cases like that, right? You would clearly expect that You look at this program can probably analyze and be like that's not even possible But the kernel can't analyze your program and know what that what it's going to do. Maybe, right? But yeah, that is a good question But that's why it has to close its own right end because then it negates that possibility Cool Cool. All right. So we do not have that much time But we will do an exam style question We can follow it up on the discord if you want or see it on the other but Quickly for so people love asking these in exams. So for each program State whether it will produce the same output each time it is run Or whether it produces different outputs when it's run multiple times So that means if I run it once Will it behave the exact same way if I run it again? So This thing Looks scary because there is a while loop with a fork and last time I showed you a while loop with a fork It was a fork bomb and it went on forever. Thankfully this one has some limits So To start arguing about this one. Well, we can create a process a hundred That is created for us that starts executing main so In main This process declares a variable called I and sets it to something like four So it would then check this while loop does I equal zero? Nah So I is not equal to zero. It's going to fork. So that is going to create a new process I will call it one oh one because I'm super Creative So at the top I will put the parent child relationships. So parent 100 is the parent of 101 so 101 is going to be copy of process a hundred at the time of the fork. So in 101. Do I have a variable called I? Yes, what's it equal to? four All right, which one is going to execute? Which one is my curl going to execute process 101 or 100? Either of them have no idea So I'll pick 100 for example So In process a hundred. What did it return from fork? Process a hundred what did fork return? 101 So it creates a variable called PID assigns it to 101 It would go into this else Print off whatever four is so it would print. I'll put it in black It would print four and then it would exit zero. So it's now done So If we so that process is done now we have no other choice But to run 101. So what did 101 return from fork? zero So 101 Would create PID it would be equal to zero it would check it with zero is p is zero equal to zero Hopefully hopefully on this planet So it would subtract one from i so i would go from four to three and then It would go here to the wow So that PID variable goes out of scope so we can delete it. So now does i equal zero? No, so we go into the loop We go ahead and we create a new process Again, this is why I don't get to name children. So I'll call it 102. Thank thankfully. I don't have to give it a name Is it going to have a variable called i? What's it going to be equal to? three And at the top 101 created 102 So now Which one's going to run? I have no idea Let's say 102 runs While 102 fork would return zero So PID would be zero So it would subtract one from i And go three would go to two So because I did this do I also I subtracted one from i should I do this as well? Why? Yeah, they're different processes. You can't affect memory like that on a different process. They're independent So that wouldn't happen So 102 would go PID would go out of scope it would fork again eventually Creates 103 We don't really have time Eventually this thing is going to go. So the tricky part is when it creates 104 in 104 It will decrement i will become zero and then in 104 In this while loop it's not going to execute. It's going to fall through And it's going to hit this return returning for main is the same as exiting So 104 is just going to die And then as a spoiler this process prints three two And one And you don't know what order they're going to print in so they're not the same Yeah, so each time you run this you might get a different order So you might get four three two one If you get unlucky you might get one three four Whatever I didn't say Or you might get three two one four right the order is not specified Yep Some So yeah, the next question is hey, it just throws whip weight pid into it And guess what if you throw a weight pid before the print it will always print one two three four And we can leave that for discord and stuff like that. So just remember pulling for you