 Alright, welcome back. Thanks for showing up again. This room definitely looks too small. But I guess that's a good problem to have. So hopefully the camera angle is a bit better now that it's not looking up directly at me. This isn't terribly ideal, but I guess it's an improvement. When we last left off, you're all at the edge of your seat about that little executable. So we'll figure out why that works today. Just a bit of also housekeeping in the Discord. So on Quarkus, there's a link to the Discord too if you haven't joined it already. If we can set office hours, that would be great. It looks like so far Tuesday is the leading day for this section. So if you want to throw in your voice for that, because section two has outvoted you thus far. So if you don't want it Tuesday, please go in the Discord vote. And then if it still looks like Tuesday, I'll put on some time suggestions and we'll figure out what time to set them. So yeah, and also throughout the course, just another bit of housekeeping. I'll hopefully make the lectures fairly interactive and post code that you can try yourself, mess with. We'll screw it up during the lectures. So I'll just post it all on GitHub. It should work. If you're using Linux or Mac OS, if you're on Windows, well, I hope you have a virtual machine. So they'll be posted there again, right? This isn't the slides. It's a link. You can click it and all that good stuff. So to kind of elaborate on what exactly an operating system is, because, you know, that operating system had kind of that fuzzy boundary where it kind of depended on what application it was. Well, the part that is very not fuzzy is what a kernel is, which I didn't really explain yesterday. So here's the explanation. A kernel is the software that runs in kernel mode. So on your CPU, there's different privilege modes on your CPU that controls what instructions you can access. So thus far, you've been writing programs in user mode, right? You write a program in C, you compile it, you execute it, it runs in what's called user mode. So that's like kind of the general CPU operations, you're allowed to touch them. If you happen to write your own assembly by hand in your program, which I don't know why you would do that, I think I'm the only one crazy enough to do that. You couldn't access some instructions that tried to interact directly with hardware. If you tried that, you know, you'd get an exception. The operating system would yell at you and you wouldn't be allowed to do it. But the code that can actually execute those instructions have to be running in something called kernel mode. So those are all the instructions that interact directly with the hardware and it's separated so that you are not allowed to touch them and only the operating system can touch them. So the kernel is a part of the operating system that runs in kernel mode. It is the only software that is allowed to interact directly with the hardware because it's still running in user mode, but it's like a privileged user that can do more stuff. So we'll see a bit more later when we get into kind of the whole rest of file system, but pseudo stuff still runs in user mode. Yeah, I mean you could write them any other way. Yeah, I guess it might make more sense. I don't know why the convention is to do it this way. But basically, so you're in user mode, you can access your instructions like add, multiply, so on and so forth. And then in kernel mode, you can access instructions that deal directly with the hardware. And also in that mode, you can access all the instructions in user mode. So if you write kernel code, God forbid, you can still use all your normal C things, which is why the kernel is written in C. And this hypervisor thing you might notice is kind of weird and we'll get into that way later in the course. So the kernel of the operating system runs in kernel mode. It is a very important part of the operating system because that's what can talk to hardware. So there's this clear kind of separation between user space and kernel space. So so far, we've only been dealing with user space. No one has called into kernel code, at least directly that they know about even though you have. So the way to kind of bridge this boundary is with something called a system call. And that is the only way to transition from user mode to kernel mode. And all your applications had to have used this because they interact with something, they show you something on the screen, you get, you know, output from your hardware. So eventually, whatever you do in your program goes to kernel mode. And you have not been aware of it thus far. So that's kind of where this course is going to live kind of at this boundary between user space and kernel space and kind of explaining what the interfaces between them will be using. You'll be writing programs that are in user space that kind of more directly use system calls. And you'll kind of understand in this course what is going on in kernel space without having to write any kernel code because it gets kind of bad. So for right now, you can think of system calls, right? They just behave like regular functions. They're C functions, but everyone loves C, right? So they're pretty much C functions. So we're going to look at two today. So we're going to look at write, which is one of the system calls to communicate. So if we look at the signature, it takes three arguments. The first one is a file descriptor, which is just a magic number that represents something that we could write bytes to or read bytes from. In this case, since the system calls called write, we are putting bytes somewhere. So whatever number is associated with the file descriptor, that's where we're going to put bytes. And it's just an integer. So believe me, just a number. Second argument is a pointer. So this is why we all have to get used to pointers. So it's just a pointer to some data. Void star is just C's way of saying, it's a pointer, I don't care what the data is. It's just a pointer to something. So the second argument is just a memory address. And then the third argument is how many bytes to take from that address to write to that file descriptor. And error is negative, and you don't have to know that for now. The second one is called exit group that takes a status, which looks kind of familiar to how you have written C programs. Kind of looks like main, although how did you exit from a C program? Returns you. Yeah, so you just return from a main. But if what I told you is true and you have to interact with kernel mode, and this is how you like exit out of your program, you know that when you return from main, eventually whatever number you return there makes it to this exit group and just copies the number and then does that call for you. So already that's one of the things C does for you. It saves you from having to write these system calls. And like we saw in the kind of first lecture, the solution to every problem is just a layer of indirection. So if I have to write, you know, a programming language that runs on multiple kernels, well, I don't want to write system calls, I will just make up something called the C standard library, which would be a layer of indirection. And then instead of calling system calls, you could call into the C library. So that's another reason why it exists, that layer of indirection. And then exit code just has void in actuality. Your program's dead after you call exit group, so it doesn't actually return from this function. Your program's now dead. So let's go ahead and let's just execute our program again just to make sure, I rearranged it a little bit, so let's make sure we're all on the same page. So everyone can see that right at the back. Nods, okay. So if we execute our hello world, we're on Arch, right, escape bytes, so we should be able to understand it. So a file, right, a file is just a format. There's, it's just a bunch of magic, kind of. So a file is just a bunch of bytes, and the only way the operating system knows what a file is is essentially, essentially by reading the first four bytes, and if there are some magic bytes there, it knows what that file is, and then assumes the rest of the bytes in the file or follows some specified format. So on Linux, and like FreeBSD, your PlayStation, whatever, has an executable format called ELF. If you're macOS, it uses something called MockO, but for this course, we'll just stay on Linux and assume everything is ELF. So if you read, this is like an ASCII escape code, so it doesn't try and interpret the rest of the file as text, and then it's followed by the ASCII letters ELF, and that's the only thing that specifies it's an executable file. It's kind of appropriately called magic, and this is how like every single file on your computer works. You'll notice that if you change the extension, at least on Linux, it doesn't change what you can do with the file. You can call a JPEG whatever, and you can still open it. JPEG will follow the same thing like this. I even think it starts with the same byte, and then it's just like JPG, and if it starts with that, hey, it's a JPEG. I should interpret the rest of the bytes as kind of pixels. So there's some other stuff in this header file. We don't have to overly concern ourselves with it, but basically any file is just a bunch of magic. So if you want to kind of read into the executable file format a bit, I don't suggest you really do it. This is kind of just for curiosity's sake. You can use a command called read ELF that will give you all the information about it. It contains a header file which has things like what operating system should this run on? What architecture did I make this for? Most importantly, it has the entry point of the program. So what instruction should I start executing at, which will basically initialize your program counter, right? It has some other stuff in it. It has program headers that are required for executables and section headers that are required for libraries. You don't actually have to know. The long and short of it is there's a header file, and if we try and explain every byte in that 168 byte program, the header file is 64 bytes and a program header is 56 bytes. So there's kind of 120 bytes, which is most of the file. It's just some housekeeping information. The header file just says it's an executable and where to load it into memory. And you get to specify where you want to load into memory. You have complete control over it. If you see, right, it just picks something for you. But in this case, if we just write an ELF file, we have control over it. So if you read the program header, it basically just says load the entire file that the 168 bytes into memory starting an address was at like 10,000. No, so this is all virtual addresses. Yeah, so good point. So these are all virtual addresses. And you see, if I was fast, you could run multiple of these at the same time. They're all going to execute at some address, right? But they're all going to be in different places. And if you write like your web browser is an ELF file, you can read that. It has an entry point address, but you can open up many Firefoxes. And through the magic of virtual memory, which you'll see way later in the course, it just magically works. So 120 bytes are just header information that basically says what it is. And we're going to load it at address 10,000. So the next 36 bytes are the actual instructions, which is kind of what you wrote in your computer organization class, right? You did a little bit of assembly, right? Yes, I see some nods. So that's your whole assembly. So you should be able to understand the assembly, right? No problem. So there's some instructions there, which are 36 bytes. And then the last 12 bytes are for hello world. And that's just the string. So hello world has a new line character at the end. So if we load everything at address 1000, instructions would start at 120 bytes in, because it's after all that header information if we just put them in the same order. So the instruction should start at 10,000 hex 78. And then the string should start at 10,000 hex 9 C, right? Which is 156, you know, because it's after all the header files and after the instructions. So does this make sense? Hopefully a good question. So in this case, I just picked arbitrarily. So if I wanted to, I since I wrote the bytes in this because I'm sadistic, I could have put the string at the front. And if I put the string at the front, all I have to do is change these addresses, right? So I have to make sure the entry point is after the string. And then I've referenced the string, right? And zero is reserved. So zero is your null pointer. So the offerings, you'll see kind of how it organizes virtual memory essentially doesn't let you touch the first page, which is four kilobytes. So you have to at least be over four kilobytes. And sometimes it depends on the architecture too. Yep. Yeah, because we're just running on Linux. So all the user programs, everything uses virtual memory. The only way we would use physical memory is if we were writing the operating system ourselves. Or you had the embedded machine that doesn't even have a kernel or anything, right? You're just programming directly to memory. Okay. Oh, yep. Oh, so the first four bytes just says it's an elf file format. And then this first 64 bytes, including that magic, is the header file for the elf format. So it's part of it. And within the rest of the header, it'll say like what the architecture is. We can see here, let's pull it up real quick. So if we do a read elf on that executable, dash a, oops. So essentially, everything up here is all the information that is in the header file. So it says, hey, it's in two complement, little endian, right? It's version one, which it's been version one since like whenever this was invented. So the operating system is UNIX. It's an executable file. It runs on ARCH64. And there's our entry point. So it's contained within the header. So we can see we're starting at the first instruction we define. And then it also defines the size of this header and size of program header. Which is kind of weird because it's defined in the file in the format itself. So they kind of over engineered this a little bit, but you know, it's only 64 bytes. So I mean, who really cares? And then this is the program header, which you don't have to read it. Basically, it just says load everything into address 1000. You can use this read elf though on, you know, the executables that C produces, and you can see all the other magical stuff C inserts into it. Yeah. Yeah. So yeah, there's hacking you can do. So you need 64. And then you need the 56 because it has to tell it where to load in the memory. Yeah. There's like a special, there's like a blog post somewhere people really hacking this format because Linux curl doesn't like strictly follow it. And you can, there's a lot of zeros in the header. So you can actually shove all the instructions in the zeros and then reuse it. But it's like, it's super sketchy. This is like the smallest thing that actually is compliant. But we'll explore kind of what C does. But kind of as I said, we're going to be living at that system call interface. So I'm going to teach you the command that will get you through this course and even help you whenever you're programming literally anything, because any program you write is going to have to go through this interface. So if you want to kind of see what system calls your programs using, and you can have fun, you can do this on, you know, all your different programs, use a command called s trace. And it shows you that your program makes. So if we do s trace on the our little executable, spoiler, we'll see this next lecture, don't worry about it. So our program makes a right system call to magic file descriptor one, why one, because, and then the second argument is, if it's a string, it'll try and print it out nicer for you. But remember, that's just a pointer. So it shows that we're praying Hello world with a new line, and then 12 bytes. And then after that, we do exit group with zero. Also, can anyone tell me what's weird about the string as opposed to how you've been using it in C? Is there any notable difference here? So what do strings always end with in C? Yeah, null, right, a zero. C just decided that you don't have to do that, right? That doesn't have a null at the end of it. If you look at this, right, it goes all the way to the A, which is a new line. So no terminated strings are just a C convention, right? You don't actually need them. Okay, so quick aside, so we'll be using that s trace a lot, especially if you need to debug something. But just a quick aside, the difference between an API and a BI. So an API is more abstract, it just kind of tells you what something is. So for example, what I showed at the beginning was an API, right? I just showed like a right system call as arguments. I didn't give you any details if you actually had to implement it, right? Because everyone did function calls in that computer organization course, right? And you laid out your arguments somehow in registers. So how you lay out in registers goes is actually called an API. And if you need to make system calls or do C calls, there's a specific API that tells you where to put all your variables and everything like that. So yeah, the ABI is just how to lay out the data, what arguments go where, so on and so forth. So you set up some registers, made your call, and then, you know, their caller saved one, call these saved ones, which is kind of a pain, but we don't have to know those details. You've survived all those details, so you get to ignore them now. So and the C calling convention, right, instead of using registers, it uses a stack, but probably won't be relevant for this course. So the system call ABI for Linux just so you can, if you ever wanted to write this in assembly, which again, you don't have to, I'm just slightly sadistic. So you enter the kernel with an SVC instruction. And basically, this is the only interrupt you are allowed to raise in user mode. So SVC is a supervisor request, so it's like, you know, you need to call your parents or something like that. So if you execute an SVC instruction, the interrupt gets raised to the kernel, right, which in you, so it gets to handle the interrupt, and then it could go through and actually perform the system call. So before you call this SVC instruction, the calling convention is it passes arguments through registers. So the x8 register is how you specify what system call you want. So each system call just has a number associated with it. And then, you know, registers x0 through x5 are just one argument one to six. So anyone tell me what an obvious limitation of this kind of ABI is? Yeah, yeah. So if I have seven arguments, right, I'm screwed. So if you wanted to do seven arguments, right, you'd have to do the computer science solution and go through a layer of indirection. So you could just pass a pointer through one of those that had like a more elaborate data structure if you wanted to pass more data. But, you know, for this, you only have six arguments you can use. And they're also all one size, right. They're all 64 bits and you can't change it. So if you had a smaller data type too bad, you're going to waste, you know, the other bits. Oh, yeah. Yeah, so there's, okay. This is like compiler course, I'll answer it anyways. So there's like a C calling convention that uses a stack, but there's also an other C conventions that have fast calls that will use registers. And your compiler's smart enough to use those if they're available. Yeah, or sometimes they'll just get rid of a function. But yeah, but the standard no optimizations, no anything, it'll use a stack. All right. So if we plug in the next, the actual instruction bytes into a like online disassembler, it looks something like this. So all we're doing is moving values into registers. So x eight was a system call number. So if you look it up in the table for this system call 64 in decimal, which is 40 and hex is the right system call, right, we wrote it to file descriptor one. So we would put the value one into x zero. And then next we move kind of the lower bytes because this is arm, we have to do a move in two steps. So the first move instruction will zero out all the other bits and put in like 0, 0, 0, 0, 0, 0, 0, 9 C and then move K will not touch any bits except for what it's setting. So it so between these two instructions, it will set x one to 10,009 C, which is the location of our string that we saw before. And then there's a type of here that I have to fix. We move the value 12 not to C into the third argument, which is how many bytes to actually take from that memory address. And then, you know, we call home, we do an SVC and then this would do the right system call. So we're in user mode, this would prompt a interrupt to the kernel, the kernel would go ahead and handle it. It would look at all the registers, see that we're doing a right system call, execute that right system call all in kernel mode. And then what it's done, it would put the result back in some register and then return to us. In this case, when we use s trace, it returned 12 bytes that it wrote. In this case, I just kind of ignore the return value. I just assume it wrote the bytes. I don't do any error checking. I assume none of you has anyone ever error checked printf. Okay, so I'm not alone. I didn't error check anything. So printf can fail, the kernel can just refuse to write bytes if you want. And then, so that's our right system call up to this SVC. And then we do the exit group. So if we look up 94 is the number for exit group, you might be asking why is it called exit group instead of exit? And the answer to that is we'll figure that out once you know what a threat is. And then, right, it only takes one argument. So we put zero into x zero and then do a, and then do an SVC, which would exit and then kill our program. Yep. So that's the only interrupt you're allowed to raise in user mode. And then the kernel has special interrupt handlers for that code. So as soon as you raise it, the kernel will figure out, you know, it will intercept it and then handle it. In this case, it would do a system call. There's other things you can do that trigger the kernel, like do a null pointer exception or something like that, in which case, you know, you'll seg fault and then the kernel will handle that and kill your program. Yep. Yeah. So if you want to, you know, explore random system calls, you could put random values in there if you want. Weird stuff is going to happen if you don't know what you're calling. And there's only 451 of those. So there's only a few you can do. But yeah. Oh, yeah. So that's a good question. So that's for more general purpose stuff. For this, the value doesn't matter. So you could put, it just does a placeholder value in zeros as good as any other value. Okay. So if we finish up our example, we only have 12 bytes left. And that's of course, the string that's asking coded. If you have to look at ASCII strings a lot, which I feel sorry for you if you do, but if you do a quick little hack is you can always see if something's a lower case or upper case by looking at bite face was a pain unless it was just one bit. So very efficient back in the day. So that's every single bite of our program. So let's go ahead and see what C does. And I preempted this. So we already saw that, you know, in this, no terminated strings aren't a thing the kernel doesn't care about. So let's go ahead and see what C does. All right. So I just have the plain hello world, right? If I execute it, it looks like hello world. If I want to see what's really going on, what command should I throw in front of it? S trade on, let's throw an S trace. So let's see the overhead of C. So if we run S trace on what C compiled, it kind of bars, it throws a lot of stuff up at us. But at the very end, we can see here that it does exactly what we did before. It writes one to hello world 12 bytes gets, you know, 12 bytes back, who knows if it error checks, we just called printf and then does exit group. And then it does a lot of stuff beforehand. So let's see kind of what C is doing. So here's all the stuff it did. So we're starting here. So if we start up there, let's see if we can just kind of guess what it's doing. So brk, we don't know what that is, mmap, we have no idea caching is good, let's go good. The closes it, then this looks somewhat familiar, right? Anyone guess what that is? Yeah. Yeah. So that's your C standard library. So it opens the C standard library and open returns of file descriptor. So it just gives you the number three. And then you can see that it reads three. And then you can see that it's an elf file, right? So this, these four things here, it doesn't look like seven F because this is written in octal for some bizarre reason. But that seven F don't ask me why they want to display things in octal. It was cool back in the day. And luckily that's before my day too. But you can see this is the escape code and then ELF. So it's an elf file. And then it just has a bunch of information. So the C standard library is an elf file, like everything else. And that you can see. So it read something from the standard library. It does some kind of magic here with nmap. Spoiler for later in the course, mmap is kind of how you manage virtual memory. But for now, we don't care about it. There's some un-maps. We're protecting something, protecting something, closed. Set some address who cares. We don't know what any of this does. C makes things much simpler. Now if we get to the end of this, so it gets a random number for some reason. And then here, so everyone knows what a heap is, right? So like there's a stack and a heap in their standard program. So you've never had to manage your heap before, right? Yeah, everyone's used malloc. And that manages the heap for you so you can allocate stuff. So if you just write an executable yourself and you don't have the niceties of the C standard library, you have to manage your heap. So BRK actually gives you the address of your heap. So if you call it with null, it returns where your heap starts. And then this is how you allocate and deallocate. You just grow your heap down or up and that's it. So this is malloc essentially allocating space for a heap for you. And thank God you don't have to manage it because that would be awful. If you thought malloc and free was bad, try managing the heap yourself. It's truly awful. But then after all that, it goes ahead and does what we expect it to do. So and you can do this with literally any other thing. If you write hello world in any language, you can use strace on it and see what it does. I guarantee you that if you write strace on something in Python, it will be even larger than that. And it will also load the standard library. If you do it in, if you run strace on some JavaScript application, it might be a few gigabytes because yikes. So we did that. So yeah, so basically it finds the standard library, loads the standard library, then it sets up the heap and then prints the values for us. Right. But at the end of the day, the only two system calls that actually matter are the right and the exit group. So you can think of the kernel as just a long running process too. It's the only difference between a kernel and what you write is just it runs in kernel mode. But if you want to add code to the kernel, it's a bit weird because it starts before anything you can do starts, right? So if you want to add code to the kernel while it's running, you can't use main, you have to kind of in kernel parlance, you have to create a module, which is essentially kind of a library. And then you tell it what code executes when you load it, when you unload it, you can have the kernel call that code when new hardware is added and so on and so forth just to make it a bit more modular. Otherwise you can tell if something is running in kernel mode is if you update it and then you have to restart your computer, then it was probably some kernel code because otherwise you can't just restart it, right? It's one long running process that survives as long as your computer is on, right? But if you go ahead and write a kernel module and insert into the kernel, you are running in kernel mode and you are allowed to do anything you want. So you have to be careful and that's why debugging it is kind of a mess. Because if you have a null pointer exception or something like that and it's all running in kernel mode, your kernel dies, your computer dies, good luck debugging it. And you could also do weird things. You could just kill everyone's program running on the machine if you wanted to, if you touch a special file, right? There's lots of weird things you can do. So there's different ways of organizing the kernel. Like the only thing for sure is the kernel runs in kernel mode, but there's kind of a debate about how much code should actually run in kernel mode. The easiest thing to do is say anything that has to even remotely deal with hardware should run in kernel mode and that's called a monolithic kernel. And this is what Linux does. So it just puts everything in kernel space. So if there's a bug in anything, it just crashes your kernel. But luckily they're very good at writing, you know, hardware code. So it'll do things like manage what process is running, so what program's executing at any given time. IPC, which is just how processes communicate together. File systems, how actually data is laid out on the disk and device drivers will all be done in kernel mode. So the flip side of this design is this is put everything into kernel mode. And the flip side is put the least amount of things in kernel mode as possible. If one of those things has a bug and it kills your whole system, well then less code the better, right? So the other kind of flip side to a monolithic kernel is a microkernel. So a microkernel says, whoa, hold on here, let's put the least amount of things possible running in kernel mode. So at the very least, you have to manage virtual memory because there's special instructions that you can only access through kernel mode to deal with that process scheduling. So you have to be able to steal the CPU away from different processes. So you need a higher privilege level. And then basic IPC to communicate just a few bytes between programs at the very least. It doesn't have to be super involved. And then in user space, you can write your file systems in user space, device drivers and more advanced types of RPC. So this sounds like a good idea if anything in kernel mode would crash it and then just move everything to user mode. Can anyone think why this architecture might be bad? Yeah. So the privilege levels on the CPU are still the same. But the kernel is smaller. So in a way, it'd be less hard to hack. Yeah. Sorry? Slower. Yeah. Yeah. So it will be slower because we have this boundary between user space and kernel space. And we can only go through that with system calls, right? So anytime you need to support some new type of hardware or whatever, you have to add more system calls. And also, it's not free to transition from user space to kernel space. It has to go through and interrupt and go do a whole bunch of stuff. So doing system calls is actually slow. If you want to be fast about things, you just put stuff in kernel space and then hope you have written very good code. So there's other types of kernels. Those are pretty much the two extremes. Most kernels kind of fall hybrid, kind of in the middle, depending on what whatever developer feels like. So in Windows, in most kernels, some emulation services will be in kernel mode. In Windows, it's in user mode. So it's a bit closer to, it's mostly monolithic, but it has a little bit of micro kernel stuff in it. Fun fact, it also has a bit of super macro stuff in it. So back in the day, because that system call interface is slow, Windows decided to put a web server in kernel mode just to make it faster. That proved to be a very bad idea because, hey, if you hack that, you have kernel mode. So that's very, very bad. On macOS, the vice drivers run in user mode. So it's more of a micro kernel, right? It has kind of a more clear separation. If you get into research, one of the directions you could go is micro kernels are even too big. You can come up with something called a nano kernel or a Pico kernel that kind of tries to push the boundaries and have even less stuff running in the user space. Yep. You have to do process scheduling, at least in here, in kernel space, yeah, because it needs to be more privileged. And it also has to do some basic IPC. I won't go into the research stuff, like even more stuff you can take out of that. But for the purposes of this course, this is like the minimal stuff we can put into it. We won't worry about cutting in research yet. But yeah, there's different lines you can draw with kind of different trade-offs. All right. Any questions about what we saw so far today? Yep. That's a good question. What is the difference between curl and BIOS? So BIOS is what runs when you first boot on your machine and it's responsible for essentially booting your kernel. And so it will start when you start your computer, it's like the most privileged instruction mode does some initialization stuff, and then it will boot the kernel, and that will be in kernel mode. And then the kernel will initialize the rusty operating system. And then after the kernel is done initializing, we'll see when we talk about processes, what happens after that. But yeah, the BIOS will run first. It's been replaced by something called UEFI, but that's another discussion. But that runs, you know, when you first turn on your computer, that's what executes and then it's responsible for loading your kernel. And then your kernel boots up the rest of the stuff. Yep. Oh, okay. So that's a good question. So what's the difference between a hypervisor and kernel mode? So remember, in virtual machines, how you can create a virtual machine that manages like you can have multiple offering systems running at the same time. So there's special hardware support for that. That's more privileged than kernel mode. So it's managing different kernels. So they came up with this hypervisor mode. So we won't see hypervisor mode to like way later in the course. But you can see historically these ring numbers are from x86. And you can see kind of houses historically old they are. So ring zero was designed to be the most privileged level ever. Nothing can be more privileged than that. And this was like in the 70s or something like that. So it got ring zero. And obviously virtual machines did not exist back in the day. So they thought they were done. And then as soon as virtual machines came out, they're like, Oh, they need a higher privilege than kernel mode. Oh, we're at zero. So I guess we have to go into the negatives. So that's why it's called negative one. You might also see that there's two numbers also missing from here. Anyone hazard a guess as to what they might do? Yeah. Oh, that wasn't a hand that you're just. All right. Anyone hazard a guess? Yep. Close. You think about kind of between these two, what we threw out interrupts. So device drivers hardware stuff. Yeah. So back in the day, they tried to do different privilege levels. So they tried to separate out device drivers and its own privilege level at like ring one or ring two in order to have different modes. So you just didn't have, you know, user and kernel. So you have more fine grained control. No one decided to use it. So everyone was lazy, they're just like, Yeah, there's two modes. So they tried, it didn't work. And that's why there's just a user mode and a kernel mode. And I guess now a hypervisor mode, but they tried to do more fine grained design and hardware and no one, no one bought it. All right. Any other questions? Okay, well, then let's wrap up. So the important thing to know here is that, you know, the kernel is kind of what's operating between the two CPU modes. Code running in kernel mode is the kernel. So luckily that's a tautology that's easy to remember. And system calls are the interface between user mode and kernel mode and everything has to go through this. So if you write after the lecture, go, go ahead, write hello world and Python use S trace on it and just see what happens. And you can use that for any of the programs you've written, I guarantee they will all use the same interface. So we saw the file format and instructions for kind of defining a simple hello world in 168 bytes, we understood at least marginally, every single byte, the difference between API, which is just more descriptive and an API, which is like what registers you have to use. And then we saw us, our first system calls, we'll see in this course and we'll see more next lecture. And then if you have different kernel architectures that can shift what runs in kernel mode and what runs in not, typically, you know, you try and get device drivers out of kernel mode. And with that, just remember, I'm pulling for you, we're all in this together. All right.