 All right, good afternoon. Welcome back to your operating systems. So first bit of housekeeping. Course evaluations are out. I please fill them in because my job depends on it. So there's also a bunch of other programs at the university. You have a bunch of great TAs in the course. So if you wanna nominate them for things, you can. They would definitely appreciate that. I think there's other things so you can go ahead but please fill them out because that's like the feedback the university sees. So went to try and restructure the course so hopefully it worked out. Feedback helps new iterations of the course. Hopefully it's new iterations you don't have to see. So I like having you guys but I don't wanna see you guys again. So we'll try and pass the course. So with that, we're kind of winding down on the content. So today and tomorrow we'll have new content. Past that will probably just be review or open office hours because you'll probably be slammed with other courses. And may as well, we already did the heavy lifting at like the first part of the course where we didn't understand what the hell was going on but we have a pretty good grasp of it now hopefully. And yeah, I'll also do the early marking of lab six just so you can have a clear conscience about that. So I might do it today at some point and then definitely do it again Wednesday or whenever I can remember to run it. But today we are going to end up with end in allocators. So we talked about some general allocators before that you would do for malloc if you do happen to have to implement malloc but we'll talk about some allocators that are actually used. The slab allocators basically what you're doing in lab six your bitmap because everything's the same size and we'll see the buddy allocator because it's got a sweet name and also it works out quite well and the kernel actually uses it. So we were talking about the general problem where allocations are just random sizes that you have to deal with. Well, the buddy allocator is not as general as that. It actually restricts the problem. So whenever you request memory it gives you back a block in a size of a power of two which typically works pretty well for computers. And if you're something like the kernel or something like that where you control all your allocations you can make sure you allocate everything in size powers of two so everything works out nice. So typically this is how allocations work anyways. So the kernel uses that. So the only blocks the memory allocator gives you are of size powers of two. So it won't return you 37 bytes. No weird sizes are all gonna be powers of two. And if we restrict our problem to just be powers of two while we can do fast searching we can do fast merging and we can do things really, really simple gives us a nice algorithm and our only sacrifices were not as general as we could be. So the most basic strategy for implementing a buddy allocator is to use multiple lists. So before when we were talking about free list we just had one general free list and that free list kept track of how big the holes were what size allocations it can actually do so on and so forth. So for buddy allocator instead of having one general free list we have a free list for each power of two. So you restrict the request to be powers of two so if you wanna formalize it a bit more all the requests are gonna be two of the K where K is some number, some whole number or integer where it'd be zero up to N and N is the greatest thing you can support. So if capital N was 10 then you could support one kilobyte which isn't that much so typically it would be much larger than that. So no matter how many levels you have your implementation uses N plus one free list so it has a free list for each specialized size of a block and now our algorithm becomes really simple well if there's a request of two of the K well we just search the free list until we find a big enough block. So if the request comes in it's two of the K so it's two to the eight something like that while we would search the free list for the two to the eight free list where we know everything is like 256 bytes we would search through that if that doesn't have any free blocks which means we can't perfectly allocate it to a block then we're just going to search the next biggest one because we can't search the next smallest one. So we would search to the power of nine or 512 then if that fails we go up another level so another power of two, do, do, do, do until we finally find a free block and then what you do as soon as you find a free block you recursively divide it in half until it's the correct size. So if I find something that's like to the power of to the power of 10 and I actually want to the power of eight while I would take that to the power of 10 blocks split it into two to the power of nine blocks and then take one of those to the power of nine blocks and split it into two to the power of eight blocks and then use one of the two to the power of eight blocks. So the idea behind this is that since we're recursively dividing it and it's powers of two so each entry we take and we divide it into two, they're actually buddies so that's where the buddy comes from because they are beside each other like they're contiguous addresses so essentially you have a buddy so someone was broken apart with you, your twins whatever you want to think of it as and then for de-allocations the hardest part if you actually implement the free list that we saw yesterday is actually coalescing memory together so if you free something that's beside another free block and you want to make a larger free block well that involves a lot of searching sometimes and it's kind of a pain if you actually go to implement it while if you have this buddy algorithm and you de-allocate something well you can check if the buddy is also free so if you get freed and your buddy is free you know ahead of time that you're contiguous so you can just coalesce them back to one bigger block and add it to the free list that is bigger than you and you can also do this recursively so if that made his buddy available then you can coalesce it, go back up and merge everything back together so there's a recursive solution for doing the allocations and the de-allocations so this is what it would look like so for this picture we'll assume that our buddy allocator is managing 256 bytes so at the top so anything that has a dark background up there is just for illustration it's what the original block was and it means it's not used and it's not free it's just an illustration of what the buddy blocks look like so if it is managing 256K or 256 bytes well if it needed to do some allocations so here there's a natural allocation for 32 bytes and 64 bytes well at some point it would have had to broke apart this 256 into two 128's that are buddies of each other and then it broke apart both 128's into 264 and then in one case one of the 64's was broken into two so that it could actually do an allocation of size 32 so anything with a colored background means it's allocated and the program's actually using it for something so there's a 32 byte allocation that's in use there's a 32 byte block free that is of size 32 and for 64 bytes there's 264 byte blocks free and then one of the 64 byte ones is actually used by an allocation so if you were to draw this out too and had a free list so you would have a free list for every level so the free list for this top level of 256 it would have no entries because nothing is free for 128 it would also have no entries because everything is free for 64 bytes it would have two entries so there's 264 byte blocks available and for 32 bytes there's one entry available yep, yep, yeah so the starting configuration here would be that there's one you'd start off with one 256 byte block in the free list and then that's it so you start off with one entry in the free list the total amount of memory you're managing and then all the other free lists are just empty so whenever there's an allocation you can go ahead and recursively break it, yep so the 64 byte one has two blocks available because of this one here and this one so there's only two things available at that level because the blue box is like a natural allocation and then this one with the dark background was broken into two to do the 32 byte ones so 128 has zero available so it was broken apart into a bunch of 64 byte ones yep, yeah so ideally we would want the blue 64 byte one here maybe but this is after the programs run for a bit so you can't determine anything like that so you'd probably end up with a situation that looks more like this when you're actually running a real program but yeah, you're right ideally it would be allocated to the left so its buddy is already in use so if both of them got deallocated we could just go ahead and do it okay so now let's assume we have an allocation someone request an allocation of size 28 bytes well if someone allocate or request 28 bytes I can't do that because it's a we only deal in powers of two so the easy thing to do is well we round it up to the next power of two so what's the next power of two to 28? 32, yeah so I'll round that request up to 32 and I will start searching for blocks that are 32 and I'm going to lose four bytes due to internal fragmentation so because the allocator only deals with blocks powers of two so A, the wasted space is within a block so that would be internal fragmentation so in this case I would be looking for anything of size 32 because that is free and right now this I have a free block of size 32 so I don't have to worry about splitting buddies or doing anything like that I could just go ahead and make the request and I'll illustrate it in green so now the interesting thing comes in if we have another request for size 32 currently if I look if I had a free list beside 32 I have no 32 blocks free so I would have to look up to the next power of two so I would have to look up I see oh I have 264 byte blocks free so I should just split one in half and then I can make a 32 byte entry so I'd pick the one on the left for example I would split it into two 32 byte ones one would be left free because it's the buddy I just made and then we would fulfill that 32 byte request so any questions about that? Okay well then what will happen if we free the 64 byte block one then so which is the one in blue or teal or whatever you want to call that color yeah so I would free this one so if I free the 64 byte one well now it's free and as part of the algorithm you can check your buddy so it's like the buddy system or anything else that's called like that check if it's also free in this case it's also free so I would just coalesce them back to a big 128 byte block so now if a request comes in for 128 I can go ahead and fulfill that and that's where we would be at so any questions about how this works at all so yeah this is an easier way to coalesce memory everything has a partner everything has a buddy so you're essentially guaranteed that if the buddy's free well you can coalesce the memory back together because hey you're friends right you're twins so these are actually used in the Linux kernel so buddy allocators are used in pretty much every kernel I know of and it's because it has some very nice properties it's really fast especially compared to the general dynamic memory allocation where you have to worry about different sized free blocks maybe you have to search the entire free list to see if something is free here you can just check a list that's the right size and then know whether you have to go up or go down it's essentially logarithmic which we all like logarithmic algorithms so you don't have to check every single possibility you only have to check a log of possibilities so it also avoids external fragmentation because all the free physical pages everything's contiguous everything has a buddy so you're always guaranteed to be able to coalesce everything every block of the same level is the same size so you have no issues with that and the disadvantages is well there's going to be internal fragmentation so if your requests aren't in powers of two then you are screwed because you only deal with powers of two so you're going to be wasting some memory because every request we're going to round up so we're always going to have that but if you're something like the kernel or you implement this for your own program if you really really want to do your own memory allocation well you do all of your own allocations so if it's not a power of two you can just not do that you can choose to not do that if you're the only one using the allocator so the next one is a slab allocator and this is basically the approach for lab six so slab allocators take advantage of the fact that if all your allocations are the same size everything's the same fixed size well we can just allocate all objects from a dedicated pool and all the structures of that type are the same size so if every object is the same size one slot is as good as any other slot so it doesn't really matter and you could extend this so you have every object type has its own pool of blocks with the correct size so for lab six you essentially have a slab that keeps track of what blocks are allocated so it keeps track of everything of size block size which for you is one kilobyte and then there's also a bitmap that keeps track of all the inodes and all the inodes are the same size so all your inodes are like 128 bytes so they're all the same size so one slot in the table is as good as any other slot and you only need one bit to keep track of your inodes if it's used or not so if you extended this so your file system essentially just has two types of objects it has blocks and inodes but if you did this for your own program well you could do that same approach for every object that has the exact same size so you could just have a pool that I don't know say you just put aside a megabyte or something and all your objects are of size I don't know 16 or even just a weird number while you can just allocate objects in and out of that one megabyte of memory and manage that one megabyte of memory yourself just using something like a bitmap so you can kind of think of like a slab it's just like a cache of slots and all the slots are the same size so your inode table in lab six that's like your cache of inodes so there you essentially have 128 inodes of size 128 size so everything is the same size one thing is as good as any other slide so instead of a link list so you'll note in your file system there's no link list to keep track of allocations there's just a bitmap and what a bitmap is just so you have it here in the slides as well it's just one bit corresponds to one slot and zero means that slot is free and usable and one means that slot is allocated and you shouldn't use it so if you need to allocate something well you would just search that bitmap and search for a zero anywhere and if you find a zero anywhere you find where that bit corresponds to in terms of like inodes for example and then you would return that slot and set the bit to one and then for deallocations they're really really simple you just set one bit from zero to one and then you can just reuse that slot you don't even have to zero it out if you don't care about security at all or anything like that so that's what your inode table basically is and your operating system would actually like your kernel knows how to use that file system so if any of you are far enough in that lab where you actually mount your file system and can see it well it's just a normal file system so if you wanted to you can create files on there you could put you could copy some of your lab there it's not big enough to fit everything probably but you can copy files and everything and then look at your file system in that file afterwards and find that hey the kernel changed it because it added files to the file system because you edited a file there or something like that and you can also have combinations of this too so a slab could be implemented on top of a buddy allocator where you essentially use a buddy allocator to manage huge swaths of memory and then you treat each one you give each one like a bitmap and then slabs of different sizes so like this is what it could look like if you're using a buddy on top of a slab so say you have two objects of size A and B well what you might do is you might allocate a large slab that you use for multiple objects of that type and you might allocate that from the buddy allocator so you might ask the buddy allocator for a large amount of memory so say I want one megabyte I want two megabytes something like that and then you fit all of the objects on that big block of memory from the buddy allocator and you just have a bitmap and then from that bitmap you have have or may slots fill that so in this case the slab one A would be from the buddy allocator and then it would have a bitmap in this case it's just keeping track of four objects of size whatever and then we would have another slab that would be from the buddy allocator that has another four slots in it that would also have a bitmap to keep track of whether they are free or not and then you could have slabs B1 or B2 that have larger objects so they again would be from the buddy allocator and then you're managing the big blocks of memory you get from the buddy allocator so in this case I can fit only two and in this case the red ones mean that they're allocated and the clear ones mean that hey they're free you can use them so you might notice a bit of a weird thing here that hey if I have two slabs for A1 and A2 I should probably just coalesce them together into one big slab and I might also be able to fit another A here so instead of having eight in this example I might be able to fit a ninth one if I don't have separate slabs so that's also something you could do but you would have to do your own coalescing and manage an even bigger block of memory so it's just kind of up to you however you implement this so in this example we don't we have two separate blocks for A's objects and within a little slab here there's some internal fragmentation because again we used a buddy allocator for this so any questions about this? Cool. All right well that's even more memory allocations so we blasted through this so the kernel, the kernel has to do its own memory allocation but it doesn't have to go as general as if you have to deal with user programs that use malloc so what the kernel actually does is something called the buddy algorithm which is real world restricted implementation so it only deals with powers of two and keeps a free list for each individual powers of two and has very nice properties where because we just split everything in two and we have powers of two well everything has a buddy so if we need to merge and make a bigger block well if we split off from a buddy we know we can do that without any complicated checking or anything like that and then the next thing we saw is the slab allocator that takes advantage of having fixed size objects to reduce fragmentation the nice thing about that is if everything is of the same size well you don't have any fragmentation because that was one of our conditions for fragmentation and keeping track of the allocations is really really fast, really simple we don't need link lists at all we can just use a bitmap where just one bit keeps track of whether something is free or not so with that I will just stick around and we can work on lab six or I'll deal with any lab six questions or any other questions you have but just remember, pulling for you we're all in this together