 All right, some people aren't even in their seats yet. All right, good morning, everybody. Good morning. Try that again. Good morning, everybody. That was worse. I'm just supposed to extort you to, I don't know, to greet me with some sense of excitement. All right, so it's Monday. Last week was a really short week. We only met once. That was kind of weird. I hope everybody enjoyed the time to forget all about everything we've been talking about before. And that's what I did. So today's going to be a little frustrating for me because I'm without one of my favorite technological tools. I'm actually going to have to come and touch this thing on a regular basis, if I don't want to do. But OK, so what we're going to do is I optimistically thought that you guys would escape with learning all about operating system structure in one lecture that didn't happen. So today we're going to go back to talking about operating system structure. We're going to finish up. We're going to talk about micro kernels, which we just started to get into last time. And then we'll talk about some more exotic kernel designs. And then today we will finish up talking a little bit about interfaces. And I think this is a good conversation to have right now because it's highly relevant to assignment three. And speaking of assignment three, so assignment three is out. Design doc is due on Wednesday. You're a design executive summary. This assignment is one that stresses design a little bit more. It stresses interface design a little bit more in particular because, oh wow, it's another rough spell in day here at CS41 Central. So for assignment two, it was really, the way I like to think of assignment two is that you were writing this fairly thin layer of code. There were parts of it that were fatter than others. But really what you were doing is a good portion of the assignment was essentially kind of connecting the file system call interface down to the VFS layer. Collecting a little bit of information and then firing those calls off to the VFS function. So that wasn't that tough. That shouldn't have been that hard. Assignment three is really more of this rotund and blob of code. It has a very thin interface on one side. But then there's a lot of internal structure that you guys will have to design. You're going to have to design your own page tables. You're going to have to design a core map or reverse page table so that you can find out things about physical pages and memory. You're going to have to design your page table entry so that you can figure out where your pages are and whether or not they're on disk, et cetera, et cetera. So all these things are really up to you guys to figure out. And I think that makes assignment three more fun. But it also means that it's more challenging. The other thing about assignment three is going to make it more difficult. Is that when you guys configure your kernel, how many people have configured a kernel for assignment three? Oh, wow. So let's get started on assignment three. When you guys configure your kernel for assignment three, it's not even going to boot anymore. So because we are tearing out that little fig leaf of a VM that's been in there up till now, dumb VM, nothing will work. So once you configure for assignment three, the thing is not even going to boot. And there's actually a fair amount of work you guys are going to have to do just to get it to where it will boot again. And because there's a lot of code to write before things even start to work, it's very difficult to attest assignment three as well. So good luck. We will be covering assignment three in recitations. Also, we'll have office hours and stuff like that. But I really encourage you guys, I'm out of extensions for the term. I mean, the assignment three deadline is kind of pushed as far back against the summer as I feel comfortable doing. So there won't be extensions. There's going to be a lot of extra time, so get started. I wish I had more time to give you guys on the assignment, but I don't. And then we're going to get the slides and the video back on the website at some point soon. People pointed out that that would be helpful for assignment three. And it will be. So it'll be done. Questions about logistics? We also have grades up online. I'm so excited. I feel slightly less embarrassed because we've managed to post the grades to the 20 code reading question. So that was exciting. There's more coming. Any questions about logistics assignment three, et cetera? Alex? Yes, we are. That's something I'm working on today. We have a solution set for you. I think it works. I didn't actually write it. It's old solution set code that I've just kind of hacked some pieces off of so that it works with some of the changes that were made. But yes, we will have that for you if you would prefer to use. And the procedures will be more or less the same. I think this time, instead of distributing it as a patch, for people who want it, I'm going to set it up as a get repository that you can just pull from and do the merge yourself. Any other questions? Good question. Yeah? What's that? Oh yeah, there are plenty of test cases there already. So if you look in the, I mean, the first thing to do is just to get the stuff that worked in assignment two to work again. That's like getting four tests to work and stuff like that. Just doing that. But there are assignment three stress tests. So if you look in the test directory, there's something called huge. There's something called map molt. And there's something called sort. And then there is something called triple huge. There's something called triple map and triple sort. And then the ultimate, the top of the mountain thing that once you can run this successfully and reliably, you will have completed the assignment, is a fairly nasty test called parallel VM. That not only stresses your VM systems ability to move pages back and forth to disk, but also forks a bunch of threads and does all sorts of things in parallel and kind of really stresses the concurrency aspects. This is one of the things that's gonna bother you guys about this assignment is that especially once you start moving pages to disk, remember that those disk rights take a long time. So if you don't, you know, there's a lot of times where people, you know, they get started and they get it working and then they try to add swapping and things start to break, right? Because what happens is those long delays that are caused by moving pages to disk cause synchronization problems that have not manifested themselves to suddenly rear their ugly heads, right? So, but anyway, I mean, we're gonna give you guys as much help as we can on the assignment through presentations and other. Yes, if you pass parallel VM, you will get an A on the assignment. How about that? Is that enough of a science, or do you? I don't know if I call it low or bound. Low or bound, right, I mean, I mean, so if your PASH compiles and starts up cleanly, then that's a start. So, I mean, just getting it back to the point where we can boot and use the kernel is gonna earn you some points on assignment three. Because again, once you configure for assignment three, nothing will work, right? So, yeah, well, again, I'm just behind on this stuff, but we'll try to get some idea what the rubric is. But there are tests in the directory and there's kind of a testing procedure in terms of things that we would expect you to do. And when we test your kernel, we'll have a sort of set of things. You know, the first thing will be, we'll run your kernel with a lot of memory, but we'll run things out of core, then we'll try to turn on swapping and things like that. So there's a kind of a set of things that we'll do to sort of, so after assignment three, almost everything in bad call should work, right? I mean, there's not a lot new for assignment three in bad call because you're only adding one system quality or kernel, right? Most of the bad call stuff was actually for assignment two. So most of that's over, but there is bad S break and you need to figure out how to handle that problem. Good questions. Any other questions about assignment three? Happy to answer them. Okay, so on last Wednesday, we talked about monolithic kernel design, right? The sort of common and fairly popular, sadly, way of structuring kernel. So any questions about this stuff? We talked a little bit about some of the problems with monolithic design, specifically we talked about rigidity, we talked about solutions to rigidity using loadable kernel modules, and then we talked about safety, and we talked about some work that's been done on device driver design and other forms of device driver safety. So any questions about this before we do? Just a smidgen of review. No, okay. So monolithic kernels. This is a commonly used way of design in operating systems. Who can tell me something about monolithic kernels? What characterizes a monolithic kernel? I'm up in the front today, so I'm gonna be picking up people in the front. I can't really get too far at that. Mike, one thing about a monolithic kernel. Give you a kernel, how do you know it's a monolith? What's that? The current OSs are monolithic kernels. Well, many kernel OSs are monolithic kernels, but what characterizes a monolithic kernel pen? So everything runs in one big blob, but what do we mean by that, Sean? It runs as one thing. It runs as one process, meaning what else? If one thing fails, the entire kernel fails. Okay, why though? What is, I mean, you guys are starting in assignment three. So what distinguishes processes from each other? Address space, right? So monolithic kernel, the entire kernel runs in the same address space, right? And then what have we been talking about all semester in terms of special powers that the kernel have? If everything runs in the same address space, what does that mean about all the kernel code? There's no isolation, and it's all running in the same address space, but how would we describe all of that code? Trusted or privileged, right? All of it's privileged. We've talked about how one of the things, the operating system has been elected by the other programs on the system to do is allocate resources. And in order to do that, it needs some special powers. But in a monolithic kernel, because all the code is running as one blob and one address space, all that code has that power, right? So all of the code can execute instructions that modify the page tables. All the code can write or read to drivers if it wants to or thinks it wants to, right? All of the code can overwrite important kernel data structures, right? So this is kind of the problem. It's all running as privileged, and it's all structured as kind of like one big potentially messy, right? Potentially messy. It can get messy. There's nothing stopping it from getting messy, right? Certainly, we can hope that the code wouldn't get too bad, but there's nothing really preventing it from getting kind of disgusting, right? So why do people do this? Remember, there were two main reasons that I claim that this is done, despite the fact that there are a lot of problems that we started to discuss. What's one reason? It's simple. It's easy. It's just the way that things start to happen when you just sit down and start hacking, right? If you just sit down and start writing code, monolithic kernels are kind of what a merge, right? Unless you really impose, this is terrible. A lot of extra structure on it, right? And then what's the other big advantage that we're gonna come back to today about monolithic kernels historically? They're fast, right? So why are they fast? Well, it's up on the slide now, right? Because I've got these wide interfaces between components and all the only overhead, so what's the overhead? What does the user program have to do to communicate with the kernel? System call, what does the system call involve? Contact switch, saving all the registers, switching address spaces, flushing the TLB, all this sort of stuff, right? Really high overhead, just to make a function call. I mean, from the perspective of your user process, the system call interface just looks like a special set of function calls that allow it to interact with certain parts of the system, right? But there's a high overhead to invoking those function calls. Inside the kernel, you know, I could, again, and what we'll talk about today is a kernel design that moves a lot of kernel functionality out into user space. The problem is that now, every time I want to invoke a kernel procedure, I have this high overhead for a boundary crossing, right? So the speed of just being able to make a function call, right? It's like the difference between using the kernel and just the C library in user space, right? The C library is just a piece of code. It would just make a function call. You know, I push some registers onto the stack and I just jump to a new address and start executing instructions. There's none of this extra blob of code that's necessary to protect the kernel, right? To get across the kernel user boundary, right? Oh, man, I'm really missing my thingy. Okay, so last time we looked at, there were three big problems with model with the kernel designs. Who can remember one of them a week? So, but how would you characterize that? What about kernels? They're what? They're, what's that? Okay, so there were security issues, so safety was one of them, right? That's good. What else? Rigid. They're rigid. They're inflexible, right? And then, what was the last problem? We didn't really talk about as much, but we're gonna talk about today. What's a problem inherent with this big blob? We'll call it, we'll call it the difference between modular and blobular, which is a complexity, right? Like the code can get really gross, complex. It's making all these calls across these internal interfaces and things can get really nasty, right? Okay, so that's, as far as we got on Wednesday. Any questions about this stuff before we go on? We talked a little bit about rigidity. We talked about kernel modules. We talked about safety in terms of device drivers. I wanna talk a little bit, and I skipped over this last time, but I think it's what we're talking about. So, one of the interesting new kind of approaches to, well, let me ask the question this way. How many people are really enjoying writing C code in this class? They feel like C provides all these great features that make it easy not to make mistakes, that make it easy to write correct code, that make sure that when you call malloc, for example, you're actually getting back something that's the correct size, et cetera, et cetera, right? So C is like this, you know, remember at the time, go back 50 years, C was a high level language, right? No, no, no. I mean, we've kept building stories on top of the language tree, right? So C is now, you know, feels like you're down in subbasement B or something, right? It's not really a high level language anymore, right? From our modern perspective. And so there's this question about, you know, kernel safety has always relied on using hardware to enforce boundaries between kernel and user code and in other ways trying to exploit features of hardware to protect the kernel from malicious user programs, right? But maybe we're relying on hardware too much and maybe what we can do is try to take advantage of new software features, right? New language features in particular, right? And so there's actually been a number of projects one in particular that was fairly high profile looking at can we start to write, can we start to move to writing kernels in higher level languages that would allow us to do a lot of the type checking and static analysis that modern sort of managed code is capable of doing, right? So if you've written things like C sharp or Java, you know that because of features of the language that the compiler just has so much more visibility into what you're trying to do, right? I mean, certainly in garbage collected languages, this whole problem of memory management kind of goes away, right? It's really nice. And for years and years and years, kernels have continued to be written in these low level languages, why? What's the reason for doing that? It's fast. It's fast, right? I mean, this is the piece of code on your system that has the potential to just bottleneck everything else, right? So if this thing doesn't scream, the rest of the system is going to slow down. But there have been projects that have started to rock this. So there was a product at Microsoft in particular was called Singularity. And Singularity was an attempt, a successful attempt in some ways to reimplement a kernel based on a more modern sort of type safe language, namely C sharp, right? So C sharp was kind of Microsoft's Java 10 years later and so they actually had some special dialects of C sharp that they developed for writing kernel coding, right? And in doing that gives you a lot of nice features. And again, I mean, things like garbage collection, right? Garbage collection in the kernel, right? I mean, some of you guys are all fighting with Malik and Free now, right? So how nice would it be if you could just call new and get an object and have it go away at the right time, right? Not at the wrong time and they have to sort of avoid doing some of those things. So that would be really nice. And I think that, I mean, some of these projects, there were rumors, would somebody ask a question? No, there were rumors, I guess for a couple of years, maybe five years ago that Microsoft's new kernels were actually going to be using some of these concepts, right? But it does, I mean, it kind of seems reasonable, right? At some point we will stop writing kernels in C, right? We will just make the trade off that most other programmers and other domains have made will write things in higher level languages, will sacrifice some speed and what we'll gain is a great deal of correctness, right? I actually had a boss at Microsoft and I worked there briefly who worked on an earlier project that I thought about putting up on the slide but decided not to, called Java OS. So there was an effort somewhere to write and operate in Java. And he had the enviable position of, I guess, being in charge of the performance team on Java OS, right? And this was, I mean, this was an ambitious effort, right? I mean, what this effort, I think, lacked in success, it probably picked up just sort of gumption and forward thinking. But at the same time, he said that the only way that they ever made Java OS faster, does anyone want to guess? No, by re-implementing things in native code. He's like, that's the only way we ever made things faster was by rewriting, taking things that were written in Java and rewriting them in C or some other sort of lower level language, right? So it was kind of a losing battle here. All right, so, okay, so let's talk about complexity, right? So the brief aside of sort of finished up safety, I wanted to talk about Singularity, I think it's kind of anything, right? So, but again, I mean, I think we got into this a little bit on Wednesday, but if we were normal programmers and we, and you were hired by a company, how would you structure a big complex software project? What would you do? You're trying to modularize it, right? You'd break it up, and partly, this is for a variety of reasons, right? Partly because you can hand out pieces of code to multiple developers and have them write things and be able to integrate everything together, right? That's much more difficult to do if you're all working on one big, blobular code base, right? And it's like you were working on this function. Like imagine if you tried to write a whole web browser, it's like one big main function, right? I mean, this sounds so dumb that no one would even try it, right? But I don't know, it might be really fast. So, and I mean, imagine the kind of props you would get if you could do that, right? Anyways, so in general, we try to modularize things that software programmers, because we're not idiots, and we break things into modules. We have well-defined interfaces between them. We're gonna talk at the end of class about what interfaces mean, because I think that that's important, and I wanted to sort of like pound it into your heads a little bit, especially given that you're starting to work on assignment three. But we break things up into smaller pieces, and we put well-defined interfaces with them. And in the operating system, remember, one of the things that we're trying to do when we do this, right? One of the things that we're trying to accomplish. So think about it this way. I've got this big blob of OS code, and right now it's kind of, you know, like it's got this little, it's got privilege, it's got privileged pieces sprinkled all over the place, right? So the file system has some parts that interact directly with devices, or the VM system has some things that interact directly with the MMU, or whatever, right? So I've got these little pieces of privileged code all over the place. And if I break it up into modules, but every module still requires privileged access to some piece of hardware, maybe I haven't really improved things that much, because what's gonna happen? I still have to run all this code together in privileged mode. And maybe I get some benefits that it can't stop on each other's memory. I might be able, what I might have running is essentially five or six processes that are all kind of the kernel, right? That share access, privilege access to hardware resources, but I haven't really reduced the footprint of the code that actually can, where mistakes can be costly, right? So the privilege code, not only can it, does it have these special powers, but it can use those special powers to do dumb things, right? It can crash the system, it can cause blue screens, whatever, right? So one of the things that I like to be able to do here is take all that privileged code and kind of get it into one piece, right? So can I find a way to refactor my big complex operating system so that I have nice, well-defined interfaces between the modules, but also so that there's one module that is as small as possible that contains all of the privileged code, right? Because what I can do then is I can take that little piece of code and that's the place that I'm gonna look for bugs, right? That's the place that I'm gonna optimize, that's the place that I'm gonna improve because the rest of it is just gonna be making calls in and out of that one component and so that will really improve things quite a bit. So if I can minimize the amount of code that has to work properly, then I can prove the chances that the system will work properly, right? Fewer lines of code to look at, I mean, if you take the same number of people you give them fewer lines of code to look at for correctness, then the probability that you will produce a correct system goes up, right? And this is what the whole microkernel movement was really about, right? The microkernel movement said, we wanna do two things, right? First of all, we're gonna refactor this big, ugly monolithic operating system and we're gonna create, instead of having this system where I've got everything in this one big blob of code and it's all privileged, what I'm gonna do is I'm gonna shrink that privileged piece of code down as much as I can and there was a big part of this effort is figuring out what should be in there, right? Like what should be privileged, what really, really needs to be the kernel and then I'm gonna implement everything else as essentially unprivileged user mode servers, right? So on your system, you have an Apache web server, right? That Apache web server is not part of the kernel although there are web servers that run using special kernel modules or with special permission usually because they're faster, right? But you can implement a web server in user space, right? It works fine, okay? So why can't I do this with my file server, right? With the file system, you know? Why can't I do this with most of that Unix API that we've talked about already, right? So how much of this stuff can I get outside the kernel, right? Outside of the privileged code base and into somewhere where it's going to be a little bit safer, right? And you know that the, and so this was the goal but we're gonna minimize what's going into the kernel and depending on the micro kernel what ended up in the kernel varied, right? But it was usually some set of these sorts of things, right? So, you know, you could think about it. Really, really low level VM stuff. I mean, actually modifying page tables or communicating with the MMU, that's privileged stuff. What else? What else might you, I mean, it's up on the slide, right? So anything that's necessary for protection, we'll talk about the exo-kernel movement and kind of where they came down on this. Anything that has to interface directly with hardware. And then also stuff that has to be really fast. And one of the things that a lot of micro kernels focused on and I think one of the kind of more useful products of the micro kernel effort was really, really, really, really fast IPC. Why does IPC become so important on a micro kernel system, right? Really, really fast IPC, inter-process communication. Cause everything's a process now, right? Everything's a process. So all of these kernel components that used to be able to communicate by just making function calls within this big blob of code now have to communicate through well-defined interfaces by passing messages through the kernel using IPC, right? And so IPC becomes this really, really, really critical hot path within these systems. And, you know, good micro kernels did all of these clever tricks to just optimize the heck out of IPC, right? So you had IPC systems where I can make an IPC call and it would transfer executions seamlessly to the other process so seamlessly that the arguments to my IPC call would still be in registers, right? So this is, I mean, this is like the ultimate zero copy, right? I mean, it's one thing to copy them into the kernel, copy them into another buffer, context switch to the new process that then has to read those arguments. No, no, no, these guys, you know, they found out how to make this stuff really, really fast, right? So that was kind of neat. And then again, everything else, including a lot of things that we tradition and take about being in the kernel, were implemented as user-level processes and services, right? You know, these user-level services communicate with each other over well-defined protocols using message passing, right? IPC, right? So, you know, file system needs to communicate with the web server, it sends a message, right? And then, you know, the goal or, I mean, maybe it was a somewhat fantastical idea of microkernel systems. Part of the goal was this idea that, remember, what are we trying to do? We're trying to minimize the code that where failures can cause a system fault. And so, what does this mean? Well, it means in theory, it would be great if my user-level services that are now implementing all these kernel features could fail and be restarted safely. And, you know, I think the success of getting that to work vary quite a bit, right? Because you have to wonder, like, if your system was running along and suddenly the virtual memory management user process died and lost all of its state, that would kind of be a problem, right? And that could kind of cause a lot of other processes to potentially die to, right? So, it's not always clear that you can restart things in user space if they fail, right? For other components you might be able to, so can anyone guess one typical piece of the kernel that can be implemented in user space and can fail pretty easily? It really depends on the assumptions that user processes and clients are making about the service. So, what's one part of a system where user processes make fewer assumptions about things being correct? And are more used to failures. What's that? Memory? No, no, memory is one of the worst ones, right? Because memory, I kind of assume it's always gonna be there, it's gonna be correct. Carl? Well, okay, you're headed in the right direction. Go with the nick, John. Okay, so file system might be one of them, right? Potentially, I might lose some state, right? I might have to force my clients to reopen files. Let's say the file system, you know, service failed and forgot what files you had opened, right? But see, that's another case where it's kind of like, there's no normal API for that, right? There's no normal API to tell a process, hey, by the way, all those files you had opened, they're not open anymore, because I forgot, you know? But what is one case where this is true? What's that? Network, the network. So networks fail all the time, right? You know, as my networking professor at Harvard said, oh, packets get dropped, right? And stuff fails, you know, connections get closed, you know, your router goes down or whatever, so many user processes are built in ways that are fairly resistant or tolerant of network failures. So if my network server went down and just dropped every connection and restarted, it would just, there are protocols that would allow it to cause clients to reopen connections, right? So that's a case where it's possible that this would work. But there's a lot of cases where the interfaces just don't exist, like what do you do when suddenly your address space has no more pages in it, right? It's like, oh, shoot, where was everything? Processes don't usually keep track of these things very well, right? So again, some of this was a little bit of fancy, right? I mean, some of this idea of, oh, what when the thing's in user space and then we can just restart them. Yeah, not that easy. Okay, so there was a period of time where microkernels were a really, really hot topic. There was a huge amount of work in the research community. There were commercial efforts and to build in microkernels. Sometimes I don't know why uniquely, sort of in the real time and embedded space. What do you think, so as far as the Achilles heel of this design, what do you think it turned out to be? 80s and 90s. Think about where computer technology was and what's one thing that might have allowed monolithic kernels to win despite the fact that they seemed to have so many flaws? Speed. Just pure and simple, right? Early monolithic kernel designs were slow, right? They were really slow and part of it was because they had well-defined interfaces. They forced these different components into more well-defined usage patterns and those interfaces and those messaging protocols had overhead to them, right? And so performance was a real problem with early microkernel designs and that's one of the reasons that I would say the microkernel movement never really caught on but there were a lot of really great things that came out of it, so I'm not trying to dig on it. It was a great idea. So there were two ways, two common ways to improve microkernel performance, right? I already touched on one of them with JavaOS. What's one way to improve microkernel performance? I wanna make my microkernel faster. I had this great idea. I refactored everything. I'm at this very, very small piece of privileged kernel code but it's so dog slow and so what is one thing I could do to make it faster? Move things back into the kernel, right? I just take whatever piece of functionality is bottlenecking my system and move it back into the kernel but it's kinda like destroys the whole point, right? If I take that to its limit that I have a monolithic kernel, right? But at some level you can think about this as a good design exercise, right? Because you can imagine that, you know, probably at least getting to this point forced me to think about the things that were supposed to be in the kernel. So even when I started to add them back in, it's still possible that I would have ended up with less in the kernel than I had originally. And I would have found a lot of stuff that could be moved out into user space. And then the other thing that people did was that they made the microkernel core itself really low level, really low level, right? So they just optimized the heck out of it. One of the ways they did that was they would implement it in assembly, right? So they would have these kernels where, and at some level, you know, every operating system kernel has pieces that are written in assembly. So this is not something the kernel don't do at all. The amount of assembly code the microkernels required to get good performance might have been bigger, it might have been small, right? I'm not actually sure about that, but this was another idea. And this seems to a little bit lose some of those nice properties of the microkernel itself, right? Which said, it should be easy to verify, it should be easy to check, it should be easy to make sure that it's safe. Once I start implementing things in assembly, that becomes very difficult to do. It's hard enough in C, it's gonna be even harder here, right? So in many ways, you know, again, I think despite the fact that the design itself, right, didn't really carry the day, the microkernel movement had so many different, you know, useful pieces and useful ideas that continue to influence the way that systems are designed, right? One was the focus on message passing. One is just simply the focus on clean interface design and modularity, right? That can be a good thing, even if you're writing a big blob of code, right? It doesn't matter, just because something is a blobular design, doesn't mean it has to be gross, right? Doesn't mean it has to be really, really badly structured within that blob, right? The other thing that I thought it was, I would point out is that, so again, it took maybe an extra 30 years, but a couple years ago, there was a team of Australian researchers and this was actually a really big project. I think they had like 30 people working on this for several years, right? So this was an enormous amount of man effort, but what they were able to do is, and I don't want to spend too much time explaining this, but they were able to take, they wrote a specification, for a formal specification for a small microkernel, and they were able to verify and prove that the microkernel implemented the specification, right? Now, this is not the same thing as proving it correct, right? But what it means is that they had a specification that was written in a higher level language and they could prove that their code implemented the specification, right? So to the degree that the specification is correct, the code was correct, right? Nothing even close to this could ever be done for big modern systems. They're too complex, they're too messy. The specification is too high level, right? I mean, POSIX is written in English, right? I mean, kernels are written C, right? Try to figure that out, right? So, but this was a really kind of neat demonstration of one of the things that's possible because of the fact that microkernels are small, right? Is that you can do something like this. And there are a lot of limitations to this, but it's still a really, really significant kind of a moment because I think a lot of people would have really never believed that this is possible, right? And again, a lot of the work on extremely efficient messaging and IPC has gone on and influenced other pieces of work. The other thing that the microkernels again kind of contributed was just this idea that we can design systems in an efficient way without having a lot of ugly interface usage, right? And one of the things that came out of this was this idea of hybrid kernels, right? So some weird halfway point between a macro and a monolithic and a microkernel. And this sounds like a great idea, but there's this open question of like, what is that, right? So Windows NT was one of the more, certainly more well-known, still forms the basis of machines that a lot of you guys use. Kernels that tried to market itself at least as a hybrid kernel, right? And in some ways I think what this meant is that they tried to develop something that was as clean as a microkernel, but they wrote a big blob of code and had it all run in privilege mode, right? So you can see that there's a huge amount of stuff that's still, here's the microkernel, right? This is kernel mode, right? So in a microkernel system, this box would essentially be kernel mode and everything else would be outside of it, right? But here they've got a lot of other stuff. They've got the window manager runs in privilege mode. So this is maybe some sort of like not completely stable compromise between these two objectives. But one of the things, and I think I pointed this out last time that they were able to do, which I think is really cool, is they were able to, because of some of the way that they factored their design, they were able to support multiple, you know what they called environments, right? So you can see there's a Windows 32 application that can run next to an application using the POSIX, USIX interface, it can run next to an application using OS 2, which I vaguely remember something about, but not enough to even claim that I know what it is other than it's another operating system with a different interface. So, and again, I mean there was maybe something to this, but this was my kind of take on what this actually meant, right? And again, there's something wrong with that. That's not a bad, that's not itself kind of a bad idea. And Linus Torvalds was quoted as saying that he considered this hybrid kernel term to be really nothing, nothing more than marketing. This is just a marketing, marketing shtick. So let me talk briefly about two other interesting kernel designs, right? These are continuations in some ways of micro kernels. So the exocernel idea was something that was born at MIT, and actually you guys have been internalizing some of this because I think that the way that they split the kernel between things that provide abstractions and things that multiplex resources is really, really useful. And at some level that was what they contributed was this really useful conceptual split. And you can think about it influencing what micro kernels do, right? I mean, what should be in the micro kernel? Well, it's the stuff that multiplexes resources, right? They abstractions don't need privilege, right? They shouldn't need privilege. The only thing that needs privilege is multiplexing resources, right? And exocernels, again, you can, I think there's a way of mapping exocernel design onto micro kernels, right? It's just a different set of thinking, a different set of decisions about what's in the micro kernel and why, right? The other even more modern kernel design, so there is still a fair amount of research that goes into kernel design and it's driven by the same things that have always driven kernel design research which is changing features of hardware. So a couple of years ago, there were some people at EPF, should I even say where they're from? EPFL, I think, that came up with this idea of what they called the multi kernel, right? And this is a new kernel design, they're implementing it from scratch. I think they have deviced, a lot of new kernel designs choose to have device driver compatibility with Linux, simply because if you do that, you get this huge bunch of drivers that you can just use without having to rewrite them, which is a pain. But the multi kernel is kind of an interesting idea. So essentially what they said is, you know, the operating system community spent a period of time developing distributed operating systems, right? So this was kind of a movement or a period, or a couple of decades where people were really interested in trying to manage data centers or manage a large collection of machines by writing this one operating system that would somehow run all of them together, right? And in some ways that's not what we do, right? It's not how Google works. So some of these ideas never really came to fruition in terms of how we build big distributed systems for a variety of reasons. But what the multi kernel people said is they said, you know what, if you look at a modern computer, there's actually a distributed system inside your computer, right? There's several different cores and cores are now getting far apart that the latency for communication with cores is actually starting to map, right? You have all sorts of other hardware devices in your computer that themselves have processors on, right? So graphics cards have processors, right? Your NIC has a processor. Your audio card might have its own processor. Some of these are special purpose processors, but some of them are general purpose enough that you could do useful things on them, right? I mean the whole CUDA programming framework is essentially harnessing the power of these GPUs to do these large, easily paralyzable data processing tasks, so that's kind of cool. And essentially what they said is, you know what, this is happening, right? We've got this distributed system inside the machine, there's latencies between different components and we're gonna use some of the things that we've learned through the distributed operating systems experience to design better systems to manage this emerging class of devices, right? And we'll get into too many details, but this is another sort of interesting design point. So I think the overall takeaway, I mean, you might ask, like, why are we having this conversation, right? This is a big existential question, but I think the interesting thing is to try to look at the history of kernel design and try to take away some useful lessons, right? Because in some ways it's kind of funny, especially if you look at micro kernels, the history of kernel design can sometimes, I think, be mapped onto the history, the brief history of any software project, right? I sat down and started writing it and it started to work and that was cool and then at some point I realized this is terrible, right? This is just this gross blob of code and I went through this whole process where I tried to refactor everything and then that sort of effort keeps going and I keep having new ideas about how to refactor things, what are the interfaces between components? And I think that, you know, again, for you guys as programmers going out into the world, this is a useful thing to keep in mind. On the other hand, the one big caveat I think that can make the lessons of operating systems dangerous for normal programmers is that operating systems have always had to be really, really fast and if you're not designing code that has to be really, really, really fast, right? Like, if you don't go to work for some sort of, you know, a moral high-speed trading firm or something like that, right? Then the code that you write probably is okay and better if it has well-defined interfaces between components and things that are designed as simply and as straightforward a way as possible, right? And I think, you know, again, I'm gonna just get on a soapbox for three minutes. I think as programmers, most of us spend too much time writing code, too little time writing interfaces and designing code. This class is, you know, we're trying to push back a little bit on that. I wish we had time to let you guys write five-page design documents for every assignment and we have time to read them, right? But let's talk about interfaces for just two seconds because I think this will be really useful for you guys for assignment three, right? So, unfortunately, I think that Java has polluted many of our brains, at least not mine because I didn't, I was an undergraduate here so I didn't learn Java first. About what is an interface, right? So what is an interface? What does an interface define? Who wants to help me out here? What's that? An interface is a contract between who and who, between two pieces of software they wanna communicate. Yeah, Michael, your mother and something up here that sounds useful, potentially interesting. What did you say? No, no, no, I just couldn't hear you. Okay. So an interface defines how two unrelated components interact and part of the point here is that I want those two components to be unrelated, right? I don't wanna have to write every piece of software on my system and if I don't know how another piece of code behaves, if I don't have a contract that that code agrees to follow about what it does, then heck, I mean, you know, I could just, I might have my own version of the C standard library and I just have all my functions have different names and do different things. Or worse than that, all my functions have the same names and do different things, right? Like I just decided, yeah, I don't like the name of that function, I'm gonna have to do something else, right? So the biggest part of this is understanding allowing modularity to happen, right? Allowing modularity to happen by creating contracts between components and then also interfaces define the assumptions that parts of the system are allowed to make about each other, right? And I think this is the other thing that's really, really critical to understand, right? If it's not in the interface specification, even if it happens to happen, you can't assume that it's gonna continue to happen, right? So one of the ways that people get into trouble all the time at systems is they observe side effects of interface calls and they assume that those side effects are going to continue to happen and then when the implementation changes, their code breaks because it's relying on something inside of the interface, right? So good interfaces allow programmers to make correct assumptions about other pieces of code, right? That allows you a good interface, right? A good interface is kind of like there was a famous Supreme Court case about the definition of pornography and one of the judges said, you know, I can't define it, but I know it when I see it, right? And good interfaces are like that. When you're using a well-defined module, you know it, you know, it feels good, right? And when you've written a well-defined module, it feels even better, right? You're like, wow, that was really cool. Like this makes sense. This is just the right way to do this, you know? Interfaces allow implementations to change, right? This is one of the big benefits of an interface. If you write a good interface and two months later, you have a different idea. Two months later, a boss comes to you and he says, hey, by the way, you know, we love this thing that you wrote and everybody's using it now, but we, but it's, could you improve it? You know, can you make it a little faster? Can you get it to run just a little bit faster? You can say yes, and you can go do it. And in the process of doing it, you don't have to get everybody who's now using your fancy new piece of code to change the way that they use it, right? You can change the implementation entirely and make it much more sophisticated without changing the interface at all, right? And finally, it allows things to be tested and verified individually, right? And why are we talking about this, right? We're talking about this primarily because assignment three in this class is really about designing interface. If you design good interfaces for assignment three, you will find assignment three to be pleasant. If you do not design good interfaces for assignment three, you will find yourself in a lot of trouble, right? So again, my last attempt at getting people to think about the design that's due Wednesday is to just exhort you one more time, think about the interfaces for assignment three, understand the interfaces that exist on the system for managing virtual memory and think careful about the interfaces you wanna provide between components, all right? So Wednesday, we're gonna spend the week, rest of the week talking about benchmarking and performance and I look forward to seeing you then.