 All right, good morning, everyone. Thanks for coming back. So hopefully this camera angle's a bit better. I tried. So that's better than looking up at my face. Some light housekeeping, so I organized the discord a little bit. So in the welcome channel, you should be able to click whatever section you're on to see that specific channel so you're not spammed with everyone. This section is actually good. And so I put a pull up in the general channel when what day you want my office hours. And if you can react to that one, just whichever day you want, it seems like Thursday is the preferred one for this section. So, and then after class, if anyone can make a consensus on a time or I can put another pull up on discord for a time, but otherwise it looks like there'll be an office hours for this section on Thursday. All right, so throughout all my lectures, I'll be posting some examples and I'll just make it publicly available on GitHub so you can just go ahead and clone it. And then if you want, you can follow around with me while I'm doing the lectures, play with it after, ask questions. We'll probably break a few things during the lectures. So before we just kind of had a brief overview of what an operating system was and it's kind of fuzzy where you actually define where an operating system starts and it kind of depends on what applications you're running. The thing that is not fuzzy, however, is the kernel. So it's like a hardware specific thing. So on your CPU, no matter what architecture you have, they all have something like this unless it's an embedded processor, in which case they wouldn't even bother. But on kind of more general purpose CPUs, so anything on your phone, your laptop, server has something like this. So they have rings to control access and it allows you to use different instructions. So some instructions are privileged and you can only use them if they're in kernel mode. So for now we can ignore kind of the middle of the circle, the hypervisor, we'll get into that way later in the course. For now you only can consider two rings, the kernel and supervisor ring, which on x86 is called ring zero and the user ring, which is called ring three on x86, on ARM and more sensibly designed architectures. It's just called user mode and kernel mode generally. So each ring can access instructions in any of its outer rings. So the least privileged ring here is the user mode ring that can access all the instructions that you compile and actually run in your programs. And then the more privileged instructions are in the kernel ring, that only the kernel can access, which interacts directly with the hardware. And of course the kernel can use any of the user mode instructions, because the kernel is just like a program like any other program. So you can think of it as this very fine defining line. So there is a user space and a kernel space. So as of right now, everybody has been in user space even if you haven't explicitly known that. So if you compile and run your program, it's running in user space, you kind of interact with the kernel, but you didn't directly interact with the kernel. And today we're gonna directly interact with the kernel. So in order to go between user space and kernel space, you can do that by essentially using something called a system call, which you can kind of think of as just a regular function and we'll see kind of what the differences are. So, and the only way to transition between user space and kernel space is through these system calls. As of the time of the lecture, so yesterday there's 451 of them and that is it. So your program can only call into kernel space and tell it to do 451 different things or access 451 different functions. So throughout this course, this is kind of where we'll be living kind of at this border. So we'll be understanding all the system calls and kind of seeing how we actually use them and how they've been hidden from you so far. And then kind of what goes on in kernel space without hacking around on it cause it gets a little bit dangerous and kind of annoying. So they behave just like regular functions, right? But they just all run in kernel mode. So we'll look at two important ones today that you've been using all the time. We'll look at write. So write takes three arguments here and it just looks like a normal C function here. So the first argument is a file descriptor and it's, you can see it's an int, it's just a number. It's just, and like we kind of saw yesterday, you might not even know what it represents. It's just a number you read bytes to or write bytes from or sorry, read bytes from or write bytes to. In this case, since the function call is called write, that would be how we can write bytes to it. So the second argument is a pointer, right? We all love pointers. This is why you're, if you don't understand pointers, you're going to need to. So it just takes a pointer to void, which is just C's way of saying, I don't care what the type is. It's just a byte of some type. So it's a pointer to some bytes. And then the third argument is a count for how many bytes to write. So all the kernel will do is whatever that pointer starts at, it will write that many bytes to that file descriptor, and then it will return how many bytes it actually writes, because it's not guaranteed that it writes all the bytes. So if you tell it to write, I don't know, 8,000 bytes, it might, the kernel might decide, okay, well I'll only write 4,000 of them at a time, and then you kind of have to adjust. Yep. Are they different? Are they different? Yeah, so the question is, is there different sizes for SS size and size T? So SS size T is just signed, so it can be negative. Yeah, so we'll kind of see this interface. If it's negative, it means there's an error. So that's kind of why there's a difference there. And the second instruction is, sorry, the second function is called exit group, and we'll see why it's called exit group instead of exit much later in the course, because there is a difference that you would not know yet. But it just takes one argument, a status, which is just a number, and all that does is then exit the program, which is a little bit different than how you've been programming. So far, right, if we're programming in C, how do we return some code back to the terminal? Yeah. Return the terminal. Yeah, we return from our main, right? So we don't type exit or call exit directly. In C, it's a little bit different. You return that error code, but now, if this is our interface to the kernel and we know everything has to pass through that interface, C is doing some nice things for us and eventually taking whatever main return and then calling exit when it's done. So that's one of the things C is doing for you. All right, so I kind of left you off on this 168 byte program running on ARM64. ARM64, so if we go ahead and execute that, we'll see, we should see that, you know, hello world. So we should see what we saw yesterday. It just prints out hello world, right? So let's see how that relates to those two kind of, those two system calls. Oh, actually even before that, let's talk about the magic of file formats. So what that, you know, 168 bytes is, it's an entire file and every kind of file behaves like this. The only thing that makes up a file is just some magic at the beginning and they literally call it magic. So the way you tell what a file is is you read the first four bytes of it. So in the case of executables, which are, you know, the format's called the ELF format. If you read the first four bytes of a file and it starts with 7F, which is like an ASCII exit code. So it kind of prevents you from reading it as a plain text file. And then it's followed by the ASCII characters ELF, all in capitals. And then that's the only way that your operating system knows what a file is, right? So it just reads the first four bytes. If it matches this, then it's executable and it will interpret it as such. And if it can't interpret it, then, you know, it just throws an error. And this is the same thing for like literally any other file. If you have a JPEG file, right? That's an image, it'll do something similar to this. I even think it starts with that same error code and just says JPEG instead, right? So the rest of what follows that doesn't really matter for the purposes of this course. I'll show you how to see that if you really want to. But that's just how pretty much every single thing on your computer works, right? There's just some magic number people make up and then you interpret it for a given format. So the ELF is a defined format and you can read the specifications if you're really, really bored. But if you want to look at your executable, so this applies to anything running on Linux, Mac OS has its own executable format called Mocko and Windows of course has a different executable format. So on Linux or any other kind of, I think FreeBSD uses it like your PlayStation would use it. You can use this command called read ELF to get all the information for that file. So if we go here, you know, to see this is a, oh sorry, can everyone see that in the back or do I need to make it bigger? No? All right, big enough? All right. So if I run read ELF on that small executable, it actually interprets it, right? So it says this is the beginning of the file, so that's our magic ELF header. So it interprets the rest of it as a executable. So some of the information contained in it, it'll tell you what operating system it's meant to run on, what architecture it's meant to run on, what it is, it's an executable file. So this is the same file format for libraries as well, so that's why there's a difference. And then it gives you a bit more information. The only one we really care about is like the entry point address. So this is kind of that layer of indirection. So in the file format, it tells you where to essentially start your program counter to start executing this program. And then this program header, we'll see later, it basically just says, hey, Colonel, load this entire file into memory, starting at address, was that 10,000, I'll say. Sorry, yeah, so an ELF file will contain a header file, and then either program headers that are for executables or section headers for libraries. You don't really need to know this stuff, this is just kind of for completeness. So how we defined our minimal executable, it just contains a single program header, and it says, hey, Colonel, load this entire program, this 168 bytes into memory, and you specify what address. So I just picked 10,000 because it makes things a bit more clear when we do some pointer arithmetic. So if you go and you dive into the file format, the header is 64 bytes and a program header is 56 bytes. So the total size of your file we can account for so far is add them together is 120 bytes. So most of the space of that executable is wasted just kind of with this header information that just instructs the Colonel what it is and what to do with it. So the actual meat of this executable is kind of the 36 bytes that are the instructions, the assembly code of something you were familiar with from other courses, and then at the end of the file is the 12 bytes that actually represents the string hello world. So if we kind of convert decimal to hex, we can see that if we take 120 bytes, that's seven, seven, eight in hex. So that's if we load their entire program into memory starting at address 10,000, the instructions would start at this address, right? And when we looked at the read-elf, we saw that that was our entry point. So we start executing at the beginning of our instructions. And then later at the end of our instructions, so if we add 120 to the size of our instructions, which is 36, we'd be at 156 and in hex, that is 9C. So the string would start at address 10,009C, right? And that would be, yeah, so we have 120 from the header, so from the header and then the program header. So that total is 120, and then the instructions take up 36. So now we're at 156, and then the string takes up 12 bytes. So the header, there's only one and there has to be one. It just describes the rest of the file. So what architecture does this run on? So on and so forth. Program headers, there can be multiple of them. For this one, there's only one and it just says, hey, load the whole entire thing into memory and it's executable code. So if you go into it and see other ones, you'll see a bunch of program headers in your file. There'll probably be space for read-only variables, read-write variables, code you're supposed to execute. For this, we just say we can execute and read everything. So that accounts for all of our 168 bytes. And now we get introduced to this wonderful utility, which you'll be making heavy use of throughout this course. It is called Strace and it actually tells you exactly what system calls your executable makes. So no matter if you write it in Python, JavaScript, C, Rust, doesn't matter because everything has to interact with the kernel through the system call layer and this tells you all the system calls you make. This will tell you exactly what any program's doing no matter what. So if we go ahead and use that to execute our file, so instead of read-elf, let's execute it using Strace, it will tell us exactly what system calls get made. So for now we can ignore this call, we'll be using it, we'll see what it does in the next lecture. But we can see here it performs a write call to one, which is remember from our function description, one is just a number that represents a file descriptor. This is, as of right now, this is just more magic that we'll kind of see later as well. And then it nicely, right, this is supposed to be a pointer but it will format it as a string if it can interpret it as a string. So it shows the string hello world and then we're supposed to write 12 bytes. And here there's a bit of interference because this is the actual output of the program. So it says hello world and then does a new line and then this is the rest of Strace, which remember it returns how many bytes actually it writes. So it means it wrote all 12 bytes, which is good. And then we call this exit group with the argument zero and then the curl tells us it exited with zero. So if we look at that, our program, we know from just looking at our program it makes two system calls, write hello world, which goes magically to our terminal and then this exit group call. Sorry? Oh, at the end of exit group? So exit group doesn't return. Yeah, so it just returns a question mark because exit group just, your program's done at that point so it doesn't return anything, the kernel just kills it. So that's why there's a question mark there. They really didn't need the equal sign there but I guess they wanted equal signs for every call. So quick, oh yep. Yeah, it does the same thing. So the only difference is whatever number you put in there by convention, you can read the number back and then by convention, zero means there's no errors and then if it's anything other than zero it means there's an error. So this is the same thing if you return one from main in C, right? It would eventually call this. If you've been learning kind of proper conventions it would just be like one means an error, zero means no errors. All right, so a quick aside about API and a BI which you might not have heard of before. An API is kind of what you're used to, it just kind of describes and abstracts away all the details. So for instance, an example of an API is just hey, a function takes two integer arguments. It doesn't say anything specific about the architecture where you're supposed to put the arguments, how you're supposed to lay them out or any details. It just says, hey, this is something called a function, it takes two integer arguments. On ABI is very, very explicit and it's tied to the architecture. So it will say how to lay out that data and how to concretely communicate. So for instance, you could say that same function with the C calling convention. So the C calling convention will place the arguments on the stack. So that same function that takes two arguments that would say, hey, to call this function, you have to put the first argument at this position in the stack and then the second argument either goes on top of it or below it in the stack and then you can make the function call, right? Have you done kind of setting up function calls in the other course? No, you wrote some assembly, right? Yeah, you did function calls? Yeah, how do you pass arguments? Just registers? Okay, so that's good you're used to that cause that's what we'll be seeing in here but C doesn't pass arguments with registers, it uses the stack. So going back to kind of your experience using registers that actually is how the system call or how you do a system call. So kind of this is the ABI which describes exactly what variables should be and what registers and how you actually make the call. So you enter the kernel with this SVC instruction on ARM and that is a way for you to interrupt the kernel so in user mode it's the only interrupt you can raise. So you're allowed to raise this interrupt in user mode and then the kernel is the one that handles it and that's how you make a system call. So you say, hey, kernel, I have stuff for you, please do this system call for me. So in order for the kernel to actually do the system call it needs the arguments in registers which is kind of what you're used to. So the ABI describes exactly what to do with the registers. So the X8 register, that's where the system call number goes. So if you want a write system call, it's a certain number. If you want a read system call or an exit group system call, it's another number. And then all the arguments follow in registers. So it's just X0, X1, X2, da, da, da, da, da and so on. So I guess if this is our calling convention are there's kind of some obvious limitations to this and who wants to tell me a big limitation of this if I'm using registers? Yep. Yeah, if I have seven arguments, I'm now screwed. But you can go around it with pointers, right? You add another layer of indirection to solve all your problems. So you could do that, but it's still annoying. And they're also fixed sized. So every register on this architecture would be 64 bits. Or sorry, it's 64 bits. So if you want something smaller, that's too bad. You're using registers, they are a fixed size. And then right in that other course, you cared about which registers were callee, save, caller, save, all that. For now, we don't have to care about that. So if we plug the 36 bytes into a disassembler, we can see it just does this. So it just populates those registers. So if you look kind of at the documentation for it, right, X8 is where the system call number goes. If you want to make a right system call on this architecture, you put in the number 64. So we put in 64 to X8. And then we do the rest of the arguments. So when we saw the right system call, the first argument was the file descriptor, it was one. So we populate X0 with one. And then next is X1 has the location of the pointer. And remember, the string starts at 10,000 and then 9C. So because this is ARM, we have to kind of play around with it. So we move 9C into the lower byte. And then we do a move K, which keeps the result there. So this would set X1 to 10,000, 9C, which is exactly where our string is. Yep. So move K, so move by itself was zero out all the other bits. And then move K will keep all the current bits there. So if I do a move, it sets 9C and zeroes all the rest. And then a move K will essentially add this to this in that register. So that's the way we have to get 10,000, 9C. Then the final argument is how many bytes to write. 2C is equal to 12, and that's the length of our string. And then this is how we make the system call here. So we just do an SVC call. It doesn't actually use the number, so we can just put in zero. Then we would get returned from the kernel, right? We don't, it actually returns that value of how many bytes it writes somewhere. But in this case, we just ignore it. We don't care at this point. And then we just kind of do our exit system call. So it's number 94, and we are putting the value zero into it, and then we just do another system call. And that's the exit group system call, and then our program's done. Yep. Sorry? So 2C is in hex. So if you do hex to decimal, it's equal to 12. Oh, 2C. It should just be C, sorry. C. Okay, need to fix that. Thank you. Good catch. Okay, so in the file format, right? The remaining 12 bytes are the hello world string itself. So we can see if we ask encoder, that's kind of what it looks like. If you really want to kind of do low level programming, a low level ASCII tip is you can always see if the character is upper or lower case by looking at byte bit five. So the values will differ by 32. So that's an easy way to convert upper case to lower case. So you can kind of see how they designed it. So now we account for every single byte in our program. So let's go ahead and see what C does. So this is where the fun begins, where we see exactly what C does. So I'll go ahead and make. And in here, right? We just create our hello world executable. It looks like it does the same thing, right? So if I want to see exactly what it does, what command should I use? S trace, right? So, whoops. If I run S trace on it, we can see. So it should pretty much do the same thing as my little executable, right? So it does a lot more. So people saying C has no overhead. Yeah, it's not real. It's low overhead, but it's not none. So let's go ahead and see what it's actually doing. But we can see here at the end, it actually does what our program does just at the very end after a bunch of other crap. So we can see that this is truly the only way into the kernel and this is how you write out hello world, right? You need a write system call and then an exit group to actually finish the program. So if you do this with whatever, you'll get the same thing. So if I wrote hello world in Python, ran S trace of it, I can guarantee you the last two things are going to be this. With Python, there's going to be a lot more crap overhead. And JavaScript is probably going to be a lot, a lot more. But at the end of the day, right? This is our interface into the kernel. You have to use this. There's no getting away from this. And it's a nice tool to use if you're debugging, right? Knowing this in offering systems, you can debug anything because you have S trace and you know what's going on. So let's start at the top to try and decipher this. Hopefully we'll be learning more at the end of the course. This is an Nmap system call. You can see it kind of doing some preload stuff here. Who knows what that's doing? It's looking at some cache. Again, who knows what that's doing? Nmap, then we can see something interesting here. Hey, this kind of looks like the C standard library. So it's opening the C standard library, right? It has to do something with that. The C standard library just doesn't magically appear and start being used. You have to actually load it. So here is it opening the C standard library. It reads and we can see that the C standard library is an L file like anything else. So when we open it, it gets file descriptor three. That's kind of the return code. And then it's reading file descriptor three. And then this is showing you what it actually reads. So we can see here, that's a weird octal notation. Don't ask me why they use octal, but that's the same exit code here. And then it starts with ELF. So it's an L file. So it's no different than our executable, except it has a lot more stuff. Then it does some more stuff with it that we don't really know. It does this Nmap, which we also don't know yet. Seems to be doing a bunch of stuff and then it closes it. So it does some stuff with the C standard library. It does some more stuff. It's protecting some memory who cares. Still doing some stuff. Now this is a call you might, has anyone ever seen this before? Probably not. When you're doing your kind of assembly stuff, did you have to manage your own heap? No? Okay, but we all know what the heap is, right? Okay, so this instruction is actually how you get where your heap is from the operating system. So BRK, don't ask me why it's named that. If you say null, it says where the top of your heap is. So this is the address of a top of our heap, which if you're C and you're managing the heap, you need to know where it is. And then this is how we actually allocate memory for the kernel and make our heap bigger. So this says, hey, make my heap bigger by 1,000. And these calls are actually made by malloc. So if you thought malloc sucked, this sucks even more. So malloc is a nice abstraction because it prevents you from doing this because it just gets a whole block of memory and then luckily for you, someone wrote malloc that manages the heap for you. But otherwise, you have to manage it yourself. So that's all kind of heap operations. And then we saw hello world with our two system calls. Yeah, so this is just for completeness. This is just our hello world in C. It's in lecture two in the examples directory if you want to play with it. But basically all the beginning of it, again, finding the standard library, loading the standard library, and then sets up the heap and then prints out our message, right? And it ends with the exact same system calls we made. We just kind of cut out the middle man, which is C. So you can think of the kernel as just a long running process. It's like any other program you would write. The only thing that's special about it is it runs in kernel mode, right? If you were to write your own kernel code, it's a bit different than what you're used to because the kernel, if you're adding code to the kernel, it's essentially already running. So you have to like inject code into it somehow. So far you've just been writing main, right? And then your execution starts at main and goes from and then goes until it returns and that's it. In the kernel, if you want to insert your own code, there's no main because essentially the kernel is already running. So you can, if you write your own kernel code, you can write something called a module and it has a explicit start function that you can say where it should start and then a function that says where it should end when it's unloaded and then the kernel gets to manage all that fun stuff from you. But thankfully you don't have to write a kernel module so this kind of thing here is just kind of for interest just in case you ever happen to write it. So where are the fuzzy definition of operating system? Well, the concrete kind of the only thing that you can say as a fact is the kernel is part of the operating system because it is the only thing that runs in this kernel space and that's reserved specifically for the operating system. So it gets a bit worse in that the amount of stuff that runs in kernel mode differs depending on who you talk to and what the architecture of the kernel is. So the kind of simplest design is something called a monolithic kernel where you put anything that's kind of related to the operating system inside the kernel into kernel space. So the kernel has to manage virtual memory, it has to manage process scheduling, has to manage inter-process communication so how to processes talk to each other, has to have file systems and device drivers and they all run in kernel mode. Kind of the disadvantage of this is if anything breaks in kernel mode, right? We all written a program that has a segfault. Well, if your kernel segfaults, your computer is dead. So the more code you have in here, one belief is the worse it gets because if it crashes, your whole system goes down. So maybe you don't want the device drivers running in kernel mode if you can help it. So kind of the opposite, oh, yep. Inter-process communication. So that's just how two things essentially talk to each other which we'll get into more definitions of IPC later in the course. But yeah, the flip side of that is hey, if one option is put as much crap in the kernel mode as you can, the opposite would be hey, let's keep kernel, the kernel as light as possible and have essentially nothing in it except all the stuff that is absolutely necessary. So things that are absolutely necessary in kernel space is virtual memory that's like a protected CPU thing that only the kernel can manage in kernel mode and then process scheduling, right? You need something that manages what essentially what programs are running at what time and that has to be in kernel mode because it has to have higher privilege than the rest of them so it can just interrupt them and take the CPU away from them. And then you have to have some basic inter-process communication in kernel space. But you can move other stuff into user space so you can move like file systems up in there so they don't run in that kernel mode. You can move device drivers or kind of more advanced IPC which we'll see way later in the course but you can think of things like, more like networking stuff like that or kind of more exotic things. So there's, yep. Yeah, so Linux is a monolithic kernel so it just throws everything and it runs in kernel mode. Example of a kind of more micro kernel is something called Minix which actually runs on like the Intel, the operating system runs on Intel chips that are like doing all the security stuff. Micro kernels are basically for like small embedded things. So, and yeah, talking about other kernels, there's like, yep, Windows, okay, yeah, I'll talk. Windows is kind of a hybrid so there is kind of a middle ground so that's kind of between a monolithic and micro kernel. So Windows is kind of a hybrid kernel. It has some weird stuff in the kernel. For example, it has emulation stuff in the kernel which isn't even, it's like more monolithic than monolithic kernel. Windows at one time also had like a web server in kernel mode which was kind of weird but Windows is closer to monolithic kernel. On MacOS, it's closer to a micro kernel where device drivers run in user mode instead of kernel mode but the line kind of gets fuzzy of where you want to put stuff. Yeah, so it's basically, basically if something in kernel mode crashes the whole thing crashes. So if your file system crashes maybe you don't want the whole kernel to crash but just to make things easier, every time we go from user mode to kernel mode we have to do a system call, and system calls if you have to do a lot of them can be slow so you can imagine device drivers would have to make a lot of system calls if it's interacting with the hardware so it's a little bit slower to also kind of do this micro kernel thing while if everything runs in kernel mode you don't have to do that transition and everything's fast so that's kind of one other main reason why you would do that. So you can also, if you get into operating system research you can actually go more extreme so people believe micro kernels are too heavy so there's something called nano kernels and Pico kernels, right? That's research related that we won't really get into but if you're interested in operating system research that's kind of one direction you can take. So there's lots of different trade-offs you can take, the only thing that's iron cloud is there's a system call boundary. There's, if you have a micro kernel and that makes it slow you can kind of, there's techniques you can use to kind of speed it up you can batch a whole bunch of stuff so you don't make little calls all the time and other techniques you can use. All right, any questions on anything we've seen so far? Yep, so a kernel is just the code that runs in kernel space. So it's like a specific mode of your CPU so back here, right? There's different modes on your CPU that limit what instructions you can have access to. So kernel mode, the instructions here are typically that interact directly with hardware. Yeah, so that's a spoiler for much later in the course but essentially all your virtual machine stuff is running in that mode so fun fact, why it's negative one is because virtual machines didn't exist back when they first made this processor so they thought this was the most privileged mode so they called it ring zero and then virtual machines were kind of invented so they can't go lower than zero so they had to go negative. Yeah, so you'll notice that ring one and two are missing so remember the difference between micro kernels and monolithic kernels is some put the vice drivers inside and outside what they thought before is they made ring one and ring two only have some specific hardware for that so that you could write the vice drivers that ran in ring one and ring two instead of having to go all the way to kernel mode and maybe you would be a little bit safer. People thought four things were too complicated when two suffices so no one used ring one and two which is why they're kind of missing here so there was an attempt to try and make people write device drivers in a different CPU mode but no one used it so BIOS fits in so BIOS runs at the most privilege level as soon as you turn on your computer and then boot process goes in and then it passes it to the kernel so I don't even know in this course if we'll get into the boot process but the BIOS will run at the most privilege level and it's responsible for starting your operating system. It would be negative one if it's supported so whatever the highest level is on your CPU so in this case it would be hypervisor to communicate yes so at the very least you have to call exit on your program and that has to go through the system call layer. If you don't call exit and you just point out a random instruction your program's just going to seg fault and then the kernel handles it anyways so there's no getting away from the kernel the kernel is the thing that manages everything and it's the only one that runs in the CPU mode. All right any other questions? Yep. Sorry? Oh the heap so that's where you have dynamically sized memory on your machine so on some architectures right there's like a stack that grows up and then the heap that kind of grows down and that's where if you malloc memory it's allocated on the heap somewhere so that system call just kind of manages that heap so you can grow it and you can shrink it. Okay cool any other questions? So this kind of hopefully demystifies some of your other courses and kind of puts your assembly. Your assembly wasn't useless because you could make up this example if you wanted. All right so to kind of wrap up so the kernel is the thing that operates the kernel interfaces between the CPU mode boundaries which are ironclad and you can't really change so the kernel is going to run in kernel mode that's kind of the definition of it and then what runs in kernel mode depends on the architecture of your kernel but it has a few basic things it needs but no matter what there's gonna be a system call interface and that's the way to switch between user mode and kernel mode and that's the only way to switch between them right. Every single program no matter what has to use this interface. We saw kind of the file format to define a simple hello world program in 168 bytes and 120 of that was just a bunch of header files or sorry header information which was mostly zeros if you actually look into it. We saw the difference between an API and an ABI so API is just descriptive and an ABI is kind of what you saw in your architecture course where if you're making a function call you have to say what variables go and what registers and you have to be very explicit about how to make a function call and then we saw how to explore system calls so if you don't get anything else from this lecture get S trace so you can go ahead and have fun try running Python hello world, Java hello world see all the other system calls it makes you can compare that to C you'll probably see that even with Python it will probably load the C standard library so you'll be able to spot that too and then finally there's different kernel architectures just kind of shift how much code runs in kernel mode so with that I'll just stick around for some questions and remember I'm pulling for you we're all in this together.