UCB CS162 操作系统笔记(十一)
P5:Lecture 5: Sockets and IPC (Finished), Concurrency Processes and Threads - RubatoTheEmber - BV1L541117gr
Can you guys hear me out on in net land too?
Are we good? Good。 Well, hello, it's nice to actually see people for a change。
Sorry about the little bit of setup there。 I think it's never quite worked the way you expect them。
but I think we're good。 I'm John Kubitowitz。 Most people call me Kubie。
And you've sort of heard me on zoom, but here I am。 I actually exist。 Actually, this is a really。
a really amazing holographic projection up here。 So I'm really sitting at home。 But we'll。
we'll pretend for now that I'm actually here。 And let me pick the camera here。 See。
Let's do like turn。 Okay, so we have been talking a lot about some introductory ideas in operating systems。
and we're going to continue now。 So if you notice。 Let's see if I can actually get this thing to。
There we go。 So we were talking last time。 I'm going to talk about this key idea in Unix。
which was unique when it was first, first came up that everything's a file or everything at least looks like a file。
So you can open files and they look like files。 That's not surprising。
but you can also open devices or sockets or whatever and you use the same interface。
And that interface is going to allow us to pipe things different programs into each other and you get to do that when you start playing with the shell。
That's homework two, I think。 But the basic ideas here are you always open everything。
And the open is where you're going to be checking permissions on things。
And then all of the reads and writes are bite oriented, regardless of what's being stored。
So even if you're storing a database that's in some special format, operating system doesn't care。
It thinks of it as bites。 Okay。 The kernel will help buffer reads and buffer writes。
And there's an explicit close operation。 Okay, so that's an interesting concept and what it means is when you start working at the system call level inside the kernel you're going to be working on that open close read right interface。
The other thing we did last time is we kind of looked at two different ways of accessing that file interface so at the low level is the system full interface。
So I would say this is the raw open read right close。
And you don't get a lot of functionality with this you give it a buffer。
And you tell it how much you want to read or how much you want to write and it just does it for you。
We contrasted that with the high level IO interfaces and pretty much they are all the ones that start with an F instead so instead of open you got F open。
And those are buffered。 Okay, and so it user level。
They actually go ahead and do some extra functionality so they're going to wrap the kernel level cis calls with the high level ones。
And so you don't even have to do system calls from this level you're just going to do F open and then you're going to do read that right up read up close。
When you open at the low level。 That open system calls going to return an integer that integer represents your file being opened and you're going to use that integer from that point on it's called a file descriptor。
In all of your region rights to tell the operating system kind of which one you're talking about。
Right, so you already went to the trouble of opening now you'll tell them which one you're going to talk about。
In the high level interface, you get back a file star so that's a pointer to a structure that's allocated at user level。
Okay, and so notice these are very different。 So don't mix F versions and non F versions because you'll get chaos。
Okay, you can mix them if they're different files but don't mix them in the same file。 Now。
the file descriptor is an integer that's only meaningful for the kernel。
And we'll talk a little bit about what that means on the other side of the kernel interface。
The file structures in user space。 So it's a it's a structure and you could probe it and look at it but you're never。
you know, you never do that。 But it's a chunk of memory for buffering it's a file descriptor for the file so clearly F open had to do an open under the covers so it keeps track of that integer。
Okay。 Every reader right causes a system called no matter what。
Whereas every F reader F right kind of buffers things and it might or might not call the kernel so that's where we're going to get some power out of using the high level ones。
So for instance, with the low level ones suppose you have a loop and you're reading from a file four bytes at a time you're going to do a system call for every four bytes and it's going to return four bytes for you。
The F reads a little more interesting right there you first call the F read as the kernel for a bunch of bytes maybe 1024。
and then it gives four of them back to you and then the next time you call you get you get four but they're out of the buffer they don't have to go back to the current。
Okay。 Everybody good on that。 Now, the other thing is。
I wanted to look a little bit here and you're going to start exploring this interface as you go but the low level read for instance。
is really just going to go directly into the system call。
And that system call is kind of like a procedure call but into the kernel and so it's got to be done in a very special way and you've already started looking at this。
So there's going to be some assembly code that puts the system call number in a register and the arguments and other registers and then it does a trap instruction which falls into the kernel。
Inside the kernel, you'll do a bunch of stuff。 We'll talk more about that over the course of the term。
and then it'll return back to user level。 And at that point。
this is kind of a system call this is what the system call is from the user standpoint。
And then there's going to be formatting in the right way to return。
The contrast with the high level is here we're actually at user level or it's going to say, hey。
does our buffer have anything in it。 Well, it does just return don't even bother。 Okay。
Otherwise it does that system call thing we talked about。
And now it's going to return some of that data into a buffer and then return it to the caller。
So that's another way to look at those two differences。 Okay。
I noticed there's some chat on the Tiazza as to what the differences was that it were。
And I thought I would give you a little bit of a handle on that。
Are there any questions before I move on。 All right。 Okay。 Now, um, moving forward。
If you notice the thing that we were talking about at the very end is we were starting to unwrap the idea of a socket。
And we're going to talk a lot more about networking as the term goes on。 But for now。
this idea of a socket is really another example of something that looks like a file。
But is actually a couple of cues connected across the network。
So it looks like a file and you read and write it like a file, but it's not actually a file。 Okay。
And so sockets are endpoints to communication。 And they're on two sides of a network。 Okay。
In many cases。 And so we talked at the very end we're talking about this connection set up。
And so there's actually two types of sockets。 So when you're a server。
you got to listen for the fact that somebody may try to connect。
So the server is the one that's listening, the client is going to be the one that connects。
And so that server socket gets put up before any communication happens。 And there's two operations。
The first one is it's going to set up listening。 And that listening is going to be listening on a special port in the case of TCP IP。
where we have the IP address of the server, but then it's also a well known port。
And those well known ports are things like 80 is the standard web browser。
443 is the protected browser, etc。 And so that listening does basically nothing else until a client sets up a socket for communication with the server。
And it has its own IP address and its own port, which is often randomly chosen。
And it connection request goes to the server socket。 The server socket, well, at that point。
create a brand new socket purely for that connection and to connect them。
And so after the connection is set up, now, well, we have this unique connection and that server socket can have many other connections coming in。
That's why a web browser can handle, or excuse me。
a web server can handle many different connections at once every one of them will be one that has come through this process。
Okay。 And notice that that connection, which is yellow in this diagram here has a five tuple that makes it unique。
There's an IP address on either side。 There's a port on either side。 And then there's a protocol。
which in this case is TCP IP。 So as long as any of those things are different。
you can have a completely different connection to that server。 Okay。 And often the client port。
like I said, randomly assigned done by the OS。 And the server port is what's called a well-known port。
And that's you're connecting if I'm connecting to a regular patchy web browser web server。
I keep saying browser。 It would be 80。 If I'm connecting to your bank, it might be 443, etc。 Okay。
All right。 And I want to pause there。 That's where we left it last time。 Questions。 Yeah。
What exactly is a port? Well, a port is a 16 bit number。
but that isn't really what you wanted to know。 So port is a unique service in a given server。
And so that port number identifies a service that that particular IP address can handle。
And if you think about it, if you have a server, just the server hardware has got its own IP address。
That's kind of how packets get to it。 But that isn't really enough。
You really need to identify something running inside。 In fact。
you want the server process to be identified。 And that identification happens as part of the port setup。
Okay。 Good。 Any other questions? So, so now what I wanted to do is just quickly take you through the web server idea。
Okay。 And, you know, why do we do web servers this way? Well。
you can see that that listening basically allows many clients to talk to the server。 And in fact。
sometimes many different tabs could talk to the same server or many different pieces of a web page could talk to the same server。
Each of them could have their own unique connection。 Okay。 So it's a many to one。 And let's look at。
for instance, this, this use of sockets in a little more detail for the web。
And I'm not going to go through this fairly quickly, but I wanted to make sure you've both seen it。
So the server creates that server socket。 It binds it to the address that says, gee。
we're going to do IP address, whatever I've got in my port。 And then it does a listen。 Okay。
And meanwhile, that's all it does until somebody else is going to do something。 And so in a loop。
it's waiting for an accept。 And that accept is totally blocking。
So really what happens is the server does all this and then it goes to sleep in the accept。 Okay。
the client comes along creates a socket, connects it to the server。
which really means there's a communication to the listen。 That listen causes an accept protocol。
which we'll talk about later in the term。 And when you're done。
you've got two sockets that are now connected to each other that can read and write。 Excuse me。
So that is a bidirectional connection that yellow arrows bidirectional。
And once those two things are connected, they can send data down each side, which are bytes, right?
Remember, everything's a bite。 And at the other side。
it can be picked up so it can go in both directions。 Okay。 Yes, question。 Yes。
so for each connection, it's a new socket。
Okay, but notice just to go back to this picture because it's important。
Notice that there's one server socket that guy sits there and listens。
But every new connections got a new little green thing on the server side。 Okay。
and that's got to be distinguished by something in that five tuple。
And so that means that if you've got the same client is connecting。
it keeps making new ports for each connection。 Yes, the back。 Yes, good question。
There is the well known port。 Let's say this is 443。
This socket on the server side will have 443 as its server port。 Yes。
so every every green thing here will have the same IP address and port for the server side。
Distinguished by the IP address and or the port from the other side。 That makes it unique。
And it basically lets the the network protocol stack underneath sort out all the packets that come in and can figure out kind of where to send them。
And which one of the sockets based on that five tuple。 There was another one down here。 Yeah。
Is there a limit on the number of sockets? So I could be philosophical and say there's always a limit。
Yes。 What happens usually is if you bombard the server with too many socket requests。
it crashes the server。 Better hardened things will start start for refusing to actually accept。
But you have to you have to make sure you do that often yourself in order to make that work properly。
Okay, one last。 Yeah, go ahead。 That's a good question。
I'm going to show you where forking comes into play here in a moment with this scenario。
But it's really just making a new structure inside of the kernel that knows what packets it's supposed to be receiving and where they go when they go out。
So it's not it's not a process。 It's like a data structure plus a queue because every socket endpoint has a queue associated。
Okay, you guys are going to be great。 You're actually asking questions。 Isn't this great?
You're actually like in person in a classroom and people are raising their hand。
It's like radical。 Okay, so。 So what I'm showing here is once the connections happen。 This server。
which is not a very bright server, we're going to have to fix it。
But notice that it's totally consumed right now with that one connection。
You'd never want to build a web server this way。 But let's look at this one first。
So notice that after the server and the client have cooked up。
Now they're just communicating back and forth。 Okay, and sometime later。 You know。
the client is done。 It's going to close its socket。
The server will close its socket in this particular not very bright version will then go back and accept another incoming connection。
Okay, this is a not very bright version。 Okay, you guys are with me on that。
So notice a couple of things here。 How does the server know things are done? Well。
one thing is if you close down the socket, the client side, the server will get notified。
So it'll find that out。 The other thing is you're going to need a protocol going over this back and forth。
This sort of format what the bytes look like because the operating system only knows them as bytes。
We'll talk about that later in the term。 Okay。 So, you know。
here's what that simple protocol looks like on the client side。 I just wanted to give you。 You know。
we program C in this class。 So if you notice, when we create the socket。
We need to start out with an add or info structure which defines things like what's the IP address and port name or where I'm going。
And we can have look up host later in the slides。 We'll decide whether we want to look at it。
The socket itself kind of has a family and a type and a protocol that was part of this address structure。
So for instance, the protocol is going to be TCP, okay, etc。
And the socket type is going to be a stream because TCP actually is a streaming protocol。
And once that socket comes back, notice it's an integer。 Big surprise, right? Everything's a file。
It all looks like a file tastes like a file。 Actually, I'm not sure what a file tastes like。
but it would taste like a file if you could, right。
Which means that now I can do region rights to it as if it were a file because it's a file descriptor。
Guys get the idea here, right。 So if you notice, so that's grabbing the host name and turning it into a structure I can use。
This is creating the socket。 This is trying to connect。
So it's reaching out to try to find that listener on the other side and assuming that succeeds。
the connect is blocking, but it'll exit。 When it's succeeded and now I can run the client doing whatever I want。
sending bytes back and reading bytes。 And then eventually I close and I'm done。 Okay。
so the client's kind of not very exciting。
The server is also here。 I'm just going to give you a couple of pieces here。 So once again。
we set up the address。 This time, maybe we only give it a port because we know what the IP address is。
It's local。 We get ourselves a socket。 This time we bind it。
This is where I'm telling the server socket。 What address do you have to think you are at this point。
Okay, and that's both the local IP address and the port。 And then I execute listen。
And I only do that once。 Because when it returns from listen, there is now an ear in the server。
Okay, and at that point in a loop that particular code, waits to accept a new connection。
So this is blocking。 It'll only return when it's got a new connection and oh look it's a it's an integer。
which means it looks like a file。 Okay, right about that。 Okay。
And then once that comes back and I get past this except。
I now have a file descriptor to a socket that looks like a file that I can read and write at。
And by the way, when I do that read and writing, I'm talking to the other side。 Okay。
so when I write the client reads when it read when it's writing on its side, I can read it。 Okay。
bi-directional。 And then when I'm done, I close it。
And notice since there's two sockets when the server is totally done for whatever reason we haven't given it any way to exit here at the moment。
we'll also close the server socket。 Because that's the last socket。 Right。
so each except since it's a unique connection gets a new socket associated with it。
which we close each time in this not so bright version。
And then at the very end we close the listener socket and we're good and done。 Okay。 Yes。 Okay。 So。
the problem is that a socket is a got a descriptor that came back and an F read needs a file star。
So hopefully you guys all remember from high school chemistry you got to match the units。
Right integer file star don't match。 So something's got to happen in the middle now fortunate for you。
there actually is a, there's a call that will turn a descriptor into a file star that you can use。
So the answer to your questions yes but you got to check you got to catch that mismatch first。 Okay。
All right。 So here was a question。 So this client needs to know the server sockets port to make the initial connection。
Yes。 Okay, that's a, and why is that that's because every port is a different service on that server so you need to know what port you want。
So when your web browser goes to browse。 It knows you're either going to port 80 or port 443。 Okay。
and that's part of the connection process。 Okay, question。 Yes。 This is a not a very bright version。
all it does is it waits for connection。 It serves that one connection and then it closes and goes back and does another connection so there's no threading no nothing we're going to fix it。
Okay, this would, this would fail in fact to be a good connection for a single web browser because most modern browsers make more than one connection simultaneously to get pictures and stuff。
At the same time as text so that's why you see the pictures are simultaneously loading all over the page that's because each one's got a separate connection in some instances。
Okay, depending on how it's done。 Yeah, question。 Good question。 What happens in this。
especially with here it's clear, because when I'm serving something here I'm not accepting so what happens to all the connections from the rest of the world。
The answer is there's an internal queue in the operating system of connections that are waiting。
So the listener, the listener socket has a cue that's a great question。 Okay, any other questions。
Okay, since there seems to be a lot of interest let's fix this guy okay but we might as well right。
So for instance, one of the things it's kind of interesting about this is。
we run this serve client thing in the same process as the server。
That might not be desirable from a security standpoint。
wouldn't it be nice if the serving to somebody remote was a separate process just to protect the server。
So now what, what are we going to use to have that happen altogether。 Fork。 Okay, so here we go。
Here's a very simple change notice all I did here was after I accept。
I'm going to execute a fork and the parent is going to close and wait for the child to be done and then the child is going to do what we just did so we haven't gotten any parallelism yet but we've gotten some security。
Okay。 And so if you look really, the only difference there is that we still listen we still accept。
Now we're executing fork in here。 And if PID equals zero what's that mean it's the parent of the child。
Child, so the child goes ahead since the child was a was forked from the parent it's got the same。
All the same file descriptor that the parent had at the point that the fork executed。
including this server socket so this one isn't useful to the child so it closes it。
And before there are two of them that point to the same socket。 Okay。
So I'm just point pointing that out just to get it rattling around your brain。 But if you notice。
once we close that now we're going to go ahead and serve the client notice we're using the connection socket which is the one we got from the accept。
Okay, and when we're done with that we'll close it。 And then we just exit。 Okay。
I didn't put a highlight on that but exit says this child is done。 And why do I do exit zero。
What's that mean success right nowhere。 What's the parent to well the parent closes the connection socket because it's not going to talk to the remote client。
And then it does wait。 Okay, and there's lots of versions of weight this one doesn't even care that there was a zero coming back just because I'm being simple about this but take a look through man wait you can see there's lots of them。
Okay, and then when we're done, we'll loop and do it all over again。
And we'll close when we're done in the back yeah。 Well if the parent doesn't close this then they're going to start building up a bunch of stuff。
Now, of course, when the parent finally exits the leftover sockets will close at that point but you want to be clean enough。
Okay, yes。 So the socket it。 No。 Okay good so there's question there's the sockets should be unique what if the child doesn't close it so first of all the socket is unique。
What happens is the file descriptor member it's an integer。
Every process has a table that goes from integers to the individual things they're pointing at under the covers。
So it really is one unique socket to handles on it。 Okay。
and so you can actually both communicate over the one socket if you want to but that would probably cause some chaos in this application。
Okay, so the handles are just integers and integers not a file but an integer could point at a descriptor of a file or a socket or whatever so the thing underneath the covers is the thing that's unique。
Okay, and I'm actually, I'll have enough time I think to show you that in more explicit detail with when we get a little after the past this example。
Okay。 Good。 Any other questions。 Yeah。 Say that again。 How do you get out of the while loop。
You don't control C。 Yeah, so this this is not intended to be a full server this is just a sketch。
but that that's a good catch。 Okay, yeah this this one, there is no exit of this。
To get that close to that close it's code that would never actually run if you compiled this particular。
Yeah。 Every process has its own table of file descriptors。 Yes。 Good。
That's why I'm glad I'm putting some things up in just a moment。 Okay, so。
so far what we've done is we've gotten a separate process for each connection but we still don't have any concurrency or parallelism。
Which means that we can only handle one connection at a time。 So what's the obvious thing? Well。
when the parent is done closing this extra connection because it the child is handling it then it just goes back and accepts again。
So notice the only thing I removed there was the weight。 Okay, so in this instance。
we're now good to go。 Okay, now really this one kind of looks like this where I commented out the weight。
All right, now really, you got to be a little careful that things don't pile up and so you really want to free up children that have finished。
And so there's a little more complexity to that than here but I'm, I'm。
for now this example I just want you to think we're not waiting and so now we can do some more。
Okay。 All right。 So now this, you can look through this。
the slides and so on to see this full example, but for instance to set up addresses for the server side。
We have to make a structure of a certain type and clear it out and set some parameters and so on and then do something like get at her info。
giving it the port and some other information and that makes a structure that then we can use to bind and fall。
Okay, and I don't want to go into the details and that too much right now。 But。
similar for the client。 You might have something like this look up host where you give it the host name。
you know, www。berkeley。edu and the port 80 and then it constructs some stuff for you so that then you can make a socket that then you can connect with。
Okay。 So last, how do we do this without protection。
So you could imagine that producing a bunch of processes might actually be kind of slow。
And so for a moment, if our protection comes from different ways that what would we really want to do that doesn't have the overhead of making a brand new thread。
excuse me a brand new process for every connection what let's do a brand new thread for every connection。
right。 And in this case, it's much more efficient to spawn threads and so here really instead of a fork we might do something like p thread create。
which I'll talk about a little bit more but that'll create a new thread。
And in that instance we're all in the same process but we've got a nice low overhead thing that can talk with one client and meanwhile we can go back except another system called create more threads and so now we get a thread for connection。
which is really, most modern servers do。 Okay。 And they sandbox and protect you in slightly different ways because this is a big performance improvement not to have a process for everything。
All right。 And finally, the problem with this is this is great but it's still subject to something。
that's used to call the slash dot effect。 All right。
or something becoming a little too popular what happens。 Well, if everybody in the world。
goes to our lecture because you know it's kuby teaching and it's awesome。
You cross you end up crashing the server right。 So this is actually subject to a denial of service where too many incoming connections generate too many threads and it fills up all the memory and the system crashes。
Okay, so this is really not a real solution。 If you actually want a real solution what you do is you do something like a thread pool which we'll talk about in a couple of slides or a couple of lectures little more。
where you limit the total number of threads that can be alive at any time。 So。
if you've succeeded that then you don't make new threads you wait until threads go away。
And then you then you take a new connection and you rely on that cue inside of the kernel of connections that are waiting。
Okay, and that's so then you can kind of trade off the amount of concurrency or parallelism you want versus the memory that you need。
Okay。 All right, everything is looks like a file。 Okay。 I have office hours。
Sorry I wasn't there today。 I had a little mini disaster I was dealing with and I may or may not be there tomorrow but I definitely will for the rest of the week。
I mean the rest of the term。 And Anthony's got some hours that he's going to post as well。
I'll let you know if I'll be there tomorrow Wednesday if anybody wants to come see me。
Important thing to note is if these office hours don't work and you want to talk with me just send me mail or something let me know。
We'll set something up。 Okay, and how many people would like me to try to have a virtual version of office hours。
Okay, probably not so much the people that are here。 Let's see。 Anyway, I have virtual is great。
Okay, so we'll see。 So one person did ask here by the way。
let me let me put back one more question still a bit confused on how the multiple connections collide on the same。
Don't collide on the same port。 The answer here is that each socket。
but is the answer each socket connection has a unique something of those five parts right。
And it's going to be from the client side。 There's a unique IP address and port on the client side server will have the same IP address in port。
And then there's the TCP IP。 And so the answer to why they don't collide is every connection has something that's unique about it。
And therefore when we hand that socket into the thread that thread is talking to a socket that's unique and it's connection is unique。
Okay。 All right, good。 So, so the socket on the server side sees the unique client IP or, or port。
It could be multiple from the same IP but different ports。 Okay, so, Friday was drop deadline。
If you forgot to drop and you're out in cyberspace or you have or probably you guys didn't want to drop。
which is great。 But if you know somebody who forgot to drop。
You need to tell them that they've got a mess they got to clean up。 I already had somebody tell me。
I didn't think I was going to get into the class。 Now what I was like well it's out of my hands you got to talk to your department。
The recommendation again is read assigned readings before lecture。
The group sign up should have happened。 And for those of you that didn't quite have four members in your group I think we'll try to find you other ones。
The goal here is for。 Now, shall count to four, you know, not to three or five。 Okay。
before you launch your group。 And we're going to try to make sure you all have the same TA in your group。
Mid term one conflicts, we're going to handle them。 I say next。
probably starting this week so if you have a conflict with mid turn one。
which you now know when it is and what time it is on the schedule。 Please let us know。
We're going to try to figure this out。 And then lastly, let's see。 Any questions on this so far。
Yeah。 I don't think so。 I think they're still working on them。
So sections are all on Friday now we've managed to get the rooms to be that way。
And so we have a day or so, but we'll let you know, but you should be。
you should be attending your regular sections。
I see that I've got, I missed the, I missed the animation on this so we're back in person。
So there's good chance that I'll be in VLSB 2050。 Tuesdays and Thursdays。
Hello to everybody who's actually out there。 And we'll try to get secret assume working we did that。
May take a couple of tries get it right。 I succeeded。 Etc。 So start planning。
On how to collaborate with your group。 I think we're going to have even an info session on this because there's nothing worse than dysfunction in a group。
Okay, and I won't ask you to raise your hands, but I'm sure most of you out here have had a dysfunctional group one way or another。
So plan early。 Okay, and plan to meet regularly。 Okay。 Coffee。 Whatever your favorite place。 Okay。
meet over, you know, food。 Virtual interactions, you know that you're probably going to get sick during the term or something I hope not。
You may have a member who's being a little bit careful with how much they interact。
you may need to have virtual interaction so figure out how that works。 And I。
my biggest recommendation if you're interacting virtually, hopefully, occasionally not all the time。
camera。 Most important thing is camera and a camera that you turn on。 Okay。
It's very important and try to meet multiple times a week。 All right, very important。 Okay。
And we'll, we'll say a lot more about making functional groups。
The other thing camera is I wanted to do a little bit of interesting here so I try to do computers in the news。
You've all, I hate to say it probably are too young to have remembered why 2k the problem。
But now we got why 2k to to the problem。 I don't know if you've been aware of this, but。 Yeah。
so if you write your date in the form y y m m d d h m m that's 10 digits。
And if it happens to have a year that's to to。 And it's a 32 bit number to to is got the hybrid set。
And if your designer of the software wasn't very clever and they made it a signed integer。
Then suddenly when we went to January 1。 That's not too long ago。
All of the dates were negative and a bunch of stuff broke。 Okay。 So there are a bunch of Honda cars。
Every time they turn them on it resets to two o'clock January 1, 2002。 Now my car。
which is also Honda is a little bit later than that。
And it's just thinks it's in daylight savings time。 And so I got in the car coming over here today。
And almost had a heart attack when I looked at the time and realized I thought I was an hour late。
But you'll also read about the fact that Microsoft exchange failed。
Cued up a bunch of messages that didn't go out sonic wall。
which is a firewall producer had a bunch of email security and firewall issues。 So。
welcome to the 22s。 Okay, we have why 2k to two。 And it's yeah。
It kind of emphasizes the point which I made a couple of times in lecture already。
But always think about the error cases。 Okay。 Now what's problem about this is they probably thought they tested it because it worked great as long as the year is no greater than 2021。
This works fine。 But yeah。 Be very careful about using signed integers in places where you're really doing something。
There's nothing signed about this right this is absolutely unsigned。 All right。
Anybody run into why to take to two。 You guys about lucky。 Me。
I just have to hop in the car and try not to have a heart attack every time I get in there drives me nuts。
So yeah, the question in here in the chat is they use the sign type for date and time and the answer is yes。
Okay。 There's one last question in the chat here from the last。
The set of questions we're asking which is how are client sockets protected to get to each other if they're in the same process。
The answer is the code has got to be hardened and you got to be very careful that it's written correctly or has proper sandboxing and other techniques。
So we may talk about that later in the term。 Okay。
So if you remember the process control block is a structure inside the kernel that represents a process and it's got a lot of stuff。
Like what's the state what number is the process ID, where's the program counter a bunch of stuff。
It's got things that the scheduler will be able to use to schedule the process and let it run。 Okay。
and the scheduler which we're going to talk about。
In a couple of later lectures but we're going to get close to it today even is going to give out pieces of CPU to different processes and ultimately to different threads。
But that process control block serves as the root of describing the process。 Okay。
And one of the things that's in here and I've heard questions about on piazza a bunch is well。
What does it mean for there to be a list of open files in the process。 Okay。
and so let's look here for a moment。 Remember that a file descriptors an integer。
And that integer inside the kernel has to go to something other than an integer it actually goes to something called a file description。
which has a bunch of interesting stuff。 So for a for an actual file it, for instance。
has something called an i node which we'll get later in the term describing how the file is stored on the disk。
And it's got like what's the offset how many bytes have you read so far from the beginning。 Okay。
so what that translates to is if we have the process structure。
There's kind of part of the processes in user space part of it's in kernel space。
So the user space portion is going to be things like thread registers and some of the address space contents。
And then the kernel among other things are the file descriptors。 Okay。
and if remember that the file descriptors and integer outside the kernel。
but there's a table inside so if we do open food。text。 There's a file description here。
which says hi, this is file food。text where position zero。
And you know what are the i node on the disk, you know describing the file and everything。
And so really what happens that comes back from that open is a integer three。
And what that means is the process only knows about three。
but the kernel can map three to a pointer to the actual file description。 Okay。
so that thing that's an integer is really a pointer to this thing in the kernel。 Okay, and then。
you know also there's the special zero one and two。
which we'll show you in a second which represent the console input and output。
But certainly three here is mapping to this file and so now if we read a hundred bytes from file descriptor three at the current at the user level。
that's going to go ahead and change things inside the kernel and by the way it's going to move its position to be a hundred。
For instance, okay, because we just read a hundred bytes。
And so the next time we would read we would start a position 100。 Okay。
and then we'll close and it'll go away。 Okay, poof。 Right, so that's kind of the。
that's the lifetime of a file that you open。 And if this is not an actual file and if it's instead like a socket。
but we're pointing out here is a socket structure with a buffer and stuff。 Okay, same idea。 Now。
the idea of what happens about fork came up and I just wanted to show you。
So here's the thing I just showed you。 If we execute fork。
we're going to get duplication of a lot of the stuff that was there。 Although。
if this has got more than one thread only one of them gets generated with fork。
So that's a little tricky part, but it's file descriptor table is going to have the number three and it's going to point at that same thing。
Okay, and so now both parent and child can be reading from descriptor three and they will be sharing that underlying file description description description description。
Okay, question。 Yes。 Yes, so current location here is shared。 Yep。 Okay, now。
so the file descriptors copied file description is alias。 All right。 If you look here, for instance。
if we read a hundred from the buffer, then this is going to go to 200。
but then this guy could read 100。 And it's going to go to 300。
And so the two of them are trading off who's getting data from the file。
So this is one way you could do parallelism is you open a file, and you do。
44444 fork and each of them kind of reads a chunk of stuff out of the file and processes for a while。
Okay, so that's a pretty simple pattern。 Okay。 Now, so the file descriptors copied。
but the file description is shared。 Okay, so when I close, notice this guy closed。
but this one's still open。 Okay, and that should suggest something that's kind of like that pattern we looked at in our web server right where the child closed the parent server socket because it didn't need anymore and the parent closed the child's connection socket because it didn't need that。
Okay, everybody clear on that。 Good。 All right。 Now, everything's a file。
And so this all works for everything。 But I wanted to last a lot。
least show you something that you'll get to play with when you're doing with your shell。
So the other file descriptors that are always open for every new process is zero one and two file descriptors zero is input file descriptor one is regular output file descriptor two is error output。
And if you notice, they're connected to the terminal typically。
But using that little pipe symbol or, or greater than or less than symbols will actually change these to point at files instead。
And the in the code can think it's writing to the screen but really it's going to a file because your shell has altered these that's something you get to play with。
Okay。 And of course, if I fork a child process here。
notice that all the file descriptors are pointing at the same place。 So if the shell is the parent。
and it makes a new child process if it doesn't do anything。
the input and output of that child process is just going to go to the same console。
Kind of like the way it does right that's what you're used to。 Okay。
I'm going to leave this because I don't want to do too long on this but any other questions。 Okay。
Yeah。 Yes, well, you need to close it in both of them eventually or the files still open underneath the covers。
Now if the process exits, it'll close it。 Automatically。
but typically the pattern is that after the fork you close the things that you don't need anymore。
Okay, good。 Yeah。 Yes, there would be a different file description for it。 Yes。
That's a good question。 Okay。 All right。 So。 So, the other things that are shared sockets are shared。
Pipes are shared。 We'll talk about pipes another time。
But what I'd like to do is let's briefly take, I'm going to try to do this。
What throughout the term is let's briefly give you guys about five minute rest。 You can stand up。
walk around, come back and we'll pick up a slightly new topic。 Okay。 Take a breath。
And then that lay had folks out there。 I'll just pause the recording for a little bit as well。 Okay。
settle down everybody。 So, um。 See if I can get this to go。 Hello。 Okay, so let's now。
Go past ton of the ideas of interface and let's get。
Let's go into the kernel a bit and actually ask how do we actually multiplex。
Two processes to make it look like they're both running, even if we only have one CPU。 Okay。
so that's going to be our goal。 And so we're going to need to figure out how to give that one CPU or core out。
In some way to the different processes, so that we can make it be concurrent at least so it looks like they're both running at the same time。
Okay。 And again, this is a function of the scheduler to decide which one gets to run for how long。
Okay, and。 But we're going to look at instead is the scheduler has a lot of policies we're going to look at how do we keep things organized in a way that will allow us to hand out。
CPU cycles to different processes cleanly, and then we'll think about different ways of scheduling later。
So what is the distinction I'm making here the distinction is scheduling is a policy decision。
I'm going to give that one 60% of the CPU cycles miss 140。 That's a policy decision。
We're going to talk about under the covers the multiplexing decision about how to take that one CPU resource and share it。
Okay。 That's our current task。
And of course, we've already started talking about this and so the idea here you've seen this figure。
where process zero is running process one is running and there's operating system code。
And in principle, the idea here is that you know process zero is running in user mode。
And then we hit a switch point and we go into the kernel and we save a bunch of state for a process zero into the。
And then we go back into the kernel and do the same thing in reverse and by that doing that we go back and forth and one zero runs for a while。
then one runs for a while, then zero runs for a while, then zero runs for a while。
then zero runs for a while, then one runs for a while, then one runs for a while, and it looks like。
like we're running simultaneously。 If we do that fast enough。 Okay。 Okay。
One CPU one for and notice the stuff in red, red is there for two reasons。
And I apologize for those of you that might be color blank as red green combinations are not good。
And then in the middle is about overhead right now that's purely overhead。
because what when it's executing in the middle, it's not getting anything done on the outside。
So we want to make sure when we're doing this that whatever's going on inside here is quick。
or at least the small fraction of the total stuff going on the outside。
So I'm going to give you a number I'm going to say like 10% is a good number to keep in your mind。
If I got more than 10% overhead, it's probably bad。 Okay。
it's starting to get into what's called thrashing where we're really wasting more time doing the overhead thing than we are doing the actual。
process。 Okay。 Now, you know, this is an OS class and we all love OS is in theory what's in the middle here is the cool stuff but I will tell you。
we're trying to make this OS for somebody to get something done which means what's on the outside is what they're actually trying to get done not spend all the fun time in the middle。
All right。 So, what's the life cycle of a process from this standpoint so we start out by making a brand new process structure。
And we admit it to what's called the ready queue, and it'll be in a ready state。
which is it's waiting to run but it's not currently running so it's like it's process one in that diagram when process zero is running。
Okay, it's ready to run but it can't actually run。 And what will happen is when it's time comes。
it'll get to run。 Okay, so running in this instance is actually using the CPU。
Ready is ready to run but not using the CPU and then an interrupt like a timer interrupt will come around。
And we'll go back to the ready queue so what happens is you get the picture we can go back and forth a bunch of times。
And so we go from ready to actually running and back again。
And notice that I have an interrupt up there。 This is one of the key ways we're going to talk about for instance。
How to get the CPU back from a process。 Okay, so that process is busy doing my favorite thing。
which is computing the last digit of pie。 And it's not going to return the CPU because it's got a long time to compute right。
And so what we have to have is something to reach in grab the CPU away so that we can give time over to the other process。
And a good example of that is going to be for instance a timer interrupt。 Okay。
and so this grabbing the CPU away from somebody who's running has to be some mechanism and we'll talk about many mechanisms but for today think about the timer。
Okay, it's a great one。 So what happens here as I'm running is sometimes I may actually try to do I oh。
So when I'm doing I oh it might be an example of I'm going to a desk or I'm trying to read from a socket but there's nothing there yet that can take a long time。
Okay, and so what should I do when I'm about to do a long running operation。
Well clearly I'm waiting。 But what else should the OS do。 What else。 Right。
We do not like to waste cycles in this class so when I'm waiting on IO I want to arrange things so that the other thing that might be ready can run。
So this life cycle here is just showing you what one processes view of the world is there's another one who now gets to run because the first one is waiting。
Okay, and pretty much you can think of read ready waiting。
Certainly our weight states the ready weight state is one that says I'm definitely ready to go the waiting state is any of a number of possibilities where whatever I need to continue isn't ready yet。
So I asked the disc for something it's not ready yet。
I went for some IO it's not ready yet I'm waiting for a keyboard stroke it's not ready yet。 Okay。
Eventually, when the data comes back and I'm ready to go I just get put on the ready queue and now the scheduler there's getting that word there gets a chance to now make me run a bowl again。
Okay。 And then eventually, I'll be done and I'll exit。
And there's this idea of a terminated state which is where I'm done running。
I've executed exit everybody remember exit。 And, but my parent hasn't gotten my exit code yet and so my process is just sitting there。
Not useful for running anymore but holding an exit code and not the allocated。 Okay。
and this is sometimes called a zombie process。 Okay。
Attack of the zombie killer exited processes right。
So that's kind of the final state there and when when the final weight happens or when the parents weight happens or the grandparents wait or somebody's weight happens then that terminated thing can be totally deallocated。
Okay。 Now any questions。 So, this idea of scheduling is really can be looked at like this。
where the process control blocks kind of move from Q to Q。
And occasionally get in the ready queue and get to do CPU。
But then after their say their time slice expires because they've run too long they go back to the ready queue。
So, where if they do IO requests, they get put in an IO queue and eventually get back to the ready queue。
or here's a good one fork。 Will actually take one process turn it into two。
which might be on the ready queue。 Okay。 And remember, remember there's only one process。
So that one, you know, when a process executes fork and now there's two a parent and child。
and they're both on the ready queue only one of them's running at any given time。
So it isn't the case that when you generate a new child process with fork that suddenly you've got things running in parallel。
They're running concurrently。 Okay, meaning that they're alternating between each other。
They only look like they're running in parallel。 So。
that's not too long from now we'll start admitting the existence of multi core and all that sort of stuff and then they could actually be running at the same time but for this。
these lectures, they're not。 Okay, any questions。 And by the way。
this whole idea of how do you decide which one gets pulled off the ready queue。
That's a whole series of lectures of interest on scheduling。 So。
that's something to look forward to a little bit later in the term。 I'll pause any other questions。
Nothing in net land there。 Oh yeah, go ahead。 So, so first of all。
remember from a couple of lectures ago interrupts are actually from the outside。
So the answer is that a timer would go off and it would generate a timer interrupt, for instance。
that would cause the kernel to get entered。 Okay, that's an easy one。 Other ones might be。
for instance, if a network packet comes in off the network that'll generate an interrupt。
And it might temporarily stop things running and then continue it again。
There's lots of types of interrupts。 Okay。 Good。 Any other questions。 All right, so。
so the ready queue and various other IO device cues sort of in a process is not running the PCB is on some scheduler queue。
And there's a separate queue typically for every device or signal or condition。
And so you can think of these as lots of cues the ready queue means I'm runnable。
But then there might be, you know, the USB controller or disk zero or just to network, etc。
These are all places I can wait。 And so you can think of these cues as holding a linked list of things that are on that queue。
And so in this case, by the way, that little ground symbol。 Yes, I was electrical engineer。
you'll have to forgive me for that。 That means no, right。 That's the end of the queue。 So。
if you notice, this thing, this ready queue has got three things in the linked list。
which means that there are three things ready to run。
The disk here has a couple of things that are waiting for disk packets or for things to come back off the disk。
Here's an example of somebody waiting on a socket trying to get something off the network。 Okay。
and so those PCBs have to be stored in a queue somewhere。 Okay。 Now。
So up till now we've been talking about the idea of a process as, you know。
it's running or it's not running。 Okay, but we made a little finer distinction than that last lecture lecture before that。
which is that a process a modern process really has an address space。 And one or more threads。 Okay。
so we were kind of talking simply just now that the process had one thread but in fact it could have multiple threads。
All right。 And so multi threading is this idea typically of a single program with lots of different threads in it。
Running concurrently。 Okay, and why do we separate threads from processes? Well。
the process is the whole environment plus the things executing in it。 So it's like an。
it's an environment。 And the threads are these individual things that are getting CPU cycles and have a program counter and a staff and represent independent computations going on inside the process。
Okay, and there could be more than one。 Or for the simplest way of thinking there is only one per process that's also okay。
Okay, are there any questions on that。 I think we gave this picture right so this is showing the idea of a single threaded in a multi threaded process。
Single meaning single threaded meaning one thread multi thread meaning multiple threads right。
Every thread has to have a place in memory to store its registers when it's not running at a stack。
Okay, and so that means that when you have multiple threads you have multiple stacks and register storage。
But they all share the same block of code, the heap and their file descriptors。 Okay。
So the address space is encapsulating the protection portion of this。
Why have multiple threads for address space。 Yeah。
you might be each computing something or each some of these threads might be handling IO。
like waiting for keys to be pressed while other ones are computing and drawing things of on the screen。
So typical games are exactly that way you have threads that are drawing things and moving things around。
You have other threads that are waiting for input from the controllers and other things。 Okay。
so you, so it's a very easy way of programming where you have multiple threads in a single process and so that's。
you'll get used to that。 By the end of this term threads will be easy for you to think about I hope。
Okay。 How many people here have ever programmed games and had multiple threads going。
Do you have any game creators in here, or you're not going to admit it。
How many game players all of you guys。 Okay, so, so what is the state of a thread。 Okay。
so it's the state shared by all threads in a process or address space is sort of the content of memory。
global heap, etc。 So in those states, the state private each thread is a thread control block。
And so each process control block has a linked list of bread control blocks inside it, right。
one or more。 Okay, and that's going to have CPU registers execution stack, etc。
And what I wanted to say a little bit is remind you a bit about what stacks are but here's another view of the shared versus the per thread state。
Okay, so here's an example of things that are shared with all the threads, the heat。
the global variables, the code, and then the per thread state are things like each thread control block and then stack and registers and etc。
Okay。 Now there's a question are there other ways to create new processes other than fork。
And the answer is primarily fork is the biggest way to create new processes。 Okay。
there are some simpler versions that Linux has。 But let's stick with that for now。 And so。
let's remind you 61 C alert。 Okay, you're being reminded of what, what's inside a stack。 Okay。
and so how many people remember stacks from way back in 61 C。 Okay。 Yes。 Go ahead。 Yep。 Right。
and why are stacks important, they hold the local variables, why are they important。 Yeah。
so if you think about recursion, what's our favorite recursive procedure Fibonacci right how many people remember Fibonacci isn't that great。
So, let's look at what happens here in a typical scenario here where you have a of one。 And it's。
is going to call the procedure, and it's going to call it with the argument one in the temporary bowl。
And so what happens is we get a stack frame allocated。
There's a stack pointer we push on there the fact that a stack frame has temp equal to one。
And when we return, we're going to go to exit, because if you look here when we call day of one。
the thing after our call is an exit。 So what went on the stack there is two things the local variables and what to do on return。
Okay, and now let's look at what's inside of a a says is temp less than two。 Yep。
So in that case it's going to call be。 So when we call be。
we're actually going to make a new stack frame for be but be doesn't have any local variables。
And so all that really happens is it reminds it puts us on the stack that when it returns from be。
it's going to go to a, which is here plus two, which is kind of。
of think of this as an instruction address, which is what happens after be done returning。 Okay。
so what gets pushed on the stack is both the local variables and the return。 Okay。
and then here when we hit see。 Okay, we're going to make a new stack frame for see。
And again the return in this case is going to be be plus one。 Okay。
and then finally notice that see is going to call a up to again。
So now we're calling a but in this case temp is equal to two so notice on this stack frame。
So we actually have two instances of a simultaneously on there one is a little further in the stack。
They represent the time ordering because stack is going down here and so things that are deeper got called later。
Okay, and then is temp less than two。 No, we're going to print out something so we're going to print out the number two。
Okay, and then notice that when we get up done with print。
we're going to exit a and so we look here where do we go after we exit a。
Well in that case we're going to return to wherever C plus one is。 Okay, which is down here。
And now it's because it was on the stack。 And now we're returning from see that's going to take us to be plus one。
And then we're going to return here which is going to take us to a plus two where we print。
And then finally, we're going to exit。 So really, if you think about the stack of this call of a of one it's got all of these things on the stack and eventually we're all done。
Okay。 Does that remind you of what a stack is questions。
Can you see why Fibonacci works right because every time you call recursively it goes down in the return。
So, I've been asked like the easiest recursive thing to teach but I've always thought it will silly。
Yeah, go ahead。 So this, so a here is we entered the a procedure。
Let's go back in fact just for the heck of it here。 We entered the a procedure。
So this is a stack frame。 And oftentimes on a stack frame it remembers the return place。 Okay。
or it depends on it depends on whether it's stored in the previous or not but it needs to be here。
In this example it's here because when we return from a we need to know where to go back to。
And so this stack frame of a has not only the temp is equal to one but it also remembers where to return to when it's done。
That help。 Okay。 So I would say return, make sure to look back in 61 C for stacks because you've already encountered stacks right if you've been doing your various assignments you've already run into them。
So let's talk about some motivation for having multiple threads。
And then I'm going to leave you with a thought process。 And so let's look at here。
Here's a program that in C main is typically the very entry point that you get to worry about。
It's not really there's something a little on the outside of main but for now we'll think about main is the entry point。
And here's a case where we compute pie and store the result in。 And then we print the class list。
So what's the behavior here。 3。1415。 Nice。 38。 38。 2795。 284。 979。 38。 979。 28。 979。 1095。 1095。
1095。 1095。 You never get the class list right so this seems like a great example。 Well。
you probably would never quite do this but it's a great example of why you might want two things happening at once right or at least concurrency so that they're interlead。
Right, because here we're never going to get there so you could。 You could have a thread。 Okay。
and there's lots of different types of threads so you could have essentially a thread for some sort and in this case。
you're not only computing pie but you're also printing the class list。
At the same time and again it's in quotes because there's only one CPU in our instances so far right so it's not really at the same time。
But it looks like it。 Okay, and the switching time might be 10 or 100 milliseconds so it's definitely quickly interleaving。
So what is thread for do it's got to start an independent thread running the given procedure。 Okay。
now, we're getting back to this picture that we had kind of maybe the second day of this interleaving between magenta and cyan which represents the two different virtual CPUs。
Okay, and so our different threads get interleaved as if they're running at the same time。 Okay。
and I don't want to over stress this but that makes sense to everybody。
And we'll be a little clearer about concurrency versus parallelism as we get further on but for now。
really, you know, right now they're just interleaving there's only one CPU。
Somebody asked in the chat is this the holding problem revisited no this just won't hold。
There's no problem here we know exactly what won't happen so。
So what's the memory footprint of this right so if we stop this program and looked at it。
we'd say two sets of CPU registers because we have to be able to alternate and keep the other threads going right two sets of stacks in the same address space。
So questions we might ask is how do we position stacks relative to each other。 Well。
they're in the same address space because threads are sharing the same address space。
So that could be an issue as well as how much space we allow here so threads are not in the same process are not protected from each other。
They can screw each other up。 But what's great is they can share the same memory so that and they're low overhead so this is good versus bad right it's good because they share the same memory they're low overhead to create to give you good concurrency。
They don't they're not protected against each other。
And somebody give me a good reason for why that might be okay for them to not be protected against each other。
Why not yeah。 So I think that sure it's easy to share I think the way you should think of this is they're written by one person who's trying to get something done and if they screw each other up that's called a bug。
Okay that's not。 That's not a malicious use of threads it's you screwing yourself up right so that's a bug。
Okay so think of that that way threads are something that you've created to help you with your program get it's execution processes give you a protected space so the two of them can't screw each other up。
You could create a process for your own work if you're not sure whether something is correct that's a way of sandboxing or protecting a piece of your code against the other code。
Okay we'll look a lot at this later okay, but what maximum size should we choose what if threads violate this while they override each other stacks and things get really broken。
How do you catch violations。 Well once we start getting virtual memory in here we can do something like if this stack grows into a certain set of guard pages then we get a fault that we recognize and we hit the debugger saying well one of your threads is growing too much maybe there's a bug。
So we can protect things against each other as long as we're not trying to maliciously screw our own threads up okay but that's probably not a good plan anyway。
Okay doctor it hurts when I do this。 What do we say。 Don't do this right。 Okay, so there's a。
Pausex。 Okay, which is the standard of a lot of Unix。
I owe interfaces has a set of thread creation things which you're going to get a chance to play with if you haven't already。
So there's a p thread create p thread exit p thread yield where you voluntarily give some CPU up p thread join。
So these are the equivalent of fork but for threads。
And you can do man p thread so man is your friend。 Okay, at least when you're typing it a prompt。
Okay。 Now, the dispatch loop of a typical operating system so let's leave you with a couple of thoughts here。
So one says we're going to in a loop we run a thread。 We choose the next thread。
We save the current thread state。 We get new thread state and we loop this forever。
There is some argument to be made that that's all that an operating system does right there。 Okay。
so we're done we'll have our final next week。 This is an infinite loop。
There's all that an operating system does okay。 And notice the running the thread is basically the operating system giving control over to user level。
Then we yank it back somehow。 In we're up whatever then we choose the next one that's scheduling。
And then we're going to do some saving and restoring to make sure that the two threads are kept separate from each other and now we're good。
Okay。 Now, should we ever exit this loop well we exit this loop for various crashes。
You could argue that maybe IO or interrupts exit the loop。 So how do I run the thread。
I loaded states I load the environment I jumped to the PC。 How does the dispatcher get control back。
Well lots of ways either the thread gives it up voluntarily or the thread gets preempted。 Okay。
and so next time I'm going to look more about different ways of getting things back and then we're going to look at how did the stacks。
Actually look when I'm going back and forth between two threads。 Okay。
because they're both trying to run and it's going to be interesting。 Okay, so in conclusion。
We talked about the socket being an abstraction of network IO queue。
An inter-procedural communication mechanism。 We talked about one or more threads plus an address space。
And we talked about concurrency happening with multiplexing CPU time。
And we're going to dive next time we're going to dive into this in much more detail about how does the CPU actually。
How does the OS collaborate with the CPU to multiplex threads。 All right。
you guys have a great rest of your evening and we'll see you next time。 Okay。 (silence)。
P6:Lecture 6: Synchronization 1 Concurrency and Mutual Exclusion - RubatoTheEmber - BV1L541117gr
Okay everybody, welcome back。
Let's see, is that loud enough? Can you hear me? All right。
Welcome back to the second in person lecture。 This is going to be a habit, I hope。 So this is good。
You know, last time we were talking about some basic ideas in concurrency and we were starting to really talk about how threads can be implemented and so we're going to tackle that today and hopefully by the end of the day。
you'll not only have a better idea how we get threads implemented but you might。
actually have a better idea about why we want threads in the first place so, So。
let's see what's going on here and will my slides actually go forward。 Okay。
just if you remember last time, first of all, just to remind you。
we were talking about setting up sockets for communication。
And the key thing there was that you'd set up a server socket to listen on a particular IP address and port。
The client would make a request a connection of that IP address and port by telling its own IP address and port。
And, and then a connection would be made and a brand new socket gets created for every new connection and every new connection is unique。
Five to pull of source address destination address source port number destination port number and protocol。
And there has to be a unique port for every connection and typically that happens because the client randomly selects one from a range。
Okay。 All right。 And then well known ports are things like 80 or 443 or 25。
They're typically things from zero to 103。 So, good。 Questions。 Now, of course。
we showed you this basic web browser。 And this one was one that's using processes so this listen gets executed once and that's sort of putting the ear out。
And from that point on in a loop, we accept the next connection this is blocking until there's somebody to accept。
We fork a new process。 This sort of child code talks to the connection。
And notice that we close the server socket since we're not using it。
And but we got a copy of it because of fork。 And then when we're done we close our own。
The parent meanwhile closes its the connection sockets since the child is going to handle that。
And then it we're not going to wait in version three of the protocol we just go back and we accept another one。
And we can keep going。 And that will make sure that there can be as many parallel connections as people who want them。
All right。 Questions。
All right。 Good。 So, now we're going to get into today's topic。
So we started by talking about each process is described by a process control block。 And, you know。
this is a chunk of every inside the kernel。 It's not accessible at user level。
And among other things, it's got the state。 To restore that process。
If there's only one thread that state could actually be embedded inside the process control blocks of the program counter registers。
etc。 As we'll see later in the lecture, if you have a process with more than one thread。
then typically you've got a linked list of these thread control blocks。
That describe each thread that's running。 There's a scheduler that we're going to spend a lot more time on during our scheduling discussions in a few weeks。
And then after the midterm that decides which process gets the CPU at which time。
And then the other thing to keep in mind is that a scheduler can also hand out non CPU resources like access to the network or access to disk I。
So we're going to pursue this multiplexing thing a lot more today。 And so if you remember。
this is also a picture from last time that if we have two processes and they only have a single thread for now。
Process zero process one process zero runs for a little while。
And at some point we get an interrupt, we switch into the operating system where we save out all the process zero state。
reload process one state, and then go back to user level to run process one。
And then it runs for a little while and then we get another interrupt come back into the kernel save process one store process zero and continue。
And so we go back and forth。 And then an effective this is that we've got two processes and they look like they're running at the same time。
Okay。 Now we had some good kind of discussion in Piazza about well, if you only have one CPU。
why do you want to do this, because you're just, you know。
you can never have things running at the same time。 You know。
process zero is running when process one isn't invites versus so why bother with this。
Are there good reasons for that。 What do you think。 Yeah。 So the best。
that's a great first thing to think about is if this process is running and it blocks on IO。
because it's waiting for the disk。 This one can run。
So just by making sure that we have multiple processes there。
then we have the potential that we can have concurrency going where we're actually letting we're overlapping IO and computing。
Now, what we're going to get even further as we go into this process in the term。
is you're going to see that this idea of concurrency isn't just the simple one of overlapping IO and computation it's also going to allow us to assign a thread。
To do something very simple like pull in all the input from the keyboard and do something with it and we can totally let it do its thing。
Well, we go off and we draw aliens on the screen or whatever our game might be doing。
And those two things can be separated from each other, and we'll run perfectly well。
even if there's one CPU。 Okay, so one CPU is not a good reason for avoiding multiple processes or multiple threats。
Okay。 Everybody good with me so far。 And by the way, until about 2002 one CPU was the norm。
and then we started getting multi for so multi core is a fairly recent thing in the grand scheme of things。
Although perhaps all of your life was multi core。 So the other things that are kind of interesting which we're not going to do too much with today we'll get a little is these crossover points where we go kind of from user to kernel and vice versa。
I'm going to show you that pretty explicitly with respect to stacks。
but I think next time I'm actually going to show you in Pintos or a real operating system。
What is that transition really look like, and we'll actually show you more details on that。
And then the last thing was what's the process look like it starts when we generate a new one with fork。
it gets put on the ready queue which really means that it's runable。
And then we can basically go back and forth between things that are on the ready queue and the thing that's running so right now in this next several lectures assume there's only one CPU or core。
So there can only be one thing in the running circle, all the rest of them are on the ready queue。
Okay, and it's going to be up to the scheduler to pick the next one off the ready queue put it on the run queue and then they switch back and forth。
Okay, and so you can have many things on the ready queue, but only one thing running。 All right。
How many people have actually looked at the task manager or done a PSA UX or looked at whatever to see what's running right you look there's many。
many things right hundreds。 And so those are all mostly sleeping somewhere。 So。
and they're not all running because if all those hundreds were running you'd be in trouble。
And so notice that every now and then something that's running needs to do I oh and then it gets put on a weight queue。
And the most of the things that are in the task manager are waiting。
And then when the date when the I oh comes back then we're back on the ready queue and we can get some more cycles and then eventually we exit。
And we go into a terminated state until we can give our exit code to our parent or grandparent and then we are done。
Okay。 Now, the thing to keep in mind here is this notion that we can have a process is a container address space with one or more threads in it。
This on the left, the single threaded process is kind of what we used to call a traditional process or a heavy weight process。
And today, most operating support system support the thing on the on the right。
which is basically many threads in one process。 Okay。
and each one of those threads of course has its own register and stack storage。
because well it's a full thread of execution, it needs to know where it's executing from it needs to know what it's stack pointer is it needs to know what the registers are。
And so we have to have space for that for each of the threads。
On the other hand the threads are in the same address space so they're sharing all the same code the same data the same files。
Okay。 Good。 Now that's all kind of repeating for last time so I'm going to pause here and see if we have any other questions before we go forward down。
Yeah。 So, in the concept of a child, the parent with threads so threads are typically a little more flat。
Okay, so the threads are kind of in the process。 There。
there are occasionally some operating systems that give you a little bit more detail with that but for now let's think of it as a flat thing inside the process。
Okay。 So, of course, we've got this view as well which is sort of shared state for the threads or the heap and the global variables in the code。
and the per thread state is, as I mentioned stack and registers and metadata and etc。 Okay。 Now。
So let's say, so the core of concurrency, the core of the operating system is this。 Okay。
what we've got here is looping, where we run a thread。 We choose the next thread, we save the state。
we load the state and we loop。 Okay, and we do this over and over again。
And as I kind of joked at the very end of lecture last time, there's the operating system。
you got it all let's take our final next week we're done。 Okay。 This is an infinite loop。 Okay。 Now。
You could argue this is all the operating system does。 Okay, and I, you know。
I was able to summarize it for you in these few lines。 Now, can somebody tell me, you know。
should we ever exit this loop and why would we ever exit this loop。 Shutting down the system。 Good。
Where else, why else。 An exception like a big one。 Blue screen, the blue screen of death, right。
That's another example。 Now, the other thing that's a little more subtle here is you might think。
why I'm busy running, I might actually exit the run to go off and do some。
I owe or some interrupt handling and then come back。 Okay。
so that's another way of thinking of getting out of the loop。 So yes。
it's nice to think that the OS is just this but we perhaps we should have a few more details。
What do you think。 Sounds like a good idea。 Let's。
let's hold off the final for forever and I'll not have a final so。
So let's talk about this running of the thread here right so let's consider the first portion。
And what does that mean so we got to load the state of the thread into registers of the physical CPU so the thread。
when it's not running is sitting in memory。 So it's a thread control block。 It, you know。
where it left off because you've got its PC, you know what the stack pointer was and so on。
But in order to actually run it you got to load it back into the CPU。 And。
and then load the virtual memory space if we're changing processes, and then you jump to the PC。
And now you're running。 So, what is a couple of things here also that the fact that we have to load up the virtual memory space for a thread。
If we're, if we're changing processes means we must be running this loop that I showed you in at kernel mode in order to be able to actually modify the threads。
But then when we go to run we're going to jump back to user mode so there's some transitions going on here right that was the red to green you saw in a previous slide。
So how do we make sure, for instance, that this dispatcher loop here will only run the thread for a little while。
and then get control back to choose the next thread and run the next thread。 So that's a。
an important question。 I mean, how do we do that。 Anybody。 Time are interrupt, right。 Now。
how many of you have ever gone to like a computer museum。 I don't know。
maybe your parents room you found Macintosh from, you know, the 80s or something。
Did you know that it didn't used to be the case that operating systems were that hardened。
And so you'd load up a bunch of things and they'd be on the screen。 And what you'd see is。
well that's great until one of them crash and then the whole system crash so one frozen application would actually freeze the whole system and that's。
it's a precisely explained here。 And by the fact that it would run a thread that would go into an infinite loop and would never exit back into the operating system and so there was no way to take control。
Okay。 So that timer, which we'll talk a lot about today is something we've got to be careful with。
Okay, we've got to somehow make sure it happens properly so that we don't lock up because of a bug。
So, so one of the ways, however, is that we're not going to rely on the timer let's talk about how that Macintosh might have worked。
And that's what we call an internal event where the process is running for a while。
And then what it says is yield。 And that says okay fine operating system what somebody else run。
So those early operating systems relied on this notion of yielding。
And so it was a process whereby an application that's running would periodically say okay let somebody else run okay let somebody else run。
And so whether it ran properly and multiplex properly dependent crucially on did you put enough yields in there。
And did you ever hit a yield。 Okay, and those are what we call internal events。
External events is this example of the timer interrupt。 Okay。
so let's talk about the internal ones for a moment。 In addition to explicitly yielding。
you might actually try to do IO like I'm about to read a file。 And in that case。
you're giving control back to the operating system and the operating system may make a decision to reschedule somebody else at that time。
Okay, one reason for that of course is that I always a long running process takes a long time。
You might as well let somebody else go。 The other is well。
they've decided to break up what's going on here so I'm going to take that opportunity to switch in somebody else。
Okay。 So, let's say good rule of thumb。 I think I gave you this lecture to ago for how many instructions you lose to access a physical spinning disk。
Anybody remember。 Million so millions of good number。 Okay, plus or minus a few orders of magnitude。
We'll talk a little bit like an astronomer here but, we'll talk about it in instructions。
So that's important to black and I'll and let somebody else run。
So another example might be if this given applications running。
and it's going to wait on a signal from another thread。
It's going to go into the operating system and say, well, I'm。
I can't do anything right now let somebody else run while I wait。
And then yield of course is the thing I just mentioned。 So if you look here。
Here is how we could make a version of compute pie that might actually play well with others。 Right。
So in an infinite loop we compute the next digit, then we yield compute the next digit yield and as long as we keep doing that。
then we know that the other applications in the system could keep running。
even on an old Mac and touch。 And then, somebody see the flaw in this particular。 Statement。 Huh。
No you're computing the next digit so you'll yield。 Yeah。 Yeah, each digit if you。
if you ever I had a, I was a person who liked to come up with ways of computing pie and my got mute so I had many algorithms I looked at。
And the problem is that if you compute each next digit takes longer and longer and longer and longer to get。
And so this would work well for a while but then eventually you know you'd be taking days before you yield it so this is probably not a particularly good example。
But for now we're going to use this to understand more about what goes on with the stacks。
Let me tell you why I mean the stack so if we've got two applications running。
and they're switching between each other。 They both have a stack。
Somehow those stacks have to be coordinated and cleaned up because we're loading and unloading things on the processor so let's see if we can peer into that process just a little bit。
So here we go。 So we're computing pie for a little bit and then we execute yield so what's the cyan color here is going to be user level。
And it, this is something that the user code execute so the user code is executing the next digit of pie。
And yield is going to yield into the operating system with a system call。 Okay, now。
why is it a system call。 That's why yield needs to be a system call。 Yes。 Yeah。 For now。
we're assuming that to go to another thread we actually have to go into the kernel so why do we know about calling the kernel in terms of the stack。
but has to happen with the stack when we call the kernel。 What was that。 Well。
the state needs to be saved and what about the stack。
Do we trust the user to have a good stack pointer when they enter the kernel。
Pretty much any question I say do we trust the user your answer。 Oh, right。 Okay。
So what we have to do is one of the first things that happens on that transition is when we enter the kernel we're going to change to a kernel stack。
which is not necessarily contiguous with the original user stack。 So there's a user stack。
There's a kernel stack。 Those two are married together right now in terms of a process。
a user level process is always going to have both a user stack space and a kernel stack。
And we can treat it kind of like they're together。
because what happens is this transition from user mode to kernel mode。
comes with a process of switching the stacks to a well defined kernel stack that we know is good because we're inside the OS and we've vetted it and it's good。
Okay。 And we're going to basically put。 We're going to put the user state will store on the kernel stack here so that's why it says kernel yield。
So this system call system, this system call is really like a procedure call that crosses into the kernel。
saves the state for return。 So that when we return from here will return back to user level。
But what that really means is we're going to have to set things up and doing a return from trap that's going to then restore the user stack。
Okay, but we're going to be able to think of it like we had user called into the kernel。
and then it's going to run new thread。 And then it's going to call switch。 Okay。
and we're going to have to talk about what switch is in a moment。
But so all we're doing is we computed a digit of pie。
we executed the yield system call that took us into the kernel we switched the stack。
We execute run new thread。 Here's run new thread run new threads a procedure。 What does it do。 Well。
it picks a new thread。 Kind of good given that it's called run new thread right。
And then it does this switch magic。 And the switch magic basically takes the current thread and the new thread and it switches them in the sense that it saves the current thread out to its thread control block and loads from the new threads control block into the hardware。
and registers。 And then we might do a little housekeeping like keeping track of how long the previous task was running and so on so we can get statistics later。
Okay。 And so how does that switch actually happen。 Well。
you got to save anything that the new thread may trash program counter register stack pointer and then maintain isolation for each thread。
And then we don't overlap each other。 And we have to make sure we do that cleanly。 Okay。
So I want to pause here just to make sure I've thrown a bunch of terms that you。
And I've thrown this sort of stack picture which we're going to use a bunch of times in the next hour so I want to see does this all make sense everybody。
Ask your question now。 Yeah。 And that's every process and in fact。
for now every thread is going to have its own kernel stack。
So it's not like there's one kernel stack。 There's a kernel stack per user thread。 Good question。
And that's why we can explicitly think of them as matched up。 It's not hard。
It's like when I switch to a new thread I'm going to get a new kernel stack as well。 Yes。
So you're wondering where the TCB is stored。 So the TCB is stored inside the process control block right each process as a set of threads。
Okay, so that's one answer。 And the different answer which you're going to find when we get to so that's the high level answer when you get to penthouse and you look inside you're going to find that a kernel stack is allocated with a unique page of memory for K。
And a little bit of that page is TCB and the rest of it is kernel stack and those are always allocated together for cleanliness。
Okay。 So for now just think of the TCB as in a separate place in memory and it's linked together for the process control block。
Good。 Other questions。 So, chat here, can we inject code into a running process。
Talking about how the JIT compilation mechanism works。
So the answer is if you're in that process and you're a thread in that process then potentially you can inject code。
And it may be to that a little bit later, another another time。
but usually the code block is explicitly read only unless we're allowing JIT。 So that's a。
Any other questions about this。 Yes。 So the user。 So this is。 So there's two ways to look at this。
The question was how is this kernel thread and user thread related to each other, right。
One answer could be, it's really only one thread because there's only you're either running at user level or kernel level never at the same time。
Okay, so the one answer is they're not two separate threads they're really one thread。 Okay。
Now there is a lot of terminology that even I will use because it's pretty standard talking about when we're running down here in the red portion we're running the kernel thread associated with the user thread。
That's confusing。 But for now just think about every user half is attached to our kernel half and you're running in one or the other。
Keep it at that way for now and we'll get more sophisticated at other times but I think certainly for the next couple of weeks that's the easiest way to think okay。
Good question。 All right, should we move on。 So is everybody get what I mean when we go down here right we showed you this last time but compute pie is running and then it calls yield。
And then it call which goes into the kernel calls kernel yield and then calls run new thread and then call switch。
Okay, and it's somehow we're going to have to turn this into switching to the next process。
And so what are the stacks really look like here and so I'm going to forget pie for a moment let's look at something simpler。
And if you look here's an example where we have a procedure。
A that calls be and then procedure be says while true yield and it just yields in a loop over and over again so this is the ultimate do nothing just yield all the time。
Okay。 And so, suppose you got two threads s and T what do they look like well here's thread s。
And so, so this is a first and a calls be which is that while loop。
be calls yield yield goes into the kernel and switches。 Okay。
Now what are we switching to well this was thread s thread s and T I running we got to get to T。
So here's the magic。 Saves out all of the registers of thread s into the thread control block and loads registers from T from it's control block back into the hardware。
One of those registers is the stack pointer。 So when we switch stack pointers。
we're outside of this figure and we're suddenly inside this one。
Because we switched the stack pointer。 Okay, and because we switched the stack pointer。
We're still in switch but we're in switch。 We're in the stack frame from switch for the last time thread T switched。
And so when we return from switch。 We're going to return to run new thread which will return back to user mode。
which will return from yield which will go to the the wild loop which then we'll call yield。
which then will call red run new thread which then will call switch。
which then will switch the stack pointer and now we're back to the other way and we're going to go back and forth and back and forth。
And now we have two things that are doing nothing but boy they're multiplexing well。 Okay。
Now I'm going to pause here because this this either is obvious or drives people nuts。
I found both cases。 So if you're one of the it's driving me nuts, ask your question。 It's fine。
Good。 Good。 I like that question。 So the question is this this only makes sense if bread tea has already been running。
Right。 But if Freddie hasn't already been running, this doesn't make sense。 Okay。 Yes。 That's true。
So he has to be already ready。 What we're going to do in a few slides is we're going to show you how to set up a brand new tea in a way that will still work with this pattern and it'll look like it's already been running。
But when we switch to it, it'll actually start itself up and look like this。 Okay。
so what I would suggest you guys is for a moment。 Don't worry about how did we get here。
Just worry about what we're doing while we're here and we'll talk about how to start this up in a moment。
but that's a great observation。 Okay, so you only understand that this works。
If thread tea was already running and they're already executed switch。 Okay, good。 Another question。
Yeah, in the back。 Nope, we got to switch all of the。
we got to switch all of the registers because we're going from one thread running to another thread running and it's a virtualization。
where the threads got essentially, you know, all of the user level registers。 Okay。
and so we're going to switch them all and in fact I'm going to show you assembly code for a version of switch in a second。
Good。 Yes, go ahead。 Well, this case, the stacks are going to stay the same size。
because notice that once we look at this code here right this code said be executes while which executes yield。
And then sometime later later we're going to return from yield and go back and call yield again。
So the fact that we've returned from yield means that we've actually returned across this boundary。
which is shrinking the stack。 So we call call call call call call all the way down。
and then eventually, even with switch and then we're going to return return return and back up。
and then we're going to call down and so this particular example。
which is doing nothing useful except being an example。 The stack grows down grows up, you know。
grows down shrinks growth shrinks growth shrinks over and over again and the net effect is there they don't get any bigger than this。
Okay, good question。 Yes。 Ah, well, so the question is why are thread T and executing the same code。
So, think of this as a great example, where the program on disk。
or the application is like a proto process and when you loaded into memory now it's a process and it's running。
you can do that lots of time so there's no reason that we couldn't have compiled this program started on disk and then started S and T as two instances of it。
Okay。 All right。 Other questions。 Okay, so this is kind of magic right。 Let's look at。
So this is often called context switch。 So we're saving。
We're switching to threads so we're switching the current thread and the new thread。
And so what does that mean it means the TCB for the current thread。 We。
we unload the physical registers and store them back, including this stack pointer。
and maybe the return PC so this is like a risk five version of this。
And then we load back all the registers from the new thread。 And then we execute return。
And what is returned to return says, well, I'm going to return from this switch function call。
but I'm returning based on the stack and registers I just loaded and so I'm returning back the way T would have done it back when T was running。
Okay, and that's why this looks like this。 Okay, you know we got。
we run down down down down and then we switch over to T。
and then we return return return return return, but now we're on T stack and then we go down down down down down switch return return return return return return return return on S is stack and back and forth。
And so part of the reason this is so weird is we've actually changed the stack pointer。
And so we kind of changed the locality of execution to this other thread。 And believe me。
if this is a good thing to ponder at parties and whatever you can be a great。
great hit at the party to like can I tell you something really weird that I learned class today。
but I found it sometimes just sleeping on this will help a little。 Any other questions。 Yes。
one more go for it。 Is this code executing different address spaces that's what you're asking。
So the answer is in this simple example we're not changing any of the address space so think of this is two threads in the same address space。
Okay, because if you look at, you know this switch here doesn't change the any of the translation page tables anything。
One more yeah。 How does thread access thread T structure。
So the answer is we went into the kernel and the kernel has access to everything。
And so those TCB's are distorted kernel space and so it chooses one chooses the other。
So there's no there's no security violation here。 Yes, there's only one kernel。
one kernel to rule them all。 Okay, these are little rings。 Yes。 There's only one kernel。 One kernel。
There's only one kernel, but so the kernel is the code that's running at system level。
So only one kernel, we go into the kernel, we come out of the kernel, we go into the kernel。
we come out of the kernel。 One kernel to rule them all。 Only one kernel。
So both T and S have the same kernel。 Now, T and S have different kernel stacks。
but it's one kernel so so in fact look, see this code here。
this is a sub routine running it kernel mode。 And it's accessing, you know。
S is TCB and then T is TCB。 So, keep in mind that what is the kernel doing the kernel is the thing that is producing this virtual machine view that the threads are running it。
So it's turning what was only one CPU into something that looks like it's got a bunch of CPUs but it's one kernel。
many threads。 Good。 Okay。 So。 So, there's only one kernel, multiple kernel stacks and or threads。
Okay, because the kernel stack is really defined by some chunks of memory so it's not hard to have lots of them。
Okay, so should I go on。 We good。 Okay。 So what if you make a miss see this is a fairly simple routine。
What if you screw it up。 What happens like you forget a register。 Yes。 Leak data。 Perhaps yes。
But it's even worse than that。 So leaking data kind of somebody is a privacy violation that's bad。
but it's worse than leaking data yes。 So, you know, it's not going to be overloading in what sense。
So anybody want to else want to take a stab at what happens if you don't correctly switch。 But。
Well, the system crashes yes or worse。 It's subtly screws everything up and you don't know what's going on。
Okay, because those registers。 How many people have looked at the output of a compiler。 So。
you know, it's a very simple thing。 You should take a look sometimes。
There's a lot of register optimization。 The way registers are used is very complicated。
And if we forget to save and restore one register。
and maybe the thing runs perfectly well for a while。
but it's going to start failing in some very subtle ways。 Because the whole notion of a thread。
It's own CPU registers that are going to be good every time it's running and when you put something in a register。
It's there the next time you look。 But if we screw up the switch routine it's perfectly possible that tea put something in a register then we switched over to s。
s screwed it up when we came back to look for that thing it wasn't there properly。
And now we got maybe a crash。 We'd be lucky with a crash maybe we've got something worse which is a corrupted computation。
And then we're going to get to the switch。 So switch is important。 Okay。 And。
and what's interesting about this so you can think of this is the most important thing inside the kernel that you got to get right is you got to get switch working properly。
And can you come up with an exhaustive test of the switch routine to make sure you never forgot anything。
So it would just be badly exponential and you'd never figured that out so you got to stare at it very carefully and then you get your friend to stare at it and then you get your friends friend to stare at it。
And fortunately we don't ask you to, to write switch too much in this class because that would probably trigger our illegal cheating if you did too much of that but。
And so the point is switch is important and you got to be careful with it。 Okay。
And there's a cautionary tale that I always like to tell which is kind of fun so there was this kernel。
Topaz。 And this is back in the days where one instruction made a huge difference because you can imagine that the switch overheads important。
And so somebody was clever and managed to save one instruction in the switch routine。
And they documented it they wrote a whole block of comments that said that as long as the kernel isn't bigger than a megabyte in size this is fine。
Okay, and then several years later people forgot。 And they didn't read the comments。
And so they just added a couple of things and the kernel got big enough that it was no longer a megabyte in size and then all of a sudden weird stuff started happening。
Can you imagine so your kernels working fine you had a little bit and all of a sudden it's totally crashed。
Okay, so you got to be really careful with the switch routine。 Okay。 By the way, how many bits。
where are they dealing with where one megabyte mattered。 Quick。 Good。 You should know these things。
This is good。 You know, again, this could be a party trick right。 I know my powers of two。 Okay。 So。
I'm going to give you a lot of really good social recommendations here in this class。
You listen to me, it'll be great。 So, project one is in full swing was it was put out yesterday。
You should be doing it。 Okay。 And part of the first assignments with with project one is going to be a design document。
There are some。 There are some basic sketches for what a designs document should look like that we're going to give you out。
But, you know, think about the design document is something you would give to your boss or something which is explaining。
What's your high level concept if there's going to be code it should be pseudo code。
And not dumping 50 lines of C code。 Here's my here's what we're planning to do because the whole point is here's what we're planning to do right but we wrote 50 lines of C code。
So, you know, think of the design doc is a high level description of what you're doing。 Okay。
now the paradox here of course is what if you need code for the design document so pseudo code。
Try to describe it in a way that your TAs can get it easily。 Okay。
You have a permanent discussion session now since we did our group assignments。 And so。
So we should be attending your permanent to discussion session Friday。 Okay。
that'll be your first time。 There we want to have you come so your TAs can get to know you。
If you're sick, don't come。 And that's also true of lecture here。
We have ways around if you're sick。 Okay。 So, for one thing。
there's a way to make up a loss discussion session, which you can find out。
I think we have that on piazza。 We're also going to have rotating section leaders are going to record sessions so that there'll always be a recording of the discussion session for that week that we have for archival purposes and so you can study with it and stuff too so。
Okay。 Mid term one is two weeks from today。 Okay, and it's seven to nine。
We need you to fill out the the conflict quickly so we can figure out what to do with conflicts。
We are that's two tomorrow so it's up on the。 It's up on the schedule。 Okay。
And the topics are pretty much everything up to the day of the midterm。 The previous lecture。
There might be a couple of things in there。 They won't be heavily tested but。 Okay。
So Anthony points out that perhaps we're actually going to ask you to do a little bit of switch for。
Point point units in project one so you'll get a little bit of a flavor on that。 Any questions。
So we're, we're diving right in we're we're starting the class how many people have actually started the project like loaded it and looked at it。
So for those of you that are on the on the net there everybody raised their hand。
They're really on top of it so you guys ought to be too。 All right。
So are we switching contacts in these previous examples well I called it a context which。
Earlier we sort of said a context which was between processes so when you say a context which。
You need to be clear what your context switching from okay so if you stay within the same address space and switch threads between each other。
That's the example I previously gave you。 Okay, and that's much cheaper than switching the address basis in fact here's some numbers that are kind of give you a little bit of a flavor so from Linux。
For instance, they contact switch every 10 to 100 milliseconds switching between processes is about three or four microseconds where switching between threads in a process is 100 nanoseconds it's faster。
Okay。 Why is that well thread you to switch and registers。
You actually have to switch the address context you have to flush。
And so I wanted to give a little terminology here just so we kind of are on the same page here but if you notice we're talking about this。
where we have one user thread per kernel thread now this is that possibly confusing terminology which I wanted to make sure everybody got。
really one user stack per kernel stack。 Okay。 The reason they call this a kernel thread is this is a home for this this bottom half of this user thread to run when it's in the kernel。
But there's several other options so here's an example where we can have a bunch of threads that are multiplex that user level and one kernel thread underneath。
So the original Java was like that where you'd start a bunch of threads and there's only one kernel instance underneath。
and the library multiplex the registers totally at user level。
And as long as it properly executes yield, then you can have the equivalent of the kernel scheduler running totally at user level much cheaper than anything because you don't even have to go into the kernel。
Okay。 Can anybody see what the downside of this model might be over this one。 Well remember we're。
we're threads inside a process right now so you know at worst a bad thing is a bug not a security violation right。
Anybody else have any idea what might be。 Yes。 So really。
now remember we're assuming the kernel is okay so or the code's good so it's not that this one of these others let you screw it up but the point here is。
if any one of these user threads goes to sleep in the kernel and I owe them they're all put the sleep because there's only one。
kernel thread。 So you can think of the kernel half is a point to go to sleep on aisle。 Okay。
And then last but not least you can have many threads and many kernel threads and you can multiplex them。
We're really talking about this particularly simple model, which by the way。
because of Linux has become fairly inexpensive。 And so the default threading model in Linux is basically this one on the left。
Good。 So, let's go。 Let's push now with our stacks since we're all comfortable with them。
So what happens when you don't execute yield but you're busy executing IO。
So you're in a copy file routine。 You, you do a read。
And that reads got to go into the kernel so a system call goes into the kernel。 Right。
It gets the kernel stack executes kernel read actually showed you that code a few lectures ago。
Okay, which does some stuff to set up the disk, let's say。
gets it ready to go but now we need to switch to a different thread because I always a million instructions worth of time right。
So here we call run new thread and switch。 And so this routine yielded implicitly by calling read。
and then that read did its thing but then did a switch, and now we'll run something else。 Okay。
the stack looks exactly like that example we gave earlier。 Okay。
And it's similar for thread communication like signals and joins and networking all of this is kind of similar。
And notice, now we can say it doesn't matter what caused you to switch。 Is it a yield, is it IO。
It just works and switch will take you and gets another guy running, right。
What about external events。 Okay, so what happens if a thread never does any IO never waits never yields while we said that that was a bug with the early versions of the Macintosh actually of Microsoft Windows too。
And so, you know, we got a problem because what if compute pie never calls yield never calls prints anything out。
We need a way for the dispatcher to get control and this is the external events。
So the simplest idea is we set a timer off that's generating an interrupt and that interrupts causes us to enter the kernel and that point lets us do a schedule and switch。
Okay。 But in principle what I'm going to show you here for timer work fine for any interrupt so packet coming off the network could take control back。
Okay, and here you just have to make sure the external events happen frequently enough and then you can make sure that you're properly switching。
So if you remember by the way, I showed you this picture so I'm not going to go into a detail but interrupts again。
come from the outside their external, including a timer。
They generate a physical signal that causes the CPU to jump to an interrupt vector。 Okay。
And you'll be able to actually look at that code now that you've downloaded the process。
The start the first project。
Okay。 So, here's an example of what happens during a network interrupt。 So, to remind you。
we didn't talk about this explicitly but here I'm executing along。 Okay。
this is kind of like risk five assembly just for the sake of argument。 And the interrupt comes on。
So what happens is that internal interrupt actually tells the processor stop executing。 So。
it's a pipeline flush to get rid of the partially executed instructions。
It saves the program counters and disables interrupts goes into kernel mode, kernel mode runs。
And then it does the work of say transferring the network packet from hardware to kernel buffers。
If you had a process that was running that web server code we showed you last time。
And it executed a read from network, it's could be that it gets wick looking up at this point and it gets taken off of a weight cue and put back on the ready cue in this green。
So, for everything we go back, and the user process keeps going。
So the idea behind interrupt is it's a temporary interruption of what's running to go do something else and then come back。
And the way that we can use this to help us switch is when we're in this green portion doing the interrupt handler portion of our timer。
At that point, we can go ahead and do the switch and start somebody else running。
And so here's an example, where I'm busy doing something, whatever it is。
the timer interrupt happens。 So I've called, you could think of an interrupt as calling into the kernel。
but it's a forced call that the user doesn't do anything with the interrupt forces you from user mode into the kernel。
Okay, and what does that forcing do well it puts a kernel stack there。 Okay。
and now that kernel stack, we run the interrupt code part of which will eventually call run new thread and switch。
and now we can switch somebody else in, and we've just shown you how a timer interrupt can make sure that we keep switching regularly。
Thanks。 And here's an example, you know our timer interrupt might do some periodic housekeeping and do other things and then call run new thread。
All right, questions。 All right, yes。 So this is a good question so explain the red is what you're asking here so what happens when an interrupt happens。
The first thing that goes on back in this picture here you can see is that the interrupt disabled bit。
enables everything until the CPU can clean up the current interrupt and disable that one thing and get things ready to interrupt for other stuff and then it'll turn it back on again and go。
So immediately disable everything then we'll get stuck in this loop where we're the hardware is just interrupting over and over again。
whereas instead what happens is we enter, we turn them all off that's done in the hardware and then we disable that one interrupt and then we can turn them back on again。
So, you can do it in different ways。 The question is shouldn't we reenable interrupts just at the very end。
That always re enables them。 But you might want to re enable things that are higher priority that might be even more important than the one you're handling。
Okay。 And we, we can talk more about this another time I don't want to get too distracted on this but you get the basic idea。
And we can view that it's kind of raising priority of the interrupts will take will only take ones that are really high priority higher than the one I'm working on。
Okay, good。 Any other questions about this。 So I think the key thing to get out of today's lecture is there's a wide variety of ways to get to switch。
Okay, and if you design the way your stacks work in the kernel。
then it's really easy to have external and internal events of a wide variety of types。
Bring you back to the kernel scheduler so you can make sure that things keep running。
And that's when you get to where you actually look at how that works inside the switch routines in the kernel。
You'll see how that works。 Okay, and I think I either next time or the time after I haven't decided when exactly I'll actually show you even an explicit stack kind of arrangement for x86 and give you an idea how that works。
Okay。 But so the idea is this way as long as we can get to this switch, we can change things out。
Okay, any other questions, should I move on。 Now, let's get to the question that came up earlier。
That S and T example was fine but that assumes that T has been running forever。
And so let's talk about something that we could loosely call thread for。 Remember, I gave you the。
the p threads API last time that's on a slide so there's p thread create and so on but let's just call it thread for for a moment。
It's a user level procedure to create a new thread。
The arguments to thread fork are in this case a pointer to the application routine you want to run so and arguments to it。
And how much stack。 Now the reason this is different from fork is remember fork is giving us a brand new protection context and it's going to duplicate everything。
We can't really pass pointers into somebody else's security context because that's kind of untrustable。
And so that's why for just sort of says we're going to exactly execute where we were and give you a brand new security context。
If we're inside of a threat process and we're making new threads。
We can certainly give it a pointer to a routine so thread for just says well run this function in a new thread。
Okay, here's the pointer。 Here's the arguments。 And then we'll do the size of the stack。 Okay。
and so how do we build this。 Well, we'll do some sanity checking just to make sure that we don't screw anything up。
We're going to enter kernel mode sanity check again。
Just to make sure that the user isn't giving you total garbage here and then we'll allocate a new stack and TCB will initialize the TCB。
Place it on the ready list is rightable and we're good to go。 But remember。
we need to do this in a way that once we put it on the ready queue。
even though it hasn't ever run before it looks like it ran before so that we can switch to it without changing any of what we just talked about。
Okay, that's, that's going to be our goal。 So just like ST ST right。
we want to be able to do that with T the first time T runs and in order to do that we have to fake out things a little bit so the stack looks just like it did as if he had been running for a long time。
Okay, everybody with me on that。 And so how do we do that well。 We're。
we got to get a new TCB because it's a brand new thread。
We got to initialize some fields stack pointer in their pointing to a stack a PC return address。
Pointing at something we might call thread route to argument registers。
maybe for function and for the function pointer and the argument pointer。
And what do we do with the stack。 Well, usually we don't have to do too much with the stack。
we might at minimum, well, maximum, we might have to put like the return address on there。
But otherwise there's no setup required。 But what we want to do is we want to fake this so that that stack。
When we switch to it creates the thread。 You want to put it in a format so that switching to it。
the system thinks it's been running forever, but what really happens is it just creates a thread right then in there。
Okay, and the trick is, we're going to set it up。 What happens when you switch to thread T is what well you're going to return at that point。
And so all we really have to do is set the return address in that new thread control block so that when we return we really instead of returning to something that was already there we just returned to the start of thread。
and that's going to start everything。
Okay, and so this is the way to think about it。 Here's another thread。
This stub is a TCB that we've just crafted in a way that we can switch to it using the same switch code but when we go here it actually starts creating the thread。
Right then in there。 Why can we possibly do that well。
This the will switch to a new stack that new stack has got a return address on it。
If we do return just like what happens when we switch to this one we go as if we were at the bottom here and we do a return as the first thing so we set it up so when we do a return it runs what we want。
Okay, so you can think of it like this that we set up the new thread by setting up the registers in the TCB to have what the new stack pointer is to have a return that points at thread route and a few other things。
And then when we switch to it it's going to do a return first thing and then the code is going to execute thread route whatever that is to set the thread up。
Okay, the pause there for a sec。 Yes。 Can you say a little louder。
Right so we're going to allocate the space for the new stack。
So we allocate the stack will allocate a thread control block。
But then we'll put a pointer to the top of the stack into the stack pointer in the TCB so that when we load that TCB we've got a new stack pointer pointing at the new stack。
And the only other thing we need to do is set it up so that when we execute that one return at the very end of switch it returns by executing the beginning of the initialize routine for the thread。
Good。 Again, this is one of these things that takes a little bit to stare at。 Okay。
One more quick yeah go。 So thread route here I'll show you what thread route is。
Okay, here you go。 So it's something that takes a function pointer and some arguments。
And it does some startup housekeeping like it sets up some zeros some counters and so on。
It goes to user mode。 And then it calls the function pointer with the arguments。
And then if that ever returns back it calls thread finish which shuts everything down。 Okay。
so it's it's really pretty simple。 So startup housekeeping records start of time of thread so on。
And so really if you were to look at the stack right after thread route starts running we have this little bit that's it user kernel mode。
And then it calls the thread code kind of right here where we call the function pointer。 Okay。
and so at that point we have a new stack frame which is for the beginning of the function we said we wanted。
And from that point on it'll work fine because however we get to switch whether we yield or we get an interrupt whatever that's all now in process。
So the only magic was really the very first time getting to where we kind of get to the beginning of this routine and executed and from that point on it just works exactly like everything I said earlier。
And so in the case of thread T。 What happened originally was there was a thread route。
It did a user mode switch it called that function a。
And now it got this stack and now it starts running and eventually we get to a point where we enter the kernel on yield and switch and then we go over to whatever else to run。
Okay。 So here's a question what is switching from user mode to kernel mode look like and vice versa。
Is it a flag so it's a processor flag。 There's a, remember we talked about dual mode very first day and second day。
There's something in the hardware that says your kernel motor user mode。
And going from kernel mode to user mode easy, because there's no security violation the kernel one kernel kernel rule them all。
Going from user mode to kernel mode that's where we have to be careful and we said that last time。
No, a couple of times ago。 It's control。 So you can do it with a system call you can do it with an interrupt whatever but it's got to go through a vector to well define piece of code in the kernel before you go to kernel mode。
Okay。 So that's that controlled entry。 Okay。 All right。 Questions。 Yes。
So good questions you're talking about right here with the call。
How does it know to put things into the thread stack instead of the kernel stack is that what you asked。
So the good question so the way we know the reason this function call has the correct stack is we set up thread route so that when we switch to it the very first time it gets a pointer to the new stack。
In the stack register that was how that's the, that's the whole magic behind this。
Notice right here we set the new stack pointer into it into the TCB。 So that one switch loads it。
Well, we have the new stack right then and there。 And so from that point on we're just executing normally and it uses the stack because it's got the stack。
Okay。 Yes。 Yep。 Well, there's only so every thread has one stack。 Well。
it has to it's got a kernel and a user stack but that there is a stack。
There's no difference between execution stack and another stack there's the stack。 So, so the stack。
So we're not actually storing the。 So we're the function here。
We're not executing this code in the stack。 This code is in the code block we're executing this code using the stack。
So this is representing all of the variables that are being stored。 Yeah。
I'm sorry if this has been so that would have been a good question earlier if that's been confusing so when we say this。
Every function call allocates a new stack frame。 And what this says is a did get a stack frame but there wasn't any data the store in there。
So the only thing actually and there's going to be the return。 But。
so this actually this isn't the functions we're calling and this is the actual stack frame and I'm showing you the traces we go through and back。
Good。 Sorry。 I'm glad you asked that question。
Yes。 So the question is, is it reasonable to assume that this top has a thread route of every thread looks like this。
Yes, that would be true。 And then of course this is going to depend a lot on the operating system as to what it actually looks like but everything I say here is something that would go in every thread when it starts up。
Good。 Okay, so let me talk a little bit about processes versus threads so we have one core。
You can think of one core we're multiplexing back and forth。
You could have many threads per process and now you can start to see how we can just have many things executing at the same time。
And then we have two quotes, even though we only have one CPU because we just switch back and forth。
Okay。 Now, multi core by that notion isn't that much more complicated。 Really。
we just have a bunch of cores, each of which can be scheduled in the way we're talking about it so now you can have say in this case。
four things actually in parallel。 And then a scheduler can choose to take one of them away from one thread and give a different thread and so the scheduler has multiple physical processes。
processors to use。
Okay。 And then of course we talked about simultaneous multi threading。 In that instance。
now this diagram hopefully will make a little more sense in the middle here。
we have a two core situation where we have two cores, each of them are super scalar。
And so time goes down and so here we see three possible slots。
only two of which are doing something useful on core one。 And then we have two slots。
three slots only two of which are doing something useful on core to hyper threading we just mix those together so we fill up all the slots and do better hardware utilization。
But from a model of today's standpoint。 You could think of this is actually being。
even though we have four cores with two hyper threads per core you could think of this as eight total physical cores running simultaneously from the standpoint。
right now。 And then we're talking about, there are some performance differences there but from the standpoint of this lecture。
this is just like an eight core processor。 Okay。 And that swapping back and forth is something that you hopefully can generalize when you have more than one core or more than one hyper thread。
something you can easily think about unloading, loading。
and you do that on all the cores at different times and you can get scheduling, Okay。 So。
so we can start thinking about some of our options here right so most operating systems have either one or many address spaces。
most of them today have many。
And they can have one thread for address space or many threads for address space so this gives us four kind of options right here in the middle in the one one category is the early Mac and Tasha's MS dots。
where there was kind of one address space unprotected and one thread running at once。
Pretty primitive。 We can have sort of one address space and many threads that's kind of where bed and operating systems that you might have on little devices might run。
You can have sort of one thread for address space and many address spaces that's kind of traditional Unix where you only had one thread for process。
And then modern things are all in this lower corner that where you can have many processes many threads for process。
Okay。
So, as we have about, we have about 10 minutes left today I want to。
I didn't get a break in today I apologize, but you guys stay with me for 10 minutes。
I want to do a few definitions here just to be on the same page so multi processing typically means multiple CPUs or cores going on at the same time。
Okay。 And here I'm using CPU and core interchangeably。
Multi programming is typically multiple jobs or processes really says there's more than one app running at the same time kind of。
Okay。 And then multi threading is typically used as multiple threads per process。 Now。
I did see on Piazza a question once well with hyper threading can each of the two hyper threads be in a different process and the answers。
Yes。 Okay, so they really are like different course but for now when I say multi threading unless I'm saying。
unless I specify assume I'm talking about multiple threads in a process。 Okay。
and so what does it mean to run two threads concurrently。
This says that the two threads look like they're running at the same time。
So we've been talking about concurrent execution today。 One CPU multiple threads alternating。 Okay。
And multi processing is really when here's three threads green magenta and blue。
And basically they really are running at the same time because there's three cores。
We really do have these things running at same time。 Multi programming is wider and includes。
includes concurrency。 So here, we can have ABC。 And there may be only one CPU so these aren't actually running at the same time but they're alternating。
And so a B and C have to be properly designed so that they'll work correctly。
No matter what the interleaving so for instance notice that I give you two interleavings。
one at the top where a runs until it's done be run so it's done see runs till it's done。
Down here we alternate ABC ABC ABC come up with some other random way of doing that you should assume the scheduler could do that to you。
Okay, so this is what I like to think of is assume adversarial scheduling。
And then in our code we're going to assume that if you have a bug that could get screwed up by a particular ordering the scheduler will find it for you and it'll probably be like three in the morning。
Okay。 Everybody with me on this。 So this at the bottom ought to be something to really motivate us moving forward the next couple of lectures because whenever we've got either multi processing where things are really running at same time or multi programming with concurrency。
We need to design our applications so that they work properly no matter what ordering happens to come to pass。
And that's going to be designed for correctness, not design test the bechievers out of it。
And hope you find all the bugs right because that will be guaranteed to never work properly。 Okay。
and you'll discover the problem at three in the morning。 Okay。
or actually you're even worse your partner will discover one of your partners will discover it three in the morning and you'll be catching a nap。
At the time, so, so correctness for systems with concurrent threads is interesting right so if a dispatcher scheduler can schedule the threads in any way。
programs must work under all circumstances。 All circumstances, can you test for this。 No。 Now。
there's some very cool work being done by folks in our faculty and other faculty trying to test whether something is mostly correct under concurrency。
They can do it to a certain depth of inner of exchanges and so on。
But you have to imagine that it's very hard to test all possible schedules to see whether any of them have books。
Very hard。 Okay, and so really we want to design correctly for the at the beginning and that's going to be our goal。
We're going to understand how to do synchronization。 It's a word you'll learn a lot about。
In a way that the code is correct。 Now the one slight case where you don't have to worry so much about this is if the threads are really independent of each other they're not using the same data。
You know there's no, there's no state shared with other threads。 The result can be deterministic。
Okay, where the input state determines the result no matter what's going on because they're just running。
Doesn't matter that these other two threads are computing E and pie simultaneously and doing some other complicated thing。
This thread is unrelated anybody else it can run to completion。
And you get a deterministic reproducible result。 And it doesn't matter the interleaving right because this thing runs for a while and then you switch up and it runs again。
It doesn't matter what these other threads did because this is totally independent。 Awesome。 Okay。
unfortunately, that doesn't happen too often。 Especially since you're all sharing the one operating system girl。
That's a spot in the middle, but cooperating threads give you much more power。
but they're also more problematic and why we have to be careful about how we design things。
So the problem is if you have two threads and they're sharing data and they're scheduling concurrently。
then。 And they're non deterministic problems it's not reproducible。
We often call these highs and bugs。 Okay。 And they really are like the Heisenberg principle because these are the kind of bugs that if you got a bug。
and you put print apps in to try to find it the print apps will make the bug go away。 Okay。
so if you if you look at it, it's gone。 Okay, so highs and bugs are annoying and dangerous and we want to make sure that we have as few of them as we can by designing for correctness。
Okay, have I gotten off my soapbox yet designed for correctness design for correctness。
So interactions。 I just leave the print up and it's what came up on the chat here。
And I'm not going to leave me people do that。 So, is any program truly independent。
So every process shares the same file system OS resources network。
You can imagine example buggy device driver causes thread a crash independent of thread B。
but now be is done right。 And imagine an evil C compiler。
which modifies files behind your back by inserting errors into the C program unless you insert the debugging code and then it compiles it perfectly。
That you laugh。 There were actually a couple of famous hacks like that where it figured out whether you're trying to debug it and did the right thing when you weren't when you were debugging。
But the thing to note is debugging statements can actually overrun your stack in some instances and so by inserting the debugging statements you can screw everything up。
So this is going to be something to think about。 And non deterministic errors which are errors that are different every time you run it are very hard to find。
You know memory layout of kernel and user might change each time you run it。
or I oh my cause problems okay so these are going to be interesting to play with it。 Okay。
you have now entered the domain of the twilight zone of non deterministic books。 Okay。
those are not the bugs that you were looking for。 So why even allow cooperating threads so people cooperate。
Computers help and enhance people's lives and you need to have interaction which is going to mean concurrency。
because we're going to have people entering stuff on the one hand on a keypad plus we got to compute stuff plus we have other input。
We're looking at a camera。 So we're going to be able to have concurrency。
And so we're going to have to figure out how to do that。
And so one advantage of cooperating threads is really you can share resources one computer。
many users, one bank balance many atms etc。 You can get speed up so if you have multiple core and more threads。
then you can actually get things go faster if it's parallel you've learned about that and 61 see I think indeed probably did some parallelism right。
And there's actually a modularity advantage to splitting things up into different processes。
for instance, so the compiler GCC actually calls the CPU processor and pipes it into two code phases and pipes it into an assembler and a loader。
By splitting into separate programs it might be easier to debug。
And then you just time together and that turns out to be a nice pipeline so there's a lot of advantages to parallelism if you can get it under control。
You know, we're going to close with a couple of quick examples and then we're going to pick this up next time but if you remember the web server we were talking about where we have this non cooperating version of a web server。
where we accept the connection。 And we get a new fork and go forward。 That can be kind of expensive。
Because every connection has to load its own kind of file state and web cache and all that sort of stuff。
Wouldn't it be nice to have the threads together。 Okay, and so this was the threaded version。
where we accept the connection and fork a new thread looks the same but it has a bunch of advantages from a performance standpoint the threads are lower cost to create。
And you can share state。 So they have two different people request the same thing。
You have state that's cash and it can be a lot faster。
And obviously you are all thinking about the security concepts, but let's hold that off for now。
But if you remember, I mentioned this one big problem with a lot of threads for performance is now somebody can figure out how to exploit that and that's sort of the so called。
effect, where they actually cause so many connections that you generate so many threads that you crash your operating system。
Okay, and if you remember, I gave you this idea, which is bounding your concurrency by using something that's typically called a thread pool。
All right, and the idea is see all these threads here。
So the connections come in for the outside they're picked up by a master thread that does accept。
It puts them on a queue, and then we have a bounded number of threads 20。
And the idea is that we're only running 20 connections at a time and when a thread exits it grabs the next connection。
And therefore we have a bounded pool of threads。 And you can kind of see the idea here is the master puts it accepts and puts on the queue。
and the workers pull off the queue。 Okay, and that's a way to control threading。 So。
with that we've run out of time I just want to say in conclusion, hold on, don't go away。
Processes have the two parts they have the threads which we spent a lot of time talking about that's the concurrency piece。
and the address base。 And the question piece。 Various textbooks talk about processes they often mean an original old style Unix process with one thread for process。
We'll talk about multiple threads。 We started to talk about concurrent threads being very useful next time。
We're going to dive into the question about what happens when you have threads that are sharing data。
And that's not fail。 Okay, and that's going to be the problems that they're introduced so you guys have a great weekend。
Get working on your project, because I know you all raised your hand when I asked who was。
and we will go to your section tomorrow and we'll see you on Tuesday。 second。