 Okay, it's Jeff Challen again. We've looked a little bit in last screencast at the semaphore implementation. So now I wanna show you a little bit of the test code and give you a sense for how semaphores get used. I'll show you the tests that we have for semaphores. And this will also give us a chance to think a little bit about what distinguishes a semaphore from a lock. And that's an important thing to understand as you implement your own locks. So the test code for pretty much everything we've given you is in the test directory. It's important to point out that not all of these tests that we've, so first of all, we haven't given you tests for everything and some of the tests that we've given you don't actually work properly. Some of them require that you process the output yourself. So we definitely encourage you to rewrite the tests in this directory. However, when we test your kernel, we're gonna overwrite everything in this directory with test code that we've written that's more rigorous and make sure that it detects test things correctly. So, however, there are some tests that we've given you that we do expect to work. That includes the test for the semaphores, locks and condition variables. Those are really important to get right because these are key synchronization parameters that you're gonna use throughout the rest of the assignments. So those tests are in synctest.c. So let's look at the sem test down here, okay? So the first thing to figure out when you're trying to understand what a test does is what test is actually being run. So, for example, if you, the way to figure that out is to start with the menu. So what gets run on when I actually type SY2? So this is the print. And here's the mapping between the string and the function of run. So sorry, I meant SY1. When I run the semaphore, what's labeled as the semaphore test? What function actually gets run? So this runs semtest. And if you were looking for this, you could grab forward and you could see, okay, here we go, here's the definition of semtest in test in synctest.c. So that's how we would find it, okay? And here is the function itself. So like all the other synchronization tests, semtest has both a driver, piece of driver code, and a function that's run by threads that get created. We're trying to test synchronization primitive. So we have to create some concurrency in your system. We do that by calling thread fork a number of times and we'll describe that as we walk through the test. Okay, so here's my semtest code. It ignores its arguments, it calls inititems. So inititems is just a shared initialization routine that's used by all of the synchronization tests. And this just sets up some global shared variables. You can see here, it's important to note that test sem, which is used by the semtest, gets created with an initial value of two. And that's an important thing that the semtest looks at initially. So I call inititems, I initialize my test status to fail. And then you'll see here, prints this message if this hangs, it's broken. Well, the reason for this is if I've initialized your semaphore to a count of two, therefore I should be able to call down or P twice on that semaphore without blocking. So by either one of these P's blocks, it probably means that you didn't initialize the semaphore count properly. So I call P twice. Now, when I get to this line, the test should, at this point, the value of test sem should be zero. So, okay, so I've gotten through that. And this is sort of the main body of the test. So I call thread fork and threads times, and the variable nthreads is defined as, it looks like 32. Okay, so I call 32, I create 32 threads. And if I run out of memory, I panic. And that'll happen sometimes when you're using testing things for assignment one and during assignment two because DumbVM is leaking a lot of memory. And so eventually, after you run a test a number of times, it'll fail. And that's something that you'll fix in assignment three. Okay, so I've started all these threads. And then down here, I have this interesting loop where IV one semaphore and P another semaphore. So what is that supposed to do? So let's look over here. We'll get to another part of the, so let's look at the sem test thread. This is the next critical piece of this test to understand. Okay, so now the random yielder and random spinner code, you'll see this throughout our tests. This is really just designed to make things more interesting. Random yielder will cause the thread to yield a certain number of times. So what this means is that if you fork off a bunch of threads and they all, let's say they all happen to start some test thread in order. So I start 32 threads. Notice that the thread driver passes I to, so these last two things right here are the arguments to the function. This is the name of the function sem test thread. So junk is no, but unsigned long num is going to be I. So I'll get from zero to 31. Random yielder just causes the thread to yield up to four times with a random value. And what this does, it just creates a good mix of threads. So even if they all started in order from zero to 31, by the time we're all done yielding a random number of times, I have essentially shuffled the order in which they should get to this next step. And this is, so this is something, you know, random yield or random spinner. These are the things that we introduced just to create more mixing within your tests and make the results stronger and make the running of the test less predictable. Because remember, if the output of these tests is determined by the order in which the tests run, then that by definition, that's a race condition. And we're trying to introduce race conditions because these tests are supposed to use synchronization primitives to make sure that there is no race condition and the output of the test is deterministic. Okay, so what does each test thread do? It pees test sem. Now remember, once I start firing off threads here in sem test, the value of test sem is zero. And so all the threads are gonna hit this. And if your semaphores work correctly, they should all block, okay? Once it's released from the semaphore, it sets, so sem test current is a global variable. It sets that to my thread number. It prints off my thread number. Then it loops here for some number of times. Printing off, essentially it's thread number, a character. So this turns out to be like A, B, C or D. This just takes your thread number and maps it into the space of characters. Just so it prints a character rather than your number. I don't know why. It yields a random number of times again. And then what it does is it checks this global variable again. Now if this variable has changed, it, based on the semantics of this test, it means that there are multiple threads running inside this loop and that should cause the test to fail. So if you didn't have any semaphores here, if there was no P here and all these threads ran in parallel, well what happened is they're all modifying this shared variable. They're all inside of this loop. And so it's very likely that the variable will change from when I set it here to down here because some other thread is also setting the global variable. However, because I'm using the semaphore here that shouldn't happen. And this is an important piece of interaction between this piece of code and the test driver. So I P the test sem, I come through here and when I'm done, I V this done sem, all right? Now, again, if there was no test sem here, if I commented out this piece of code, the test would fail because everything would be running in this loop. However, what does the driver do? So the driver comes down here. Now, init items also sets up a done sem and done sem, if we look, done sem has its value initialized to zero. So done sem starts at zero. Go back to the sem test thread. So when I get to this point, sorry, I go into the other window, when I'm here, when I start running this loop, test sem and done sem should both have a value of zero. So I started all these threads, they're running independent of me, but they're all starting out by trying to probe the test sem. And so they're all going to block. And here's what's going to happen as I run through this loop. I go through this loop and threads time. I hit the test sem, so I up or V the test sem. That releases one of the threads. Then I P the done sem. So when the driver comes over here, it V's test sem, I remember V never blocks, but it's going to P the done sem. Now, until the sem test thread that I just released continued, that done sem has a value of zero. So the test thread driver is going to wait. So when I start this loop, nothing has happened. All my sem test threads should be sleeping. Then I release one of them. It comes through here. Sorry, geez. It comes into sem test thread. It does all this work right down here. And then it's done. And then it V's the done sem, that frees the driver. So now the driver goes back to the top. So these two pieces of code together, you can think of the driver as essentially signaling threads to proceed through this loop one at a time. So I V the test sem once that releases one thread. Then I wait for it to finish by P the done sem. And then I repeat that and threads times. And so all the threads should go through this loop one at a time. And the check for the shared variable here is the way that we ensure that only one thread is executing the loop at one time. When I get down here, I V the test sem or up it twice, just so it returns this value to zero and then I'm done. So this is both an example of a good test and it gives you a feeling for one of the interesting differences between semaphores and locks. So a lot of times people think of a binary semaphore is just a lock or a lock is just a binary semaphore. But semaphores actually have very different semantics. And so I would encourage you to think about this example and think, how would I do this with a lock? Is there any way to do this in a valid wave block? And it turns out the answer is no and I'll let you convince yourself as a why. But because the semantics of a semaphore are looser than the semantics of a lock, they allow you to do this kind of signaling. So hopefully this was helpful in the next installment of the screencast, we'll look at the synchronization problems that we've given you and how to start approaching those.