第三节 bitmap 位分配器

(待确定)看看 [rust_index::bit_set] 能否直接引入,用于简化 bitmap 的实现。

在各种内存分配的实现方式中,基于 bitmap 数据结构来表示和管理内存块,是相对简单直接的一种。我们可以用每一位 bit 来代表一个内存块,通常 1 表示空闲可用,0 表示已分配;内存块的大小可以根据需要设定,粒度小到字节 byte,大到页面 page。

这一节我们实现一个 bitmap 位分配器,下一步它将作为正式的页分配器的核心,管理内存页的分配与释放。位分配器默认配置 1M 个位,每个位代表一个页(4K 字节),这样下节的页内存分配器可管理的内存总量就是:

1M * 4K = 4G

这足以支持我们内核的需要了。

出于对效率的考虑,我们进一步把这 1M 位以嵌套的方式进行层级化管理,形成若干级 bitmap,每一级 bitmap 包含 16 位,如图:

bitmap-allocator

从上往下,第 1 级只有一个 bitmap,它每一位指向第 2 级的一个 bitmap,共 16 个;如此嵌套,直至最底层 leaf level,leaf level 对应 1M 位,所以包括 64K 个 bitmap(1M / 16 = 64K)。进一步来说,每一级的 bitmap 的 bit 位对应管理着不同数量的连续页空间,第 1 级 bitmap 的一个 bit 位代表 64K 个连续页(1M / 16 = 64K),而最底层 leaf level 上每个 bitmap 的每个 bit 位仅对应一个页。

当搜索空闲页面时,只需要从上向下逐级递归查找,如果发现一个位是 0 时,就说明它对应的连续页空间已经被完全占用,不必再递归下一级,直接查看下一个位;否则说明其下对应的范围内还有空闲页,需要递归进去进一步查找和确认。

创建新组件 bitmap_allocator,核心数据结构的定义:

#![no_std]
use bit_field::BitField;
use core::ops::Range; 

// bitmap_allocator/src/lib.rs
pub type BitAlloc1M = BitAllocCascade16<BitAlloc64K>;
pub type BitAlloc64K = BitAllocCascade16<BitAlloc4K>;
pub type BitAlloc4K = BitAllocCascade16<BitAlloc256>;
pub type BitAlloc256 = BitAllocCascade16<BitAlloc16>;

#[derive(Default)]
pub struct BitAllocCascade16<T: BitAlloc> {
    bitset: u16, // for each bit, 1 indicates available, 0 indicates inavailable
    sub: [T; 16],
}

#[derive(Default)]
pub struct BitAlloc16(u16);

// bitmap_allocator/Cargo.toml
[dependencies]
bit_field = "0.10"

我们需要的是包含 1M 个位的 BitAlloc1M,包括它自己共由 5 级 bitmap 嵌套构成。最基础的元素是 BitAlloc16,包含 16 个位。

每一级的 bitmap 都实现了相同的 trait BitAlloc,以简化实现:

// bitmap_allocator/src/lib.rs
pub trait BitAlloc: Default {
    /// The bitmap has a total of CAP bits, numbered from 0 to CAP-1 inclusively.
    const CAP: usize;
    const DEFAULT: Self;
    fn alloc(&mut self) -> Option<usize>;
    fn alloc_contiguous(&mut self, size: usize, align_log2: usize) -> Option<usize>;
    fn next(&self, key: usize) -> Option<usize>;
    fn dealloc(&mut self, key: usize);
    fn insert(&mut self, range: Range<usize>);
    fn remove(&mut self, range: Range<usize>);
    fn is_empty(&self) -> bool;
    fn test(&self, key: usize) -> bool;
}

其中比较重要的是 insert(...)alloc(...)alloc_contiguous(...) 三个方法。bitmap 初始化时,所有位都是 0,表示没有可用的空间,而 insert 负责按照空闲空间的位置插入 1。另两个分别是单个页和连续页的分配方法。

我们需要对 BitAllocCascade16 和 BitAlloc16 分别实现这个 trait:

