Operating Systems 2026F Lecture 9
OK, let's go ahead and get started. I'm gonna have to just record audio today because, well, my laptop is not talking to the projector today. So that is a little unfortunate. I think the capture went okay from yesterday's lecture, so... And we want to... Okay, store, you all again. Okay. Just need to log in here. You'll have to do two factor authentication in all your accounts, don't you? You know, I... Yeah. Yeah. Oh, good. Okay, so... at least get your attendance for the few the brave have managed to show up this morning. So, um... Let's move this to a new window. Um, not what I want, so if I give you the code back. So... what I want to talk about today is first, um... It's basically go through the code of the tutorials, go through the solutions for Assignment 1, um, so just to review where we are, um, today, this is Lecture 9 on, uh, tomorrow and Friday. I'll do midterm review, the midterm is in class next week. So on next Wednesday, you'll have the midterm. On Monday, I'm going to... I'm going to post a poll to see what times work on Monday, 'cause I have an open schedule then, for a review session. Um, that's gonna be an optional question answer. I do all my review this week. But I will not have gone through explicitly the solutions from assignment, too, because I'm going to post those on Sunday morning. But as you will see, I have already gone over everything from assignment to already. It all there. It just... pieces parts. So, if you know the tutorials, you know the assignments. Um... But let's first talk about, so to talk about things I want to talk about assignment one, I haven't gone through the solutions in class yet. So let's go through those. Now, let's make this a little bigger. A little better? Yes. What? Oh. Because... Okay, that makes no sense. There are two screens here, and there's nothing to indicate. No wonder you guys weren't in the Wu Clap yet. Okay. Okay, fine. There you go. So, how do you, so, assignment one, how do you define a, like, get up the code for 3000 menu? Siri, we got the code for 3,000 menu, and we've got the... Irritating. I think someone wired up that, I think what happened is they wired this up wrong. Someone made a change to it, because normally these two... Normally, these, if you set the computer on these, it's the same display on both screens. So, not sure what they were. Anyway, I will tell ITS that that's an issue. Um... So, so how do you define a custom command for 3,000 menu without changing its code? What parts of the 3000 menu implement custom command support? So for custom command support, especially to look at Maine, and look, this is the code. Because, you know, basically, it's from the Get Em command in Maine, all the way, you know, through this if statement. Because what this does is, this just gets the value of menu command, the environment variable, and then adds it to the menu, because if you look at the top, the menu code, you'll see that custom is just an, you know, the fifth element of the array, and you just replace that. And if it's not, and if you don't have a custom command, you overwrite custom with quit, and does shorten the, and put the a nul there, so it's to shorten the right. So, it's... That one was... That one should have been pretty straightforward. Um, next one is, can menu commands include command line options? Why or why not? Um, so the question is, so if you were to, so if this custom command, or if you modified this command line argument, like make it LS dash L, rather than just LS, would it work? And in this version, it would, because it is calling system. Kim, let me see if I can get into the VM here. Come here, prompt. Okay, and you show up on the wrong side. Okay, let's see if I can... Can you all read that in the back of the text too small, or can you see it? Hmm. Let me see if I can... I try to remember how to... There is an option to... settings. and appearance. Uh... I think we could just do control plus. I was trying to do Control Plus, and it wasn't. Oh, I hit the shift key, and it decided, because I was doing actual control plus. Second point. Thank you. Okay, so, what I want to do here was type man. What was I, um... I guess, system. I could have done a search for it online, but I wanted to show, this is what I was expecting you to look at. 'Cause if you look at this, it says that the system library function runs the command, but by doing an exec VE of the show. And so this is literally what it's doing. Um, Exactly, L. So if exact VE would be exactly the same, except you add in the environment variables. So, this will come up more important later. So, we're... So what this is asking for is, you know, can you have command law? Yes, because it's being parsed by a shell. When you do SH C, it's running, oh, sorry, sorry, keep this. I did, kept that covert up here. Well... Uh, that's there. Um... Is what compiler directive is present at the start of every function? Was at the end of every function? So that I was expecting you to look at, the start and end of these functions, and you notice, here's run program. Here's the first one. See if I start Proc, then you go on, go on, go on, and then you see it ends with CFI and Proc. And that, so I was saying, is this is the end. You could have, like, said, well, there's a size thing here. That's why I said the name. Ignore that. So these, these directors, where I was talking about, start and end. And this is start, that's end. Um, if you said something about the neighboring ones, but you should say, see if I start Proken and Prok. Please do ask questions if your things unclear here. So, um, which line in 3000 menu implement the ifL statement for run program? How do you know these are the correct lines? So this one, this is probably the trickier one. Actually, I'll do five, actually, first, because it's easier than I'll do this. So, which light implement the call to get line and choose per? How do you know that these are the correct lines? So, let's look at the call to get line here. Um... So, the call to get line is right here. You guys got three arguments, has a result. So where is get line in this code? See, right here, here's the get line. Here are the three arguments. to it. And you see, there's a print f before that. If you work out, that is the message before it. But you just see it's just lines 108 through 111. That's your call. And then there's instructions after that for dealing with the return, you know, in return value and processing it. But again, you can see this because it has call, get line. And if you look at the things above it, oh, look, it says standard in. But the things before it, it's got references to the stack pointer, so this is where the other parameters were on the stack. But again, you can just sort of guess that this is there, because it's after a call to print F. And you know from the code that, you know, just before the call to get line is a print F of your choice. Which is... Oh, that's interesting. Huh. That's actually really interesting, because here, so what I'm pausing for, this is, you know, there's a print F here. This has to be a print F. And this has to be your choice. And... Let's... Do they combine those? So, hair's LC4, and here is LC5. Your choice. Those two options. LC4, LC5, and it refers to... So, LC4 is here, and that's what the print checks. So that's the one with the three arguments. And then this is L.C. 5. So what I'm surprised by, so what am I surprised by? Okay, you'll know what I'm... Hmm? Were you expecting it to be a foot 67 for 10? Yes, exactly. I was expecting this to be a put S. Because if we look elsewhere in the program, you'll see that it generally has, um... There's, there should be some... Okay, it's free, get mine. Yeah, see, there's a put S right there. So it normally optimizes static strings for that. But... it missed it. Huh. So, this just goes to show when it optimizes things, it doesn't always optimize everything, but I have no idea why GCC missed this. That's kind of funny. I didn't notice that before. Um... Anyway, but that, so that was, you know... This is lazy. So I just explained a little more that the rest of it's doing the A2I call, which gets turns into a string too long call by the compiler. For whatever reason. Um, but the, which lines implement the if else statement from, um... and run program? So let's go to if else and run programs. So, if else, run program, you see it's going... This is the FL, so it's just, if result is zero, command failed, versus command succeeded. So looking at that, then we go to, we go at the assembly, and before, we see there's a call to system. So we look in the code here, we see that there's one program. Uh, let's look for the call to system, is right here. So, presumably, the code after this is the if else. Right? Now, how does this work? It's been testing the value of it. If it's equal to 0, it goes to L2 versus L1. So L, well, L2 versus these two instructions. So this is one half of the statement. This is the other half of the statement. So it's printing two messages. If you look up LC1 versus LC2, it'll show you the string for which one it is. But they both then end up running this code. This code starts with the ad cue and ends with the ret. What's the rat? Return. That's the return from function. So this is always run at the end of this function. No matter which branch you did in the FLs. Now, why, why is this a little interesting? Well, it's sort of looking at this ad Q, pop Q, pop Q, any idea what those instructions are before you return from the function? I don't know about that, but I was reading about pop, and it's related to, like, the stack. It's, like, popping from the Yep. So the ad value, remember the, um, so this ad value is is adding to the stack pointer. So the stackboarder, you know, basically points to the bottom of the stack. And the stack grows downward. So adding it pushes it back up. And you'll notice that at the beginning of the function, there should be a subtraction. So this is the allocation. The subtraction is the allocation of local variables. And the addition is the deallocation of those local variables. And remember where the local variables we're talking about, we had an integer and a character pointer. Oh, sorry, sorry, that's Maine, sorry. Run program had, um, uh, into I, choice. No, no, second. Let's use the right program. There we go. It just has one funk, one variable, into I, into Integers, on a 64 bit system, are 8 bytes. So, we allocated eight bytes on the stack for that result, and then we deallocate it. And then the other things were, okay, where's one? So, run program. The other parts of it are popping, basically restoring the value of these two, um, registers, uh, BX and uh, BX and BP, base pointer registers used for accessing local variables and stuff. Those, if you look elsewhere, you'll notice that the collar... Actually, no, I think, so, so, yeah, no, so it had pushed those registers onto the stack to save them. So this is a classic thing you do in assembly language, is you want to use a register, but someone else expects there to be a value in that register. So you save it, and then restore it. So when you enter the function, you save all your registers, and then allocate local variables, and then exiting, you deallocate local variables and stuff. So you'll notice this is exactly reversed. We push the base pointer register, then the BX register, and then we allocate on the stack, and then we undo each of those operations. Thus leaving the stack in the same state as it was before. Well, the stock and the registers, in the same state, it was, would entered the function. So you'll notice there's a lot of trust between functions, functions when you call a function, it's going to mess up the registers. But the color is basically saying, these registers, I want them the way they work. You need to put it back the way it was. So, you can see there's an inherent trust in calling a function between code. There's not a firm barrier between them. If you mess this code up, they're gonna mess each other up big time. But again, you're all running in the same address space, so, of course, there are no protection boundaries. That's why we have processes, to separate things from each other that don't trust each other. You don't have to know that. I am just walking you through it since we're here. Uh, going back to the answers. So... so that was four. And then five. And then, so, printer, call, get trained on two different calls. What are those two calls? We already just went through that. Print F, check, and put S. But, again, that's not consistent. I thought it was. I don't know why. This is one thing you'll find if you actually spend time looking at the assembly language output of compilers, is there's a lot of... nondeterminism and unpredictability. And what exactly assembly language you'll get will depend on how optimizing the compilers. Like, if you don't include the optimization thing, the assembly language, so I always have been compiling with optimization. We go, Oh, and the reason why is, if you don't do that, the output is unoptimized, and it's really stupid. Like, it just it's clearly doing redundant things. If you do at least base level of optimization, the assembly language looks pretty clean. If you do further levels of optimization, it will do more transformations. Some of which can be more bizarre than others. So if you're actually wanting to read the assembly, oh, is good. If you start, O2, you're gonna have to go, like, why did it do with that? And you're getting a little more confused. You can see here an optimized, but it didn't try that hard. That's my assumption why the, um, why it didn't convert the print, um, the print F to a put S there only did some of them. They probably did some optimization where I just looked for the first one or something. And if you go to a higher level of optimization, I wouldn't be surprised if they... both... all of them ended up as possesses. But it might do some other code transformations. Like, things like changing the structure of loops and stuff are things that the compiler can do. Compiler optimizations... is a complex field, I will say that. And the only reason why people have any faith that compilers actually work the way they do is because modern compilers go through very complex test suites. to produce, um, things. So, um, you know, to check to make sure they produce code that works. Um, but it doesn't always. Compilers do have bugs. Does a code in 3,000 menu make any system calls directly? No, it doesn't. And the way you know it doesn't is because everything's a call. There's, there's nothing... no other weird things. just calls, or jumps. So you're all in the address space. If you, this was a system call, you would see a sis call instruction. Somewhere in here. And you can't do that from standard C, unless you use special constructs. And how could you modify 3,000 menus so that run program no longer used system instead of instead used fork? Exactly get any of the code from the first two turtles. So here is, so the basic idea, there are other ways you could have implemented this. You could have copied more code from 3000 shell, but, you know, or simple shell, and done this. But what I was really looking for you to do was just do a very basic fork exactly E. And imitate what the system thing was doing. And so what you do here is, you just have a fork, which creates, you know, creates a child process. We, you know, in the parent process, we wait for the child to exit, and we, you know, and when we do, we have its return status. If it exited properly, we return the status, otherwise, we return minus one. And in the child, we statically create a command line argument, which, you know, is just an array, one is shell. This should be session. Oh, yeah. Yeah, this should be actually SH, to be strictly speaking, to copy the system command. It actually was SH not been SH, if you look at the man page. But, so I should probably change that my solution. But, you know, that, and then you run the program here, give it the arguments, give it the environment. And you have a return thing, because if you get here, the exec VE failed. So. Pretty simple code. And, like, this part is just, you know, strictly need that. Yes? So, for a question like this, um, is it expected that, like, the, uh, code that you write is actually going to, like, run into work or are you more so looking for flags that we get, like, like we have the fork and we have the weight? So, To get full credit, your code should run. It should be runnable. That's kind of, like, 'cause, like, how would you modify so noise? So, um, If you explain the key things your coach should have, you'll get most of the points. But, um, but yeah, because you don't really know if your solution's right until you, you know, test it on that. Now, I will say on the midterm, If I were to ask something like this, I'd expect you to give the high level description and not give me the actual code. Because, yeah. That's, you're gonna make mistakes and... Um... Um... Yeah, you know, can I say that you may not use any other external code? Your modified version of equivalent functionality of the original. So... So this one I was expecting you to come up with the code, and really, you just needed a simple function that did fork an exact feet. Now, one question is, is, what if instead of running a shell to run the command, you, um, you just ran the command directly? I think, in that case, you'll probably, you'll get, like, one point off for that, because it's not equivalent functionality, because it can't support any command light arguments. And the shell can do lots of other things. So, you know, the system command does that. So if you're probably equivalent functionality, you have to do it the way system does, which is to create a show. and have it do things. So it's not, um... But again, if you got the fork exec V, you should get most of the points. Um... Okay, so that's that's this one? So that's assignment one. What I then wanted to do is, let's go back here. So, one question I had. Is... How hard do you find it to read C code? Because you'll notice in this, I was showing you code, and I'm gonna be showing you more code. And the reason I... Actually, we'll go here. Because in the course outline, I don't know if you noticed... under... This right here. Reading and modifying code by John Acock. He's over at Calgary. He's a buddy of mine. If you, uh, this is a short book. It's really, it's like... like... The summaries, like, 57 pages of, like, really big text, small pages. But this is a very useful thing to sit here and read for learning strategies on how you read code. And I highly recommend you go through this. Because if you're, let's see, now, how are you all doing on this? Who else? There we go. Oh that's what I decide. Where did Windows put my mouse cursor? Oh, I think I went off to the wrong desktop. There we go. There we go. Uh, so, let's go here. So most of you have answered it. Great. Let's close that. And let's go to check results. So, doable with effort, that's about what I expect. But if you're finding it to be pretty hard, definitely, but even if you find it with some effort, it's worth, you know, spending some time learning that. Now, let's go back to... Here? My want to now talk about is, let's... go through... I wanted to go through the code of 3,000 shell. And the reason I want to go through 3,000 shell is because there's lots of good stuff in here and I don't think I've gone through everything in quite as much detail as I should. And if you have particular questions... So what I'm expecting you to do with the tutorial code, and definitely with the assignment code. is understand it on a line by line basis, as well as on a architectural basis. It's not that much code. But it's the reason I give you this code is to understand, like, how things work. And the way programmers communicate with each other, I mean, they do use natural language, like English, but to talk precisely about something, we use code. Code is not just a means of communicating with the computer, it's a means of communicating with other programmers. So, you should be able, and I, you could say, well, shouldn't you put lots of comments in your code and stuff like that? It's like, well, Not... not really. I mean, having comments for parts of code that are hard to understand, or there's something tricky about them, sure. But you shouldn't need comments to understand code, and if code requires comments to be basically clear, the code is not well written. So let's go through this. So, first off, we've got our main. Now, when you're reading a program, it's always good to start at, you know, you can only start at the bottom of a source file, 'cause typically, that's, I mean, it depends on where you are, but, you know, C program, start with Maine. 'Cause that's where the program is gonna start. At least for your code. And so now you go, like, What are we doing in Maine? We can divide this up into a few portions. One is, we have, we declare this Sig action signal handler struct, and then we have this code, the code here from Mem set all the way through the calls to Sig action, the if statements. What is this code doing? It's setting up the signal handlers. So that means that there is default behavior every program has when it receives a signal. Here, we are changing that default behavior. The way we do that is we first take the struct and zero it out, because this is technically out. I mean, probably it would have zeros in it, but you should never count on that when you allocate a date. When you allocate data structures, just like you malloc memory and stuff, So, like, C has Maloc and Kalloc. Catalog initializes the memory, um, to zero's, Maloch doesn't, so you could get old data. When you're on the stack, in general, you should assume some other code may have been using the stack. You should always initialize what's there. Just in general, initialize your variables. So you got this struct here, you might go, like, I don't know what's in that struct. I don't know all the fields. So, when you get these structs where you haven't initialized them, and you don't have an explicit initializer, you zero them out. And so that's what the MM set call is. And I notice this MEM set will wazeer out all the fields even without listing them all. It's janky. It's not how you do it in more modern languages, but this is C. And then we set the values of two fields that we actually care about, which is the signal handler one and essay restart. Um, and so, so, signal handlers just the function that's gonna handle the signals. We gonna walk through that in a bit. And then we pass in, when we make the calls to Sig action, we say the signal we want to handle for, and we give it the struct that describes it. Did you all figure out what essay we start does? Hmm? As they restart is a, um... It's a fun little option that really gets at what's happening when you do a signal. So when a signal comes in, wherever the program is executing, it's gonna get paused. which means the current instruction pointer is going to get pushed onto the stack, and then it's going to jump to the signal handler. So there's some standard signal handling code, and it looks, and it has a list of vector of all these signal handlers, and this, you know, it says, Okay, what single is this? And we've now here defined special single handlers, but if not, they're default ones. And they'll run that code. And once the signal handle code finishes, it then pops the stack to restore the instruction pointer, and then continue, and thus continues running the code from exactly where it was. So, your regular code will never know that a single handler run ran unless it does something explicit, because it literally pauses, you know, you're in the middle of instructions, and then you go back to run the next instruction. No semantic difference. It's kind of equivalent to how your process, at any point, can get paused by the kernel to do, you know, to allow another program to run on the CPU. This happens transparently, you don't notice it. But... what happens if your process is in the middle of a system call? So, you, for example, is relevant here. What if your process is in the middle of doing a read? And, you know, this is a show. It's gonna be reading all the time, and it's mostly just sitting there with the prompt. So most of the time, when you get a signal, what should happen with that system call? There's three logical things that could happen with the system call. One is, it could pause it just like it does in user space. Save that instruction for where it was implementing the system call, and then continue. That's not a good idea in the kernel. For various reasons, as you see how the kernel's design, you don't, the kernel doesn't pause itself, and then restore things, at least not in that way. If it does, it does so in very well defined ways and very carefully, because it's having to manage a lot of things. It wants to finish the task it's doing before it starts with another task. It won't just pause at the middle 'cause bad things could happen. So in general, the kernel will not just pause in an arbitrary spot in the middle of executing a system call. The options for the system call is either you complete the system call, or you cancel the system call. And by default, when a signal comes in while a process is suspended, while handling a system call, that system call terminates at that moment. So, for example, you're doing a read, and a signal comes in, that read, after the signal is finished, will terminate. It will just return back to the process. Even if it got no, got nothing from the file scriptor. And it could have potentially gotten some data. So the user hasn't typed anything in, the reed should only return when there's data. But in this case, with the signal comes in, it will return with no data. And if you look at this code, if you look in a prompt loop, what's the... What's the heart of prompt loop? We have a... Come on. Shh. We have a wild loop, and then in the wild loop, we do a print F, and after the print F, we do an F, get S. So the print F is doing a right. The FGS is doing a read system call, and that read, when it completes, we expect there to be data, because if we got nothing, if it was an empty string that got, you know, that got returned, we got no data, but it's not an empty string, we got a null, saying no data was read. We think we've reached the end of file, i.e., and we should terminate. That means the terminal has been closed or whatever. Like, someone type control D or whatever. We're done. So that means every time you get a signal, the shell terminates. Under default signal handling. You should try this, get rid of the essay we start. Just get rid of that line that defines that value in the struct. And what you should see is whenever you run a command, the shell should just terminate. But when you do restart, instead, what it will do is, it will, the essay restart flag tells the, tells the colonel, if a single came in and interrupted the system call, do the system call again. So it will do another read. And you can actually see this if you S trace the process. You can see it doing the interrupted system call, and then it restarting the system call and things. So... But that, so it's a little option that helps you understand what actually is happening in the hood. Then after this, we get a couple of environment variables and assign them to global values. So not global to local variables that were then passed on a prompt loop, the user environment variable, and the path environment variable. And then we go to prompt loop. And then when prompt blue finishes, we return. 'cause we're done. So what does prompt loop do? So it allocates a bunch of things. We got an infinite loop here. It prints a prints a prompt, gets the argument. If we didn't get anything, we say we're done and exit the loop. And we parse. Sorry, we parse them here. And what does parsing do, it fills in the args array. And so, if, and the number of args value. And so if there were no arguments in there, we'd go continue. The user didn't type in anything interesting. So we go back and print a new prompt. So there's probably the person just hit enter. And then we handle these. Exit and P list. So what would we call exit and P list? As, um... as command. Are they external commands? No. They're not external commands. So in shell terms, these are built in commands, because the code for implementing them is in here. It's in the, you know, exit, we're implementing it right here. We're calling the exit system call. Here, we're calling P list. I'm gonna look at P list, but that's a function. Those are built in. Built in commands are really important to shells. Uh, one, um, there's one built in command that's really important in shells that this one does not implement. Which is CD. If you think about the change directory command in the shell, can you implement that as an external process? and as external command? No. You can't. Because change directory changes the directory of the current process. And of the shell is what you're doing, and then it's passed on to any other commands you use as the current working directory. If you do it in an external command, it'll change. It's directory, but that's the child. That won't change the state of the parent. So, CD has to be a built in command. Something to... Um... And then, you know, so... Here we've got, um, we're checking to see, is the process, um, running in, you know, are we supposed to run in the background? So we first set it to zero, we assume that we're not running the background, and then we look at all the arguments to see if there's an ampersand somewhere in the arguments. Actually, if the last argument is an amperset, that's specifically what it's looking for. And if it is, it gets rid of that, reduce the, you know, removes it from the list. That is what all this manipulation... Well, first, it sets the flag background to one, and then sets that. And so, someone's got to do something with its background flag, but that's what's set by there being ampersent at the end of the line. And then here, this whole block of... God, that's annoying. So this block of code is, we have... Come on, when I click on it, I do not want to scroll. Uh, from standard out file name on 293, all the way through 306, this is handling the redirection of standard out. And so what we are is looking at all the arguments and looking for an argument that starts with greater than. Now, the regular shell, you can have greater than a space and a file, 3,000 shells not that smart. It has to be in one argument. Because it's just checking, you know, so it's checking in the beginning of it. Is it a greater than, if it is, then the, after that grand name, that file name is assumed to be the file we want to open to redirect standard out. Which, we're going to remember, is filed scriptor. One. We're not doing the redirection here. All we're doing here is assigning a standard out file name to that argument, and then, you know, reporting this on the command, according this to the user, and then getting rid of that value from the array. Because that final name is not for the command, it's for the shell. And so we've got to rewrite the array to remove that. So that's what all this four loop stuff is. is just to move it. And then we call run program. And we give it the arguments that we created, we get the background flag, the standard out, file name, path, path environment variable, and the environment. That's all pasta run program. Now we go to run program, we've got all those. And here's our fork exec VE. This is what's actually running the external program. Do the fork? What's the parrot doing? Is checking to see if it's the background flag is set. If the background flag is set, we just say it's running the background, and we return. But if we're not, during the foreground, we have to wait for the process. That's all we do now. Now otherwise, we call fine binary. What's fine binary doing? Conceptually, it's searching the path for the command. So, like, if you type in LS, find binary is saying, Where is the LS command? It's somewhere in the set of directories defined by the path environment variable. So it has to do a search. We'll look at the, um, we can go through the fine binary one. I wasn't gonna walk through the code, but it's searching. It's searching the path. And if that, and so what this will do is, this will get us the, you know, get us something for this. And then if, and then if we have a file special redirecting standard out, we redirect standard out. What is meaning reader? No, this is all in the child, so it can just change the state of the process. So it has to open that file for writing, and so we do create, because we're basically making a new file. We're assuming we're overriding it. In shell terms, we're clobbering it. And then we... we're gonna get some random file descriptor here. So we'd use dupe 2 to duplicate that file descriptor onto file descriptor 1, because that's where standard out is. And then we close FD because we don't need the same file to be open on multiple file scriptures. So, you know, clean things up. So we do that, and then we do our exact VE. And if it returns, there was an error and we were putting out the error. So, running program's kind of the heart of things. Signal handler, just... That's what we, you know, defined it, and that's in Sig action. And if it's the SigHup, we just print, Hey, we got CCO. And if it's not Sig Child, at this point, we go, like, we don't know what this hell the signal is. I have no idea why I'm being called. Um, and then we just wait. And then print the, and then return the exit status. Now, this weight here on line 205 is a different weight. from the one in 234 and run program. I mean, it's the same call, but they'll behave differently. The one in 234 in the main, you know, in the main run program, will block the process. It will wait, it will actually wait. But the way call in signal handler won't wait. It'll return immediately. Why does one wait, why does one actually wait, and the other one return immediately? What's the difference in the semantics? So... go ahead. Uh, like the child process is already dead. Well, the child process is already dead, because we received Sig child. That's the whole point. Why is a single handle being run? Because you receive sick job. Why do you get sick child? Because the colonel's saying, Hey, your child process terminated. We got some info for you. We got us return value. So, when, so, literally, you're just, this system call will return essentially immediately. Theyll return the value. There won't be any pause, and then this will continue. So... weight only waits if the child process is running. If the child process isn't running, there's nothing to wait on. That's not clear you should ask. And then here is P list, P list is... It's, you know, you can walk through this, but all this is doing is if you go, it's literally going into... Go to slashproc, and you look in slashproc, there are files that start with numbers, and there's files and directory that start with numbers. Things that start with numbers are all directories. There's it, and there's everything else. So what it's doing is finding all the entries in slash proc, that start with a number. Going into those directories and outputting the contents of the comp file. along with anything else. I'm gonna say, why the compile? Well... So we're right now running Bash, which is in 37797. Now. So, you see, if you get the value of the com file in that, it basically gives you a short version of the command that's running in that process. of the program that's throwing in it. It's not a full path name or anything, 'cause sometimes it's not, but it's just, it's a short string that the kernel keeps track of his information on that process. So, P list opens the directory. You couldn't, you know, it's air, it probably should return here. I think that's a bug, rather than just continuing on. And then it reads the directory, and, um... This four loop is just looping through all the entries in the directory, and checks to see if the name of the directory entry starts with a digit. And if it does, then it sets up the calm file name, so it basically is depending on the, um, the current name of it with the calm file name, which is just slash calm, COMM. It then opens it, reads it, closes it, and then prints it out. And, you know, couldn't close Brock, so... Oh, so here's a funny thing. Why would clothes fail? Can you think of a reason why the close of a file or a directory would ever fail? Seems like a silly thing, right? Clothes should always work. So... So, in traditional UNIX, clothes would essentially never fail if you're doing everything locally. But if you're dealing with a network file system, where is anything remote, clothes can fail because clothes can effectively be a flush and commit. You can actually be, that's where you actually write the data to the remote server. And if so, it is possible for clothes to fail, and if clothes fails, it means the data that you wrote to the file, or directory, hasn't actually been written. You won't see that in standard local file systems. Well, some fancy ones might now. There's some things, but, like, the classic files, there's a classic network file system called AFS. that clothes can, because it does a lot of cashing, clothes can fail. So, you should check these things. Okay, so that's P list, set up com function. It's just doing string manipulation, string manipulation, and C is stupid. It's painful, but we're just really trying to copy this and to see. So, with this prefix, so... And then there's fine binary, fine binary is just... It's just literally searching the path. And so what it's doing is it's taking the name that you have in there, and it's parsing up the path directory. Uh... Yeah, so string... So if it turns out, if it's this, we don't bother searching the path. We go, like, this is either a relative path, but it starts with a dot, or it's an absolute path cut, starts with slash, so we don't have to do anything, and we can just copy it, and we can return. If this fails, then we actually have to parse it. And if you work through this, what it's actually doing is it's looking for, see, the colons, it's splitting up the path directory is a bunch of colons separate strings. It's separating them based on those colons, and for each of them, it's then making a copy, putting the command that was entered at the end of it, and then call it, and the heart of it is calling stat. So if stat, stat is a way of checking, does this file exist? And well, specifically what it does when you call stat, it gives you all the information. It gives you all the metadata about the file. Like file permissions and other things. It's going to the I note and getting all that information. Stat is also just used to see if a file exists or not, 'cause if you can't stat it, it's not there. So, here, it's doing a stat, and if it... that, you know, that doesn't, if stat didn't, you know, give, if it exists, we found a file that exists, and we return it. If he wanted to be special, we could actually check to see it was executable file, but we're sort of assuming in the path everything's executable. But otherwise, not, then we've gotta try another entry in the path. So, I mean, as an exercise, it's potentially worth seeing how this works, but, um, always remember, uh, sea conditionals when you have these double ampersands, that those... That's for saying and, uh, two vertical bars is or, and if you, when you have that sort of condition, you do the first, the thing to the left of it, and only if it's, um, true, do you do the next one? If it's false, you don't execute the other statements. So, these sort of conditionals, like, it has to go through in linear order and try them. So that can be useful, 'cause you can have commands that will actually would have an error. Like, you can check for air conditions earlier in the joint ones that, like, you can check to see, is this pointer not null? And then after that, you can de reference it. If you always deferenced it, you generate an error. But you now know it's not null, so when you do the after the end, you can do processing. Okay, find environment variable, same basic idea, but this is just walking through the environment array. Which is, like, a command line argument, which key value pairs, and you just, you're just walking through it to get that. Doing it linearly because they're not sorted. So you have to walk the entire environment. And then parse args, I... This is an implementation that came with, um, simple shell, that I kind of inherit it in 3,000 shell. I hate this implementation of parsargs. It's very hard to read. Uh, I have, that's the rest of the code. I will say that in assignment, too, I finally got around to making it a bit clearer. I am not sure exactly how clear it is, but make args. Here is essentially pars args. But not completely. There's one difference in its semantics. The hint I'll give you is, it's, this is not exactly the same as Parse Args. There's one difference. And if you see how it uses the arguments, you can understand what it is. But otherwise, the logic of this, I hope, is a bit easier to follow in how it's parsing the command line arguments. But again, string manipulation in C is really painful. I think if you're doing anything with strings, it's like, either use a library and see, or just using, like, don't use see. It's like... I mean, it's really, this is really more like what you have to do in assembly. Any questions about the code here? Hmm? Uh... And now, let's... So, where are we doing time wise? Okay, we've still got some time. If you go to Tutorial 4, we've got 3,000 user login. This is a lot simpler of a program, but it's doing some fun things. I actually did an experiment in class yesterday. I won't do that today, but, um, because I had a question on how this code ran, so I actually, uh, downloaded it, compiled it, and we ran some tests. And I'll explain to you what the question was in a sec. But walking through, so here, we don't have multiple functions to go through, because we just have this. But we should talk about the make file. You'll know about... Oh, what, before I do that? I had, um... What I talked about 3,000 shell is today's info about 3,000 shell new to you. With a woo clap, do you see all the questions in the woo clap at once? No, you just when I display it, do you see it? Yeah. Okay. So, is... I'm just curious to see if that was, but... So when we talk about a make file, this is make is the granddaddy of programs for managing builds. We have a lot of newer tools for building software, like Android applications have a program called Great Old. I think there's, like, ant. There's lots of programs now for building things. If you're using something like rust, there's cargo, which both is a package manager and manages the build of programs. Make is the ancestor of all these. And makes a little funny, because it's like Python and that the tabs matter. You have to intend things. So, one of the big ways you'll get messed up with a make file is you won't have a tap in the right place, and it won't work. But the idea is that you set targets and normally the target is a file that you're trying to produce, and it tells you in order to make that file, make, get it. You, this is what you have to compile, and then you typically give it some ways of, you know, how you, you know, so this is a, the first line is a dependency. In order to get 3,000 user login, you have to use, you have to look at 3,000 user login.c. And the reason you have the sort of dependency is because it now knows if 3,000 user login.c has been modified, you now need to rebuild this. And 'cause for more complex builds, you let's say you have 100 C files, and you modify one. Do you want to compile all 100 C files? Ideally, no, you just want to compile the ones that have potentially changed. So this tells it, okay, that C file change, yeah, change that. But then, like if it depended on a header file, if you list all the header files, it would know, oh, all these programs, all these C files depend on this header file. So, I'll build it that way. We have this phony thing because set UID is not actually a file to be built. This is just a thing we want to do. And so, the reason we do have this make file is so that you had a nice place to keep. this set of commands. And notice here, again, we're doing the double ampersand, so this only happens if the second pseudo only runs if the first pseudo succeeds. But that's, you know, this is for, this is for making it set you ID route. You make it owned by route, and then you set the set you ID bit. And then, now, in terms of the actual program here, we got a bunch of includes. Find some variables, check to see if the argument. Check the command line arguments, and then we do this print F that outputs the current user ID, effective user ID, group ID, effective group ID. Why is this, um, you know, this is very useful for debugging purposes, of course. This is just a toy program to show you, I don't think, how those things work, but... remember, what's the difference between the effective versus the regular UID and GID? Or the effective UID and the effective GID are what are actually used by the kernel to determine permissions. Whether it looks at the comparatively processes UID, effective UID, with the UID of the file. And similarly, it compares the effective group ID with a group ID of a file. And if those are equal, then it uses the group permissions or the user permissions respectively. Otherwise, it would use the other permissions. I said, normally, effective UID and UID are equal. Except if use the set UID and set GID mechanisms. Then those change. Or if a program deliberately manipulates them, you can't normally manipulate them unless you have privileges, i.e. your running is root. Uh, so, we now see our privileges, we get the username, we get the password entry for the username. Remember, the password file is in. Let's see, password. So get PWNAM is literally just parsing this file, the SC password file. And if you look at the fields here, and you, man, get PW, you'll notice that. Um, it has a, it defines it, the struct that's returned, the struct password, has name, password, UID, GID, hosting, you know, user information, directory, shell. This is all the fields that are colon separated in the password. file. You can also get, but why, why don't we just manually parse the password file? Well, there are ways to have this information come from other places. There are distributed approaches to managing accounts. It isn't always a password file on modern systems. Get PWNM will do the right thing. But, of course, on this system, it's just parsing that. We get the password entry, you know, if we can find the user. And then what we do is we set the group ID of the current process to the group ID in that password entry, and then we do the same thing for the UID. Why do we do the group before the user? Any thoughts? Yes. Um, I believe if you, uh, change the visor before the group you made, there's certain information. Exactly. So what's, so what you're saying is, if you set UID drops privileges. When you're, if you have effective UID of zero, and you do a set UID to another user, your effective UID becomes that. The UID and the effective UID gets set to the same value. And once that's done, you no longer have root privileges. You can't be messing with the UID and GID of your process. So, you gotta first set the group ID, and then set the UID, because at that point now, you're just running as that user. But you want to do this, you need to set the group ID, because otherwise, you'll have roots group ID, and that would be weird. and could give you extra privileges you shouldn't have. So we do that, and then we change to that user's directory. So you want to know what happens when you do a CD, you're doing a change directory. That's all CD has changed directory. So this is now the default directory of that process, their home directory, because you're logging in as that user. And then you set, you know, the, we set up the shell to be what it should be. and set the environment, We clear the environment, so we only have these environment variables defined. Why would you potentially want to clear the environment? Well, you know, who knows? Might be some stuff there. There's actually been a number of security vulnerabilities over the years through environment variables. So if you're actually worried about things, like, you should be a little careful with that. So this is being paranoid. Clear it all the environment variables, set them to these things, and then do an exact fee. So, nothing too complicated in terms of this, but there's touching on a lot of operating system concepts. Which is really why I say, go through this code line by line. to understand what's going on. So let's see this. Let's go here. So, let's see the results. Uh... So, a little. Okay, so you guys learned something about 3,000, Shel. Interesting. Okay. That's why I want to check. Okay, so that's... Any questions about... this code? Yes. Curious about the environment variables. If that's something that, like, exists only in the shell, or, like, are the environment variables managed by the operating system? Like, is that in the process table or like the structure for the process? No, no, you know, you know how environment variables are managed. You've already seen it. Because you see that the environment variables are passed into exec VE. And when you look at the memory map of the process, you see the environment variables in process memory. Environment variables are just, it's just data in the process, but it's data in a process that was put there by exec fee. So, what normally happens when you do an executive E, the entire address space is blown away. You know, it's all it's all blown away, and it has to be initialized based on whatever program is being loaded. But how do you change how that program's going to execute? Because otherwise it's just gonna come in there and go, like, what am I supposed to do? So what state does it get? It gets open file descriptors. Any open files they can access. And by default, three should be. Defined. Standard end, standard out, standard air, which is file scriptor 0, 1, and 2. So we can read and write to the world. Without those, it would be cut off from the world. It wouldn't have any way to communicate unless it opened a file and I have to know what files it could open. Then also neat gets command line arguments, and environment variables, to tell it what to do. And those are both passed into it by exec VE, and the way exactly he does it is by putting them in the address space. At the top of memory. And, um, the, when you think about the, why is it at the top of memory? Because when your program starts executing, it doesn't know anything. It has to look in memory for the data that was given, and it basically knows, go to the top of memory. The kernel left something for you, and it just starts parsing it. And because there's a well defined structure to it, it just goes there, and it knows when the command line arguments end, because it gets a no value, and that knows the environment variable start. And it goes until it gets a no value. So it knows, at the top of memory, that's where the command line arguments and the environment variables are by convention. But that's left behind by the colonel. Because of the exact VE. So it's... that's at stake. And we'll actually see in the second half of, I think I'll show you the implementation of that. Showing you how exactly you works. We'll see if we have time for that. But it's a... You just have this question, how do you pass in information into a running program? you know, in a secure way, in a well defined way, you don't, like, you want to have... And so UNIX has these conventions. This is essentially the API. So, the API, the API of a process of a program, is this. It's the argument exactly. But no, this stuff is not in kernel memory. It's in the process's memory. It's passed in, because it wasn't kernel memory, it wouldn't be very useful. So, you can do, so, environment, variables, command line, arguments are for the process's use. It can overwrite them. It can change them. It's memory that has been pre allocated. But it's allocated in a very minimal way. So, like, you can't really grow it so easily. If you wanted to grow it, you really need to allocate more memory and copy it to it and expand it and stuff. Because everything's packed in. But, but it's there. For your use. Um... Good. Good question. So now I just wanted to spend a little time on going, whatever time. Whoa, whoa, we do not have any time left. Um, we got, you know, we are basically at the end of time. So, the plan for Friday is I'm just gonna walk through what we've done at a conceptual level, and just try to, you know, kind of, like, the key points that you should understand, refer back to code, and stuff, and just, sort of, walk through the material. as a review. And we'll maybe try making up some questions. And then, uh, I will post something to teams in a couple of days for Monday for a view session. It's an optional review session, where I'll just go over the answers for assignment, too. But if you look at assignment 2, if you look at its code, you'll notice its code 3000 menu. What is it doing? It's parsing arguments. It's got a P info. It's got this become, which looks a lot like the, um, you know, tutorial four. You've got a run program, which does a fork and exec VE, and also has some set UID and set GID stuff. Um, redirects standard out, you know, from 3,000 shell. And then has a little menu and stuff, and, like, it's just, and then has a signal handler. You know, there's, like, it's just a mashup of the code you've already seen. You need to, in order to understand this, you have to understand that other code, but it's just a mashup of code you've already seen. So, and because it's been mashed up, you actually have to read the code because there's little differences here and there. But if you conceptually understand the other code, this is very straightforward. Okay, and I'll stop here. See you all Friday.