 Welcome back to three five three. All right. There's only a few people that want to actually do all in this lab. So So this lab in order to get you up and Starting and at least looking at quickly. There's like five percent of this lab is something you have to submit a week from now It's just like an early test. It will take you all of five minutes if you want it to So go ahead and do that. Otherwise, we are doing user threads. So We're going to do cooperative user threads in this and I will show you the Functions that will do the heavy list lifting because essentially We are implementing our own threads and remember threads are basically just registers and a stack So I will show you The library function we can use that will do all the heavy lifting for us for Swapping in and out registers and saving them and all that good stuff and I will also allocate you a stack and This will be Also the first lab where you will have to use link lists So because this is the first lab. Well, you'll have to use link lists I will show you a library for link lists first because you shouldn't ever have to write your own So for this You can access all of these examples. They're in that materials repository. I actually pushed it So you can use this as a basis to go ahead and start your lab So for the first one, I will run through it really quick Instead of implementing your own link list. Guess what? They are already written for you. You don't have to do that. So with this We're doing cooperative user threads We're going to have to do some first in first out scheduling which necessitates probably a queue Which it would be nice to have a doubly link list because well, we probably want to Get a thread from the front to run and then and if we create a new one it goes on the back so probably want a doubly link list and No one should ever have to write their own doubly link list even though everyone seems to love doing that So there is this library called the queue library and inside of it are a bunch of macros for a bunch of functions that will go ahead and Write these things for us. So a doubly link list in this library is called a tail queue don't ask me why and It's basically a whole bunch of macros. So tail queue head means I want to define Essentially a list and I will give it this name So it will create a struct called list head and that will represent our list So like this it would be the structure that has like a pointer to the first and the last element of our list And then also we tell it what the structure is of each element We are going to hold in this list So in this case, I called it list entry up here. I define struct list entry So all I'm going to do is have an ID So my fields are just gonna have a single ID if you want to add more to it You can but for this lab you should only need like a link list of thread IDs in what order to run them in and Then there is this till queue entry So you just give it the name of the struct and then the name just some unique name that you use later And that will go ahead and define in this case like a next and a previous pointer for you You don't have to do that and the nice thing about it is if you for some reason Wanted to make this a singly link list all you have to do is change the macro name from tail queue I think it's S list and boom. It's a singly link list. You don't have to do anything different so This defines the pointers for us. This defines a structure called list head So we're only going to create one list So we'll create a global variable called list head just call it list head and then we'll scroll down to main So how? How we actually use it we have to call a knit to initialize it So we have to call a knit to initialize it we give it the address we can there's this empty function So we can ask yes or no as the list empty well because we just initialized it we haven't added anything to it it should be empty and then I'll Just create a list entry on the stack. You probably can't do this You probably have to malloc your entries, but in this case I create one on the stack I give it the ID one and then I do insert tail So there's insert tail to put it at the back of the list There's also an insert head macro if you want and you just give it the address of the list the address of the entry you want to add and then the name of your pointers field that should Exactly match whatever you used for the entry function above here So you do that goes ahead and that and inserts one into the list Yay, we don't have to write our own list and then here if I print list so in print list all I do is print list and then Declare a pointer to a list entry and then there's this tail queue for each That will go ahead and iterate over every single element of the list for you No dealing with next and next pointers or previous if you're going backwards anything like that You give it the pointer you want it to set the address of the list itself And then again the name of that pointers field for the entry and Here well, we only have an ID so we can print off the ID and that's it so after that we create a new entry ID to we can Insert it to the end so we can print off we inserted it Print off the list again now it should be one two and then we can print list last So there's other functions if I just want to get the last element I can use till queue last There's till queue first if you want to get the first one so we can go ahead and just Just once the address of the list and then just the name of the structure And then that's all we do and we can get the last element and then if we want to remove it We can go ahead and remove it using till queue remove. We just give it the address of the entry to remove That's it. No playing with pointers. Isn't this much easier than implementing your own list? I suggest you do this so If I go ahead and run this Should be not very exciting. So I inserted one. There's a one in my list. I inserted two My list has one and two The last element is two if I remove one my list just has two in it Questions about that. Yeah Sorry so the pointers is just the name of the entry here and This will essentially in this case That'll be like the name or like the namespace of like the next in the previous pointer So it's it's just how it works. It's it's see so it just does hacky stuff with macros But it works. You don't have to implement your own link list All right. Good for link list Highly recommended to use this one. All right. Let's go to the hard part so There is this thing called a you context So you context is A library function you are going to use that will do all the heavy lifting of saving registers swapping registers in and all of that fun stuff So you can think of you context as just a copy of all the current registers So there is a few there's four you context functions. We're going to use and The first one is get context so what get context does is it just takes an address of a you context and It will save the current state of the thread like all of the threads registers to that structure So we can go ahead and restore them later So that would save Literally everything the stack pointer the instruction pointer every single register and what their value currently is So if there is so all this does it initializes this you context that we just called Thread zero you context. So if we go up here, I just declared three global variables. I just declared Three you context. So we have we're going to essentially create three threads in this program so We're going to call thread zero We're just going to give thread zero the name of the main thread. So we'll just assume that it's being zero or thread zero So if I do get context here It just saves the state of the current thread that is executing it. So like I said literally everything So questions about that so saves all the registers and everything to that Okay, so we can ignore the comments for now The fun thing is that is the save and then set context is essentially to restore all the registers So if I do something like that if I do set context what it will do is it will Read all of the registers that are in this Context structure and just restore them all to whatever they were saved at previously so if I do this if If I set context what's going to happen is Well, I saved them last up here at this line Including the program counter and everything like that if I set context what it will do is it will restore it So my code will go back up here and it will look like it's just coming back from git context So if I do this And I run it I Do something like this What's going to happen is I'm going to have a fun infinite loop So it's just going to have an infinite loop here Because what's happening is I get context I save all the registers to this thread 0 u context Then I print f high and then I set context which means I restore it back to the last place I saved it so it goes right back to get context we print off high and then we restore it go back to get context print Hi restore it go back to get context print high. We have infinitely looped ourselves immediately So questions about that. Yeah, how do I what? So, how do I progress pass? set context You we probably want to switch back to something else and then maybe resume and not do this So we probably want to avoid this at all cost. So, yeah, the next question is how do I make it be more useful than this? All right, so we'll get to that in a second, but any questions about Why this is an infinite loop does everyone understand why this is an infinite loop? I just keeps going on and on and on and on and on again You may encounter this if you actually do that because essentially For your threads, this is going to be all your registers. So you are managing this thing so That clearly a bad idea so in order to make it useful well If we just like kind of rewind a thread back in time Doesn't seem that useful if we have threads. We're doing concurrency, right? We're switching back and forth between them. So we at least need one other thread So there are some other functions. I gave you so there is a function called new stack What that will do is allocate a stack for you. So you just call new stack you get a pointer back for your new stack and You can go ahead and use it. So If we want to create a new thread well each thread needs its own stack So I declare a new stack for I'll call it thread one and then We need a you context to represent the threads registers So we just need two things to represent a thread a thread and registers. So after this step, we have both. Oh Oh, we have a good question. All right. So if malloc was incremented between set and get context with the variable increase malloc variable Yes, so if I did something like whatever I'll shortcut it. Yeah, so whoops wrong Compile So whoops, I Need it infant loop myself again. Sorry So yay, it increases because well, it's on the heap doesn't really matter So if I malloc it between this I'm just incrementing that memory location over and over and over again So it would go ahead and grow Over and over and over again. So yeah, you would get into trouble with that So Let's go back to creating a new thread. So we're going to create thread one Like I said, we need a stack and then registers. So in order to initialize the You context for thread one. We just have to use get context even though we're not planning on Resuming this at all. It's just used to initialize something. So we always have to use it so we do get context and Save the current registers, which is like the main thread to thread one you context But we're going to overwrite it shortly. You just have to initialize it with this. Don't ask me why and Then in the you context there are some fields we can do that will go ahead and Tell that you context that I don't want to use the stack that I currently saved I want to use a different stack. So you can set its stack pointer to The stack we got back from the new stack call And then you also have to say what the size of the stack is in this case new stack defines Gives you a new stack that is this size back 16 kilobytes don't really worry about it too much. It's just this macro So that is the size of our stack that we will be using for this code Now in order After that to actually Have this function go ahead and update all of the registers. We can use make context So make context takes as the first argument the address of the you context to use that should be already Initialized to with get context that we're going to go ahead and change some values of So we change this so it would go ahead and find the register for like the stack pointer and go ahead and update it to Actually point to the stack that we allocated for it And in addition it will change the program counter for us to actually start running This function we give it so the second argument is a function and it will initialize the program counter So that if we do a set context to thread one You context now we will start running this function called thread one run and all these the function just takes No arguments and returns nothing, but it turns out you can actually optionally give it some arguments Which you may or may may not want to do for your lab There's different ways to implement it. So if you want to give it arguments the next The next argument to this function is argc so that's how many arguments you're going to pass to that function and You can only pass integers to it. So here. I'll just pass one integer to it and then just give it the value 42 and this is the argument we can pass we can only pass integers So in my thread one run There's one integer argument. I can say Hooray, if I'm actually executing this, I'll say hooray I got arg zero print off the value and then I'd be done with this thread kind of like p threads So any questions about that? so what I can do is Well after I make context, I'm currently running the main thread, but I can just immediately Do this I can set context to that and what that should do is Immediately just swap the current registers with whatever I saved in thread one you context which should Use thread ones stack and then start executing this function and then it should say hooray I got arg zero whatever that would be so if I run that I get yay I did it hooray. I got arg zero So questions about that So I just changed what thread I have now So I created a new thread called it thread one gave it a new stack told it to run this function So kind of looks like p threads, right? Except guess what their user threads a kernel has no idea what the hell we're doing. We're just kind of playing with stuff ourselves so Other rules are with you context if it actually just Reaches like the end of this function Well guess what it just exits the process Process is done nothing we can do so in order to make sure that I Don't just exit the process. Well, maybe I'd be like, okay Well, now I know thread one is done So I shouldn't run it anymore, but there's something else I should run So what would happen if I did something like this or it's like, oh, yeah, just go back to thread zero I can move that out of the way something like that Do the same? Yeah, kind kind of it kind of looks like I do an infant loop again. Wait was that Yeah, so if I do this Essentially, I just create a bigger infant loop So I do this I run it I get hooray got 42 over and over and over again. Why? Well, what happened here is I? Saved the current registers to thread zero you context as the first thing I did in Maine Then I created a new stack Then I initialized thread one you context I set it stack and then I did this make context which went ahead set the program counter set the Stack pointer registers and set everything up. So it would start executing that function I did a set context to the thread one. I just set up So I immediately start executing this function with thread one stack and then at the end I do set context to thread zero so that will go back and restore whatever was saved in threat to thread zero and Whatever was saved into thread zero was last done at the beginning of Maine So it would come back here and then thread zero would go ahead create a new stack again set up thread one again And then switch to thread one thread one would run then switch back to thread zero That the da da da da and this keeps on going infinitely Yeah Set context just restores all the registers in that you context so make context will essentially Initialize a register so you can so it changes the stack pointer to start running that function that you gave it Yeah, yeah, so make context as you can see it's kind of like p thread create Save where you currently are and then that way if you do a set context back to it It looks like you just return from get context Yeah, it looks like it just continues executing after the get context call Yeah Yeah, yeah, so that's why we got an infinite loop because here if we get context so we save it and then Set context restores it so it looks like it just starts executing here And then next line is set context so we go back there set context go back set context go back context switch Yeah, sounds good, but there's some difficulty there because yeah What are you saying is well if I want to switch to another thread? Well, I should probably like save where it currently is and then set context to a new thread And then when it's done it should you know set context back to me But you have a little bit of a catch-22 situation there, right? So if I did Get context so let's just say I went ahead and I save this Yeah, so if I do this this like has the right idea, right? But I'm saying okay I want to like just resume where I was go like where I was where I was before and Then I'll set context and then I'll switch to something else. Well now that something else will go ahead Run and then set context back to the last get context which is here And then set context and we just infant looped ourselves again Right, but except we did it this time We infant looped ourselves But instead of having to recreate the whole thread zero again We infant looped ourselves much more efficiently this time so we didn't have to go ahead and set up the whole threads again and Yeah, and the other comment was about p thread create so p thread create kind of does this make context But p thread create is all That so p thread create would need to allocate a new stack and do the make context. There's two things going on, right? So the allocate a new stack it just happened in whatever thread like my main thread that just created it Doesn't really matter what creates it Okay, so yeah this Idea here so everyone clear why this is also an infant loop, but more efficient so what your idea is pretty sound of like how to go back and That is the fourth function call so there is actually something called swap context So what swap context will do is exactly what you wanted it to do so if I do swap context and so If I do swap context t zero So if I do swap context swap context is a bit more magical So what swap context will do is it will go ahead and it will do a get context on this and Kind of fudge it so that if you do a set context back to thread zero context It resumes right heat it resumes right here Instead of trying to do the set context again, so it does a little bit of magic If I wanted to I could ask you to implement that, but I'm not sadistic you turns out you can implement it because Turns out stacks are unique per so you can kind of tell whether or not you came back or not But you might figure that out in a later example I won't make you write that for this course or for this lab. You can just use swap context So it essentially does a get context on this so it'll come back after the swap context and do a set context on this view context But let's go ahead and get rid of that first so other ways we can oh Yeah, so other things we can do is First let's just in order to play with stuff. We'll just create a new thread called thread to So I just get a new stack. I have to initialize the you context. So I initialize thread to you context. I set What the initial stack pointer should be the size of the stack and then I do a make context And I give it thread to run to run and then give it no arguments So now I have to you context set up that I can use so if I go ahead and I just Do a swap context what will happen now is I? Save the context to thread zero. I set it to thread one So I should execute this line and if I just get rid of this What's going to happen is I just end my process, right? My process just ends immediately But if I wanted to what I could do is well, I could set context all the way back to thread zero and if I do that then It will come back here to the swap context print main is back in town and then delete thread to stack and That's it Yeah, you would Probably want to delete the context in this case are all global variables Yeah, I just have them they exist for as long as my process exists because this is a little example You want to be smarter about that so you probably need to malloc some stuff So that that's on you, but yeah Well in this case, I know I'm only making two threads. You don't know how many threads your test case is going to have to create So if I wanted this to do something more so first everyone okay with this so Thread what zero that main initial thread swaps to thread one Thread one prints. Yay, and then we select context back to thread zero So we resume back here prints main is back in town delete thread to stack which we'll see why in a second and then Returns from main which means we exit the process which means the process is done. Everything's done Okay, well then let's do something more fun. So what if I was like, okay. Well after thread One is done running Well, if this was like our actual program and we created thread one and then thread two thread two should probably Run after thread one. So in this case, I can set context to you thread to you context and then Should print off T2 should be done switch back to T zero So I can go ahead and now here I might just delete T1 stack because I know I'm never coming back to it and then I can set context back to thread zero and then thread zero will go ahead and print main is back in Town and then delete thread to stack and then everything's nice and cleaned up. I've cleaned up all the stacks. I've allocated and When I run that This is the message I get from thread one This is the message I get from thread two and this is the message I get whenever I come back to the main thread So are we good with that? ish Okay, so other things you could do So Other things I can do in thread one. So if I just delete this line and I run it again Well, thread zero switches to thread one thread one runs this function till it's done And because I don't switch to another thread or do anything process ends so one way to do this is if I Want to run another thread that I already know what it should run ahead of time, which May or may not work for you. You may want a cleanup thread or Some thread that you know exists, but in this case there is another field in this you context called you see link and You set this to a you context and the rule is Whenever this run function is done instead of just ending your process It will just go ahead and automatically do a set context to this you context that you set it So if I do this I set you see link equal to thread two context So that means when thread one is done running it will automatically be like it has a set context here to thread two but without me having to do that so You just have to do that before the make context so it can go ahead and set up all of its magic And if I go ahead and I do that Boom still works So thread one executes because I set you see link whenever it's done It set context to thread two thread two goes ahead It runs this crap the leaps thread one stack set context back over to thread zero Thread zero runs main it back in town that the dot the dot everything works cool right so any questions because This is like most of your lab except you're just moving stuff and functions and making it a bit nicer. Yeah Make what? Yeah, so the question is is there a way to make delete stack automatic as well um Depends so you can like one strategy you might have is Maybe like you know in p thread it needs a function to run So maybe you write like a little wrapper function to run that you know find some Your real thread function to run What I call like start routine so you set it up so that your run goes ahead and Does some of your own setup calls the start routine that like p thread would want you to have want you to do and then maybe it? delete stack and then maybe it you know does like a Like an explicit thread exit because well The rule of your threads are going to be like if that thread is done It's start routine just like with p threads. It should implicitly call exit so you have to implement that so maybe you think of doing it something like this and writing a different function You could do this, but if you delete the stack You cannot delete your own stack if you're still running that will cause lots of issues So that is one very very very common thing in this Lab is if you try and delete your own stack while you are running Which means you require a stack you will have a very very very very bad time and it will You will probably get at best. You will get a segfault at worst. I don't know you'll be debugging it for a few years All right. Yeah, so you said what if So in this case in my main thread, I could delete all the stacks so In this case maybe but in general the main thread might exit before the other ones Right, so you might have something Like maybe you know another thread is dead so you can clean up its stack But you can't really depend on the main thread to do everything because the main thread might not exist in This in this example the main thread is the last one that exists, but Yeah, so in this lab you'll also be responsible for cleaning up the stack So every new stack you do should have a delete stack whenever it's done you're implementing exit So when you actually you're also implementing a join So you can clean up the you can delete the stack whenever you join on it for example probably a good time to do it and then this Thread calling join can delete the dead thread stack and that is fine. You're not deleting your own stack All right, any other questions with that? Otherwise, we get to do a fun example all right, so This is a question that was not that there we go This is a question that was on one of the exams at one point and Through doing this question. You can probably guess how Swap context is was actually implemented so in this case We're going to assume we have two threads a and b They view context all set up one is going to go ahead and Thread a is going to set up and start executing this Thread b is going to be set up to start executing this line. So I Will have two pointers to help me. So whoops. Oh That's going to be great So I will have a pointer for Oh this Stop moving. All right. I will have a pointer for you context of a to tell me where it's going to resume from and the pointer for you context a b to tell me where it's going to resume and then otherwise I will just use a Blue to keep track of my variable or where I'm executing So those aren't global variables. I don't really care about the you context But I have a global variable called I and same rules apply. It's a global variable Threads only have their own independent stack. So That's shared between the threads Within thread a I have a very I declare a variable called D and I initialize it equal to zero So currently in this thread D is equal to zero Then I have a while loop. So while I is less than three currently I is zero So I go into the loop and then I change I from zero to one Then I print F. I would print a is one and Then I set D and In this case it would be thread a's D Equal to zero and then I do a get context of Ua so I know that if I resume Ua it comes back right here So then I check if D is currently equal to zero currently it is So it's currently zero then I set D equal to one and then I do a set context to U B so I start executing here So start executing thread B thread B has its own stack as well. So in this thread D is equal to one Then I check is I less than three No, I is currently one. So I would increment I on this line From one to two print F It would print B is equal to two or B colon two Then I would change D which is this threads D back to one Then I do a get context So if I do a get context I set UB essentially equal to here and then I check if D is equal to one Oh, currently it is I set D equal to zero and then I do a set context here to Ua so I Come back and I resume right here and because I have resumed right here It would restore the stack pointer, but remember it's just a stack pointer The value of D currently on its stack doesn't get reset back like it's We already wrote to it in memory. So D is currently equal to one So D does not equal zero. So we would skip over this. So see we selectively skipped over it So this is essentially how it would implement that swap context. So we would only we would skip over The set context the second time right? This is essentially that one step implemented swap context So now D is currently still one. So I would not go into the if statement I would go back up here to the while. Yeah Yeah, though the whole point of this is each thread has its own stack If you update the value and then you come back to it the value is still the same Yeah, so threads do shared memory, which is why they both see the same I But they have their own stack Sorry, so D does not become become one it it was one before Right right before we did the set context over this thread B. We set it equal to one So when we come back it's still one because This like updates that memory location in thread a stack Right and the you context just restores the stack pointer. It doesn't like reset all the values like if I changed 10,000 values it wouldn't roll them all back, right? It's just a stack pointer So it moves the stack pointer back. So they're in the same spot like If I didn't declare D up here Well, if I went ahead and like I came back and then I declared a new variable Then it would replay that stack pointer and like reallocate it again and probably overwrite what was there But because I defined it way up there. It was already in scope. It's still the same Yeah No, I as a global variable. It's in global memory. Everyone can access it Global variable. It's just a random virtual address that the compiler picked for you whenever you compiled it Yes, yeah so The stack is What each thread has so a stack is a data structure keeps track of all the variables and a function and everything like that No, it's a global variable So it exists all them technically these threads If they knew the addresses of each other stacks, they could access them. There's nothing preventing that the only thing giving us sanity is that each thread should only access its own stack which Is what you're actually going to do for that lab as well because you could try and share stacks between threads You will never ever ever ever ever debug that Just like you won't debug deleting your own stack as you're trying to go ahead and use variables All right, where were we? Oh, yeah in this case. Yeah, so D Still one so we'd come back up to the while check i i is to we'd increment it from two to three Our print we would print a colon three Then we go ahead and we would set D on thread a stack to zero Get context again, so we'd resume at the same place check. Oh D is equal to zero. We set it back to one We set context back over to thread B. So we resume right here Thread B's D is currently equal to zero. So we would skip it here Go check the while is three less than three. No, okay. We're done So thread B would reach the end and because they're you context We can just assume that if it reaches the end it just ends the whole process So our final answer would be this And yeah, this is where like in the first C programming courses You might have heard like there's the stack. There's the heap and there's global variables and they're all in three distinct locations Didn't matter before Guess what? Definitely matters now Otherwise you're going to be in a world of hurt So a questions about this fun example Because yeah, if I really wanted to if I did something like if I had Like say what I said before if I had like in X here right after the get context well that Gets another four bytes on the stack and it would essentially just replay that every single time So it probably get four bytes then the stack point gets reset before it got the four bytes And it gets the four bytes again that the da da da da a lot lots of fun All right any other questions about Threads we can all implement them now But yeah, so just remember to that all the code examples I gave today You can access so you can do the ye old copy and paste if you would like But other than that that's the whole example Showed you how to make a threads registers you context stack new stack and delete stack So good luck and do the early thing should only take five minutes So just remember pulling for you. We're all in this together