// bitmap_allocator/src/lib.rs
impl<T: BitAlloc> BitAlloc for BitAllocCascade16<T> {
    const CAP: usize = T::CAP * 16;
    const DEFAULT: Self = BitAllocCascade16 {
        bitset: 0,
        sub: [T::DEFAULT; 16],
    };
    fn alloc(&mut self) -> Option<usize> {
        if !self.is_empty() {
            let i = self.bitset.trailing_zeros() as usize;
            let res = self.sub[i].alloc().unwrap() + i * T::CAP;
            self.bitset.set_bit(i, !self.sub[i].is_empty());
            Some(res)
        } else {
            None
        }
    }
    fn alloc_contiguous(&mut self, size: usize, align_log2: usize) -> Option<usize> {
        if let Some(base) = find_contiguous(self, Self::CAP, size, align_log2) {
            self.remove(base..base + size);
            Some(base)
        } else {
            None
        }
    }
    fn dealloc(&mut self, key: usize) {
        let i = key / T::CAP;
        self.sub[i].dealloc(key % T::CAP);
        self.bitset.set_bit(i, true);
    }
    fn insert(&mut self, range: Range<usize>) {
        self.for_range(range, |sub: &mut T, range| sub.insert(range));
    }
    fn remove(&mut self, range: Range<usize>) {
        self.for_range(range, |sub: &mut T, range| sub.remove(range));
    }
    fn is_empty(&self) -> bool {
        self.bitset == 0
    }
    fn test(&self, key: usize) -> bool {
        self.sub[key / T::CAP].test(key % T::CAP)
    }
    fn next(&self, key: usize) -> Option<usize> {
        let idx = key / T::CAP;
        (idx..16).find_map(|i| {
            if self.bitset.get_bit(i) {
                let key = if i == idx { key - T::CAP * idx } else { 0 };
                self.sub[i].next(key).map(|x| x + T::CAP * i)
            } else {
                None
            }
        })
    }
}

impl<T: BitAlloc> BitAllocCascade16<T> {
    fn for_range(&mut self, range: Range<usize>, f: impl Fn(&mut T, Range<usize>)) {
        let Range { start, end } = range;
        assert!(start <= end);
        assert!(end <= Self::CAP);
        for i in start / T::CAP..=(end - 1) / T::CAP {
            let begin = if start / T::CAP == i { start % T::CAP } else { 0 };
            let end = if end / T::CAP == i { end % T::CAP } else { T::CAP };
            f(&mut self.sub[i], begin..end);
            self.bitset.set_bit(i, !self.sub[i].is_empty());
        }
    }
}

对于 BitAlloc16 的实现:

// bitmap_allocator/src/lib.rs
impl BitAlloc for BitAlloc16 {
    const CAP: usize = u16::BITS as usize;

    const DEFAULT: Self = Self(0);

    fn alloc(&mut self) -> Option<usize> {
        let i = self.0.trailing_zeros() as usize;
        if i < Self::CAP {
            self.0.set_bit(i, false);
            Some(i)
        } else {
            None
        }
    }
    fn alloc_contiguous(&mut self, size: usize, align_log2: usize) -> Option<usize> {
        if let Some(base) = find_contiguous(self, Self::CAP, size, align_log2) {
            self.remove(base..base + size);
            Some(base)
        } else {
            None
        }
    }
    fn dealloc(&mut self, key: usize) {
        assert!(!self.test(key));
        self.0.set_bit(key, true);
    }
    fn insert(&mut self, range: Range<usize>) {
        self.0.set_bits(range.clone(), 0xffff.get_bits(range));
    }
    fn remove(&mut self, range: Range<usize>) {
        self.0.set_bits(range, 0);
    }
    fn is_empty(&self) -> bool {
        self.0 == 0
    }
    fn test(&self, key: usize) -> bool {
        self.0.get_bit(key)
    }
    fn next(&self, key: usize) -> Option<usize> {
        (key..Self::CAP).find(|&i| self.0.get_bit(i))
    }
}

另外,对于连续分配的方法 alloc_contiguous(...),需要一个辅助函数:

pub fn find_contiguous(
    ba: &impl BitAlloc,
    capacity: usize,
    size: usize,
    align_log2: usize,
) -> Option<usize> {
    if capacity < (1 << align_log2) || ba.is_empty() {
        return None;
    }
    let mut base = 0;
    let mut offset = base;
    while offset < capacity {
        if let Some(next) = ba.next(offset) {
            if next != offset {
                // it can be guarenteed that no bit in (offset..next) is free
                // move to next aligned position after next-1
                assert!(next > offset);
                base = (((next - 1) >> align_log2) + 1) << align_log2;
                assert_ne!(offset, next);
                offset = base;
                continue;
            }
        } else {
            return None;
        }
        offset += 1;
        if offset - base == size {
            return Some(base);
        }
    }
    None
}

