第五节 实现字节内存分配器

上节我们完成了从早期的页分配器到正式页分配器的切换,下面来处理字节分配器,我们将基于经典的伙伴算法(buddy)实现这个内存分配器。关于伙伴算法,简单来说,一个内存块可以分成对等大小、地址连续的两个内存块,它们称为伙伴。当进行内存分配时,如果没有正好符合要求的空闲内存块,则需要对更大的空闲内存块逐级平分,直到划分出符合要求的最小内存块;内存释放时,尝试与它的伙伴进行合并,直至不能合并为止。这个算法兼顾了分配速度和碎片化问题。它的实现原理如下图所示:

buddy内存分配

从图中可见,buddy 内存分配器的实现结构简单,就是通过数组 + 链表来维护空闲的内存块。

数组索引称 Order,每个 Order 维护一组具有相同大小的空闲内存块,它们通过链表结构连接在一起。Order 与本级所维护的空闲块大小的对应关系:空闲块大小 = 2^order^ 字节。在分配时,每一级 Order 上的内存块都可以平分为一对伙伴内存块,挂到更低一级的 Order 链表上;反之在释放内存块时,查看它的伙伴内存块是否也是空闲,如果是则合并成上一级的大块,挂到上一级 Order 的链表中。

这里提到的链表是第三章第六节引入的侵入式链表 linked_list,其特点是:链表的节点直接嵌入到空闲内存块内部。

先来看 buddy 内存分配器的主体结构 Heap:

// buddy_system_allocator/src/lib.rs
#![no_std]

use core::cmp::{min, max};
use core::alloc::Layout;
use core::mem::size_of;
use core::ptr::NonNull;

pub struct Heap<const ORDER: usize> {
    free_list: [linked_list::LinkedList; ORDER],
    used: usize,
    allocated: usize,
    total: usize,
}

impl<const ORDER: usize> Heap<ORDER> {
    pub const fn new() -> Self {
        Heap {
            free_list: [linked_list::LinkedList::new(); ORDER],
            used: 0,
            allocated: 0,
            total: 0,
        }
    }
    pub const fn empty() -> Self {
        Self::new()
    }
}

// buddy_allocator/Cargo.toml
[dependencies]
linked_list = { path = "../linked_list" }

第 10 行:free_list 的类型是长度为 Order 的数组,数组元素的类型是侵入式链表 LinkedList。对应上图的设计结构。

// buddy_system_allocator/src/lib.rs
impl<const ORDER: usize> Heap<ORDER> {
    pub unsafe fn add_to_heap(&mut self, mut start: usize, mut end: usize) {
        start = (start + size_of::<usize>() - 1) & (!size_of::<usize>() + 1);
        end &= !size_of::<usize>() + 1;
        assert!(start <= end);
        let mut total = 0;
        let mut current_start = start;
        while current_start + size_of::<usize>() <= end {
            let lowbit = current_start & (!current_start + 1);
            let size = min(lowbit, prev_power_of_two(end - current_start));
            total += size;

            self.free_list[size.trailing_zeros() as usize].push(current_start as *mut usize);
            current_start += size;
        }
        self.total += total;
    }
    pub unsafe fn init(&mut self, start: usize, size: usize) {
        self.add_to_heap(start, start + size);
    }
}

pub(crate) fn prev_power_of_two(num: usize) -> usize {
    1 << (usize::BITS as usize - num.leading_zeros() as usize - 1)
}

第 3~18 行,初始化内存分配器的核心方法 add_to_heap,根据新增的内存段的大小找到合适的 Order,插入对应链表。

