Files
MentOS/mentos/src/mem/buddysystem.c
T
2021-10-19 14:32:08 +02:00

436 lines
16 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/// MentOS, The Mentoring Operating system project
/// @file buddysystem.c
/// @brief Buddy System.
/// @copyright (c) 2014-2021 This file is distributed under the MIT License.
/// See LICENSE.md for details.
/// Change the header.
#define __DEBUG_HEADER__ "[BUDDY ]"
#define __DEBUG_LEVEL__ 100
#include "mem/buddysystem.h"
#include "mem/paging.h"
#include "assert.h"
#include "misc/debug.h"
#include "system/panic.h"
/// @brief Cache level low limit after which allocation starts.
#define LOW_WATERMARK_LEVEL 10
/// @brief Cache level high limit, above it deallocation happens.
#define HIGH_WATERMARK_LEVEL 70
/// @brief Cache level midway limit.
#define MID_WATERMARK_LEVEL ((LOW_WATERMARK_LEVEL + HIGH_WATERMARK_LEVEL) / 2)
/// @brief Bitwise flags for identifying page types and statuses.
enum bb_flag {
FREE_PAGE = 0, ///< Bit position that identifies when a page is free or not.
ROOT_PAGE = 1 ///< Bit position that identifies when a page is the root page.
};
/// @brief Sets the given flag in the page.
/// @param page The page of which we want to modify the flag.
/// @param flag The flag we want to set.
static inline void __bb_set_flag(bb_page_t *page, int flag)
{
set_bit(flag, &page->flags);
}
/// @brief Clears the given flag from the page.
/// @param page The page of which we want to modify the flag.
/// @param flag The flag we want to clear.
static inline void __bb_clear_flag(bb_page_t *page, int flag)
{
clear_bit(flag, &page->flags);
}
/// @brief Gets the given flag from the page.
/// @param page The page of which we want to modify the flag.
/// @param flag The flag we want to test.
/// @return 1 if the bit is set, 0 otherwise.
static inline int __bb_test_flag(bb_page_t *page, int flag)
{
return test_bit(flag, &page->flags);
}
/// @brief Returns the page at the given index, starting from the given base.
/// @param instance The buddy system instance we are working with.
/// @param base The base page from which we move at the given index.
/// @param index The number of pages we want to move from the base.
/// @return The page we found.
static inline bb_page_t *__get_page_from_base(bb_instance_t *instance, bb_page_t *base, unsigned int index)
{
return (bb_page_t *)(((uint32_t)base) + instance->pgs_size * index);
}
/// @brief Returns the page at the given index, starting from the first page of the BB system.
/// @param instance The buddy system instance we are working with.
/// @param index The number of pages we want to move from the first page.
/// @return The page we found.
static inline bb_page_t *__get_page_at_index(bb_instance_t *instance, unsigned int index)
{
return __get_page_from_base(instance, instance->base_page, index);
}
/// @brief Computes the number of pages separating the two pages (begin, end).
/// @param instance the buddy system instance we are working with.
/// @param begin the first page.
/// @param end the second page.
/// @return The number of pages between begin and end.
static inline unsigned int __get_page_range(bb_instance_t *instance, bb_page_t *begin, bb_page_t *end)
{
return (((uintptr_t)end) - ((uintptr_t)begin)) / instance->pgs_size;
}
/// @brief Get the buddy index of a page.
/// @details
/// ----------------------- xor -----------------------
/// | page_idx ^ (1UL << order) = buddy_idx |
/// | 1 1 0 |
/// | 0 1 1 |
/// ---------------------------------------------------
/// If the bit of page_idx that corresponds to the block
/// size, is 1, then we have to take the block on the
/// left (0), otherwise we have to take the block on the right (1).
/// @param page_idx the page index.
/// @param order the logarithm of the size of the block.
/// @return the page index of the buddy of page.
static inline unsigned long __get_buddy_at_index(unsigned long page_idx, unsigned int order)
{
return (page_idx ^ (1UL << order));
}
/// @brief Returns the pointer to the free-area manager for the given order.
///
/// @param instance the buddysystem instance.
/// @param order the desired order.
/// @return pointer to the free-area manager.
static inline bb_free_area_t *__get_area_of_order(bb_instance_t *instance, unsigned int order)
{
return instance->free_area + order;
}
/// @brief Checks if the page is FREE and has the same order.
/// @param page the page to check.
/// @param order the oder to check.
/// @return true if the page is buddy, false otherwise.
static inline bool_t __page_is_buddy(bb_page_t *page, unsigned int order)
{
return __bb_test_flag(page, FREE_PAGE) && (page->order == order);
}
bb_page_t *bb_alloc_pages(bb_instance_t *instance, unsigned int order)
{
bb_page_t *page = NULL;
bb_free_area_t *area = NULL;
// Cyclic search through each list for an available block,
// starting with the list for the requested order and
// continuing if necessary to larger orders.
unsigned int current_order;
for (current_order = order; current_order < MAX_BUDDYSYSTEM_GFP_ORDER; ++current_order) {
// Get the free_area_t at index 'current_order'.
area = /* ... */;
// Check if area is not empty, which means that there is at least a block inside the list.
if (!list_head_empty(&/* ... */)) {
goto block_found;
}
}
// No suitable free block has been found.
return NULL;
block_found:
// Get a block of pages from the found free_area_t. Here we have to manage
// pages. Recall, free_area_t collects the first page_t of each free block
// of 2^order contiguous page frames.
page = list_entry(/* ... */, /* ... */, /* ... */);
// Remove the descriptor of its first page frame.
list_head_del(&/* ... */);
// Set the page as allocated, thus, remove the flag FREE_PAGE.
__bb_clear_flag(/* ... */, /* ... */);
// Check that the page is a ROOT_PAGE
assert(__bb_test_flag(/* ... */, /* ... */));
// Decrease the number of free block of the free_area_t.
/* ... */ -= 1;
// Found a block of 2^k page frames, to satisfy a request
// for 2^h page frames (h < k) the program allocates
// the first 2^h page frames and iteratively reassigns
// the last 2^k 2^h page frames to the free_area lists
// that have indexes between h and k.
unsigned int size = 1UL << current_order;
while (current_order > order) {
// At each loop, we have to set free the right half of the found block.
// Refer to the lower free_area_t and order.
area--;
current_order--;
// Split the block and set free the second half.
size >>= /* ... */;
// Get the address of the page in the midle of the found block.
bb_page_t *buddy = __get_page_from_base(/* ... */, /* ... */, /* ... */);
// Check that the buddy is free and not a root page.
assert(/* ... */ &&!/* ... */);
// Insert buddy as first element in the list of available blocks (free_list).
list_head_add(&/* ... */.siblings, &/* ... */);
// Increase the number of free block of the free_area_t.
/* ... */ += 1;
// Save the order of the buddy.
/* ... */ = current_order;
// Set the buddy as a root page.
/* ... */;
}
// Set the order of the page we are returning.
/* ... */;
// Set the page as root page.
/* ... */;
// Clear the free-page status from the page.
/* ... */;
#if 0
buddy_system_dump(instance);
#endif
return page;
}
void bb_free_pages(bb_instance_t *instance, bb_page_t *page)
{
// Take the first page descriptor of the zone.
bb_page_t *base = instance->base_page;
// Take the page frame index of page compared to the zone.
unsigned long page_idx = __get_page_range(instance, base, page);
// Set the page freed, but do not set the private
// field because we want to try to merge. FIXME: Add this line on zone page deallocate!
// set_page_count(page, -1);
unsigned int order = page->order;
// Check that the page is free, or that it is not a root page.
if (/* ... */ || !/* ... */) {
kernel_panic("Double deallocation in buddy system!");
}
// Performs a cycle that starts with the smallest
// order block and moves up to the top order.
while (order < MAX_BUDDYSYSTEM_GFP_ORDER - 1) {
// Get the base page index.
page = __get_page_from_base(instance, base, page_idx);
// Get the index of the buddy.
unsigned long buddy_idx = __get_buddy_at_index(page_idx, order);
// Return the page descriptor of the buddy.
bb_page_t *buddy = __get_page_from_base(instance, base, buddy_idx);
// If the page is not a buddy, stop the loop. So it should not be free
// and having the same size.
if (!(/* ... */ &&/* ... */))
break;
// If buddy is free, remove buddy from the current free list,
// because then the coalesced block will be inserted on a
// upper order.
/* ... */;
// Decrease the number of free block of the current free_area_t.
/* ... */;
// Get the page that gets forgotten, it's always the one on the right (the greatest)
bb_page_t *forgot_page = buddy > page ? buddy : page;
// Clear the root flag from the forgotten page.
/* ... */;
// Set the forgotten page as a free page.
/* ... */;
// Update the page index with the index of the coalesced block.
/* ... */ &= /* ... */;
order++;
}
// Take the coalesced block with the order reached up.
bb_page_t *coalesced = __get_page_from_base(instance, base, page_idx);
// Update the order of the coalesced page.
/* ... */;
// Set it to be a root page and a free page
/* ... */;
/* ... */;
// Insert coalesced as first element in the free list.
list_head_add(&/* ... */, &/* ... */);
// Increase the number of free block of the free_area_t.
/* ... */;
#if 0
buddy_system_dump(instance);
#endif
}
void buddy_system_init(bb_instance_t *instance,
const char *name,
void *pages_start,
uint32_t bbpage_offset,
uint32_t pages_stride,
uint32_t pages_count)
{
// Compute the base base page of the buddysystem instance.
instance->base_page = ((bb_page_t *)(((uint32_t)pages_start) + bbpage_offset));
// Save all needed page info.
instance->bbpg_offset = bbpage_offset;
instance->pgs_size = pages_stride;
instance->size = pages_count;
instance->name = name;
// Initialize all pages.
for (uint32_t index = 0; index < pages_count; ++index) {
// Get the page at the given index.
bb_page_t *page = __get_page_at_index(instance, index);
// Initialize the flags of the page.
page->flags = 0;
// Mark page as free.
__bb_set_flag(page, FREE_PAGE);
// Initialize siblings list.
list_head_init(&(page->location.siblings));
// N.B.: The order is initialized afterwards.
}
// Initialize the free_lists of each area of the zone.
for (unsigned int order = 0; order < MAX_BUDDYSYSTEM_GFP_ORDER; order++) {
// Get the area that manages the given order.
bb_free_area_t *area = __get_area_of_order(instance, order);
// Initialize the number of free pages.
area->nr_free = 0;
// Initialize linked list of free pages.
list_head_init(&area->free_list);
}
// Current base page descriptor of the zone.
bb_page_t *page = instance->base_page;
// Address of the last page descriptor of the zone.
bb_page_t *last_page = __get_page_from_base(instance, page, instance->size);
// Initially, all the memory is divided into blocks of the higher order.
const unsigned int max_order = MAX_BUDDYSYSTEM_GFP_ORDER - 1;
// Get the free area collecting the larges block of page frames.
bb_free_area_t *area = __get_area_of_order(instance, max_order);
// Compute the block size.
uint32_t block_size = 1UL << max_order;
// Add all zone's pages to the largest free area block.
while ((page + block_size) <= last_page) {
// Save the order of the page.
page->order = max_order;
// Set the page as root.
__bb_set_flag(page, ROOT_PAGE);
// Insert the page inside the list of free pages of the area.
list_head_add_tail(&page->location.siblings, &area->free_list);
// Increase the number of free block of the area.
area->nr_free++;
// Move to the next page.
page = __get_page_from_base(instance, page, block_size);
}
// Check that the page we have reached with the iteration is the last page.
assert(page == last_page && "Memory size is not aligned to MAX_ORDER size!");
}
void buddy_system_dump(bb_instance_t *instance)
{
// Print free_list's size of each area of the zone.
pr_debug("Zone %-12s ", instance->name);
for (int order = 0; order < MAX_BUDDYSYSTEM_GFP_ORDER; order++) {
bb_free_area_t *area = instance->free_area + order;
pr_debug("%2d ", area->nr_free);
}
pr_debug(": %s\n", to_human_size(buddy_system_get_free_space(instance)));
}
unsigned long buddy_system_get_total_space(bb_instance_t *instance)
{
return instance->size * PAGE_SIZE;
}
unsigned long buddy_system_get_free_space(bb_instance_t *instance)
{
unsigned int size = 0;
for (int order = 0; order < MAX_BUDDYSYSTEM_GFP_ORDER; ++order)
size += instance->free_area[order].nr_free * (1UL << order) * PAGE_SIZE;
return size;
}
unsigned long buddy_system_get_cached_space(bb_instance_t *instance)
{
unsigned int size = 0;
for (int order = 0; order < MAX_BUDDYSYSTEM_GFP_ORDER; ++order)
size += instance->free_pages_cache_size * PAGE_SIZE;
return size;
}
static void __cache_extend(bb_instance_t *instance, int count)
{
for (int i = 0; i < count; i++) {
bb_page_t *page = bb_alloc_pages(instance, 0);
list_head_add(&page->location.cache, &instance->free_pages_cache_list);
instance->free_pages_cache_size++;
}
}
static void __cache_shrink(bb_instance_t *instance, int count)
{
for (int i = 0; i < count; i++) {
list_head *page_list = list_head_pop(&instance->free_pages_cache_list);
bb_page_t *page = list_entry(page_list, bb_page_t, location.cache);
bb_free_pages(instance, page);
instance->free_pages_cache_size--;
}
}
static bb_page_t *__cached_alloc(bb_instance_t *instance)
{
if (instance->free_pages_cache_size < LOW_WATERMARK_LEVEL) {
// Request pages from the buddy system
uint32_t pages_to_request = MID_WATERMARK_LEVEL - instance->free_pages_cache_size;
__cache_extend(instance, pages_to_request);
}
list_head *page_list = list_head_pop(&instance->free_pages_cache_list);
bb_page_t *page = list_entry(page_list, bb_page_t, location.cache);
return page;
}
static void __cached_free(bb_instance_t *instance, bb_page_t *page)
{
list_head_add(&page->location.cache, &instance->free_pages_cache_list);
if (instance->free_pages_cache_size > HIGH_WATERMARK_LEVEL) {
// Free pages to the buddy system
uint32_t pages_to_free = instance->free_pages_cache_size - MID_WATERMARK_LEVEL;
__cache_shrink(instance, pages_to_free);
}
}
bb_page_t *bb_alloc_page_cached(bb_instance_t *instance)
{
return __cached_alloc(instance);
}
void bb_free_page_cached(bb_instance_t *instance, bb_page_t *page)
{
__cached_free(instance, page);
}