1 00:00:00,690 --> 00:00:04,720 So today we're going to continue our discussion 2 00:00:04,720 --> 00:00:06,440 of enforcing modularity. 3 00:00:10,510 --> 00:00:13,430 And in particular if you remember last time we spent 4 00:00:13,430 --> 00:00:15,189 a lot of time talking about -- 5 00:00:21,680 --> 00:00:23,430 Last time we spent a lot of time talking 6 00:00:23,430 --> 00:00:28,690 about how we can create a virtual memory so that we can 7 00:00:28,690 --> 00:00:32,200 have multiple programs running on top of one piece of hardware 8 00:00:32,200 --> 00:00:33,950 that all appear to have a separate address 9 00:00:33,950 --> 00:00:35,620 space or separate memory. 10 00:00:35,620 --> 00:00:40,520 So, two lectures ago, we talked about having 11 00:00:40,520 --> 00:00:42,025 one module per computer. 12 00:00:47,990 --> 00:00:50,450 And then last time we talked about this notion 13 00:00:50,450 --> 00:00:57,565 of wanting to have a module per virtual computer. 14 00:01:02,560 --> 00:01:06,417 And we saw this notion of virtual memory. 15 00:01:06,417 --> 00:01:08,250 OK, and now this time what we're going to do 16 00:01:08,250 --> 00:01:10,340 is we're going to talk about the other piece 17 00:01:10,340 --> 00:01:12,923 that our virtual computer needs, which is a virtual processor. 18 00:01:21,670 --> 00:01:24,640 OK, so the idea that we want here 19 00:01:24,640 --> 00:01:27,620 when we are designing a virtual processor 20 00:01:27,620 --> 00:01:30,370 is to create, have our programs be 21 00:01:30,370 --> 00:01:35,360 able to run on a single physical processor, 22 00:01:35,360 --> 00:01:38,100 but have different programs be able to run as though they 23 00:01:38,100 --> 00:01:40,820 were the only program that was executing on that processor. 24 00:01:40,820 --> 00:01:43,110 So we want the programs to be written in a style 25 00:01:43,110 --> 00:01:44,487 where the programmer doesn't have 26 00:01:44,487 --> 00:01:46,570 to be aware of the other programs that are running 27 00:01:46,570 --> 00:01:50,670 on the machine, but where the machine creates sort 28 00:01:50,670 --> 00:01:53,090 of a virtual processor for each one of these programs 29 00:01:53,090 --> 00:01:58,560 that's running so that each program has its own sort of set 30 00:01:58,560 --> 00:02:00,230 of instructions that it executes. 31 00:02:00,230 --> 00:02:02,030 And those instructions appear to be 32 00:02:02,030 --> 00:02:04,700 independent of all the other programs that are running. 33 00:02:04,700 --> 00:02:09,000 So in order to create this illusion of a virtual processor 34 00:02:09,000 --> 00:02:11,640 -- 35 00:02:11,640 --> 00:02:17,880 -- we are going to introduce a very simple concept. 36 00:02:17,880 --> 00:02:22,300 We're going to have each program run in what's 37 00:02:22,300 --> 00:02:23,080 known as a thread. 38 00:02:27,560 --> 00:02:30,110 And thread is really short for a thread of execution. 39 00:02:34,870 --> 00:02:39,102 And a thread is just a collection of all of the state 40 00:02:39,102 --> 00:02:40,560 that we need in order to keep track 41 00:02:40,560 --> 00:02:42,880 of what one single given program is doing 42 00:02:42,880 --> 00:02:44,560 at a particular point in time. 43 00:02:44,560 --> 00:02:51,110 So a thread is a collection of the instructions 44 00:02:51,110 --> 00:02:53,720 for the program that the module is running, 45 00:02:53,720 --> 00:02:57,320 as well as the current state of the program. 46 00:02:57,320 --> 00:03:02,660 In particular, it's the value of the registers on the processor 47 00:03:02,660 --> 00:03:07,030 including of particular import for this lecture the program 48 00:03:07,030 --> 00:03:09,400 counter and the stack pointer. 49 00:03:09,400 --> 00:03:12,260 OK, so there's a bunch of registers on the processor, 50 00:03:12,260 --> 00:03:15,879 say, 32 registers, and there also 51 00:03:15,879 --> 00:03:17,920 are these special registers, the program counter, 52 00:03:17,920 --> 00:03:19,720 that keeps track of which instruction is currently 53 00:03:19,720 --> 00:03:21,740 being executed, and the stack pointer which 54 00:03:21,740 --> 00:03:24,160 keeps track of where on the stack 55 00:03:24,160 --> 00:03:29,860 values are currently being pushed or popped from. 56 00:03:29,860 --> 00:03:35,910 So these things combined together with the stack 57 00:03:35,910 --> 00:03:38,870 are really the things that define one single thread. 58 00:03:38,870 --> 00:03:41,702 So, and the way to think about this 59 00:03:41,702 --> 00:03:43,660 is if you think about a program that's running, 60 00:03:43,660 --> 00:03:46,860 you can really encapsulate almost everything about what 61 00:03:46,860 --> 00:03:51,900 that program is currently doing by the instructions 62 00:03:51,900 --> 00:03:55,310 that it has to execute, the value of the registers that 63 00:03:55,310 --> 00:03:56,880 are currently set in the program, 64 00:03:56,880 --> 00:03:58,550 and the state that's on the stack. 65 00:03:58,550 --> 00:04:00,760 Now, programs may also have some sort 66 00:04:00,760 --> 00:04:03,160 of data that's stored in the global memory 67 00:04:03,160 --> 00:04:05,330 in the global variables our things that 68 00:04:05,330 --> 00:04:08,300 are stored on the heap that have been allocated via some memory 69 00:04:08,300 --> 00:04:09,435 allocation call. 70 00:04:09,435 --> 00:04:11,310 And so those things are a part of the program 71 00:04:11,310 --> 00:04:13,870 as well, although I don't want to explicitly list them 72 00:04:13,870 --> 00:04:16,010 as being a part of a single thread 73 00:04:16,010 --> 00:04:18,089 because if two programs, as we'll see, 74 00:04:18,089 --> 00:04:20,349 two programs can share the same address space. 75 00:04:20,349 --> 00:04:22,640 And if two programs are sharing the same address space, 76 00:04:22,640 --> 00:04:25,410 then they're going to share those global variables, 77 00:04:25,410 --> 00:04:27,590 the stuff that's stored on the heap of the program. 78 00:04:27,590 --> 00:04:31,830 So think of a thread as these things, plus there 79 00:04:31,830 --> 00:04:34,160 is some additional, we'll call it a heap, 80 00:04:34,160 --> 00:04:37,842 set of memory that may be shared between several different 81 00:04:37,842 --> 00:04:40,300 threads that are all running within the same address space. 82 00:04:40,300 --> 00:04:43,660 And I'll explain more about that as we go. 83 00:04:43,660 --> 00:04:45,670 OK, so as I said, now what we want 84 00:04:45,670 --> 00:04:48,520 is to create this illusion that each one of these threads 85 00:04:48,520 --> 00:04:52,380 has its own virtual processor upon which it's running. 86 00:04:52,380 --> 00:04:56,590 So how are we going to accomplish that? 87 00:04:56,590 --> 00:04:58,830 So if you think about this for a second, 88 00:04:58,830 --> 00:05:01,360 the way to accomplish this is simply 89 00:05:01,360 --> 00:05:04,560 to allow each one of these, each thread 90 00:05:04,560 --> 00:05:07,430 to update these various registers, 91 00:05:07,430 --> 00:05:09,180 push things onto the stack, and then 92 00:05:09,180 --> 00:05:12,050 to have some function which we can call, 93 00:05:12,050 --> 00:05:13,980 which will save all of this current state 94 00:05:13,980 --> 00:05:16,800 off for the current thread that's executing, 95 00:05:16,800 --> 00:05:19,640 and then load the state in for another thread that might be 96 00:05:19,640 --> 00:05:21,937 executing, and stop the current thread from executing, 97 00:05:21,937 --> 00:05:23,270 and start the new one executing. 98 00:05:23,270 --> 00:05:29,830 So the idea is if you were to look at a timeline of what 99 00:05:29,830 --> 00:05:33,870 the processor was doing, and if the processor is running 100 00:05:33,870 --> 00:05:36,130 two threads, thread one and thread two, 101 00:05:36,130 --> 00:05:39,530 you would see that during some period of time, 102 00:05:39,530 --> 00:05:40,910 thread one would be running. 103 00:05:40,910 --> 00:05:43,172 Then the computer would switch over 104 00:05:43,172 --> 00:05:44,630 and thread two would start running, 105 00:05:44,630 --> 00:05:46,280 and then again the computer would switch, 106 00:05:46,280 --> 00:05:47,738 and thread one would start running. 107 00:05:47,738 --> 00:05:50,810 So we are going to multiplex the processor resource in this way. 108 00:05:50,810 --> 00:05:54,670 So we have one processor that can be executing instructions. 109 00:05:54,670 --> 00:05:56,419 And at each point in time, that processor 110 00:05:56,419 --> 00:05:57,960 is going to be executing instructions 111 00:05:57,960 --> 00:05:59,610 from one of these two threads. 112 00:05:59,610 --> 00:06:02,115 And when we switch in these points in between threads, 113 00:06:02,115 --> 00:06:04,740 we are going to have to save off this state for the thread that 114 00:06:04,740 --> 00:06:06,990 was running, and restore the state for the thread that 115 00:06:06,990 --> 00:06:08,096 is currently running. 116 00:06:08,096 --> 00:06:09,720 OK, so that's a very high-level picture 117 00:06:09,720 --> 00:06:11,595 of what we're going to be doing with threads. 118 00:06:14,526 --> 00:06:16,650 And there's going to be a number of different ways, 119 00:06:16,650 --> 00:06:18,600 and that's what we're going to look at through this lecture is 120 00:06:18,600 --> 00:06:20,550 how we can actually accomplish this switching, 121 00:06:20,550 --> 00:06:23,750 how we decide when to switch, and what actually happens 122 00:06:23,750 --> 00:06:26,880 when this switch takes place. 123 00:06:26,880 --> 00:06:33,790 OK, so we're going to look at several different methods. 124 00:06:33,790 --> 00:06:35,610 So the first thing we're going to look at 125 00:06:35,610 --> 00:06:38,690 is an approach called cooperative scheduling, 126 00:06:38,690 --> 00:06:40,990 or cooperative switching. 127 00:06:40,990 --> 00:06:43,120 So in cooperative switching, what happens 128 00:06:43,120 --> 00:06:46,710 is that each thread periodically decides 129 00:06:46,710 --> 00:06:49,070 that it's done processing and is willing to let 130 00:06:49,070 --> 00:06:50,690 some other process run. 131 00:06:50,690 --> 00:06:54,050 OK, so the thread calls a routine that says I'm done; 132 00:06:54,050 --> 00:06:56,250 please schedule another thread. 133 00:06:56,250 --> 00:06:58,530 So, this cooperative approach is simple, 134 00:06:58,530 --> 00:07:00,900 and it's easy to think about. 135 00:07:00,900 --> 00:07:03,810 In the grand scheme of enforcing modularity 136 00:07:03,810 --> 00:07:06,830 of creating this illusion that each program really 137 00:07:06,830 --> 00:07:09,030 has its own computer that's running, 138 00:07:09,030 --> 00:07:11,170 and where each program gets to run no matter 139 00:07:11,170 --> 00:07:13,640 what any other program does, cooperative scheduling 140 00:07:13,640 --> 00:07:17,180 has a bit of a problem because if we 141 00:07:17,180 --> 00:07:18,980 are doing cooperative scheduling, 142 00:07:18,980 --> 00:07:21,700 then one process can just never call this function that 143 00:07:21,700 --> 00:07:24,100 says I give up time to the other processes running 144 00:07:24,100 --> 00:07:25,550 on the system. 145 00:07:25,550 --> 00:07:28,110 One module may just never give up the processor. 146 00:07:28,110 --> 00:07:30,750 And then we are in trouble because no other module ever 147 00:07:30,750 --> 00:07:31,470 gets to run. 148 00:07:31,470 --> 00:07:33,259 So instead what we're going to talk about, 149 00:07:33,259 --> 00:07:34,800 the alternative to this, is something 150 00:07:34,800 --> 00:07:38,410 we call preemptive scheduling. 151 00:07:38,410 --> 00:07:40,300 And in preemptive scheduling, what 152 00:07:40,300 --> 00:07:44,210 happens is some part of the computer, say, the kernel, 153 00:07:44,210 --> 00:07:47,410 periodically decides that it forces the currently 154 00:07:47,410 --> 00:07:50,040 running thread to give up the processor, 155 00:07:50,040 --> 00:07:51,380 and starts a new thread running. 156 00:07:51,380 --> 00:07:55,860 And we'll see how that works, OK? 157 00:07:55,860 --> 00:07:57,579 I also want to draw a distinction 158 00:07:57,579 --> 00:08:00,120 between whether these threads are running in the same address 159 00:08:00,120 --> 00:08:01,750 space or in a different address space, 160 00:08:01,750 --> 00:08:04,160 or in a different address space. 161 00:08:06,700 --> 00:08:10,310 OK, so we could, for example, have these two threads, T1 162 00:08:10,310 --> 00:08:11,340 and T2. 163 00:08:11,340 --> 00:08:14,700 They might be running as a part of a single, say, application. 164 00:08:14,700 --> 00:08:19,620 So, for example, they might be a part of a, 165 00:08:19,620 --> 00:08:23,000 say for example, some computer game that we have running. 166 00:08:23,000 --> 00:08:28,500 So suppose we have a computer game like, but let me just load 167 00:08:28,500 --> 00:08:29,810 up my computer here. 168 00:08:29,810 --> 00:08:31,830 Suppose we have a computer game like 169 00:08:31,830 --> 00:08:35,440 pick your favorite computer game, say, Halo. 170 00:08:35,440 --> 00:08:42,280 And we are running Halo, and Halo 171 00:08:42,280 --> 00:08:47,060 is going to consist of some set of threads 172 00:08:47,060 --> 00:08:50,170 that are responsible for running this game. 173 00:08:50,170 --> 00:08:53,360 So we might have one main thread which, 174 00:08:53,360 --> 00:08:58,940 what it does when it runs is to simply update; 175 00:08:58,940 --> 00:09:02,090 it repeats in a loop over and over again, 176 00:09:02,090 --> 00:09:03,960 wait for a little while, check and see 177 00:09:03,960 --> 00:09:07,890 if there's any user input, update the state of the game, 178 00:09:07,890 --> 00:09:10,476 and then, say for example, redraw the display. 179 00:09:10,476 --> 00:09:12,350 And because Halo is a videogame that somebody 180 00:09:12,350 --> 00:09:14,100 is staring at and looking at, it needs 181 00:09:14,100 --> 00:09:15,990 to update that display at some rate, 182 00:09:15,990 --> 00:09:18,720 say, once every 20 times a second in order 183 00:09:18,720 --> 00:09:20,920 to create the illusion of sort of natural animation 184 00:09:20,920 --> 00:09:21,419 in the game. 185 00:09:21,419 --> 00:09:25,530 So this wait step is going to wait a 20th 186 00:09:25,530 --> 00:09:27,440 of a second between every step. 187 00:09:27,440 --> 00:09:30,730 OK, so this might be one example of a thread. 188 00:09:30,730 --> 00:09:35,300 But of course within Halo, there may be multiple other threads 189 00:09:35,300 --> 00:09:36,340 that are also running. 190 00:09:36,340 --> 00:09:39,800 So there may be a thread that's responsible for looking 191 00:09:39,800 --> 00:09:41,772 for input over the network, right, 192 00:09:41,772 --> 00:09:43,480 to see if there is additional data that's 193 00:09:43,480 --> 00:09:46,224 arrived from the user, and from other users that are remote 194 00:09:46,224 --> 00:09:47,640 and updating the state of the game 195 00:09:47,640 --> 00:09:49,460 as other information comes in. 196 00:09:49,460 --> 00:09:51,750 And there may be a third thread that, say for example, 197 00:09:51,750 --> 00:09:55,180 is responsible for doing some cleanup work in the game 198 00:09:55,180 --> 00:09:58,880 like reclaiming memory after some monster that's 199 00:09:58,880 --> 00:10:06,720 running around the game has died and we no longer need 200 00:10:06,720 --> 00:10:08,190 its state, OK? 201 00:10:08,190 --> 00:10:11,240 So if we look at just this, say, main thread within Halo, 202 00:10:11,240 --> 00:10:12,850 as I said here we can encapsulate 203 00:10:12,850 --> 00:10:15,210 the state of this thread by a set of registers, 204 00:10:15,210 --> 00:10:17,820 say, R1 through Rn, as well as a stack pointer 205 00:10:17,820 --> 00:10:19,300 and a program counter. 206 00:10:19,300 --> 00:10:21,420 And then, there also will be a stack 207 00:10:21,420 --> 00:10:24,690 that represents the currently executing program. 208 00:10:24,690 --> 00:10:26,300 So, for example, the stack might, 209 00:10:26,300 --> 00:10:28,770 if we just have entered into this procedure, 210 00:10:28,770 --> 00:10:30,540 it might just have a return address 211 00:10:30,540 --> 00:10:33,350 which is sort of the address that we 212 00:10:33,350 --> 00:10:37,010 should jump to when we're done executing this Halo 213 00:10:37,010 --> 00:10:38,189 program here. 214 00:10:38,189 --> 00:10:39,980 And then finally, we have a program counter 215 00:10:39,980 --> 00:10:41,600 that points to this sort of current place 216 00:10:41,600 --> 00:10:42,520 where we are executing it. 217 00:10:42,520 --> 00:10:44,430 So we might have started off where we are just 218 00:10:44,430 --> 00:10:46,930 executing the first instructions here, the init instruction. 219 00:10:49,160 --> 00:10:55,080 OK, so what we're going to look at now 220 00:10:55,080 --> 00:10:57,270 is this case where we have a bunch of threads, 221 00:10:57,270 --> 00:11:00,211 say for example, in Halo all running within the same address 222 00:11:00,211 --> 00:11:00,710 space. 223 00:11:00,710 --> 00:11:02,376 So they're all part of the Halo program. 224 00:11:02,376 --> 00:11:05,650 They can all access the global variables of Halo. 225 00:11:05,650 --> 00:11:07,550 But we want to have different threads 226 00:11:07,550 --> 00:11:09,880 to do different operations that this program may 227 00:11:09,880 --> 00:11:10,755 need to take care of. 228 00:11:10,755 --> 00:11:13,296 So we want each of these threads to sort of have the illusion 229 00:11:13,296 --> 00:11:14,647 that it has its own processor. 230 00:11:14,647 --> 00:11:16,730 And then later we'll talk about a set of programs, 231 00:11:16,730 --> 00:11:18,480 say, that are running in different address 232 00:11:18,480 --> 00:11:20,990 And we'll talk about what's different about managing 233 00:11:20,990 --> 00:11:24,250 two threads, each of which is running in a different address 234 00:11:24,250 --> 00:11:26,527 space. 235 00:11:26,527 --> 00:11:28,110 OK, so the very simple idea that we're 236 00:11:28,110 --> 00:11:32,190 going to introduce in order to look at this situation 237 00:11:32,190 --> 00:11:34,620 where we are going to start off by looking 238 00:11:34,620 --> 00:11:37,540 at cooperative scheduling in the same address space. 239 00:11:37,540 --> 00:11:43,120 We are going to introduce the notion of a routine called 240 00:11:43,120 --> 00:11:44,100 yield. 241 00:11:44,100 --> 00:11:51,580 OK, so yield is going to be the thing the currently 242 00:11:51,580 --> 00:11:55,670 executing thread calls when it's ready to give up the processor 243 00:11:55,670 --> 00:11:57,340 and let another thread run. 244 00:11:57,340 --> 00:11:59,134 So the thread is going to run for a while 245 00:11:59,134 --> 00:12:00,550 and then it's going to call yield, 246 00:12:00,550 --> 00:12:03,390 and that's going to allow other threads to sort of do 247 00:12:03,390 --> 00:12:04,500 their execution. 248 00:12:04,500 --> 00:12:06,750 And this is cooperative because if a program doesn't 249 00:12:06,750 --> 00:12:09,472 call yield, then no other program will ever be 250 00:12:09,472 --> 00:12:10,680 allowed to run on the system. 251 00:12:13,830 --> 00:12:17,730 So the basic idea is that when a program calls yield, 252 00:12:17,730 --> 00:12:21,410 what it's going to do is exactly what I 253 00:12:21,410 --> 00:12:22,970 described just a minute ago. 254 00:12:22,970 --> 00:12:30,560 So yield is going to save the state of the current thread, 255 00:12:30,560 --> 00:12:33,800 and then it's going to -- 256 00:12:33,800 --> 00:12:39,670 -- schedule the next thread, so pick the next thread to run 257 00:12:39,670 --> 00:12:43,121 from all of the available threads that are on the system. 258 00:12:43,121 --> 00:12:45,120 And then it's going to dispatch the next thread, 259 00:12:45,120 --> 00:12:47,090 so it's going to in particular setup 260 00:12:47,090 --> 00:12:52,450 the state for the next thread so that it's ready to run. 261 00:12:52,450 --> 00:12:55,700 And it's going to jump into the return address for that thread. 262 00:12:58,670 --> 00:13:04,160 OK, so I want to take you guys through a very simple example 263 00:13:04,160 --> 00:13:08,730 of a program that has multiple threads in it. 264 00:13:08,730 --> 00:13:11,139 And we're going to have a simple thread scheduler. 265 00:13:11,139 --> 00:13:12,680 So, the thread scheduler is the thing 266 00:13:12,680 --> 00:13:14,354 that decides which thread to run next. 267 00:13:14,354 --> 00:13:15,770 And this thread scheduler is going 268 00:13:15,770 --> 00:13:22,150 to keep an array of fixed size, call it 269 00:13:22,150 --> 00:13:28,380 num_threads, that are all the threads that are currently 270 00:13:28,380 --> 00:13:30,370 running on the system. 271 00:13:30,370 --> 00:13:35,450 It's going to have an integer that is going to tell us 272 00:13:35,450 --> 00:13:37,120 what the next thread to run is. 273 00:13:37,120 --> 00:13:39,127 And then for every one of our threads, 274 00:13:39,127 --> 00:13:40,710 we're going to have a variable that we 275 00:13:40,710 --> 00:13:44,700 call me which is the ID of the currently executing thread. 276 00:13:44,700 --> 00:13:46,070 So, this is local to the thread. 277 00:13:52,239 --> 00:13:53,780 OK so let's look at an example of how 278 00:13:53,780 --> 00:13:56,940 this might work with this Halo thread 279 00:13:56,940 --> 00:13:59,929 that we're talking about here. 280 00:13:59,929 --> 00:14:01,470 OK, so suppose we have a Halo thread. 281 00:14:01,470 --> 00:14:03,750 And we'll call this the main thread of execution. 282 00:14:03,750 --> 00:14:06,320 And as I said, we want to have a couple of other threads 283 00:14:06,320 --> 00:14:08,528 that are also running on the system at the same time. 284 00:14:08,528 --> 00:14:12,040 So we might have a network thread and a cleanup thread. 285 00:14:12,040 --> 00:14:14,690 Now in this case, what we are going to say 286 00:14:14,690 --> 00:14:19,164 is that the yield procedure is going to do these three steps. 287 00:14:19,164 --> 00:14:21,330 And I made these steps a little bit more concrete so 288 00:14:21,330 --> 00:14:22,950 that we can actually think about what 289 00:14:22,950 --> 00:14:25,880 might be happening on the stack as these things are executing. 290 00:14:25,880 --> 00:14:30,080 So, the yield procedure is going to, when it runs, 291 00:14:30,080 --> 00:14:32,490 the first thing it's going to do is save the state. 292 00:14:32,490 --> 00:14:36,160 So to save the state, it simply stores in this table 293 00:14:36,160 --> 00:14:38,660 the stack pointer of the currently executing program, 294 00:14:38,660 --> 00:14:41,810 the stack pointer of the currently executing thread. 295 00:14:41,810 --> 00:14:43,260 And then what it's going to do is 296 00:14:43,260 --> 00:14:45,020 it's going to pick the next thread to run. 297 00:14:45,020 --> 00:14:47,000 So in this case, picking the next thread to run, 298 00:14:47,000 --> 00:14:49,120 the thread scheduler is doing something extremely simple. 299 00:14:49,120 --> 00:14:51,610 It's just cycling through all the available threads one 300 00:14:51,610 --> 00:14:53,500 after another and scheduling them, right? 301 00:14:53,500 --> 00:14:56,160 So it says next gets next plus one, and then modulo 302 00:14:56,160 --> 00:14:59,700 the total number of threads that are in the system. 303 00:14:59,700 --> 00:15:03,930 So suppose num_threads is equal to five. 304 00:15:03,930 --> 00:15:05,940 As soon as next plus one is equal to five, 305 00:15:05,940 --> 00:15:07,500 and then five modulo five is zero, 306 00:15:07,500 --> 00:15:09,208 and we're going to wrap that back around. 307 00:15:09,208 --> 00:15:11,469 So, once we've executed the fourth thread, 308 00:15:11,469 --> 00:15:13,010 we are going to wrap around and start 309 00:15:13,010 --> 00:15:14,777 executing thread number zero. 310 00:15:14,777 --> 00:15:16,360 And then finally what it's going to do 311 00:15:16,360 --> 00:15:18,660 is it's going to restore the stack pointer. 312 00:15:23,180 --> 00:15:26,060 Here, I'll just change this for you right now. 313 00:15:26,060 --> 00:15:29,300 This should be table sub next. 314 00:15:29,300 --> 00:15:32,560 I didn't want to do that. 315 00:15:36,870 --> 00:15:38,250 So, we have this table. 316 00:15:38,250 --> 00:15:40,560 So, we restore the stack pointer for the next thread 317 00:15:40,560 --> 00:15:45,650 that we want to run from the table of stack pointers 318 00:15:45,650 --> 00:15:46,580 that are available. 319 00:15:46,580 --> 00:15:48,090 And then what's going to happen is 320 00:15:48,090 --> 00:15:50,040 that when we exit out of this yield routine, 321 00:15:50,040 --> 00:15:52,970 we are going to load the return address from the stack pointer 322 00:15:52,970 --> 00:15:54,522 that we just have set up. 323 00:15:54,522 --> 00:15:56,480 So you'll see how that works again in a second. 324 00:15:56,480 --> 00:15:59,370 But the basic process, then, is just going to be as follows. 325 00:15:59,370 --> 00:16:01,526 So this only works on a single processor. 326 00:16:01,526 --> 00:16:03,900 And I'm not going to have time to go into too much detail 327 00:16:03,900 --> 00:16:05,524 about what happens in a multiprocessor, 328 00:16:05,524 --> 00:16:08,460 but that book talks carefully about why this doesn't work 329 00:16:08,460 --> 00:16:11,830 on a single processor machine. 330 00:16:11,830 --> 00:16:17,720 OK, so the basic process, then, it's going to be as follows. 331 00:16:17,720 --> 00:16:19,547 So we've got our yield procedure. 332 00:16:19,547 --> 00:16:20,630 We have our three threads. 333 00:16:20,630 --> 00:16:22,463 Say we've numbered them one, two, and three, 334 00:16:22,463 --> 00:16:23,430 as shown up here. 335 00:16:23,430 --> 00:16:25,130 So, num_threads is equal to three, 336 00:16:25,130 --> 00:16:30,330 and say we start off with next equal to one. 337 00:16:30,330 --> 00:16:33,880 So what's going to happen is that the first thread at some 338 00:16:33,880 --> 00:16:35,589 point it's going to call a yield routine. 339 00:16:35,589 --> 00:16:37,296 And what that's going to do is it's going 340 00:16:37,296 --> 00:16:38,600 to cause this yield to execute. 341 00:16:38,600 --> 00:16:40,124 We're going to increment next. 342 00:16:40,124 --> 00:16:42,290 And we're going to schedule the network thread here. 343 00:16:42,290 --> 00:16:44,440 OK, and the network thread is going 344 00:16:44,440 --> 00:16:45,780 to run for a little while. 345 00:16:45,780 --> 00:16:47,560 And then at some point, it's going to call yield, 346 00:16:47,560 --> 00:16:49,250 and that's going to sort of cause the next thread 347 00:16:49,250 --> 00:16:49,860 to be scheduled. 348 00:16:49,860 --> 00:16:51,984 We're going to call cleanup, and then finally we're 349 00:16:51,984 --> 00:16:53,360 going to call yield. 350 00:16:53,360 --> 00:16:56,340 And the third thread is going to call yield and cause 351 00:16:56,340 --> 00:16:57,590 the first thread to run again. 352 00:16:57,590 --> 00:16:59,256 And this process is just going to repeat 353 00:16:59,256 --> 00:17:01,050 over and over and over again. 354 00:17:01,050 --> 00:17:02,593 OK, so -- 355 00:17:09,010 --> 00:17:11,490 OK, so what I want to do now is to look at actually 356 00:17:11,490 --> 00:17:17,994 more carefully at what's going on with, how this scheduling 357 00:17:17,994 --> 00:17:19,160 process is actually working. 358 00:17:19,160 --> 00:17:20,660 So we can understand a little bit better 359 00:17:20,660 --> 00:17:22,310 what's happening to the stack pointer, 360 00:17:22,310 --> 00:17:25,069 and how these various yields are actually executing. 361 00:17:25,069 --> 00:17:31,040 So let's look at, so this is going 362 00:17:31,040 --> 00:17:35,570 to be an example of the stack on, say, two of these threads, 363 00:17:35,570 --> 00:17:37,600 OK? 364 00:17:37,600 --> 00:17:43,750 So, suppose we have thread number one. 365 00:17:43,750 --> 00:17:47,600 I'll call this thread one, and this is our Halo thread. 366 00:17:47,600 --> 00:17:53,810 OK, and this is the stack for Halo. 367 00:17:53,810 --> 00:18:02,230 We also have thread number two, which is our network thread. 368 00:18:02,230 --> 00:18:05,400 OK, and this is also some stack associated with this thing. 369 00:18:05,400 --> 00:18:07,350 So we're just looking at the current state 370 00:18:07,350 --> 00:18:11,450 of the stack on these two things, these two threads. 371 00:18:11,450 --> 00:18:14,210 And if you remember, typically we 372 00:18:14,210 --> 00:18:16,170 represent stacks as going down in memory. 373 00:18:16,170 --> 00:18:17,900 So, these are the different addresses 374 00:18:17,900 --> 00:18:21,950 of the different entries in the stack that 375 00:18:21,950 --> 00:18:25,710 represent what's going on currently in the processor. 376 00:18:25,710 --> 00:18:28,540 So we'll say that thread one, perhaps, the stack 377 00:18:28,540 --> 00:18:32,450 starts at address 108, and maybe thread two 378 00:18:32,450 --> 00:18:37,120 starts at address 208. 379 00:18:37,120 --> 00:18:40,590 OK, so suppose we start up this system, 380 00:18:40,590 --> 00:18:43,170 and when we start it up, the stack pointer 381 00:18:43,170 --> 00:18:44,440 is currently pointing here. 382 00:18:47,220 --> 00:18:50,249 So we take a snapshot of this system 383 00:18:50,249 --> 00:18:51,290 at a given point in time. 384 00:18:51,290 --> 00:18:54,060 Suppose the stack pointer is currently pointing at 104. 385 00:18:54,060 --> 00:19:00,340 The currently executing thread is this one, thread one. 386 00:19:00,340 --> 00:19:04,600 And we have, remember our thread table 387 00:19:04,600 --> 00:19:09,100 that captures the current thread number, 388 00:19:09,100 --> 00:19:14,210 and the saved stack pointer for each one of these threads. 389 00:19:14,210 --> 00:19:16,490 So we've got thread number one, thread number two. 390 00:19:16,490 --> 00:19:19,387 OK, so let's suppose we start looking at this point in time 391 00:19:19,387 --> 00:19:21,095 where the stack pointer is pointing here. 392 00:19:24,260 --> 00:19:30,640 And if we look at this program, so if we start off 393 00:19:30,640 --> 00:19:32,309 with the program that we initially had, 394 00:19:32,309 --> 00:19:34,350 in order to understand what happens to the stack, 395 00:19:34,350 --> 00:19:37,671 we need to actually look at when the stack gets manipulated. 396 00:19:37,671 --> 00:19:39,170 So if we are just looking at C code, 397 00:19:39,170 --> 00:19:40,900 typically the C code isn't going to have, 398 00:19:40,900 --> 00:19:43,960 we're not going to see the changes to the stack occurring 399 00:19:43,960 --> 00:19:44,710 within the C code. 400 00:19:44,710 --> 00:19:46,260 So what I've done here is in yellow, 401 00:19:46,260 --> 00:19:51,037 I've annotated this with the sort of additional operations, 402 00:19:51,037 --> 00:19:53,370 the assembly operations, that are happening on the entry 403 00:19:53,370 --> 00:19:55,700 and exit to these procedures. 404 00:19:55,700 --> 00:19:58,370 So what happens just before we call the yield procedure is 405 00:19:58,370 --> 00:20:01,200 that the Halo thread will push the return 406 00:20:01,200 --> 00:20:03,220 address onto the stack. 407 00:20:03,220 --> 00:20:07,780 OK, so if we start executing the yield procedure, 408 00:20:07,780 --> 00:20:11,822 Halo is going to push the return address onto the stack. 409 00:20:11,822 --> 00:20:14,030 And then it's going to jump into the yield procedure. 410 00:20:14,030 --> 00:20:17,120 So, suppose we come onto here. 411 00:20:17,120 --> 00:20:19,450 Now if we look at what the return address is 412 00:20:19,450 --> 00:20:21,290 after we execute the yield procedure, 413 00:20:21,290 --> 00:20:23,010 it's going to be instruction number five. 414 00:20:23,010 --> 00:20:25,260 So we're going to push that address onto the stack 415 00:20:25,260 --> 00:20:27,860 because instruction number five is labeled here 416 00:20:27,860 --> 00:20:30,110 on the left side is where we're going 417 00:20:30,110 --> 00:20:31,525 to return to after the yield. 418 00:20:31,525 --> 00:20:33,150 And then the yield is going to execute. 419 00:20:33,150 --> 00:20:35,150 And what's going to happen as the yield executes 420 00:20:35,150 --> 00:20:37,540 is it's going to change the stack pointer to be 421 00:20:37,540 --> 00:20:39,687 the stack pointer of the next thread, right? 422 00:20:39,687 --> 00:20:41,020 So, I have this typo here again. 423 00:20:41,020 --> 00:20:43,646 So be careful about that. 424 00:20:43,646 --> 00:20:45,270 We're going to change the stack pointer 425 00:20:45,270 --> 00:20:47,090 to be the stack pointer of the next thread. 426 00:20:47,090 --> 00:20:50,399 And so when it executes this pop RA instruction 427 00:20:50,399 --> 00:20:51,940 that we see here, it's actually going 428 00:20:51,940 --> 00:20:55,360 to be popping the return address from the network thread 429 00:20:55,360 --> 00:20:58,150 that it scheduled next as opposed to the return 430 00:20:58,150 --> 00:20:59,640 address from this Halo thread. 431 00:20:59,640 --> 00:21:04,810 OK, so suppose that the saved stack pointer for the network 432 00:21:04,810 --> 00:21:07,120 thread was pointing here at 204. 433 00:21:07,120 --> 00:21:10,800 So, that would have been an entry, 204, here. 434 00:21:10,800 --> 00:21:14,050 And the saved return address that it's going 435 00:21:14,050 --> 00:21:15,730 to jump to is going to be here. 436 00:21:15,730 --> 00:21:19,970 So suppose the return address that it jumps to is 1,000, OK? 437 00:21:19,970 --> 00:21:24,450 So if we look at the state of these two threads 438 00:21:24,450 --> 00:21:26,100 as they have been running, we saw 439 00:21:26,100 --> 00:21:27,980 thread one ran for a little while, 440 00:21:27,980 --> 00:21:32,740 and then it called yield, and the instruction following yield 441 00:21:32,740 --> 00:21:34,130 was instruction number five. 442 00:21:34,130 --> 00:21:36,360 OK, so that was the address that we pushed on 443 00:21:36,360 --> 00:21:38,290 to the stack pointer. 444 00:21:38,290 --> 00:21:41,650 Now, we switched over here to thread two. 445 00:21:41,650 --> 00:21:44,800 And last time we ran thread two, it called yield, 446 00:21:44,800 --> 00:21:47,200 and its return address was 1,000. 447 00:21:47,200 --> 00:21:49,360 So, there was this 1,000 here, OK? 448 00:21:49,360 --> 00:21:53,260 So now, when we schedule thread two, what's going to happen 449 00:21:53,260 --> 00:21:58,740 is that we're going to read the, so this is instruction 1,000. 450 00:21:58,740 --> 00:22:00,660 We're going to start executing from 1,000 451 00:22:00,660 --> 00:22:02,910 because we're going to execute this pop return 452 00:22:02,910 --> 00:22:05,280 So we're going to pop the return address off the stack, 453 00:22:05,280 --> 00:22:06,990 and we're going to start executing here 454 00:22:06,990 --> 00:22:09,060 at instruction 1,000, OK? 455 00:22:09,060 --> 00:22:11,340 So, you guys kind of see how it is that we switch now 456 00:22:11,340 --> 00:22:13,840 from one thread to the other, by switching the stack pointer 457 00:22:13,840 --> 00:22:17,880 and grabbing our return address from the stack of thread two 458 00:22:17,880 --> 00:22:21,320 instead of from thread one. 459 00:22:21,320 --> 00:22:23,040 OK, so now what's going to happen 460 00:22:23,040 --> 00:22:25,190 is this thread two is going to start 461 00:22:25,190 --> 00:22:28,740 executing from this address. 462 00:22:28,740 --> 00:22:30,670 And it's going to run for a little while. 463 00:22:30,670 --> 00:22:32,620 And at some point later, it's going 464 00:22:32,620 --> 00:22:34,970 to call yield again, right, because it's run for a while 465 00:22:34,970 --> 00:22:36,678 and it's decided that it's time to yield. 466 00:22:36,678 --> 00:22:45,360 So, suppose that instruction 1,024 it calls yield. 467 00:22:45,360 --> 00:22:48,360 So when it does that, the same thing is going to happen. 468 00:22:48,360 --> 00:22:50,460 It's going to have run for a little while. 469 00:22:50,460 --> 00:22:52,080 So its stack is going to have grown. 470 00:22:52,080 --> 00:22:54,590 But eventually it's going to push the return address 471 00:22:54,590 --> 00:22:58,710 onto the stack, say here 1,025, and suppose 472 00:22:58,710 --> 00:23:02,080 this value of its stack pointer now has grown down. 473 00:23:02,080 --> 00:23:03,580 So it's gone 204. 474 00:23:03,580 --> 00:23:05,960 It's run for a while, pushed some things on the stack, 475 00:23:05,960 --> 00:23:09,070 and maybe the stack pointer is now at 148. 476 00:23:09,070 --> 00:23:10,740 So, when it calls yield, it's going 477 00:23:10,740 --> 00:23:13,430 to write into the stack pointer address in the thread table 478 00:23:13,430 --> 00:23:15,190 148. 479 00:23:15,190 --> 00:23:18,646 And we're going to restore the stack pointer addressed 480 00:23:18,646 --> 00:23:20,770 from here, which I forgot to show being written in, 481 00:23:20,770 --> 00:23:22,770 but what I should have written in there was 104. 482 00:23:22,770 --> 00:23:25,410 So, we're going to restore the stack address to 104. 483 00:23:25,410 --> 00:23:27,692 We're going to pop the next instruction to execute off 484 00:23:27,692 --> 00:23:30,150 of that stack five, and then we're going to keep executing. 485 00:23:30,150 --> 00:23:32,066 So you just switch back and forth in this way. 486 00:23:38,530 --> 00:23:44,316 OK, so -- -- 487 00:23:48,050 --> 00:23:52,210 what I want to do now is, so this is the basic process now 488 00:23:52,210 --> 00:23:54,742 whereby this yield instruction can 489 00:23:54,742 --> 00:23:56,950 be used to switch between the scheduling of these two 490 00:23:56,950 --> 00:23:59,310 procedures, right? 491 00:23:59,310 --> 00:24:01,551 And this is sort of the core of how it is. 492 00:24:01,551 --> 00:24:03,300 So we have these two threads of execution, 493 00:24:03,300 --> 00:24:04,970 and they just sort of run through. 494 00:24:04,970 --> 00:24:06,360 And they periodically call yield. 495 00:24:06,360 --> 00:24:09,450 And that allows another thread to be written, to execute it. 496 00:24:09,450 --> 00:24:13,180 But otherwise these threads have been 497 00:24:13,180 --> 00:24:15,310 written in a style where they don't actually 498 00:24:15,310 --> 00:24:18,546 have to know anything about the other threads that are running. 499 00:24:18,546 --> 00:24:20,170 There could have been two other threads 500 00:24:20,170 --> 00:24:22,940 or 200 other threads running on the system at the same time. 501 00:24:22,940 --> 00:24:24,030 And this approach that I showed you 502 00:24:24,030 --> 00:24:25,990 would have caused all those threads eventually 503 00:24:25,990 --> 00:24:28,330 to have been scheduled and to execute properly. 504 00:24:28,330 --> 00:24:33,910 Right, but as we said before, requiring 505 00:24:33,910 --> 00:24:35,660 these functions to actually call yield 506 00:24:35,660 --> 00:24:38,020 periodically has sort of defeated 507 00:24:38,020 --> 00:24:40,810 the purpose of our enforcing modularity, one 508 00:24:40,810 --> 00:24:42,600 of our goals of enforcing modularity, 509 00:24:42,600 --> 00:24:45,885 which is to make it so that no one thread can interfere 510 00:24:45,885 --> 00:24:47,510 with the operation of the other thread, 511 00:24:47,510 --> 00:24:49,650 or cause that other thread to crash, right, 512 00:24:49,650 --> 00:24:52,460 because if the procedure never calls a yield, 513 00:24:52,460 --> 00:24:57,760 then a module never calls yield, excuse me, 514 00:24:57,760 --> 00:24:59,970 another thread will never be scheduled. 515 00:24:59,970 --> 00:25:02,310 And so, that module will have the ability, essentially, 516 00:25:02,310 --> 00:25:04,394 to take over the whole computer, which is bad. 517 00:25:04,394 --> 00:25:05,810 So what we're going to look at now 518 00:25:05,810 --> 00:25:08,570 is how we go from this cooperative scheduling 519 00:25:08,570 --> 00:25:12,450 where modules call yield to preemptive scheduling 520 00:25:12,450 --> 00:25:14,381 where modules are forced to yield 521 00:25:14,381 --> 00:25:15,505 the processor periodically. 522 00:25:33,060 --> 00:25:38,260 OK, so this is the case where you have no explicit yield 523 00:25:38,260 --> 00:25:38,760 statements. 524 00:25:44,880 --> 00:25:48,690 All right, so the idea here is that turns out 525 00:25:48,690 --> 00:25:49,960 to be very simple. 526 00:25:49,960 --> 00:25:52,700 So programs aren't going to have an explicit yield 527 00:25:52,700 --> 00:25:53,740 statement in them. 528 00:25:53,740 --> 00:25:55,198 But what we're going to do is we're 529 00:25:55,198 --> 00:25:59,532 going to have a special timer that runs periodically within, 530 00:25:59,532 --> 00:26:00,740 say, for example, the kernel. 531 00:26:00,740 --> 00:26:03,430 So suppose the kernel has a timer that runs periodically 532 00:26:03,430 --> 00:26:08,380 that causes the kernel to be sort of woken up and allowed 533 00:26:08,380 --> 00:26:10,770 to execute some code. 534 00:26:10,770 --> 00:26:13,820 So this timer is going to be connected 535 00:26:13,820 --> 00:26:16,380 to what's called an interrupt. 536 00:26:16,380 --> 00:26:21,010 So, we're going to introduce a timer interrupt, 537 00:26:21,010 --> 00:26:23,080 and almost all processors essentially 538 00:26:23,080 --> 00:26:25,170 have some notion of a timer interrupt. 539 00:26:25,170 --> 00:26:28,410 And an interrupt is something that when it fires, 540 00:26:28,410 --> 00:26:34,770 it causes the processor to run a special piece of code, OK? 541 00:26:34,770 --> 00:26:44,360 So, basically this is going to be some processor. 542 00:26:44,360 --> 00:26:46,970 Think of it, if you like, as some line on the processor. 543 00:26:46,970 --> 00:26:49,620 OK, there's a wire that's coming off the processor. 544 00:26:49,620 --> 00:26:53,870 And when this wire gets pulled high, when the timer goes off 545 00:26:53,870 --> 00:26:56,660 and fires, this line is going to be pulled high. 546 00:26:56,660 --> 00:26:59,850 And when that happens, the microprocessor 547 00:26:59,850 --> 00:27:01,450 is going to notice that and is going 548 00:27:01,450 --> 00:27:03,770 to invoke a special function within the kernel. 549 00:27:03,770 --> 00:27:17,330 So this line is going to be checked by the microprocessor 550 00:27:17,330 --> 00:27:20,920 before it executes each instruction, OK? 551 00:27:20,920 --> 00:27:29,730 And if the line is high, the microprocessor 552 00:27:29,730 --> 00:27:35,134 is going to execute one of these special gate functions. 553 00:27:35,134 --> 00:27:36,800 So, we saw the notion of a gate function 554 00:27:36,800 --> 00:27:43,670 before that can be used for a module 555 00:27:43,670 --> 00:27:45,240 to obtain entry into the kernel. 556 00:27:45,240 --> 00:27:46,656 Essentially what's going to happen 557 00:27:46,656 --> 00:27:49,050 is that when the timer interrupt fires, 558 00:27:49,050 --> 00:27:51,550 it's going to go execute one of these special gate functions 559 00:27:51,550 --> 00:27:52,280 as well. 560 00:27:52,280 --> 00:27:54,260 And that's going to cause the kernel to then 561 00:27:54,260 --> 00:27:56,650 be in control of the processor. 562 00:27:56,650 --> 00:27:59,760 So remember when the gate function runs, 563 00:27:59,760 --> 00:28:05,350 it switches the user to kernel mode bit, to kernel mode. 564 00:28:05,350 --> 00:28:08,610 It switches the page map address register to the kernel's page 565 00:28:08,610 --> 00:28:10,449 map so that the kernel is in control, 566 00:28:10,449 --> 00:28:12,740 and it switches the stack pointer to the kernel's saved 567 00:28:12,740 --> 00:28:15,460 stack pointer so that the kernel is in control of the system, 568 00:28:15,460 --> 00:28:17,880 and can execute whatever code that it wants. 569 00:28:17,880 --> 00:28:20,540 So this is going to accomplish basically everything 570 00:28:20,540 --> 00:28:21,590 that we need, right? 571 00:28:21,590 --> 00:28:25,880 Because if we can get the kernel in control of the system, now, 572 00:28:25,880 --> 00:28:29,260 the kernel can do whatever it needs to do to, for example, 573 00:28:29,260 --> 00:28:30,850 schedule the next thread. 574 00:28:30,850 --> 00:28:36,290 So the way that's going to work is basically very simple. 575 00:28:36,290 --> 00:28:41,490 So when the kernel gets run, it knows 576 00:28:41,490 --> 00:28:45,030 which thread, for example, is currently running. 577 00:28:45,030 --> 00:28:47,360 And basically what it does is it just 578 00:28:47,360 --> 00:28:50,340 calls the appropriate yield function, 579 00:28:50,340 --> 00:28:52,090 it calls the yield function for the thread 580 00:28:52,090 --> 00:28:55,250 that it's currently running, forcing that thread to yield. 581 00:28:55,250 --> 00:29:05,980 So, kernel calls yield on current thread, right? 582 00:29:11,310 --> 00:29:15,940 OK, so in order to do this, of course 583 00:29:15,940 --> 00:29:21,189 the kernel needs to know how to capture 584 00:29:21,189 --> 00:29:22,980 the state for the currently running thread. 585 00:29:22,980 --> 00:29:24,729 But for the most part that's pretty simple 586 00:29:24,729 --> 00:29:26,300 because the state is all encapsulated 587 00:29:26,300 --> 00:29:28,310 in the current values of the registers in the current value 588 00:29:28,310 --> 00:29:29,420 of the stack pointer. 589 00:29:29,420 --> 00:29:31,330 So, the kernel is going to call yield on the currently 590 00:29:31,330 --> 00:29:32,746 executing thread, and that's going 591 00:29:32,746 --> 00:29:38,690 to force that thread to go ahead and schedule another thread. 592 00:29:38,690 --> 00:29:41,240 So the module itself hasn't called yield, 593 00:29:41,240 --> 00:29:43,802 but still the module has been forced to sort of give up 594 00:29:43,802 --> 00:29:44,885 its control the processor. 595 00:29:48,200 --> 00:29:52,330 So, when it calls yield, it's just 596 00:29:52,330 --> 00:29:54,270 going to do it we did before, which 597 00:29:54,270 --> 00:30:08,840 is save our state and schedule and run the next thread. 598 00:30:08,840 --> 00:30:12,780 OK, so the only last little detail 599 00:30:12,780 --> 00:30:15,344 that we have to think about is what if the kernel wants 600 00:30:15,344 --> 00:30:17,760 to schedule a thread that's running in a different address 601 00:30:17,760 --> 00:30:18,301 space? 602 00:30:18,301 --> 00:30:20,300 So if a kernel wants to schedule a thread that's 603 00:30:20,300 --> 00:30:21,716 running a different address space, 604 00:30:21,716 --> 00:30:24,350 it well, it has to do what we saw last time when 605 00:30:24,350 --> 00:30:26,580 we saw about how we switch address spaces. 606 00:30:26,580 --> 00:30:29,300 It has to change the value of the PMAR register 607 00:30:29,300 --> 00:30:31,850 so that the next address space gets swapped 608 00:30:31,850 --> 00:30:34,710 And then it can go ahead and jump 609 00:30:34,710 --> 00:30:37,130 into the appropriate location in the sort of newly 610 00:30:37,130 --> 00:30:38,890 scheduled address space. 611 00:30:38,890 --> 00:30:42,260 So that brings us to the next topic of discussion. 612 00:30:42,260 --> 00:30:45,270 So, what we've seen so far now, we saw cooperative 613 00:30:45,270 --> 00:30:48,060 in the same address space, and I introduced the notion 614 00:30:48,060 --> 00:30:50,792 of a preemptive scheduling. 615 00:30:50,792 --> 00:30:52,250 What we want to look at now is sort 616 00:30:52,250 --> 00:30:56,530 of what it means to run multiple threads in different address 617 00:30:56,530 --> 00:30:58,336 spaces. 618 00:30:58,336 --> 00:31:00,210 Typically when we talk about a program that's 619 00:31:00,210 --> 00:31:03,250 running on a computer or in 6.033 620 00:31:03,250 --> 00:31:07,960 we like to call programs running on computers processes. 621 00:31:07,960 --> 00:31:12,730 When we talk about a process, we mean an address space 622 00:31:12,730 --> 00:31:14,945 plus some collection of threads. 623 00:31:17,570 --> 00:31:20,410 So this is sort of the real definition 624 00:31:20,410 --> 00:31:22,640 of what we mean by process. 625 00:31:22,640 --> 00:31:25,630 And alternately, you will see processes called things 626 00:31:25,630 --> 00:31:27,330 like applications or programs. 627 00:31:27,330 --> 00:31:30,250 But any time you see a word like that, what people typically 628 00:31:30,250 --> 00:31:35,340 mean is some address space, some virtual address space in which 629 00:31:35,340 --> 00:31:37,236 we resolve memory addresses. 630 00:31:37,236 --> 00:31:39,360 And then a collection of threads that are currently 631 00:31:39,360 --> 00:31:41,650 executing within that address space, 632 00:31:41,650 --> 00:31:43,800 and where each of those threads includes 633 00:31:43,800 --> 00:31:46,640 the set of instructions and registers and stack 634 00:31:46,640 --> 00:31:50,880 that correspond to it, OK? 635 00:31:50,880 --> 00:31:58,540 So the kernel has explicit support for these processes. 636 00:32:03,800 --> 00:32:05,610 So in particular, what the kernel provides 637 00:32:05,610 --> 00:32:11,940 are routines to create a process and destroy a process. 638 00:32:14,820 --> 00:32:18,290 OK, and these create and destroy methods 639 00:32:18,290 --> 00:32:21,049 are going to sort of do, the create method 640 00:32:21,049 --> 00:32:22,590 is going to do all the initialization 641 00:32:22,590 --> 00:32:25,469 that we need to do in order to create a new address space 642 00:32:25,469 --> 00:32:27,510 and to create at least one thread that is running 643 00:32:27,510 --> 00:32:28,780 within that address space. 644 00:32:28,780 --> 00:32:31,650 So we've sort of seeing all the pieces of this, 645 00:32:31,650 --> 00:32:33,590 but basically what it's going to do 646 00:32:33,590 --> 00:32:36,280 is it's going to allocate the address 647 00:32:36,280 --> 00:32:42,230 space in its table of all the address spaces 648 00:32:42,230 --> 00:32:43,750 that it knows about. 649 00:32:43,750 --> 00:32:46,190 So we saw that in the last time, and it's 650 00:32:46,190 --> 00:32:49,770 going to allocate a piece of physical memory 651 00:32:49,770 --> 00:32:51,730 that corresponds to that address space. 652 00:32:54,290 --> 00:32:59,650 So, it's going to allocate a piece of physical memory 653 00:32:59,650 --> 00:33:01,410 that corresponds to that address space. 654 00:33:01,410 --> 00:33:05,490 It's going to allocate one of these page maps 655 00:33:05,490 --> 00:33:07,390 that the PMAR register is going to point 656 00:33:07,390 --> 00:33:12,400 to when this thing is running. 657 00:33:12,400 --> 00:33:16,470 OK, and now the other thing that it's going to do 658 00:33:16,470 --> 00:33:21,360 is to go ahead and load the code for this thing 659 00:33:21,360 --> 00:33:26,905 into memory and map it into the address space. 660 00:33:30,000 --> 00:33:33,510 So it's going to add the code for this currently running 661 00:33:33,510 --> 00:33:35,540 module into the address space. 662 00:33:35,540 --> 00:33:42,910 And then it's going to create a thread for this new process. 663 00:33:42,910 --> 00:33:50,480 And it's going to add it to the table, the thread table, 664 00:33:50,480 --> 00:33:53,360 and then it's going to set up the value of the stack pointer 665 00:33:53,360 --> 00:33:55,510 and the program counter for this process 666 00:33:55,510 --> 00:33:57,667 so that when the process starts running it, 667 00:33:57,667 --> 00:34:00,250 you sort of into the process at some well-defined entry point, 668 00:34:00,250 --> 00:34:03,720 say the main routine in that process 669 00:34:03,720 --> 00:34:06,570 so that the thread can start executing at whatever 670 00:34:06,570 --> 00:34:08,159 starting point it has. 671 00:34:08,159 --> 00:34:09,781 OK, and now destroy is just going 672 00:34:09,781 --> 00:34:10,989 to basically do the opposite. 673 00:34:10,989 --> 00:34:13,322 It's going to get rid of all this state that we created. 674 00:34:13,322 --> 00:34:17,780 So it's going to remove the address space, 675 00:34:17,780 --> 00:34:27,150 and it's going to remove the thread from the table. 676 00:34:27,150 --> 00:34:31,219 OK, and it may also reclaim any memory 677 00:34:31,219 --> 00:34:33,509 that's associated exclusively with this process. 678 00:34:37,179 --> 00:34:38,839 OK so if we look at -- 679 00:34:49,389 --> 00:34:51,780 So if you look at a computer system at any one point 680 00:34:51,780 --> 00:34:57,440 in time, what you'll see is a collection of processes. 681 00:34:57,440 --> 00:35:00,370 So I've drawn these here as these big boxes. 682 00:35:00,370 --> 00:35:03,720 So you might have a process that corresponds to Halo 683 00:35:03,720 --> 00:35:06,940 and, say, we are also editing our code. 684 00:35:06,940 --> 00:35:09,680 So, we have a process that corresponds to emacs. 685 00:35:09,680 --> 00:35:12,430 OK, and each one of these processes 686 00:35:12,430 --> 00:35:15,610 is going to have an address space associated with it. 687 00:35:19,620 --> 00:35:22,990 And then there are going to be some set of threads 688 00:35:22,990 --> 00:35:25,250 that are running in association with this process. 689 00:35:25,250 --> 00:35:26,972 So in the case of Halo, I'm going 690 00:35:26,972 --> 00:35:29,180 to draw these threads as these little squiggly lines. 691 00:35:29,180 --> 00:35:32,090 So in the case of Halo, we saw that maybe it 692 00:35:32,090 --> 00:35:33,965 has three threads that are running within it. 693 00:35:33,965 --> 00:35:36,590 So these are three threads that all run within the same address 694 00:35:36,590 --> 00:35:37,300 space. 695 00:35:37,300 --> 00:35:40,290 Now, emacs might have just one thread that's running in it, 696 00:35:40,290 --> 00:35:42,290 say, although that's probably not true. 697 00:35:42,290 --> 00:35:44,790 emacs is horribly complicated, and probably has many threads 698 00:35:44,790 --> 00:35:47,660 that are running within it. 699 00:35:47,660 --> 00:35:51,000 And so, we're going to have these sort of two processes 700 00:35:51,000 --> 00:35:53,570 with these two collections of threads. 701 00:35:53,570 --> 00:35:58,164 So if you think about the sort of modularity 702 00:35:58,164 --> 00:35:59,830 or the enforcement of modularity that we 703 00:35:59,830 --> 00:36:02,220 have between these processes, we could 704 00:36:02,220 --> 00:36:03,820 say some interesting things. 705 00:36:03,820 --> 00:36:07,840 So first of all, we've enforced modularity. 706 00:36:07,840 --> 00:36:16,510 We have hard enforced modularity between these two processes, 707 00:36:16,510 --> 00:36:21,140 right, because there's no way that the Halo process can muck 708 00:36:21,140 --> 00:36:24,250 with any of the memory that, say, the emacs process has 709 00:36:24,250 --> 00:36:25,600 executed. 710 00:36:25,600 --> 00:36:28,140 And as long as we're using preemptive scheduling, 711 00:36:28,140 --> 00:36:32,880 there's no way that the Halo process could completely 712 00:36:32,880 --> 00:36:34,510 maintain control of the processor. 713 00:36:34,510 --> 00:36:36,940 So the emacs process is going to be allowed to run, 714 00:36:36,940 --> 00:36:39,360 and its memory is going to be protected from the Halo 715 00:36:39,360 --> 00:36:39,580 process. 716 00:36:39,580 --> 00:36:41,163 OK, now within the Halo process, there 717 00:36:41,163 --> 00:36:42,396 is sort of a different story. 718 00:36:42,396 --> 00:36:43,770 So within the Halo process, there 719 00:36:43,770 --> 00:36:45,320 is sort of a different story. 720 00:36:45,320 --> 00:36:47,515 So within the Halo process, these threads, 721 00:36:47,515 --> 00:36:49,640 because they are all within the same address space, 722 00:36:49,640 --> 00:36:53,330 can muck with each other's memory. 723 00:36:53,330 --> 00:36:55,940 So they are not isolated from each other in that way. 724 00:36:55,940 --> 00:36:57,520 And typically, because these are all 725 00:36:57,520 --> 00:37:00,230 within one process in that if we were 726 00:37:00,230 --> 00:37:03,160 to destroy that process in this step, what the operating 727 00:37:03,160 --> 00:37:06,250 system would do is in addition to destroying the address 728 00:37:06,250 --> 00:37:08,150 space, all of the running threads. 729 00:37:08,150 --> 00:37:11,680 So we say that these threads that are running within Halo 730 00:37:11,680 --> 00:37:14,450 share fate. 731 00:37:14,450 --> 00:37:20,900 If one of them dies, or if one of these threads 732 00:37:20,900 --> 00:37:23,282 fails or crashes or does something bad, 733 00:37:23,282 --> 00:37:24,740 then they are essentially all going 734 00:37:24,740 --> 00:37:25,948 to crash or do something bad. 735 00:37:25,948 --> 00:37:28,772 If the operating system kills the process, 736 00:37:28,772 --> 00:37:30,730 then all of these threads are going to go away. 737 00:37:36,490 --> 00:37:39,360 So this is the basic picture of sort 738 00:37:39,360 --> 00:37:42,327 of what's going on within a computer system. 739 00:37:42,327 --> 00:37:43,910 If you stare at this for a little bit, 740 00:37:43,910 --> 00:37:47,210 you see that there is actually sort of a hierarchy of threads 741 00:37:47,210 --> 00:37:49,280 that are running on any one computer system 742 00:37:49,280 --> 00:37:50,380 at any one point in time. 743 00:37:50,380 --> 00:37:55,220 So we have these larger sorts of processes that are running. 744 00:37:55,220 --> 00:37:57,160 And then within these processes, we 745 00:37:57,160 --> 00:38:00,170 have some set of threads that's also running, right? 746 00:38:00,170 --> 00:38:04,310 And so, there is a diagram that sort of useful 747 00:38:04,310 --> 00:38:06,090 to help us understand this hierarchy 748 00:38:06,090 --> 00:38:11,280 or layering of processes or threads on a computer system. 749 00:38:11,280 --> 00:38:28,120 I want to show that to you. 750 00:38:32,610 --> 00:38:37,000 OK, so on our computer system, if we look at the lowest layer, 751 00:38:37,000 --> 00:38:40,660 we have our microprocessor down here. 752 00:38:40,660 --> 00:38:45,700 OK, and this microprocessor, what we've seen 753 00:38:45,700 --> 00:38:49,506 is that the microprocessor has two things 754 00:38:49,506 --> 00:38:50,380 that it can be doing. 755 00:38:50,380 --> 00:38:55,600 Either the microprocessor can be executing, say, some user 756 00:38:55,600 --> 00:38:58,380 program, or the microprocessor can 757 00:38:58,380 --> 00:39:05,050 be interrupted and go execute one of these interrupt handler 758 00:39:05,050 --> 00:39:05,550 functions. 759 00:39:05,550 --> 00:39:08,351 So these interrupts are going to do things like, 760 00:39:08,351 --> 00:39:09,850 is going to be this timer interrupt, 761 00:39:09,850 --> 00:39:13,980 or when some IO device, say for example the disk, 762 00:39:13,980 --> 00:39:17,250 finishes performing some IO operation like reading 763 00:39:17,250 --> 00:39:20,257 a block from memory, then the processor will receive 764 00:39:20,257 --> 00:39:22,590 an interrupt, and the processor can do whatever it needs 765 00:39:22,590 --> 00:39:24,300 to do to service that piece of hardware 766 00:39:24,300 --> 00:39:26,367 so that the hardware can go and, for example, 767 00:39:26,367 --> 00:39:27,200 read the next block. 768 00:39:27,200 --> 00:39:30,070 So in a sense, you can think of the processor itself 769 00:39:30,070 --> 00:39:32,875 as having two threads that are associated with it. 770 00:39:32,875 --> 00:39:35,000 One of them is an interrupt thread, and one of them 771 00:39:35,000 --> 00:39:37,830 is the main thread now running on top of this. 772 00:39:37,830 --> 00:39:44,080 So, these are threads that are really 773 00:39:44,080 --> 00:39:45,410 running within the kernel. 774 00:39:45,410 --> 00:39:49,350 But on top of these things there is this set of applications 775 00:39:49,350 --> 00:39:54,820 like Halo and emacs. 776 00:39:54,820 --> 00:39:57,630 And, these are the kind of user programs 777 00:39:57,630 --> 00:39:59,390 that are all running on the system. 778 00:39:59,390 --> 00:40:01,420 And there may be a whole bunch of these. 779 00:40:01,420 --> 00:40:03,470 It's not just two, but it's any number of threads 780 00:40:03,470 --> 00:40:06,280 that we can multiplex on top of this main thread. 781 00:40:06,280 --> 00:40:09,790 And then each one of these may, in turn, 782 00:40:09,790 --> 00:40:12,760 have sub-threads that are running as a part of it. 783 00:40:12,760 --> 00:40:17,770 So if you think about what's going on in this system, 784 00:40:17,770 --> 00:40:20,260 you can see that at any one level, 785 00:40:20,260 --> 00:40:24,260 these two threads don't really need to know anything 786 00:40:24,260 --> 00:40:26,790 about the other threads that are running at that same level. 787 00:40:26,790 --> 00:40:30,885 So, for example, within Halo, these individual sub-threads 788 00:40:30,885 --> 00:40:33,510 don't really know anything about what the other sub-threads are 789 00:40:33,510 --> 00:40:34,170 doing. 790 00:40:34,170 --> 00:40:37,772 But it is the case that the threads at a lower level 791 00:40:37,772 --> 00:40:39,230 need to know about the threads that 792 00:40:39,230 --> 00:40:41,604 are running above them because these threads at the lower 793 00:40:41,604 --> 00:40:44,640 level implement this thread scheduling policy, right? 794 00:40:44,640 --> 00:40:48,630 So, the Halo program decides which of its sub-threads 795 00:40:48,630 --> 00:40:50,020 to run next. 796 00:40:50,020 --> 00:40:54,860 So the Halo program in particular is, 797 00:40:54,860 --> 00:41:06,920 so the parent thread implements a scheduling -- 798 00:41:06,920 --> 00:41:11,750 -- policy for all of its children threads. 799 00:41:11,750 --> 00:41:13,530 So Halo has some policy for deciding 800 00:41:13,530 --> 00:41:14,790 what thread to run next. 801 00:41:14,790 --> 00:41:16,420 The operating system has some. 802 00:41:16,420 --> 00:41:19,290 The kernel has some policy for deciding which of these user 803 00:41:19,290 --> 00:41:21,230 level threads to run next. 804 00:41:21,230 --> 00:41:24,980 And the parent thread also provides some switching 805 00:41:24,980 --> 00:41:25,480 mechanism. 806 00:41:28,730 --> 00:41:31,570 So we studied two switching mechanisms today. 807 00:41:31,570 --> 00:41:35,470 We looked at this notion of having this sort of cooperative 808 00:41:35,470 --> 00:41:38,640 switching where threads call yield in order to allow 809 00:41:38,640 --> 00:41:39,930 the next thread to run. 810 00:41:39,930 --> 00:41:43,730 And, we looked at this notion of preemptive scheduling 811 00:41:43,730 --> 00:41:47,400 that forces the next thread in the schedule to run. 812 00:41:47,400 --> 00:41:49,580 So, if you look at computer systems, in fact 813 00:41:49,580 --> 00:41:51,440 it's a fairly common organization 814 00:41:51,440 --> 00:41:53,800 to see that there is preemptive scheduling at the kernel 815 00:41:53,800 --> 00:41:57,180 level that causes different user level applications to run. 816 00:41:57,180 --> 00:42:01,800 But within a particular user program, 817 00:42:01,800 --> 00:42:03,870 that program may use cooperative scheduling. 818 00:42:03,870 --> 00:42:05,640 So the individual threads of Halo 819 00:42:05,640 --> 00:42:08,599 may hand off control to the next thread only when they want to. 820 00:42:08,599 --> 00:42:10,390 And this sort of makes sense because if I'm 821 00:42:10,390 --> 00:42:12,860 a developer of a program, I may very well 822 00:42:12,860 --> 00:42:14,646 trust that the other threads that I 823 00:42:14,646 --> 00:42:16,520 have running in the program at the same time, 824 00:42:16,520 --> 00:42:18,150 I know I want them to run. 825 00:42:18,150 --> 00:42:21,367 So, we can talk about sort of different switching mechanisms 826 00:42:21,367 --> 00:42:22,950 at different levels of this hierarchy. 827 00:42:30,900 --> 00:42:34,530 OK, so what we've seen up to this point 828 00:42:34,530 --> 00:42:39,100 is this sort of basic mechanism that we 829 00:42:39,100 --> 00:42:42,050 have for setting up a set of threads that are 830 00:42:42,050 --> 00:42:43,680 running on a computer system. 831 00:42:43,680 --> 00:42:47,610 And what we haven't really talked at all about 832 00:42:47,610 --> 00:42:50,780 is how we can actually share information 833 00:42:50,780 --> 00:42:53,250 between these threads or coordinate access 834 00:42:53,250 --> 00:42:55,364 to some shared resource between these threads. 835 00:42:55,364 --> 00:42:57,530 So what I want to do with the rest of the time today 836 00:42:57,530 --> 00:42:59,660 is to talk about this notion of coordinating 837 00:42:59,660 --> 00:43:01,620 access between threads. 838 00:43:01,620 --> 00:43:07,352 And we're going to talk about this 839 00:43:07,352 --> 00:43:09,310 in the context of a slightly different example. 840 00:43:13,110 --> 00:43:18,638 So -- -- 841 00:43:22,630 --> 00:43:25,370 let's suppose that I am building a Web server. 842 00:43:30,717 --> 00:43:32,550 And I decide that I want to structure my Web 843 00:43:32,550 --> 00:43:41,760 server as follows: I want to have some network thread, 844 00:43:41,760 --> 00:43:45,280 and I want to have some thread that services disk requests. 845 00:43:45,280 --> 00:43:49,170 OK, so the network thread is going 846 00:43:49,170 --> 00:43:51,190 to do things like accept incoming connections 847 00:43:51,190 --> 00:43:54,460 from the clients and process those connections 848 00:43:54,460 --> 00:43:59,050 and parse the HTTP requests and generate the HTML results 849 00:43:59,050 --> 00:44:00,581 that we send back to users. 850 00:44:00,581 --> 00:44:02,080 And the disk request thread is going 851 00:44:02,080 --> 00:44:04,770 to be in charge of doing these expensive operations where 852 00:44:04,770 --> 00:44:06,880 it goes out to disk and reads in some data 853 00:44:06,880 --> 00:44:09,442 that it may need to assemble these HTML 854 00:44:09,442 --> 00:44:10,400 pages that we generate. 855 00:44:10,400 --> 00:44:15,190 So, for next recitation, you're going 856 00:44:15,190 --> 00:44:17,170 to read about a system called Flash, which 857 00:44:17,170 --> 00:44:18,525 is a multithreaded Web server. 858 00:44:18,525 --> 00:44:20,650 And so this should be a little taste of what you're 859 00:44:20,650 --> 00:44:23,420 going to see for tomorrow. 860 00:44:23,420 --> 00:44:25,830 So, these two threads are likely to want 861 00:44:25,830 --> 00:44:28,370 to communicate with each other through some data 862 00:44:28,370 --> 00:44:32,880 structure, some queue of requests, 863 00:44:32,880 --> 00:44:36,090 or a queue of outstanding information. 864 00:44:36,090 --> 00:44:40,650 So suppose that what we're looking at in fact 865 00:44:40,650 --> 00:44:43,730 in particular is a queue of disk blocks 866 00:44:43,730 --> 00:44:50,480 that are being sent from a disk request thread 867 00:44:50,480 --> 00:44:52,490 out to the network thread. 868 00:44:52,490 --> 00:44:55,720 So, whenever the disk finishes reading a block, 869 00:44:55,720 --> 00:45:00,570 it enqueues a value into the network thread 870 00:45:00,570 --> 00:45:03,380 so that the network thread can then later pull that value off 871 00:45:03,380 --> 00:45:05,380 and deliver it out to the user. 872 00:45:05,380 --> 00:45:07,550 So in this case, these are two threads 873 00:45:07,550 --> 00:45:09,820 that are both running within the same address space 874 00:45:09,820 --> 00:45:11,030 within the same Web server. 875 00:45:11,030 --> 00:45:14,310 So they both have direct access to this queue data structure. 876 00:45:14,310 --> 00:45:17,150 They can both read and write to it at the same time. 877 00:45:17,150 --> 00:45:20,982 This queue data structure, think of it as a global variable. 878 00:45:20,982 --> 00:45:23,190 It's in the memory that's mapped in the address space 879 00:45:23,190 --> 00:45:25,660 so both threads can access it. 880 00:45:25,660 --> 00:45:31,210 So let's look at what happens when these two threads, let's 881 00:45:31,210 --> 00:45:34,230 look at some pseudocode that shows what might be happening 882 00:45:34,230 --> 00:45:36,290 inside of these two threads. 883 00:45:36,290 --> 00:45:42,520 So, suppose within this first thread 884 00:45:42,520 --> 00:45:45,050 here, this network thread, we have 885 00:45:45,050 --> 00:45:53,630 a loop that says while true, do the following. 886 00:45:53,630 --> 00:45:57,920 De-queue a requestn -- call it M -- 887 00:45:57,920 --> 00:46:03,490 from the thread, and then process, 888 00:46:03,490 --> 00:46:07,940 de-queue a disk block that's in the queue 889 00:46:07,940 --> 00:46:10,640 and then go ahead and process that disk queue. 890 00:46:10,640 --> 00:46:12,250 Go ahead and process that. 891 00:46:12,250 --> 00:46:17,170 And now, within the disk request thread, 892 00:46:17,170 --> 00:46:21,440 we also have a while loop that just loops forever. 893 00:46:21,440 --> 00:46:25,300 And what this does is it gets the next disk block to send, 894 00:46:25,300 --> 00:46:29,790 however it does that, goes off and reads the block from disk, 895 00:46:29,790 --> 00:46:35,780 and then enqueues that block onto the queue. 896 00:46:40,300 --> 00:46:46,260 So if you like, you can think of this as simply the disk request 897 00:46:46,260 --> 00:46:48,871 thread is going to stick some blocks into here, 898 00:46:48,871 --> 00:46:50,370 and then the network thread is going 899 00:46:50,370 --> 00:46:54,290 to sort of pull those blocks off the top of the queue. 900 00:46:54,290 --> 00:46:57,240 So if you think about this process running, 901 00:46:57,240 --> 00:46:59,200 there's kind of a problem with the way 902 00:46:59,200 --> 00:47:01,330 that I've written this, right, which 903 00:47:01,330 --> 00:47:08,180 is that suppose that the disk request thread runs much 904 00:47:08,180 --> 00:47:10,810 faster than the network thread. 905 00:47:10,810 --> 00:47:14,620 Suppose that for every one network, 906 00:47:14,620 --> 00:47:17,780 one block that the network thread is able to pull off 907 00:47:17,780 --> 00:47:21,080 and process, the disk thread can enqueue two blocks. 908 00:47:21,080 --> 00:47:22,830 OK, so if you think about this for awhile, 909 00:47:22,830 --> 00:47:24,570 if you run this system for a long time, 910 00:47:24,570 --> 00:47:26,920 eventually you're going to have enqueued 911 00:47:26,920 --> 00:47:27,880 a huge amount of stuff. 912 00:47:27,880 --> 00:47:29,713 And typically the way queues are implemented 913 00:47:29,713 --> 00:47:31,260 is they are sort of some fixed size. 914 00:47:31,260 --> 00:47:33,180 You don't want them to grow to fill the whole memory 915 00:47:33,180 --> 00:47:33,888 of the processor. 916 00:47:33,888 --> 00:47:36,067 So you limit them to some particular fixed size. 917 00:47:36,067 --> 00:47:38,650 And eventually we are going to have the problem that the queue 918 00:47:38,650 --> 00:47:39,360 is going to fill up. 919 00:47:39,360 --> 00:47:40,220 It's going to overflow, and we're 920 00:47:40,220 --> 00:47:42,060 going to have a disk block that we don't have anything 921 00:47:42,060 --> 00:47:42,976 that we could do with. 922 00:47:42,976 --> 00:47:45,040 We don't know what to do with it. 923 00:47:45,040 --> 00:47:46,886 Right, so OK, you say that's easy. 924 00:47:46,886 --> 00:47:48,260 There is an easy way to fix this. 925 00:47:48,260 --> 00:47:54,775 Why don't we just wait until there is some extra space here. 926 00:47:58,910 --> 00:48:01,720 So we'll introduce a while full statement here 927 00:48:01,720 --> 00:48:03,860 that just sort of sits in a spin loop 928 00:48:03,860 --> 00:48:06,699 and waits until this full condition is not true. 929 00:48:06,699 --> 00:48:08,740 OK, so as soon as the full condition is not true, 930 00:48:08,740 --> 00:48:11,750 we can go ahead and enqueue the next thing on the queue. 931 00:48:11,750 --> 00:48:14,610 And similarly we're going to need to do something over here 932 00:48:14,610 --> 00:48:18,330 on the process side because we can't really 933 00:48:18,330 --> 00:48:20,470 de-queue a message if the queue is empty. 934 00:48:20,470 --> 00:48:22,510 So if the processing thread is running faster 935 00:48:22,510 --> 00:48:25,650 than the enqueueing thread, we're going to be in trouble. 936 00:48:25,650 --> 00:48:30,920 So we're going to also need to introduce a while loop here 937 00:48:30,920 --> 00:48:35,300 that says something like while empty. 938 00:48:35,300 --> 00:48:40,550 OK, so this is fine. 939 00:48:40,550 --> 00:48:42,180 It seems like it fixes our problem. 940 00:48:42,180 --> 00:48:44,210 But there is a little bit of a limitation 941 00:48:44,210 --> 00:48:48,412 to this approach, which is that now what you see 942 00:48:48,412 --> 00:48:50,120 is suppose these two threads are running. 943 00:48:50,120 --> 00:48:52,190 And they are being sort of scheduled in round robin; 944 00:48:52,190 --> 00:48:54,110 they are being scheduled one after the other. 945 00:48:54,110 --> 00:48:55,460 Now this thread runs. 946 00:48:55,460 --> 00:48:57,560 And when it runs, suppose that the queue is empty. 947 00:48:57,560 --> 00:49:00,150 Suppose the producer hasn't put anything on the queue yet. 948 00:49:00,150 --> 00:49:02,108 Now when this guy runs, he's going to sit here, 949 00:49:02,108 --> 00:49:03,980 and for the whole time that it's scheduled, 950 00:49:03,980 --> 00:49:05,650 it's just going to check this while 951 00:49:05,650 --> 00:49:06,870 loop to see if the thing is empty over, 952 00:49:06,870 --> 00:49:08,220 and over, and over, and over, and over again, right? 953 00:49:08,220 --> 00:49:09,740 so that's all whole lot of wasted time 954 00:49:09,740 --> 00:49:10,950 that the processor could and should 955 00:49:10,950 --> 00:49:13,160 have been doing something else useful like perhaps 956 00:49:13,160 --> 00:49:16,590 letting the producer run so that it could enqueue some data. 957 00:49:16,590 --> 00:49:21,070 So in order to do this, so in order to fix this problem, 958 00:49:21,070 --> 00:49:23,380 we introduce this notion of sequence coordination 959 00:49:23,380 --> 00:49:26,100 operators. 960 00:49:26,100 --> 00:49:33,050 And what sequence coordination operators do 961 00:49:33,050 --> 00:49:38,130 is they allow a thread to declare 962 00:49:38,130 --> 00:49:40,447 that it wants to wait until some condition is true. 963 00:49:40,447 --> 00:49:43,030 And they allow other threads to signal that that condition has 964 00:49:43,030 --> 00:49:46,029 now become true, and sort of allow 965 00:49:46,029 --> 00:49:47,820 threads that are waiting for that condition 966 00:49:47,820 --> 00:49:48,950 to become true to run. 967 00:49:48,950 --> 00:49:52,030 So, we have these two operations: 968 00:49:52,030 --> 00:49:56,260 wait on some variable until some condition is true, 969 00:49:56,260 --> 00:49:59,770 and signal on that variable. 970 00:49:59,770 --> 00:50:05,760 OK, so we're basically out of time now. 971 00:50:05,760 --> 00:50:07,710 So what I want to do is I'll come back 972 00:50:07,710 --> 00:50:10,830 and I'll finish going through this example for the next time. 973 00:50:10,830 --> 00:50:12,510 But what you should do is think about, 974 00:50:12,510 --> 00:50:15,020 suppose you had these sequence coordination operators. 975 00:50:15,020 --> 00:50:17,446 How could we sort of modify these two while loops 976 00:50:17,446 --> 00:50:19,320 that we have for these two operators in order 977 00:50:19,320 --> 00:50:21,040 to be able to take advantage of the fact 978 00:50:21,040 --> 00:50:23,200 that in order to be able to make this 979 00:50:23,200 --> 00:50:25,630 so we don't sit in this spin loop forever and execute. 980 00:50:25,630 --> 00:50:27,140 So that's it. 981 00:50:27,140 --> 00:50:29,510 Today we saw how to get these multiple processes to run 982 00:50:29,510 --> 00:50:31,040 on one computer. 983 00:50:31,040 --> 00:50:34,600 And next time we'll talk about sort making computer programs 984 00:50:34,600 --> 00:50:37,380 run more efficiently, getting good performance out 985 00:50:37,380 --> 00:50:40,490 of this sort of architecture we've sketched.