Power-of-Two Free Lists Allocators | Kernel Memory Allocators
The Power of Two Free Lists is an algorithm for Kernel Memory Allocation. It is used frequently to implement malloc() and free() in the user-level C library. This approach uses a set of free lists. Each list stores buffers of a particular size and all the sizes are powers of two. For example, consider the figure below there are six free lists, storing buffers of sizes 32, 64, 128, 256, 512, and 1024 bytes.
Each buffer has a header of a fixed size which leads to reduced usable area. When the buffer is free, its header stores a pointer to the next free buffer. When the buffer is allocated, it’s header points to the free list to which it should be returned. In a few implementations, it contains the size of the allocated area instead. This helps detect certain bugs but requires the free() routine to compute the free list location from the size.
When client calls malloc(), passing the required size as an argument. The allocator computes the size of the smallest buffer that is large enough to satisfy the request. This involves adding space for the header(4 Bytes) to the requested size and rounding the resulting value to the next power of two. The 32-byte buffers satisfy requests for 0-28 bytes, the 64-byte buffers satisfy requests for 29-60 bytes, and so on. The allocator then removes a buffer from the appropriate free list and the pointer in the header points to the corresponding free list. It returns to the caller a pointer to the byte instantly following the header in the buffer.
The below figure represents the conceptual view of allocated buffers and pointers to the buffers.
When the client releases the buffer, it calls the free() routine, passing the pointer returned by malloc() as an argument. The user does not have to specify the size of the buffer being freed. It is essential, however, to free the entire buffer obtained from malloc(); there is no provision for freeing only part of the allocated buffer. The free() method moves the pointer back four bytes to access the header. It receives the free list pointer from the header and puts the buffer on that list.
The allocator may handle a new malloc() request for that size in one of three ways:
- Block the request until a buffer of the appropriate size is released.
- Satisfy the request with a larger buffer, beginning with the next list and continuing the search until it finds a nonempty list.
- Obtain additional memory from the page-level allocator to generate more buffers of that size.
- The algorithm is simple and reasonably fast.
- The main goal for this algorithm is that it avoids the lengthy linear searches of the resource map method and eliminates the fragmentation problem entirely.
- The allocator also presents a familiar programming interface, with the important advantage that the free() routine need not be given the buffer size as an argument. As a result, an allocated buffer can be passed to other functions and subsystems and finally freed using only the pointer to the buffer.
- The interface does not allow a client to release only part of the allocated buffer.
- The rounding of requests to the next power of two often leaves a lot of unused space in the buffer, resulting in poor memory utilization.
- The problem becomes worse due to the necessity to store the header in the allocated buffers. For example, a 512-byte request would consume a 1024-byte buffer.
- There is no provision for coalescing adjacent free buffers to satisfy larger requests. Generally, the size of each buffer remains fixed for its lifetime. The only flexibility is that large buffers may sometimes be used for small requests.
- Although some implementations allow the allocator to steal memory from the paging system, there is no provision to return surplus free buffers to the page-level allocator.