// buddy_system_allocator/src/lib.rs
impl<const ORDER: usize> Heap<ORDER> {
    pub fn alloc(&mut self, layout: Layout) -> Result<NonNull<u8>, ()> {
        let size = max(
            layout.size().next_power_of_two(),
            max(layout.align(), size_of::<usize>()),
        );
        let class = size.trailing_zeros() as usize;
        for i in class..self.free_list.len() {
            if !self.free_list[i].is_empty() {
                for j in (class + 1..i + 1).rev() {
                    if let Some(block) = self.free_list[j].pop() {
                        unsafe {
                            self.free_list[j - 1]
                                .push((block as usize + (1 << (j - 1))) as *mut usize);
                            self.free_list[j - 1].push(block);
                        }
                    } else {
                        return Err(());
                    }
                }

                let result = NonNull::new(
                    self.free_list[class]
                        .pop()
                        .expect("current block should have free space now")
                        as *mut u8,
                );
                if let Some(result) = result {
                    self.used += layout.size();
                    self.allocated += size;
                    return Ok(result);
                } else {
                    return Err(());
                }
            }
        }
        Err(())
    }
    pub fn dealloc(&mut self, ptr: NonNull<u8>, layout: Layout) {
        let size = max(
            layout.size().next_power_of_two(),
            max(layout.align(), size_of::<usize>()),
        );
        let class = size.trailing_zeros() as usize;

        unsafe {
            self.free_list[class].push(ptr.as_ptr() as *mut usize);
            let mut current_ptr = ptr.as_ptr() as usize;
            let mut current_class = class;
            while current_class < self.free_list.len() {
                let buddy = current_ptr ^ (1 << current_class);
                let mut flag = false;
                for block in self.free_list[current_class].iter_mut() {
                    if block.value() as usize == buddy {
                        block.pop();
                        flag = true;
                        break;
                    }
                }
                if flag {
                    self.free_list[current_class].pop();
                    current_ptr = min(current_ptr, buddy);
                    current_class += 1;
                    self.free_list[current_class].push(current_ptr as *mut usize);
                } else {
                    break;
                }
            }
        }

        self.used -= layout.size();
        self.allocated -= size;
    }
}

第 9~37 行:当分配内存块时,逐级向上找到能满足分配的最小块,取出后不断平分下去,直至大小刚好大于申请目标的大小。此外,每次平分剩余的后半部分都挂到对应大小的链表上。

第 51~69 行:释放内存块时正好相反,先尝试与它的伙伴块进行合并,逐级向上尝试,直到不能合并为止,最后挂到对应 Order 链表上。

下面引入测试用例验证一下 buddy 内存分配器的功能:

// buddy_allocator/tests/test_heap.rs
use core::alloc::Layout;
use core::mem::size_of;
use buddy_allocator::Heap;

#[test]
fn test_empty_heap() {
    let mut heap = Heap::<32>::new();
    assert!(heap.alloc(Layout::from_size_align(1, 1).unwrap()).is_err());
}

#[test]
fn test_heap_add() {
    let mut heap = Heap::<32>::new();
    assert!(heap.alloc(Layout::from_size_align(1, 1).unwrap()).is_err());

    let space: [usize; 100] = [0; 100];
    unsafe {
        heap.add_to_heap(space.as_ptr() as usize, space.as_ptr().add(100) as usize);
    }
    let addr = heap.alloc(Layout::from_size_align(1, 1).unwrap());
    assert!(addr.is_ok());
}

#[test]
fn test_heap_oom() {
    let mut heap = Heap::<32>::new();
    let space: [usize; 100] = [0; 100];
    unsafe {
        heap.add_to_heap(space.as_ptr() as usize, space.as_ptr().add(100) as usize);
    }

    assert!(heap
        .alloc(Layout::from_size_align(100 * size_of::<usize>(), 1).unwrap())
        .is_err());
    assert!(heap.alloc(Layout::from_size_align(1, 1).unwrap()).is_ok());
}

#[test]
fn test_heap_alloc_and_free() {
    let mut heap = Heap::<32>::new();
    assert!(heap.alloc(Layout::from_size_align(1, 1).unwrap()).is_err());

    let space: [usize; 100] = [0; 100];
    unsafe {
        heap.add_to_heap(space.as_ptr() as usize, space.as_ptr().add(100) as usize);
    }
    for _ in 0..100 {
        let addr = heap.alloc(Layout::from_size_align(1, 1).unwrap()).unwrap();
        heap.dealloc(addr, Layout::from_size_align(1, 1).unwrap());
    }
}

执行 make test,测试通过!