 All righty, welcome back to Operating System. So you're gonna need this for lab 4. So we were talking about U-Context and Qs. So before we start that, I will start with this visualization of what a process looks like with 4 threads. So if a process has 4 threads, those 4 threads would have their own independent registers and stack, hopefully, and all the stacks would still live in virtual memory. So they could access the stacks if, for some reason, they got a pointer to it, but they shouldn't. And then the other things that exist in virtual memory are like the heap and global variables, and then the only other thing in a process that we know so far is open file descriptors. So any questions about this visualization? This is what a process with 4 threads looks like. All the threads live within a process. So they can access any of the resources that a process has. Good, good, good. All right. So remember with all these, you can access these examples. This is a clickable link. You can access this repository. It's the materials repository, and today it's lectures 18. We will see some code. So this is going to be all code and all your implementing cooperative user threads. So the heavy lifting of threads is saving all the registers and doing the context switches and all that stuff. Thankfully for you, all the heavy lifting is done by this you context library that you are allowed to use. So here's all the header files you will ever need for this lab. They're included for you and in the comments I put in what function I used from these. Most of these just you don't have to use. You really only have to use these ones. So I will have a function called die that does the same thing where it saves the error. No prints error message and then exits the process. This should be only for debugging for you because everything should work properly. You should not have any errors from system calls or anything in lab four. So this is primarily for you to do debugging. This should never execute whenever you actually test your code. There is a provided function called new stack and that will allocate a stack for you. If you need a stack for a new user thread, you should use this function. So it does an M map call for you that you can execute code on all that fun stuff. And it has this little helper function called Valgrind stack register. So what that does is if you use Valgrind on your code to debug memory leaks and that fun stuff. Well, turns out it doesn't really expect you to have your own threads that use their own stacks. If you do not have this function called Valgrind will just yell at you for literally anything. So this will tell Valgrind that shut up. This is a natural stack. Leave me alone. So that's why one of the reasons why you should use this helper function. And it declares a stack of this SIG stack size. So that's just a default stack size. We'll just use it for everything to make sure everything's consistent. The delete stack just unmapped some memory and just does an M map system call. And then here we get into the real meat and potatoes. So here we are declaring three you context types and this you context type will essentially hold all of the values for all of the registers if we want to save them. So we're and since they're saving the registers, they pretty much represent the important part of the thread. If we need to context anything because, well, stack pointer is just a register. And other than that, it's just memory. So we will set up three threads, thread zero, thread one and thread two. In here, I will set up two stacks. So a stack for thread one and a stack for thread two. You might ask why I don't have a stack for thread zero because I will bless thread zero as the current main thread that is executing in this process. So I'm going to call that thread zero and I'm going to use it stack. So the first function call we use that uses you context is called get context. And what get context will do is it will initialize this you context and essentially save the current registers of the current executing thread into the structure and also set up the stack pointer that if you were to context, switch back in those registers, it looks like it would resume from here. So it would resume from right after the get context call. So everyone okay with that a little bit. So let's see how we would use it. So the counterpart to get context is called set context. So what set context will do is it will take this you context. So whatever this is pointing to, which will have values for all the registers and everything and it will replace whatever is currently executing and swap all the registers back to whatever was last saved in that you context. So in this case, it would set everything back from the last time it saved, which would have been this get context. So it would go resume here. So if I, if I compile this, what I should see is something like oh no. So I should initialize this context here. If I do a set context back to it, it'll be resume from here. So if I set context back to it, it resumes here, it would print oh no, then do a set context back to the last get context, which is still here. Print oh no, that the dot, that the dot, we can see where this is going, right? So if I go ahead and execute that, whoops, I didn't compile it, that probably would help. I get oh no over and over and over again. This is an infinite loop, probably our first of May today. So does that make sense why that's an infinite loop? It goes over and over again. All right, any questions about that fun infinite loop or other fun stuff? So let's make something fun or at least the other class asked about this. So let's say I make a variable called x, it would be a local variable. So it's on thread zero stack. And then let's say I increment it plus plus x. Then I say oh no, x equals whatever x is. So anyone guess what this will do? Yeah, so I should see like x zero x one two, yeah, always one or increment. Yeah. He convinced you? That easy? Okay. Well, let's see who's right. She's stuck to your guns. So why is that? Yeah, so X is on the stack, right? So here we would have allocated space for an integer on the stack. So it would have bumped the stack pointer up. It would have been allocated. Then it calls get context, which also would have saved the stack pointer and everything like that. So here, whatever we increment x, it would increment, do that same load and store thing and it would be somewhere on the stack. So it would go from zero to one. We print it off and then we set context back to here where the stack pointer doesn't change. So it didn't change. We didn't allocate a new variable or anything. So its memory is still around. It's in virtual memory. So we increment it from one or one to two, print it out. How would I make you write? Well, but by moving a single line. Yeah. So, so here's another guess. All right. So this will make it keep incrementing again. You want to change your mind? You want the, oh, that's cheating. Yeah. So this is always one. Or does this increment? Oh, we got always one. Yeah. All right. Let's see. Always one. So why is it always one? Yeah. Yeah. Yeah. We're also adding at to the stack. So whenever we get context here, the stack pointer will be at a point where before it wasn't allocated, right? So it's just the default stack pointer. So this line would allocate it on the stack. So it would bump it up four and then initialize it to zero and then increment it from zero to one and then print it. And then if we do a set context, it resets the stack pointer back to its original one. So then it would bump it up to four again, which would get the same address. But it doesn't matter because we reinitialize it to zero. So it would override it and then increment it from zero to one, go back up print. Oh, no, one again. That just keeps on going in front loop. Yeah. No. So the, so it set context only changes the registers. The memory doesn't do anything. So if for some reason in here, I like had an if statement I could tell and I just guessed like if I just read the old stack value, I would get one or whatever because the memory doesn't change back. It's just the registers that change. So yeah. So this is changing this, changing values on the stack. Whenever I change the value of X, but whenever I do a site context back, the set or the stack pointer gets reset. And then this will just follow the same thing again. So I'll do the same steps, expand the stack by four again, and then that would have the old value still on it, but then it overwrites it with one or zero and then increments it again. So yeah, important thing to know that that will be a fun thing to debug that values on the stack don't stay the same. Yeah. No, this gets overwritten. So here, like stack gets saved here, increments it by four. And then when it set context back, it's the old one that was for less than whatever it currently was whenever it did set context. So then just grows it to the same amount. So yeah, that's why it's an infinite loop. And I don't get a stack overflow or anything like that. Just keeps on going forever. Okay. So that's cool. All right. So that's fun. I will show you how to make a new user thread. So I will declare a stack for my new thread because I'm not going to reuse whatever is currently executing main. So I will declare a stack for thread one by calling new stack. Then I have to call get context on it, which will save the main threads registers and everything like that. You just always have to do this whenever you initialize a new stack. There is another call we will use to use to manipulate the register values, but you just always have to initialize it with a get context here. Some things I can manipulate about the context is this UC stack. So I can set the stack pointer through this. So I will set the stack pointer to thread one stack. And then I will tell it the size of the new stack, which just uses macro and then I will do this make context. So what make context will do is go ahead, set the register, find out whatever your machine stack pointer register actually is, make sure it's consistent with that UC stack argument. And then you can also give it a function. So this will change the program counter so that whenever you do a set context to this T1U context now, it's as if it starts executing that function instead of resuming from wherever the get context call was. So if I do a set context on this now, it will try and execute T1 run. You can also give it how many argument. You can also pass it integers. So you can only pass it integers. So I have to do this casting because C types don't really like that. So here you can specify how many arguments you want to pass it. They have to be integers. And then here I'm going to pass an integer called 42 because you might find it useful for the lab or you might not. So yeah, let's go back and see what thread one run does. So thread one run, all it does is it prints whatever the argument we gave it, which in this case should be 42. So if we just make that context and before doing anything, we do a set context to it, T1, what should happen? Yeah, then what's going to happen after I print? So we got an infinite loop because this is going to call set context or something. So this returns and then it's going to come back here and then just call it again and then infinite loop. All right, let's see what happens. Whoops, what did I do? Oh, oh, whoops, I am fairly myself at the beginning. Whoops. So here just prints off once. So that was due to a rule that I haven't told you. So the rule is I would set context to T1. So yeah, I would start executing this function. But the rule is whenever this function returns, that thread would have like exited. So through you context, if you don't do anything special, it will just if that or if that returns, the process ends. It just calls exit in the process, process is no more. So everything ends. So what you could do, which is similar to what you said, is I can set it up, I can uncomment this. So there's a field called you see link and that will change the behavior of whatever happens whenever this function returns. So if I set you see link, it will use that use you context. And then whenever that function it returns, it will automatically do a set context on that. So it would automatically whenever that returns. Here it would be like essentially it did a set context T zero. And then where would it go back to? Yeah, last time I called get context on the T zero context. So then it would go through here, reinitialize that T1 you context then set context to it. If I do this, I should get myself in another fun pickle where this is another infant loop, but I just did it more. So that is our second infant loop of the day. So that was fun. Any questions about that infant loop? OK, so let us go back. So I will do this so I remember. All right, so. Let's come at this. So let's create another thread because that's more fun. So we'll create thread to initialize it with get context. And then we will set it stacked like we did before. And then we'll just do a make context that looks a bit better because we don't use any argument. So you just tell it the thing you want to initialize. And then what function to run. So we set up thread to execute T2 run. So if we have that, yeah, so if we have that, we can go ahead and let's do a set context to. So if we do a set context to T1 context, well, what should happen is we go through, we execute T1 run, we print and because I commented out that you see link again, it should just exit the process and nothing should happen. So let's go ahead and make sure that I am right. So just print hooray, yay, nothing happens. So if you want to make sure that something does happen, well, I can do this so I can make it such that whatever T1 exits, then it will switch to thread to. So in this case, I should see hooray got and then T1 run finishes. And then this will execute. So it should say T2 should be done, switch back to thread zero. In this case, I will delete thread one stack and then set context back to thread zero, which is that a good thing to do? Probably not. So if I do this, well, I'm at another infinite loop because I resume again back to the last get context, which I did not. It's at the beginning of the whole program, so it just runs again. So it creates a whole two threads, they execute each other, it comes back and oh, geez, it killed my computer. Oh, that's not good. Oh, bad things are happening. I did not kill it quick enough. Oh, that's not good. Oh, I can't even log into my virtual machine in another terminal. That's great. OK, we're back. Sweet. All right. So if we don't want to be in an infinite loop, let's see. We do it instead of calling set context to thread one. Well, we can use this thing called swap context and that will do a very nice thing for us. So swap context will essentially call a get context on the first argument, so it would look like it resumes back from this swap context call. So thread zero would resume here. And then the second argument, it does a set context on that argument. So that will save the current state of thread zero to the thread zero you context. And then do a set context to thread one context. So now in this case, what should happen is, well, thread one run would execute print hurray and then return. You see link is set so that when it returns, it automatically does a set context to thread two here. Then thread two would say, hey, thread two should be done. Deletes thread one stack. And then does a set context to thread zero, which should look like it resumes here. Then it should print main is back in town. Delete thread two stack and be all good. And then my program just exits because I returned from me. So that had three threads and I switched between all of them. Yeah. So, yeah. So instead of doing set context, I did the swap context instead. Yeah. So here, the last thing thread two does is do a set context to thread zero. So before swap context, the last time I called get context on thread zero was here. So it just restarted the whole program again and then went through that. But because I did swap context here, it's like calling get context on the first argument. So if I do a set context back to thread zero, it would resume right after that swap context line. And also does me the benefit of doing a swap set context to this. So this would start executing thread one. And then if we do a set context on thread zero, it will come back here. So any questions about that? So that's most of the threading library, right? That's essentially threads and then on top of this, you are implementing some behavior that makes it easier to use in this. Yeah. If I just delete them here. Yeah. Oh, like if I just do this. So if I do this, that will probably be bad because, I don't know. OK, it shouldn't have worked. I guess an MAP twice is fine. But this would delete thread one stack twice. Oh, if you didn't. Yeah. If you just remove this, then that's fine. But technically should delete it as soon as it's not used anymore. At this point, this is like the soonest time that I can detect that that stack is not used anymore. But yeah, you could delete it there. Lucky thing about Valgrind and them being stacks is if you don't delete it, Valgrind will not complain at you. Even though probably should. So swap context will essentially simultaneously do a get context and a set context in a way that makes your life a lot easier. So it will essentially do a get context on this. So it would write all the values for all of its registers to that you context, whatever just executed it. But it's going to play with the program counter such that if you do a set context back to it, it resumes after that call and not as part of it. And then what it does on this side is it just does a set context on this one. But you can imagine if you had to do them both at the same time. So you want to save your values and then do it set context as the last thing. But if I didn't have swap context, it would be a bit trickier to implement. I mean, you could probably do it because it would essentially get and then do a set. And I have to do it in such a way that whenever I return from this get, I have to make sure I don't call set. Otherwise, I just didn't fit loop myself immediately. So I could have been mean and say do this lab without using swap context. Technically, you can. But I will just shave off a good five hours for you. All right. Any questions about that fun stuff? Okay. So I will show you how to use a link list. So there is a sysq.h you are allowed to use. And it will define a doubly link list for you if you use a tail queue. Why they call it tail queue? I don't know. It's a tail queue. Don't worry about it. It works. So how you use it is you define a struct and in that struct, you put whatever fields you want. So in this case, for this lab, the only thing that should be in a link list is probably your ready queue. You should probably not have your process control block as part of a link list. All you really need to do is keep track of whatever the IDs of the threads are that are in your waiting or ready queue. So in ID is the only thing I want in here. And then there is this tail queue entry macro. So that needs, as a parameter, the name of the struct it lives in. And then it just needs a name for this field. I call it pointers because this will essentially define for you a next and a previous pointer without you having to do it yourself. So you can think of this macro as just defining previous and next. Then this tail queue head will define the structure for you. It will name it whatever is in this argument. And then it will be a link list of these types of structures to it. So it should be the same as entry. Then here I can declare a list head. And that is basically just declaring a new list. So this list head element, it won't have an ID or anything associated with it. It would just have a pointer to the front and the back of the list in this case because it's a double-ended link list. So that's what this list head would represent. If you wanted to, you could just call it list. I just call it list head because that's what it's named. Then how would you, you would use it where it actually saves some code is if you want to print the list, I could print f list. Then you declare a pointer to your list entry. And then you can use this till queue for each method. So you give it the pointer you want it to set for you in every iteration of the loop. Tell it the address of the list you want to use. In this case it is that global called list head. And then you have to tell it the name of the fields that you set right here. So these two things just need to match. So after that it will just iterate through that list in order. You don't have to do anything else special. It will not print the last element of the list. There won't be anything null or anything like that. You should use this to iterate through lists if you need to. Mostly in this lab you'll only have to do iterations for debugging. And that's pretty much all you need to use it for. But in this case I just print off what the idea is of every element that is currently in the list. If you want there's also some things where you can get a list entry directly. So like you could ask for the last element of the list. So you just give it the name of the, or the pointer to your list. And then the name of the data structure whatever you named it just has to be consistent. Then here it would print off whatever the last entry is of the list. So how do I use it? So let's have a main. In here we initialize that list. So we'll go ahead initialize the front and the back pointer for you. And then you don't have to deal with pointers. There is a macro to just ask if the list is empty. So here they generally just want the address of the list you use. So here I just assert that the list is entry because I just initialized it. There should be nothing in it. Then here I declare a list entry on the stack because I'm just showing you how to use this. For your program if you want to last longer well this should probably be some malloc space. So here I just set id equal to one and then I use insert tail which needs the address of the list. Then a pointer to the entry to add to the list. And then the name of the pointers field that has the next in the previous. And then it will just insert it at the back of the list in this case because it's called insert tail. There's also an insert head. So if you do man let's just call it tail queue init. It should tell you the name of every function you can use for it. You can see what the arguments are supposed to be. So for instance for us if we're like just putting stuff at the back and running stuff from the front. Well you insert it to the tail and then you might want to see what's at the head of the list. And then that will give you a pointer to the entry. And then you know you can go ahead you can use tail queue remove to remove it from the list. Let's see how that looks. So we insert one we print the list so hopefully it just contains one. Here we declare another new list entry. Set the ID to two. We insert it at the tail so it should be at the end. So our list should be one two. Nothing terribly special. We'll print list and then we'll print the last element of the list which should be two. Then we do a remove. So remove again needs the address of the list. A pointer to the entry to remove and the name of the pointer fields. So if we remove one it should be gone from the list and this should be like super not surprising. So we insert one. The list has element one in it. We insert two at the back. Our list has one two. The last element is two. If we remove one our list just has two in it. Any questions about that? Hopefully not surprising. Highly suggest you use this instead of implementing your own link list. This works. Yours may not. This plays with all the pointers for you. It's a full link list implementation. So tail queue is a doubly link list. So it has next day and previous and then the head. You have fast access to the front and the back. If you look through the documentation there's like... Do they have... It's not here but in that queue.h there's like all types of different lists. So if you want to change it to a singly link list for whatever reason all you have to do is change the name of tail queue to whatever the hell they call it. I forget what they call it. It's like an S. I think it's just an S list or something like that. And also you should have practice reading this because I use it in lab five. So but it's just a link list. Yeah. Yeah. So the pointers is just whatever you call this tail queue entry thing. So as long as that's consistent you could call it whatever you want. I would probably just call it pointers. But you could call it whatever you want. It doesn't matter as long as you're consistent. So this is just a magical macro that will create in this case and next and previous. If you change it to be a singly link list it would just create a next for you. It saves you some typing. You don't have to. You can easily change your list implementation if you really wanted to. All right. Well, let us do an exam question then and see how much we have learned about you context. So I will give you this question. Whoops. No, I will give you this question. Here we go. So this is from some exam where they say, OK, we have some global variables. We have an int i initialized to zero. And then we have two U context, one for thread A and one for thread B. And then they're initialized so that the context for A starts executing thread A and the U context for thread B starts executing the function called thread B. So it says they get all initialized and then the kernel or one kernel thread does a set context to U A. What happens? Do I see any output? If I see any output, what is it? Let's have a look at that and I will give you a minute and then you can give me your guesses after you think about what in the heck that does. Any initial guesses? It won't work. So when it does a set context to B, it's initialized here to start executing thread B. So it should work. That part should work. Prince 1? That's the start. Prince 1 at the start. That's a good start. A0, B0, A1, B1, and then ping pong like that. But not an infinite loop. Stop at 3. Okay. A infinite loopers? We got some infinite loopers. All right. Let's see what actually happens. So here, U context for A set up to start thread A, right? So it declares a variable called D. Where does that live? Stack, right? Thread 1 stack specifically. So in thread 1 stack, there'd be a variable called D and it's initialized to 0. Let's just say it's initialized to 0. So go in the while loop. So what's the value for I? Currently 0, hopefully. So is 0 less than 3? I hope to God it is. Then it would increment I from 0 to 1. Then it would print A, whatever the value for I is. What's the value for I? 1. So far not a trick question. So it would print A1. Then it sets the value for D equal to 0. And that is on A stack. So 0 gets replaced by 0. That does a get context for thread A. So now if UA returns, it would return right here after the get context call. Then it checks the value of D. What's the value of D right now? 0. So that is true. Then it sets D equals to 1. So if it sets D equals to 1, well, now its value is 1. Then it does a set context to B. So now we start going from the purple. So thread B, it declares AD. Where does that D variable live? On thread B stack, thread 2 stack, whatever you want to call it. And it's initialized to 1. So here, what is the value of I? 1 less than 3. So it would increment I from 1 to 2. Then it would print off B2. And then it would set its value of D from 1 to 1. Then do a get context of UB. So if UB gets a set context on it, it will now resume there. Then it checks the value of D. What's the value of D? 1. So that is true. So then it would change D to be 0. And then do a set context to A. So now we are back to A. Now it's going to check its value of D. What is A's value of D right now? 1. Because it was on its stack. So we resume back. Stack doesn't change. We didn't reallocate it or anything. So its value is 1. So it would not go into the if statement. So it skips through that. And then it goes back to the beginning of the while loop. So what is the current value of I? 2 is 2 less than 3. Hopefully not. 2 is less than 3. Okay. Woo. Alright. Been doing this too long. Alright. So I gets incremented. So if 2 goes to 3, it would print off A3. Then it would set its value of D to be 0. Then after that, it does a get context of UA again. Doesn't move it. It checks its value of D. Is its value of D 0? Sure is. So it would set its value of D to 1. Then do a set context to UB. So UB starts executing what's thread B's value of D? 0. So would it go into that if statement? No. Then it would go here. Check the condition. What's the current value of I? 3 is 3 less than 3. Hopefully not. So it would fall out. And then it would hit the end of the function and then return. If I don't say UC link is set to anything, just assume it's not set. So the process is now done like this. We're done. So we get A1, B2, A3. And spoiler alert. If you want to implement swap context, this is essentially kind of like swap context. Without the stupid while loop. So you could actually implement swap context if you really want. Yeah. So git context just saves the value of the registers. That's it. Like where the stack pointer is. If you modify your stack, that's not saved as part of the git context at all. If you modify values on your stack, that's not part of git context at all. It doesn't care. It just saves your stack pointer. So if you change any values to anything, stays the same. So I'm only modifying thread one stack. Nothing else will modify it for me unlike the global because they both see the global stacks should be independent, which is why it stays the same whenever I come back to it. It's the same value it was when I left. All right. Any questions about that? Oh, it's time anyways. Yeah. If thread A just finishes, and then thread A finishes, then if it doesn't have a UC link, that process just exits. Yeah. All right. So just remember, phone for you. We're all in this together.