现在 bitmap 位分配器实现完成,它的核心就是对 bit 位的管理,我们来写几个测试用例验证一下这方面的功能:

// bitmap_allocator/tests/test_bitalloc.rs
use bitmap_allocator::*;

#[test]
fn bitalloc16() {
    let mut ba = BitAlloc16::default();
    assert_eq!(BitAlloc16::CAP, 16);
    ba.insert(0..16);
    for i in 0..16 {
        assert!(ba.test(i));
    }
    ba.remove(2..8);
    assert_eq!(ba.alloc(), Some(0));
    assert_eq!(ba.alloc(), Some(1));
    assert_eq!(ba.alloc(), Some(8));
    ba.dealloc(0);
    ba.dealloc(1);
    ba.dealloc(8);

    assert!(!ba.is_empty());
    for _ in 0..10 {
        assert!(ba.alloc().is_some());
    }
    assert!(ba.is_empty());
    assert!(ba.alloc().is_none());
}

#[test]
fn bitalloc4k() {
    let mut ba = BitAlloc4K::default();
    assert_eq!(BitAlloc4K::CAP, 4096);
    ba.insert(0..4096);
    for i in 0..4096 {
        assert!(ba.test(i));
    }
    ba.remove(2..4094);
    for i in 0..4096 {
        assert_eq!(ba.test(i), !(2..4094).contains(&i));
    }
    assert_eq!(ba.alloc(), Some(0));
    assert_eq!(ba.alloc(), Some(1));
    assert_eq!(ba.alloc(), Some(4094));
    ba.dealloc(0);
    ba.dealloc(1);
    ba.dealloc(4094);

    assert!(!ba.is_empty());
    for _ in 0..4 {
        assert!(ba.alloc().is_some());
    }
    assert!(ba.is_empty());
    assert!(ba.alloc().is_none());
}

#[test]
fn bitalloc_contiguous() {
    let mut ba0 = BitAlloc16::default();
    ba0.insert(0..BitAlloc16::CAP);
    ba0.remove(3..6);
    assert_eq!(ba0.next(0), Some(0));
    assert_eq!(ba0.alloc_contiguous(1, 1), Some(0));
    assert_eq!(find_contiguous(&ba0, BitAlloc4K::CAP, 2, 0), Some(1));

    let mut ba = BitAlloc4K::default();
    assert_eq!(BitAlloc4K::CAP, 4096);
    ba.insert(0..BitAlloc4K::CAP);
    ba.remove(3..6);
    assert_eq!(ba.next(0), Some(0));
    assert_eq!(ba.alloc_contiguous(1, 1), Some(0));
    assert_eq!(ba.next(0), Some(1));
    assert_eq!(ba.next(1), Some(1));
    assert_eq!(ba.next(2), Some(2));
    assert_eq!(find_contiguous(&ba, BitAlloc4K::CAP, 2, 0), Some(1));
    assert_eq!(ba.alloc_contiguous(2, 0), Some(1));
    assert_eq!(ba.alloc_contiguous(2, 3), Some(8));
    ba.remove(0..4096 - 64);
    assert_eq!(ba.alloc_contiguous(128, 7), None);
    assert_eq!(ba.alloc_contiguous(7, 3), Some(4096 - 64));
    ba.insert(321..323);
    assert_eq!(ba.alloc_contiguous(2, 1), Some(4096 - 64 + 8));
    assert_eq!(ba.alloc_contiguous(2, 0), Some(321));
    assert_eq!(ba.alloc_contiguous(64, 6), None);
    assert_eq!(ba.alloc_contiguous(32, 4), Some(4096 - 48));
    for i in 0..4096 - 64 + 7 {
        ba.dealloc(i);
    }
    for i in 4096 - 64 + 8..4096 - 64 + 10 {
        ba.dealloc(i);
    }
    for i in 4096 - 48..4096 - 16 {
        ba.dealloc(i);
    }
}

执行 make test,所有测试用例通过!