 All right, good morning everyone. So today we get to talk about the final piece of the puzzle from that Hello World program we saw last lecture. So by the end of, it will take us a full week but we'll finally understand Hello World in its entirety. So our main abstraction this course, everything you'll be dealing with, even if you don't know it yet is something called a process and that is a instance of a running program. So a program or I kind of said it interchangeably an application is just a static definition. So it's that executable file, that L file. And so it's just a static file that contains things like the instructions that make up the program, any data associated with us with it. So for us, it was Hello World in that really small program. If you were writing in C, all your global variables, all of that would be defined in that executable file. You have memory allocations, there are symbols it uses. For example, everyone's been using the C standard library. One of the things that's also in that file is all the functions that you use. So the operating system can go ahead and find where they are. Like for example, print F, everyone's used that in their C Hello World program. So those are the things that make up a program. It's just a static definition. You can think of it as just a file. It just contains all the information you need to actually run the program. And then the kind of mechanic of actually running that program is called a process. So that is a executing program. And what the kernel's job is, which is the kernel is an undisputed part of the operating system. So if I say operating system and kernel, I'll say them interchangeably, but the kernel is definitely part of the operating system and then what's in an operating system gets kind of sketchy. But 100% no one will argue that the kernel is part of the operating system. So the kernel is managing processes which are just running instances of your programs, be it whatever you've written, right? Including all the lab one stuff. So you can think of a process as a combination of all the virtual resources that will be continually building up, but it's kind of what represents everything and what we'll be dealing with. So our easiest abstraction is like the virtual CPU. So the kernel or the operating system would at least need to track all the registers like the program counter and any other active registers. But the program counter is probably the most obvious one. So it also contains a bunch of other information. It contains all the resources it can access. So different memory addresses it can access. And then if you try and access something you're not allowed to access, that is a seg fault that we all know very well by now. Also tracks other things like IO. So files you have open, if you have network resources open, it's all tracked at a process level. So it's keeping track of everything you need to actually execute a program. So every execution runs the same code, right? You can have multiple executions of the same program all running at the same time managed by the kernel, but an execution itself. So the process is running some specific piece of code at any given time. So like the program counter is at a certain spot in the instructions. And if you have two processes running the same program, the program counter can be at different places in each of those programs. And we'll see more of this as we actually learn to create processes. But for now we're just gonna kind of stay fairly high level and try and understand it. So why would you do this? Well, processes are pretty flexible, right? They contain all the information you need to know to actually execute something. Contains loads the program, contains all the information you need to actually execute it. You can, given this, right? You can have multiple executions, like I just said, of the same program. So if your application ran for, I don't know, 20 seconds and you said that was too slow, you could have like changed terminals and then ran it again. So now there's two instances of the same program running, but they're in different processes. So they're at each process is executing a different piece of code at any given time. And that allows you to run multiple copies of a process which you may or may not take advantage of. In this course you will take advantage of it, but so far everyone's pretty much just written a program, compiled it and then ran it once, right? No one's really done multiple processes. Well, unfortunately for you or luckily for you that changes now. So to understand a bit more about the process, if we want to define it a bit more, if you want to talk with other kernel developers and sound like you know what you're talking about, a process control block is just that structure that contains all of that information specific to an execution. So if you want, you can go actually, the great thing about Linux is it's open source, so you can just go read the code if you really want to know what makes up a process. So in the kernel it's called task struct and that link is clickable. If you want to browse it on GitHub, it's just a struct, right? Because the kernel is written in C, but you can see it has like, I forget how many variables it has, it has like hundreds of different variables like keep track of different things you would need to keep track of when you actually execute a program. So some of the things that you will need to know that it needs to keep track of is CPU process state, like what state it's in, which we'll see in a few slides, CPU registers, so at least the program counter, scheduling information, so maybe how long has this program been running? If I don't want to have it monopolizing the CPU, for example, any memory management, which is a later topic in the course, IO status, so anything you have open, like any files or anything like that, and any other type of accounting information just all goes into the process control block that you need to make decisions. Okay, so first I have to get into a side because no one's really used threads. So luckily or unluckily for us, in normal English, doing things in parallel and concurrency mean the same thing because we are sane rational people, but computer scientists and computer engineers, we like to make your life difficult, so parallelism and concurrency are absolutely not the same thing if you are talking to a strange computer person like we all are. So if you take the computer definitions of concurrency and parallelism, concurrency is being able to switch between two or more things and then making progress on them, right? So you want to make progress at multiple things at a time and we'll show an example after. Paralysm is kind of more what the actual English definition means. It means running two or more things at the exact same time which means they are independent, right? And you want to run as fast as possible. So we've been getting more and more CPU cores in our machine and you want to be able to utilize all of them as effectively as you can and you just want as much running in parallel as possible while back in the day, if you only had one CPU core, you just have to run things concurrently to have the illusion that things run in parallel when really only there's one CPU executing instructions at any given time. So let's take this convoluted real life example so you'll have to bear with me a little bit. So let's assume we are all polite individuals and you are sitting at a table for dinner and you have four options of what you can do. You can either eat, you can drink, you can talk or you can gesture with your hands and try and illustrate something. But the kind of weird caveat here that I need to use to demonstrate stuff is you're so hungry that if you start eating, you just won't stop until you finish, right? So you won't be able to switch what you're doing. So given that, what tasks can be done concurrently in parallel? So let's see. Can I drink and gesture in parallel? Hands up if you think I can do those in parallel. Okay, yeah, most of us can do that, right? Because they're two different resources, right? I can use my hands and talk at the same time. Drink is similar, although I have to gesture with one hand so let's assume that it's the same case. What about if I want to eat and drink at the same time? Can I do these in parallel? All right. No, right? I have a mouth. Can I do these concurrently? We have a few hands. Okay, most people think we can do these concurrently. Well, this is kind of this bear with me thing. So we're so hungry we can't stop. So we can't do them concurrently because if you start eating, right? You won't stop until you're done. Well, if you, and then you can't switch, you can't drink. So you can't do them concurrently because you just can't switch between them arbitrarily. Let's see, can you drink and talk in parallel? Hands up. All right, can't drink and talk in parallel good because we're, although some people might be able to do that, but again, we were trying to be polite. All right, can we do these things concurrently? Yeah, so we don't have this weird caveat. So I can drink, put it down, talk, drink some more. I can make progress of these two things, even though I can't do them in parallel. And then the final one is, I think, which one is left? All right, let's just go through them again just to make sure, because generally if you can do things in parallel, you can do them concurrently, but it's kind of a weird thing. So we can't eat or drink and talk at the same time and you can't switch. So you can't do those in parallel because it's the same mouth resource and you can't do them concurrently because if I'm eating, I can't make progress on the other one. And if I'm talking, I can interrupt myself while I'm talking, but I can't make progress on the eating thing. So you're taking these two tasks kind of together. So they're only done concurrently if you can just switch between them. So I can't talk and drink concurrently because if I drink, then all I can do is stop drinking. I can't make progress on the eating unless I just do the entire thing. So I can't switch between them. So yeah, and then eating and gesturing at the same time, but not switching. So I can do them in parallel, but not concurrently. I can drink and talk at the same time, but I can switch. So I can't do them in parallel, but I can do them concurrently. And then for talking and drinking and gesturing, I can do them in parallel and concurrently. I could pick one up and I could stop doing one, make progress to the other if I wanted to, but I could just run them in parallel. So in general, if you can do things in parallel, you can do them concurrently. There might be a really weird situation where you can do things in parallel, but not concurrently, but you probably won't run into that. All right, any questions about the difference between parallelism and concurrency? Because when it comes to threading, this will be a major issue. And this topic is actually the source of many, many bugs in the Linux kernel. So most of the bugs that have been sitting there for like seven years is because someone screwed this up. So and unlucky for you, you will also have to debug things like that because you will have to be writing this type of code. But today we will save you. Yeah, yeah, yeah. Yeah, you can kind of think you have two resources. You mouth in your hands. Yeah. Okay. So we have to take a, because this is operating system course, we can kind of go back in history a bit. So back in the old days, I get to age myself down and finally feel old. Who here has used DOS? All right, one guy. Know what? Yeah. So DOS back in the day was like one of the first operating systems you could use on your machine. It had like a little floppy disk you put in. That's your operating system. And so that operating system being one of the first ones was something called a unit programming one. So it only ran one process at a time. So you couldn't do anything in parallel and you couldn't do anything concurrently. You just ran one process until it finished and then you could switch and do a different one. So you can't do them in parallel, especially back in the day, right? They only had one CPU and you can't even do it concurrently. I can't switch. I have to wait until my program is done. So, you know, if, yeah. So two, and two people definitely can't use it at the same time. So what we have now is called a multi-programming system. It allows multiple processes and that means that any two processes could run in parallel or concurrently depending on the hardware and the kernel would have to support this. So if you only have one CPU, right? You can't run anything in parallel. If you only have one CPU, you'd have to run everything concurrently. But if you have, you know, eight CPU cores, you can run things in parallel and you can't really get away from the concurrency thing. So on your computer, there's probably hundreds of processes running even if you have a very beefy machine with like 16 cores. Because you have a hundred processes, you can't run them all in parallel. So some are going to have to run concurrently. And, you know, if you are a kernel developer, you need to run as much in parallel as possible to kind of take advantage of the hardware. Otherwise, you're just kind of leaving performance on the table. So this is our process state diagram that you use if you are in the operating system. So this is kind of the life cycle of a process. So first, a process is created and the operating system, you know, loads it into memory. So for that Hello World example, it would have to load that 168 bytes and then it can go ahead and set up, you know, the initial program counter and then it goes into this waiting state which also could be called ready, which basically just means it's ready to run. So we will get into how to create a process in the next lecture, but for now we just kind of need to understand the life cycle of a process. So once in this waiting state or ready state, it is able to run but the kernel has not selected it to run and then the majority of the life of a process is just going to be pinging back and forth between running and waiting. So you are the kernel, you get to decide what processes run when. So there'll be a bunch of processes able to run kind of in this waiting queue. You will pick one, which is the job of the scheduler, which is, you know, another lecture and then you will run it for a unspecified amount of time and then you will put it back to waiting. And then in some cases, while it's running, it might go to this block state, which basically just means it's requesting some resources or like it's reading from a file or something that is slow and it needs to wait to complete to be able to run again. So if, you know, you're running a program, you're requesting a file, right? This has to read it from disk, disks are way slower than CPUs. So it will go into this block state where it can't make any progress anymore until it's actually, the kernel goes ahead and reads that whole file in or whatever it needs to do. And then after it reads the whole file in, it would go back in this waiting state where it can go ahead and run again. And then at some point, it will go from the running state and go to that terminated state. And that's when it would do that system call for exiting or exit group. So the exit group call transitions you from running to terminated and then that process is dead anymore, dead now and it's done. Yeah, or you can think of, yeah, yeah, pretty much. So waiting can just, you can think of a queue of everything that wants to run and then we'll get into how to decide that later. But yeah, so the job of deciding what's to run. So basically take out of that queue is called the scheduler and that decides when to switch and then what to switch. So when you create a process, right, you have to at least load the entire thing in the memory so you can execute it. And then when it's in this waiting state, the scheduler again coming later decides when to put it in the running state. And but first, before we even get into deciding, we'll just go over the quick mechanics of how you even switch what process is running at one particular time. So this is the core scheduling loop that changes processes. And again, we're just gonna look at the mechanics but overall this is what it looks like. So every single kernel, be it Windows, Mac, Linux, whatever we'll have this core loop as part of it, we'll be executing this core loop. So when it decides it wants to switch it will pause that currently running process, right? So it will stop executing and then you have to save it state so you can restore it again later. So at the very least you would have to, you know, store its program counter and any registers that you're going to clobber after that. Then if you're this, then from the scheduler you get whatever the next process is supposed to be running. And then after you, after it selects that process you just have to restore the state because you put it to sleep, you know, before you restored, sorry, you saved it state before so now you have to restore it. So you would load in the new program counter, right? And then you can now let it run and then this would just keep going on and on and on over and over again. So there's kind of two different approaches for knowing when to actually pause a program. So the first one is called cooperative multitasking which essentially means that the processes decide when to give up CPU. So in these types of systems, which you can already kind of guess is a bad idea, is the processes have to use a system call to relinquish the CPU and then the kernel can go ahead and schedule something else. So the obvious disadvantage of this is if you write a program you want to be, you know, run all the time, you just never make that system call until you're done and then you use 100% of the CPU until you're done and one program can just kind of monopolize the system. So that's one thing you can do if you don't want to kind of put in some mechanics to steal the CPU away, you can just let processes decide which might be okay if you control every single process and want, you know, lots of control over it but in general it's a pretty bad idea. So most kernels use something called true multitasking which the operating system retains control and pauses processes and it can do this through something like interrupts that only it can have access to. So if you have true multitasking there's kind of two ways to do it. You can give processes kind of set time slices so just say you give everything like one millisecond, one nanosecond, whatever and then you just constantly, you could round robin between them and you just give them all individual time slices or if you want you can wake up in a dynamic way and give processes more resources depending on what they're doing and have just dynamic time slices for every process and then you have to do something much, much smarter if you are the kernel. So the mechanic of even swapping processes it has a name, it is called context switching. So at minimum, right, we'd have to save all the registers which is also a kind of weird mechanic because you're using the CPU to save the registers on the CPU that it just came from, right? So which seems a bit weird. So can you imagine, you know, you have to write an interrupt routine that tries to maintain the current program counter and then save it state. You know, you would have to do a bit of math to figure out how many instructions you have and then figure out where it came from which would be kind of weird and then you probably clobber a few registers that you actually needed to save to figure that out. So it's kind of a impossible task without some help. So in general, there's some hardware support that will just, you know, you send a instruction that doesn't touch any registers and just saves it to a memory location. So there's gonna be some hardware support for saving state but a context switch is like pure overhead. So it's just being able to swap between two processes. It's not actually doing any useful work that you care about, right? It's just swapping between two user processes while if you're using your computer, you only care about your processes actually running. So it's pure overhead and you wanna make it as fast as possible. So you might not want to save everything and you want to, you know, you can be smart about it and choose what to save and kind of, if you don't need to save it, you don't save it and you might, you'd need to do that in software because it would be conditional. So there's some tricks in the operating system to save as little as possible. So yeah, you wanna minimize it as much as possible. You can imagine if your context switch, you know, took one millisecond and then your time slices was one millisecond, then in that case, you're gonna waste 50% of your CPU time just switching between processes, right? And you're just gonna leave half of your performance kind of doing useless work that your users aren't going to care about. So there's going to be some trade-off. On the other hand, right? If it took one millisecond, you could be like, fine, I'll make the overhead 1% and I'll let every program, you know, execute for 10 seconds. So my overhead's really small. Well then, yeah. Oh, so overhead is just time wasted doing not useful work, right? Just, yeah, just how much time is wasted doing something that you don't really care about? So yeah, if you had, you know, one millisecond to swap and then you gave everything 10 seconds, that other extreme would also kind of suck because, you know, if you're juggling multiple processes at once, you know, you move your mouse and then it takes, maybe there's a bunch of other processes running, it might take like 30 seconds to get around to that process again and update your mouse pointer, even though you moved it 30 seconds ago, which would also be a terrible experience. So there's some kind of engineering trade-off with how long to run things versus how long your context switches, right? You wanna get some responsiveness but you don't want to waste time. Okay, so let's go into kind of the meat of this course, right? We are going to be living at that boundary between kernel mode and user mode and that is through system calls. But to this point, you've been using system calls but you never really known about it. And actually making a system call like I crazily did in that Hello World example is rare. You typically don't do it. So instead, there's C wrappers for most system calls but they behave a bit different than the actual system call. So C is actually much nicer than doing the system call so it does some things for you. So it sets this, you can think of it for now as a global variable called error no. If anything bad happens, so you can read what the error is and it will just, you know, there's a header file called error no.h and it will have, you know, tell you what every single error code actually means and there's some utilities we'll see that actually printed out. It will also do some fun things like buffer reads and writes just to reduce the number of system calls because transitioning from kernel mode to user mode is fairly slow. So if you can kind of batch up what would be multiple system calls into a single system call, that's better. So that's one of the things the C APIs will do for you. It also tries to simplify interfaces. Sometimes some of the interfaces are made to be very composable because there's only like 451 of them to interact completely with the kernel. So sometimes you want to simplify the interfaces so sometimes they'll combine two of them into a single call just to save you some time. And in addition, they might add some new features which we will see. So one of the examples of features you can have is there's an equivalent for exit in C, right? So in the exit group system call we'll just kill your process straight up but exit in C will not kill your process straight up. It will give you some niceties. And one of those niceties is you can register functions to run when a program exits with at exit. So here, right, let's start reading at main. You can register a function called finie that will run whenever your program exits. So first thing main does is register that which should run when it exits and all that function does is just print a string that just says it's executing that function. And then it should go ahead print do main and then return. So if this actually works then that return zero from main is essentially an exit, right? We found out it pretty much does the exact same thing. So if I go ahead and run this, if I go ahead and run this, so again, this is in the examples directory. If I run it, I should see do main and do finie, right? So I don't explicitly call that finie function. I just register it to happen when it exits and it goes ahead and happens, right? So any questions about this example? Yeah. I've never seen registering a function before. Is it special in OS system? No, this is just C. You can do it. Yeah. Sorry? Yeah, it's kind of like a callback. So it just registers functions to run whenever the program exits, right? Any more questions about this? Or like, hmm. So anyone, if I do that, anyone want to hazard a guess of what it's going to print now? So hands up if it just prints the exact same thing. Okay, one, two, three. Hands up if it just prints do main now and skips the other one. Okay, all right, well, let's just try it. So if I do that, all right, prints the exact same thing. Wait, did I make? I don't know, I didn't. So yeah, prints the exact same thing, but now I see that the exit actually happened, right? Because I get the one returned here. So no matter where you exit from, it will actually run that function when you exit, no matter how you exit. Yeah, sorry, what file? I just registered it at the beginning of main. All right, any other questions or want me to do something else fun? So this, so that's just what the API describes. There's some space in C that will store all the functions it needs to run when it exits and that function called just adds that to that list. Essentially. So it's part of a nice feature in C that you can just do that, right? Because if I did a system call, it should just acts the program. Yep, two or three of these, like that. All right, so let's see what happens. If I do multiples of these, they print. And they also print, if you go ahead and read the documentation, they print in a specific order. So if I go ahead and say I do, I'll be super clever here and do one, two, three. So if I do that, there's actually, it will, if you read the documentation at ad exit, it will actually specify an order. So whatever order you register these in, it will undo them in the opposite direction. So if I do that, and if I'm correct, it should go Dufini three, two, and then one because that's the opposite order that I registered them in, right? So if I do that, three, two, blank. Okay, and to see that, this is a C nicety. What I could do too, is I can read the man pages for exit group. And the exit group one is actually a system call. So this is one way to make system calls in C, which again is really rare. So if I go ahead and just do a copy paste on the documentation, oops, include syscall.h. And then what is it, it is, so before I exit using the C one, I can use what is it, syscall, sys exit group. So that will just get me the group number and then let's give it a status of two to differentiate it. So yeah, it's input. All right, so if I do that and execute this, who thinks I will see printfini finished at all, right? Who thinks that it'll just print do main? Okay, so if I'm not lying to you, and this is a system call and that nixes the process, then I shouldn't get any of the nice C additional features, so if I run that, I only do do main and I can see that the system call was actually the thing that exited this because I gave it the status code two. So it was definitely from this system call. So I don't get the nice C features if I just use a system call, right? As soon as execution does do main, makes it to the system call, the process is now dead. Yeah, that's what they defined it as. So in general, that's more useful for people. All right, any questions about that? Yeah. What is the system call? So that's the same system call we did when we just had that handwritten program. So it's just an exit group call which just kills the process. Okay, cool, so let's go to another fun example then. All right, so remember we ran strace on our Hello World program and we got that execve thing at the beginning. Well, now we'll learn what that actually does. So that's obviously a system call and what it does is it replaces the current running process with another program and resets the program counter and all the variables to start executing that new program. So it has the following API, it takes a path name. So the full path of the program to load, which if we're on Linux, now we know that that's an elf file and then it will take what you're accustomed to when you're dealing with C in your main. So this is where argv actually gets set. So it takes as the second argument argv which is an array of strings, which of course is an array of characters. So pointer, pointer, pointer, which is great. And it's terminated by a null pointer. Yeah, it's terminated by a null pointer and then part of the thing your kernel would do, you get argc and argv. So your kernel would go ahead and count the number of pointers for you and then nicely give it to you in the number instead of going through the array. In the format of the environment variables is the same thing. Environment variables are just some more data that you can just read whenever without passing them as command line variables. So as you get more into software development, you'll see environment variables getting used more. For now, we can just kind of ignore them. We'll only care about argv. And then this function, it says it only returns an error on failure because if it's successful, it does what it's supposed to do. It replaces the current running process. So the current running process is now, it's not dead, but it's now running something else and then you can never go back to it because it just deleted all the information associated with that one. Well, sorry. It doesn't delete all the information associated with it. It just replaced and overwrote all the information. All right, so let's see that example because this is even more fun. So if we look at execve, so first off, I'm in main. I'm gonna print f, I'm going to become another process because that's what I said what it does. And then I have an array of strings. I'll put ls as the first argument because I want to execute ls and then null. And then for the environment, I'll just put null. And then I have to specify the full path of the binary I want. So it's in user bin ls. And then I pass argv and the environment. And then either this call should be successful and I expect ls to happen instead or it just returns a negative one. Then here I save the error number because part of the C API is it says if there's an error, it will set that number. So I just go ahead and save it here. And then called pr, which will use this message and then print out a human readable version of that error no. And then I return the exec return, which is the error no. So I save the error no here because funnily enough, p error can also have an error which would overwrite the other one. So I want my, I just want this program to return an error code that corresponds to the first error that actually happened. All right. So can anyone guess what happens when I execute this program? Anyone? Yep. Yes, but it should also print the first line too, right? Yeah. So if execv works kind of how I described, it should say I'm going to become another process and then go ahead and do whatever ls does. So there we go. And I have a line printed here, right? So if I was truthful, right? It's either going to be successful here to become another process in which case the process will die when ls dies, right? Because somewhere in ls it's going to call exit or it's going to have an error here, fall through and return this. So this line should never, ever, ever execute because it's either successful, it gets transformed or there's an error and it prints it out. There's no case this is actually dead code where that would ever execute because there's only two options, right? Either it works or it doesn't. So let's see the error case. I can call this bin dc344. If I execute this, I'll go ahead and see the first part of my error message and then it'll say no such file or directory because that just doesn't exist, right? So then it goes ahead and returns, it's associated with the number two. All right, any questions about this? Yeah, it transforms the current running process into ls. Yeah, into whatever program you have here. Yeah, so if this is successful, right? The current running process that's running main is now gone, it got replaced by ls. So now it's, whatever was running main now just got replaced with ls. Yeah, yeah. Or no, yeah. So it just runs, it just does ls to a system, OS system, but it doesn't run the files within the system by ls. So it runs ls, so it should give us the exact same thing as if we type ls, but in this case it's a bit different, right? If I run the two, whoops. I didn't compile it. It's a bit different, right? Then running ls, my ls one has colors, but that's because if I do which ls, oh, it's alias to ls dash dash color. So if I want it to match exactly because it's actually an alias, I could do something like this. I could just add the argument to it when I call execfe. So that's what it looks like. So if I go ahead and execute that, whoops, that's the wrong example. Apparently that's an unrecognized option. Oh, color equals my bad. Yeah, so it still doesn't quite print the color because of it uses environment variables and I don't pass them. But if you wanted to, if I wanted to say I wanted to do ls dash l and print everything in a line, right, I would specify that there. And then it's the same output as if I type ls dash l, right? Except minus the colors because of environment variables. All right, any other questions or weird things we're noticing here? Sorry? Yes, and we will see what, but yeah, we'll see what. So you're right, it's replacing it. So I didn't say get rid of. So there'll be some things left over with whatever was running before. And we'll see how to use that to our advantage later. Yeah, so this is the only way to run a different program. So it's used all the time. So if we like, you know, if we go back in S trace lecture 01, the hello world, right? That's the only way to run a different process. So the first thing, so the first thing was exec VE happened and we didn't put that in there. So I guess that's another question that's, who did that? Cause we didn't do that. So what, anyone has a guess of who made this call? Sorry? The compiler is a good guess, but no. So the compiler just made, well in this case, I hand wrote this, but the compiler just makes the executable file, right? It doesn't actually execute anything. See? Sorry? Yeah, someone said bash and in the S trace case, yeah. But yeah, pretty much bash. So whatever you typed into to run that program was responsible for running it. So something made that, it was likely your shell. And then the other question is like, huh, well then what process did that replace? Cause that's kind of weird. And that's what we'll see after. So any other questions about exec VE? Cause there's also one weird thing we can notice here. Yeah. Yeah, so not only can you run any program with exec VE, this is the system call to run programs. This is the only way to run programs. Yeah, yeah, in C, right? If you can access a system call, you can run other programs. So in Python, right? They have like sub process, was it the sub process package that will make this a bit nicer for you? But under the hood it's going to call exec VE. Cause this is the only way, at least on Mac OS or Linux to run a different program. All right, anyone see, yeah. Oh, it just means I'm not passing any environment. Like for now I'm just ignoring it, I'm just setting it to nothing. Yeah, it's usually has a lot of stuff. So if you want to see what's usually in there, you can just type ENV on your terminal and it'll show you what's usually there. So it's, yeah. All right, anything, did anyone notice like some weird duplication here? How many times do I have LS? Twice, right? And it's kind of weird that it's just been LS and then my first argument's LS, right? Which is kind of just a convention. So anyone want to guess what happens if I just do that? So will LS work now? Who thinks LS still works? Who thinks LS just breaks because I'm not actually running LS? All right, well, let's try it. Whoops, make. So LS just still runs, whoops. Go away. So yeah, LS still runs. So there's just a convention that the first argument here is the name of the program. Your program might never use it, right? Who here, whenever their parsing command line arguments has ever done anything from the zeroth argument? Does anyone ever use that? No, everyone just ignores it, right? It's just a convention. It's set to the name of your program. You don't really care about it. If you want it to be clever, so one of the programs that implements like CP and RM, like all of those little command line utilities is actually just one binary that changes what it does based off the first argument. But in general, you just never use it and it's just a weird convention. In fact, I could probably just ignore it, right? What happened if I just ignore it and be like, well, that's a stupid convention. Why, I don't need to give you any arguments whatsoever. So if I go ahead and execute this now, which actually, well, let's just execute it. So if we execute that, this is actually a brand new thing that just happened to the kernel. It says, no, you can't do that. So a year ago, if you did this, it would happily let you do that. It happened that no one really thought of that before. No one thought people would be crazy enough just to do system calls with just null as everything. So one of the security executables that controls essentially pseudo who has access to what, never checked that the first argument was null and you could overrun its buffer and then you could escalate your privilege and get root. So this is why you're not allowed to do it anymore because someone didn't realize that you could have zero command line arguments come on and they just kind of rolled with it. So that's a fun new change that you can do, but so now it won't let you do that and you have to kind of follow the convention or just put garbage at the first argument. All right, any other questions? Yeah. So you define you as? Yeah, so that's just where that executable lives. So if you do, if you go ahead and do like which, I don't know who, whoops, which who, it will tell you generally where that executable is. All right, so let's quickly wrap up then. So that's the job of the operating system. Its job is to create and run processes. We don't know how to create them yet, but we know how to transform, which is part of running processes. So at minimum it has to load the program into memory, create a process with context that we will learn how to do. Next lecture has to maintain those process control blocks, including all the state registers, open files, things like that, then switch between running processes using the mechanic called a context switch. And then we saw how to replace programs with other processes using that exact VE, which is the only way to do it in Unix. And we'll kind of see how to compose things and figure out how our shell actually works. So just remember, I'm pulling for you, we're on this together.