 Good morning. Good morning. Everybody have a good weekend? No. No. I agree with you. Still last night. Still last night. Still last night. Okay, so, other than other disasters that are going on in the world, today we're going to talk about some problems that we've created for ourselves. So, the last week we've talked about concurrency. We've talked about creating an illusion of concurrency inside the operating system through the threat of distractions. We've talked about context, which is our mechanism for allowing the system to be concurring by stopping one threat and starting another threat. Unfortunately this scenario for concurrency is going to create some problems for us. But specifically concurrent execution creates problems in making sure the data stays consistent and creates some challenges coordinating execution that's a giant spirit. I can't talk. Coordinating execution between multiple threats. Okay, so last thing I'm going to say about the game other than where it's all over. Okay. So, assignment zero is new today. Five thumb is holding office hours today from noon to two. I will be in my office after class if you'd like to come by if you need some help with things. But, assignment's new today at five. I want to get assignment zero in and move on. So, we can go on to the front side. Assignment one will come out as soon as possible. Probably today maybe five. Kind of a multiple page release. But at this point a lot of the tools that we need to release the assignments are already built. Hopefully assignment zero will go a little faster. We're still working on the recitation times. For now, we're going to stick with the ones that we had already established. Look on the website. Do note that the location of two of the recitations has changed. So, two of the recitations that were in Talbot 111 have been moved to Davis 113. Which is a much nicer room in a much nicer build. So, hopefully. Yeah, so finally, just in case you didn't realize this, when we sent emails to the class list other than suggestions for how I need to humiliate myself today after the new England football page rate lost. Emails we sent to the class list are kind of important. I try to keep them as brief and to the point as possible to please read them. When you email the staff and you say, hey, I don't know how to do this completely obvious thing that we explained over the class list about five minutes ago, then usually that makes us wonder if you really pay much attention or read. So, you're a class and we mail sent to the class list. There's an archive. I think the link to the archive should be in the emails itself if it isn't. I'll fix it. And if you need to review things, go in the archive and I'll be able to send with it. All right? Any questions about sort of administrative stuff? Okay, so let's review. So last week we talked about, again, about the CPO abstract, particularly threats. We talked about how to implement them. We talked about the goals concurrency. We talked about context switching. Any questions about stuff from last week? Any questions or any doubts? Desires to talk about things? Any questions? Okay. All right, welcome guys. Welcome. All right, so let's review. Starting in the back. I always get the same people in the back. I'm going to start in the front. I'm going to start in the middle. Operate systems. These go from privileges to complex resources. Good. Processors grant special privileges to the operating systems through. Well, the kernel is in the operating system and how do I do that? You're close. One more. So kernel. Kernel operating mode. When I'm in kernel operating mode, what can I do? I have access to what? A different view of memory. Okay, that's one thing. I have special instructions that I can execute and I have different view of memory. So, again, come back to memory in about a week. Okay. Interrupts. Three reasons that the operating system would start to execute. What are the things that will cause the operating systems to be getting run? Okay, there it is. And you took all three answers. Hardware needs attention. Hardware needs attention. Device requests some service. Software requests some service. Software wants to do something that will fire the operating system, namely perform a system call at the operating system. Do some work with devices or other pieces of the system that are privileged on the software's behalf. And finally, if the software does something unfortunate, then the software will generate an exception, which the operating system will have. Yes, you may. How do we have an exception? So, okay, fair enough. There could be cases where you're saying the hardware has some sort of fault or whatever. And those would be handled probably through the hardware interrupt method. From the perspective of the processor interrupts another exceptional condition in my book fairly similar. The difference is that there are instructions that I can execute as an application that will cause an exception. And again, everybody's favorite is spot by zero, but also things that use virtual memory will generate. It's just a little bit of a different method. I think it's worth separating those two for the purposes of software. But in general, you can think about hardware need and attention. Hopefully it will generate some sort of error. Because if the device just sits there not doing anything, then it might be very difficult for the kernel to figure out what's wrong. So the device is really dead. Right? If you go and just pull the device out of your system so it never generates another runner, never responds to another cameo, it's probably not going to put the operating system in a position where it really has very many good options. CPU limitations. So the two limitations of the CPU we talked about, the operating system tries to address one of them. Two problems, two limitations of the CPU that the operating system is going to try to pull the problem. Faster than other parts of this system. What's the other? There's only one processor. Or maybe there's four cores. But in general, the number of cores is less than the number of things the system is trying to do at any given point. Okay. Contact switching. What is the contact switch for you? Contact switching is where the process switch mechanism of multiplexing. Okay. And what? The contact switch I switch between two what? Two processors. Two processors or? In this class we think a little more about threads as the CPU abstraction. But there are times in which we will talk about switching between processes. If the processes have only one thread or if there's switching between two threads then half of people are allowed to switch between the processes. So if I switch between threads and those threads belong to the processes then I can talk about switching between the processes. Alright. Private thread state. Adress space. Well, average space is usually shared with the threads but what part of the average space? The whispering. The stack. The thread stack. That's where threads store local variables. So variable and function context store it on the stack. Those variables are private. Right? Alright. Contact switching creates the notion of what? What's the illusion that we were trying to create? Good currents. The illusion that one processor is being seamlessly divided between multiple tasks or sometimes we think of it as each task having its own processing, right? I'm executing instructions and it looks to me like a sequential seam of instructions despite the fact that the processor is being stopped and switched over to another thread and then switched back to me and so on. Okay. Thread state. So we talked about this a little bit on Friday. It's kind of slow down. Make sure that you guys get this across and maybe I just spend enough time here on Friday. So threads, states of the thread can be over it. It's running. It's executing instructions on a core. Right? What about the ready state problem? It's ready to run but it's not starting yet. It's ready to run but it's not running at the moment. So it could be run. It could be skeptical. I couldn't move it out to the CPU and it could just keep on going. Right? Waiting, blocked or sleeping. Anybody? Waiting for a smile. Waiting for something to happen. But more importantly it's not ready. It cannot be run. If I started to run this thread, something that it's asked for to happen hasn't happened yet and it wouldn't, you know, something like, for example, if it made a system call, let's say it made a read system call. The semantics of the read system call are either that has to fail or I have to copy some data into a buffer that the thread has set up for me. So if the call is not done, the data has been copied and I start running the thread again and it's going to think there's data there and it's going to process garbage or some bit of data that's half-finished. So it means that something that the thread asked to do has been completed yet or some condition hasn't been met and to start the thread up again would cause it to be in some sort of bad state or to read. It's something that it's asked for hasn't been finished. Okay. Transition between thread states. Running to ready. What does this mean? What's that? Everybody has to go everything in this file. Okay. Why does the thread transition from the running state to the ready state? What I'm looking at is a context switch. A context switch. And a context switch that caused the kernel to de-schedule this thread or stop it from running. So that's another question. Alright. Running to ready. What did the thread do that caused it to leave the running state and enter the waiting state? System call. It made a system call. Ask the kernel to do it. I'm the kernel. Identify upon material. Alright. Waiting to the ready state. What happens to a thread right here? Still. Yeah. Something that was happening way before happening. And now the thread is ready to be run. Maybe I finished writing the data from the system call after the buffer that it established. And now I can start up again at some point. Running to running. Can you call that? Yeah. It's good. I scheduled the thread. And then finally, running to terminate. What happened in this case? Is there it has been completed or it has been completed? Right. Something happened that caused me to terminate. Either called exit or maybe it did something naughty like 5x equals. Okay. So any other questions about the stuff that we covered last week? Yeah. Right. So I've had material on Friday's slides comparing implementations of threads in user and kernel mode. That is material is So that material is in the slides. I'm not going to cover it in class because I kind of think we need to keep going. But here's the deal I'll make with you guys. If I decide that I want that material on the exam, I will record maybe a 10 minute video and put it online where I cover those slides in the comfort of my own office. And then you guys can review. That stuff may appear on a test because it's a good design. There's good design questions there. But I would encourage you to review that material on your own. Same questions into the staff. Again, if I think I need to review it further, I'll just record it. Does that sound fair? Yeah, that's a cool topic. I wish I got there, but I don't want to. Okay. If a process spawns too many threads, does it get some help in the CPU? Does the CPU have to take care of number of threads created by a process? Right. So if a process, if a process on Linux, for example, creates a bunch of quote unquote then that does affect how it's going to. At the same time, the kernel is also aware of the fact that that process has a lot of threads. So you might think I can monopolize the CPU. I can get more than my fair share of time on the CPU by creating a bunch of threads. And I think, I don't know exactly how this works out. My guess is that that's true to a certain degree, but at the same time, you're not fully the kernel. The kernel knows what you're trying to do. The kernel knows that, hey, this process here has 10 threads. This other process here has one thread. And so the kernel can take those differences into account. So if I created a bunch of, like, thousand threads, I'm not in general going to get a thousand times more CPU time than I've had if I created a bunch of threads. The kernel is probably going to share a process across that level. And so that creates an we don't want to pin an incentive for processes to just one thread so that they can affect the schedule. We'll talk about that because we get to schedule it. All right, I have a question about this stuff. It's a good question. All right, so now let's talk about this problem that we've created for ourselves. We've decided computers got powerful and more people wanted to use them. We decided to start implementing this multi-programming abstraction and we started to do preemptive context question. We started to stop threads in the middle of something without asking their permission, without giving them any sort of notice, putting them aside, letting some other thread run and bringing them back. And that illusion is really powerful and useful. We talked about this last week. And, you know, threading can be a good mental model to approach how to structure GUI applications, how to structure anything that has a number of things that are going on at the same time. It can be useful to think about I'm going to start up a thread to do that task. To wait for somebody to click various pieces of the display to go and buy a certain part of a web page. And we also have showed that threading can highlight these caused by slow software devices. So rather than having to stop an entire process, I have one thread in the process that could be blocked waiting on network.io or file.io or something else while other threads in the process can continue to be used for work. So this is a good thing in general. The problem is if we go back to our example about the cooks that threading and concurrency particularly preemptive concurrency creates these problems. Which is that data there's two problems. What is coordination? So how do we allow multiple threads to coordinate and communicate effectively? We're going to talk a little bit more about coordination later in the week. What are the equally important, if not more important, this idea of correctness? So we need to establish in a way for multiple threads who are accessing shared data to ensure that those accesses are safe and that we can guarantee that the result is going to be as expected. The other thing that comes in when we start to talk about multi-threading and concurrency is that there are certain calls that have time based semantics. So if you look for example at the read system called let it's man page it states that when a read is performed if a process can show I actually don't know what the mechanism for doing this is but if there was a write that occurred and I can show that the write occurred before the read even if the write was done by another process the read is supposed to return the updated data so this is an example of time based semantics for a function called. If a read, if a write happened before read even by any other process on the system to the file then the read is supposed to be guaranteed to return the data that was written. And enforcing these kind of semantics requires synchronization. So again today we're going to focus on correctness. We're going to start building up in terms of like low level synchronization principles. And then Wednesday and Friday we're going to keep talking about higher level primitives that are built to solve problems. One of the things about synchronization is that the primitives that we talk about in this class and the mechanisms are very much responses to actual problems that systems design space. So we're going to talk about some general purpose primitives. And then there's a whole, if you go look at Linux or Windows they've got a dozen different synchronization mechanisms that are built around different patterns of data sharing. They're built to support a certain component of the system that shares data in a certain way of data efficient. We're going to talk about some of the ones that are more generally useful to solve a large array. And then the last thing I want to say is the thing that you guys are going to experience. So in general synchronization is a broader topic than just defined operations. So how many people have ever took a class that's really focused on multi-threaded programming using some of these tools? Good. At some level that's good because here it is, you're going to learn. But on the other level this is one of those parts of the class that's actually really applicable to everything other than operations. It's very likely that you guys will write multi-threaded code in your future programmers and you will need to use some of these ideas. One of the reasons that we talk about this in operating systems is that operating systems in many cases provide help in implementing some of these primitives and guarantee their brightness. The other thing that you get to do in this class is you get to work on quote-unquote patient zero mode, right, which is the operating system itself. So this is in many ways the gnarliest and the most difficult multi-threaded application that anybody needs to write. And you know, it's, why is this, right? Well, it's multi-threaded access to resources and these are inherently shared. Anything on multi-threaded is shared, right? I'm using a lot of threads within the operating system itself to hide some of the legacies that we talked about, right? And basically when I have a lot of shared state and a lot of threads I create all sorts of interesting synchronization. So the primitives that you guys are going to develop for Assignment 1, you will continue to use them and you will have to use them for Assignments 2 and Assignments 3, right? So if Assignment 1 seems a little bit arbitrary in terms of some of the things that you have to do, remember that you need to know how to use these primitives and know how to think about multi-threaded programming in order to do the later assignments. And of course, right, if the operating system goes bad then that's bad, right? I mean if your web server messes up it's multi-threaded programming and it dies if the OS messes up and that's a little bit more serious, right? Okay. So, concurrency implementing the illusion of concurrency using the mechanism of a context switch has implications for how you think about sequential execution, okay? And it's an application programmer and even within the current, right, right in the kernel, here are the things that you are no longer allowed to assume. Threads, well here's the things that you have to think about. Threads can be run in any way, right? You can't make any assumptions about how threads are scattered. At any point in time a thread can be stopped for an arbitrarily long period of time and restarted later. And when that thread is restarted the state of the world may have changed quite a bit. Now, the threads context and staff will be the same. But any other sort of shared state global variables can have changed, right? And so, this is the thing I would keep in mind. And finally, again, I mean threads can be stopped for arbitrary reasons. And this is unless you know other threads, unless you've done something about this by using a synchronization printer you have to look at your code line by line and essentially say if this code stopped to write to you and some other code ran and made and accessed these data starters, what would potentially happen? And this is this sort of incredibly defensive way in which I have to start thinking that's from a programmer when he worked with the rest of his clients. Something we'll come to later too is that things you know, I think people are tricked into these kind of erroneous assumptions by the semantics of language. So, for example you know, some hard restrictions may be atomic. We'll talk about atomic in a second. Lines of C code are never atomic. We never assume that the line of C code is atomic. You have no idea how that line gets compiled down by and what are the assembly instructions they could put out. So people will say well you know, I added one to this variable and it's just one line of C code so I figured that, you know, there was no way for the thread to be stopped. You know you look and it turns out it's three assembly instructions and your code got stopped after the first one for the second two. So this is the way to think about things. This is kind of like there's certain concepts in computer science that kind of really will mess with your mind. So recursion, I remember when I first learned about recursion, recursion is a difficult concept. It requires a certain amount of new thinking. This is the same thing. If you're feeling comfortable when you're writing some of the parts of the later assignments try not to feel that way. Try to look at them very, very closely and say okay am I making the assumptions that I should make that are up on this line. Now, again I just want to point out, in general these are good things. The reason why we've done this is so that the operands can make better decisions about how to schedule resources. But from the perspective of correct these things can be your head. They can get better. Alright, so let's go let me introduce an example. This is an example taken directly from the preterm exam. This is an example we'll use for all the rest of this class. So, consider this. I've got a thousand, so this is the fact that counts are kind of like the canonical example we talked about, this kind of I don't know why. Look through slide deck after slide deck. Maybe it's because people care about money. So I decided to connect this with something else you guys care about which is the brakes. This gives you some general sense of what my input of ratio is as far as conversion of the dollars and brakes. So I've got a thousand dollars in my bank account. Two of you guys are depositing money at the same time. One of you guys is depositing a thousand dollars. The other is depositing two thousand dollars. How many thousands of dollars should I have in my bank account when this finishes? Four thousand dollars. Not that much. This is really cell phone bank. Can you turn off your cell phones? I laid the point over there. Okay, so here's my snippet of code. Okay? Let's not get into an argument of whether this would compile or whether or not this is a good way to write your sponsor or whatever. This is how some person at your company who may be your boss wrote this. Okay? And what does this code do? Right? First thing it does is it acquires the balance in my account. The local variable. It increments that local variable by the amount that you're depositing. And then it stores that amount back into the account. And then there's this function at the bottom that sends me a little text message explaining that there's more money in my account. Okay? So assuming that there are two deposits that are happening concurrently and they're running in multiple threads on the same machine. Okay? What's the best case scenario here? What's the best case scenario from the perspective of correctness? Well, I want to have 4,000. Yes, that's true, right? But what's the way in which this is going to happen that in some level won't test any of ours on? You want to measure again? Well, okay, specific order, but more specifically what about these two threads? If the threads don't what, then there should be no problems by a guarantee. Right? Anybody? Right, so if these threads just execute one after the other, right, this thread runs and then by the schedule that thread it's finished, this thread runs, then there's no problem here guaranteeing that my final is what I want. Okay? So this is the best case, right? But again, we've created this concurrency monster and so at any point in this program, thread, you know, the red thread could be stopped and the blue thread could run. Okay? So let's go through a little less good example for me. Okay? So what's the slightly less good example? There's several ways that this can turn out. Okay? So the less good example is the red thread starts to run and it stores the balance local. At this point, bam! Contact switch. Do you like that? You working on that one? Bam! This is Jersey. Alright. So now the blue thread runs. Okay? Blue thread stores the balance locally. Okay? Bam! Contact switch. Red thread starts to run again. Okay? And the red thread finishes storing the balance into the account, which is now according to the red thread, $2,000. Alright? What does the blue thread do? What is the blue thread going to do? Well, it's going to overwrite it with what? Because the blue thread has not seen this update. And so the blue thread is just operated on its own local state. It's going to add $2,000 and it's going to store three bets. Okay? And now, you know, there's $1,000, that's just how to call it. Alright? What's the real bad case here? That's a much better question. No, no, same scenario. $2,000. $2,000, right? And I can essentially get to $2,000 by just inverting I can get to $2,000 by just inverting the previous example. So even if I just swap the two threads, I would end up with $2,000. Right? Because let's see here. Is that true? Yes. Yeah, here we go. Right? So this guy gets the balance and this guy gets it and it's also $1,000. He writes $3,000 so for a brief second of time I have $3,000. But then this guy runs and overwrites that balance with his local savings amount just $2,000. Okay? Does anyone have any questions about this? This is important to understand because this is kind of the root of this issue. This is the root of the problem. And again, this is usually done in these cities because it's easier to understand. But what really happens is that this happens this interleave and will happen at the assembly level. And so this interleaving could potentially happen in the middle of one of these instructions. Right? In the middle of the line of C code. So the line of C code might actually compile into loading the register or loading the value into a register for memory modifying the register and writing the register back. Right? So a single line of this code might pile down into three assembly instructions depending on the architecture that you're looking for. But I'm trying to explain it because it's easier to understand for people. This is just sort of race conditioning. Yeah, exactly. So what is this? This is a race condition. And when we talk about a race condition we say that the output of a process is unexpectedly dependent on timing or authoritative data. And the critical thing here is it's unexpectedly independent. We didn't expect that something about how those threads were scheduled would cause me to have either $4,000, $3,000 or $2,000 after the threads took leave today. Right? We didn't expect that. That's not the semantics that we want. Okay? There are cases in which in certain systems there can be timing dependent effects and you just don't really care. Maybe you have to guarantee that you're always... So for example there's not balance from my bank. Right? My bank usually has this disclaimer that says this account balance may or may not be up to date. And there might be actually no race conditions going down behind the scenes in terms of that when I check my balance to find out what things have finished it might be different, it might not show some of the latest things. There can be timing effects that are not race conditions because if the semantics of the operation don't guarantee that I see the latest result right? So again the critical thing in here is this is not what we expected to happen. We wanted to happen in general, again, you know banks should observe the the law of conservation by law of the conservation. Alright and then I want you guys to see I think it's kind of useful to see these things as kind of opposites. It's kind of this attention that's going on in the operation. So concurrency we implement concurrency by stopping and starting at any thread at any time, preemptive context version. Adamicity is something that we require in order to build synchronization primitives and that's the illusion that a certain set of executions that actually require multiple instructions happen kind of all at once, they're indivisible right? Adamicity is a good term it really describes what's going on. When we talk about database Adamicity it's actually about happening or not happening. On the operating system level we say from the perspective of another side that's accessing the shared state all those operations happen all at once and then there's no way for the thread to observe any of the shared state in kind of an intermediate state in an incorrect intermediate position. Like when a set of actions that I want to be atomic are kind of half done but not fully done. Okay? I'm seeing looks that either indicate that you guys are confused or that this is boring or I need to speed up or something. What is it? How many people think I should speed up? How many people think I should slow down? Come on. There's only one other option and I don't believe that all of you think the soup is just right. So show me that again. I'm serious. We can spend as much time as this has been you do. Speed up. Okay? Slow down. Stay steady. I made fantastic soup. I should do make some big soup on the side. Alright. So, again, animosity requires that I be able to stop and start threads but it also, sorry, animosity requires that I give threads some control over how this happens. Right? So, concurrency said, threads you have no control over how you're executed anymore. No control. I can stop you. I can start you. I want to tell you the same thing. Synchronization, animosity requires I think a thread some mechanisms for kind of autonomy. No, no, no, no. Don't let that guy run that instruction or don't stop me until I get to this certain point. We're going to talk about two ways of doing animosity in a few minutes. So, I think there's an interesting side here which is that so you can see that this kind of this tension that I'm building between which we get by adding concurrency and safety which we get by kind of getting rid of concurrency. Producing concurrency. And most modern operating systems have always started with speed and trying to get safe. And that's an interesting position because how do you think you find out about saving problems? Something breaks. Like Windows gets a call from some client says, probably this really weird problem and we destroyed a lot of data and we're getting sued and we think it's because of a bug in the operating system. And sometimes, that's the case. So, there are some new research efforts that are essentially trying to reverse this. So, what if I could build an operating system that was provably correct, provably safe and then work on trying to make it faster. So, in the first case, programmer effort goes to finding and repairing bugs and normally that's usually done after the fact. In the second case, I can guarantee that my kernel has a certain set of properties and then I try to work on making it more efficient. Well, not viable. So, there's a paper a couple years ago where they were actually at a formal correctness proof of a very small operating system kernel. And this took like 12 people five years or something. This was like a massive, massive, massive problem. But they did it and it was kind of cool. So, one of the abstractions that we use to think about this is what's called a critical section. A critical section is an area of code, sometimes we think about it as one function, sometimes it can span multiple functions or part of a function that looks atomic with respect to other threads executing inside that section. And the way we usually talk about this is there is only one thread quote unquote inside the critical section at one time. This is called mutual exclusion. And the guarantee, if we can provide this guarantee, then it allows us to make changes to state that require multiple instructions without worrying about the fact that anybody might observe the statement kind of at the long time. Because we have mechanisms to guarantee that this will all look like one operation. First, okay, so let's go back to our example, right? What is the local, so first question, what's the local state that's private to each thread? Who can identify that? GW has, right? This variable right here. It's a local variable, it's been clarified locally. So where does this variable live? Maybe in a register, but in general, threads that, right? It's that one piece of private data other than registers the threads right? What's the shared state that I'm accessing here? What's the problem? Why is there a, you know, what's the poor coordination? It's focused on what? Balance, balance, balance. Okay, good balance and good balance are functions, but what do they access? The account, right? So that's the shared state, you know, this account T, where's my dog? January. Did you use that one? Okay, good. Okay, now, here's the big question. I just told you what a critical section is. Who can identify the critical section? One, two, and three. One and three. Any other answers? One, two, and three. One and three. Right? This, this section of code, right? Why is this section in the critical section in this particular example? Because it beats without code. Because it's, it's, it's, it's, it's modifying an account, right? These are three instructions that from the perspective of another thread have to look like they all happened at once. I hold a balance, I modify it, and I write a channel. And to another thread that has to happen all at once, right? Because the problem before was that threads were, you know, basically were able to interleave these executions in a way that didn't produce their practice at all. A thread, so essentially what happened was that the other thread would start running right here, right? And it would see the account balance in this intermediate state. Because once I start this instruction, the next time that another thread can see the account is only once I've finished doing the department. That's the next time that another thread can see the account. This is going to be correct. Okay? Questions about, so the connection between the critical section and that is? Yeah. Joe, what's the, is there a quicker queue for us to tell which critical, like what determines it? Cool. I mean, so so in general critical sections, I mean, one of the one of the hard requirements is they have to modify shared states, right? That's not the only requirement, that's the hard requirement. In terms of, it's a great question. I bet, and I don't know the answer to this, but I bet there are software tools that are built to help identify these sorts of shared areas, right? And figure out what the semantics are. But in general, this comes down to programmer, programmer not, right? I mean, when you write this code, you have to think, what happens if the code stops right here? And another, another code runs. Now for this example, I don't want to give you the illusion that this is this easy. For this example, the only universe of code I've given you is this function, right? In general, there might be other functions. You know, take Guazmula, right? Actually, another good context for this example. Guaz is a nickname of mine. It doesn't really make any sense anymore, but if you want to know why it used to make sense. So anyway, so this is me, if you guys didn't get that. So there could be, you know, take Guazmula that also accesses the account. And in that case, that area would also have to be, we'd also have to make sure that that area isn't critical. So in general, it's hard to say, right? I mean, sometimes you have a critical section that's all organized around access to one shared state, right? And sometimes you have other critical sections that, you know, for example, you can think, you can imagine an operating system's people have really large pieces of shared state. If every time a friend needed to access any part of it that had to, you know, prevent friends from accessing all the other parts, there could be a big problem with performance. So sometimes you could probably reduce the amount of that through able money. Okay. So requirements for critical sections, right? Most fundamental is mutual inclusion. Mutual exclusion, right? No other threat should be in the critical state. I have to guarantee this. I need a mechanism to guarantee this, right? Second requirement is progress. Threads that want to enter the critical state, the critical section, should be able to eventually. If a thread, you know, needs to start executing instructions that are in the critical section and I force it to wait forever, this is not good, right? It's generally trying to do something useful, right? I don't want one of those balances to just stop forever, right? All right, and then performance, right? So in general, and again, this is tension, right? In general, we want to keep critical sections small because they limit concurrence, right? To prevent other threads from executed bodies of code. If that body of code is so large that there are large portions of it that that thread could have executed safely, then all I'm doing is I'm taking, you know, a multi-core machine or a multi-processing machine and I'm essentially reducing it into this machine that is much less parallel to what I would be, right? So again, the most broken way of doing this would be to say the entire kernel is a critical section, right? As soon as I enter a system called no other threads can run inside the kernel until that call finishes, right? But then, you know, I'm sitting there waiting for this guy out of the finish, right? That's terrible. No one else can run. So again, I mean, there are solutions to this problem. So for assignment 3 people have, you know, and it's not terrible solutions unless you tell people to use it, but there's one way of solving the assignment that essentially just has a huge locker on the entire VM system. And it says, you know, if one thread is executed, we just stop everybody else. We manage to mess that up too, but that's not necessarily a great idea. It really limits the form of the update. So there's two approaches to implementing, there's two mechanisms for implementing a critical section, right? One of them is quickly right now. So one mechanism is when a thread is inside the critical section, allow that thread to not stop, okay? So don't interrupt that thread, don't deschedule that thread until it exits, okay? The other approach is, I mean, this is my terminology, I call don't end, which means that when a thread is inside the critical section, use some way to keep other threads from entering that critical section, okay? So based on what we've talked about, how do I implement don't stop? It's actually up on the slide, right? Ah, here we go, someone's been reading the source code. Okay, so I'm a uniprocessor system, right? And again, this is kind of only of historical interest because on multi-cores these things just don't work, okay? But on a single-processor system when there was only one thread running at a time, if I could prevent that thread from stopping, by definition no other thread could be in a critical section, right? So whenever the critical section, I just don't stop executing the instruction. If I do that, then I'm okay, right? The only reason for that is that there's only one thread of execution running on the entire system. So on multi-cores this doesn't work, right? The way I do this is I do this by disabling intervals, or masking intervals. And that means that the hardware interrupts that would normally cause the system to schedule a new thread are not processed. And they're not processed until I leave the critical section and allow the system to start scheduling other things, right? Again, this design pattern is historical interest. The other thing that people would frequently do, right? The other reason why this doesn't work in many cases is, remember, I can't stop, right? Inside the critical section. I can't do anything that would stop. One way I would stop is a timer would fire and the kernel would stop. But the other way I might stop is I might do something that causes the kernel to stop me itself. So I might do this guy out, right? And I might voluntarily stop executing. So, you know, back when OS 161 was a single-processed system, we would have cases where people would think that they were implementing a critical section by disabling interrupts, but they would call a function that did I up and that function would actually cause some other thread to be skipped, right? And so they didn't get the kind of critical section that they were looking for simply because they weren't stopped preemptively. They asked me to stop. They called a function to ask the kernel I'm stopping because there's something I have to do that's going to take a little longer. Okay, so more generally we need a way to force other threads and so we'll review this at the beginning of class next time, but let me try to get through it a little bit, okay? The way we do this is we ask hardware for help. And hardware frequently provides one of two different atomic instructions. Now, in the interest of making sure that I don't confuse anybody, these are C implementations of these instructions. But this is not how you would implement them in hardware. This is just to show you what would work for me. But what hardware does, it looks like a test and set, right? So a test and set says I pass you a memory location and a value. And I want you to write the memory location but return the value that was there before you wrote when I asked you to write. Okay? And the system will guarantee that this happens automatically. So two test and sets happen in an interleaved set of variables of different things. The processor guarantees that the first one will execute completely before the second one. So questions about test and sets. Again, normally implemented like this, this is not a tough, right? Not a tough in any way. But what happens is that the CPU provides an instruction, a test and set instruction that guarantees this animosity through various tricks in hardware by locking buses and doing all sorts of things. Right? One of the things about these instructions on hardware is they tend to be fairly difficult because, again, at the hardware level, they're destroying. At the software level, they know it's okay but at the hardware level, they're actually destroying a hardware currency that happens behind the scenes. So generally, it's a program where you go to do it. Okay. I'm not going to talk about compare and swap because I want to get through this, right? But most processors provide either a test and set or compare and swap. On other processors including the MIPS that you guys are going to use, you can see that David has implemented a test and set for you in C using two MIPS instructions that themselves have animosity guarantees. So I encourage you to go look at that. I can point out, maybe first on one. I think I'm going to sign along with my point out where that code is and I would encourage you to go look at it. Okay, so let's try to finish this example using a test and set. Okay? So what do I need to do? I have this critical section which is lines one through three, right? These lines. So what do I need to do? Remember, I need to stop threads that are starting. Okay, so let's try this example, right? So I create a share variable that I'm going to use my test and set to change, okay? And then what I do is, when I start the critical section, I set the test and set to one, right? And then when I finish my critical section, I set it to zero. So I'm using the test and set to tell all the threads hey, I'm inside the critical section. Does this work? Why not? Because you're not checking the value, right? This just continues, right? So what do I need to do here? I need to check the value, right? Okay. So how do I tell if another thread is already set? So I have to use the return from test and set, right? Remember, test and set returns to old value, okay? So now here I'm going to test the value of the test and set, right? If it's one, it means that there's already another thread inside the critical section. So what do I do? Well, I, okay, I don't have any way of sleeping. Have I told you anything about sleep? That's a good answer, okay. But for this example, there's no sleeping, right? What do I do? I just check it again, right? So this is a valid way of doing this, okay? This will actually work. Of course, as you guys probably are starting to think, it's okay, right? So what are the problems? Right? And then I check this over and over again. So does anyone, everyone see how this works, okay? When the thread is inside the critical section, the test and set will return one. And I will continue to wait in this loop. As soon as the thread exits, it will send it to zero. Only, because test and set is guaranteed to be atomic, one thread that is waiting will get the return value of zero and proceed. And it will set it to one, and it will just keep getting one. So again, the hardware guarantees that one thread will be able to set this to one and it will turn zero. So is this like zero and one value we're setting, just a special register? It's a shared game, it's right up here. So this is some kind of regulation, right? That the test and set operate. And then the set and test just ensures that that entire thing gets done atomic, right? So what I don't want to happen is I don't want this guy to set it to zero, and that's to get the return value zero, because they'll both think I can go in. So the hardware guarantee is that no matter how many threads are executing this start talking over and over and over again, even on multiple cores, right? The same time, not even the spoken currency that I created using projects, which at the same time, right? One of them will get zero and the rest will get zero. Every time I set this to zero, the top will return one once. Sorry. And then for one thread, once, okay? And how does it decide which one gets zero? Don't worry about it. The scheduling, yeah. We'll just pretend that hardware does that properly, but hardware test and set will require to implement some of the features we talked about in the critical section before. So there was one thread on one core that was never able to set the test and set, that would be good. So again, we're going to go back to this on Wednesday, but the problem with this, as you guys would probably see, is not the slowest. So here's what happens, right? The red thread starts to run. It's the first thread in. It sets the test and set and breaks out of the loop. Test and set returns zero. What does the blue thread do? Let's say I have an inter-processed system, the blue thread starts to run. I just showed you. It just keeps checking this stupid test and set over and over and over and over and over until what? It gets descheduled and the red thread and clear the test and set at which point the blue thread is going to progress. So on a unit-processed system, these primitives, if they're not used carefully, would be terrible, right? Because what's happening here? The blue thread is not performing any useful work. It's preventing the red thread from performing useful work, right? Because this guy is just going to spin until his time clock runs out at which point the red thread. All right, so again, we will come back next time to this story. Thank you. All right, and I'll see you guys next time. I'm going to take this off.