浏览代码

Initial commit: add design document, memory book keeper, allocation, deallocation, reallocation (inplace and copy), block list, insertion, system primitives

ticki 8 年之前
当前提交
c28a6f006b
共有 7 个文件被更改,包括 663 次插入0 次删除
  1. 2 0
      .gitignore
  2. 10 0
      Cargo.toml
  3. 129 0
      design.txt
  4. 88 0
      src/block.rs
  5. 231 0
      src/block_list.rs
  6. 20 0
      src/lib.rs
  7. 183 0
      src/sys.rs

+ 2 - 0
.gitignore

@@ -0,0 +1,2 @@
+target
+Cargo.lock

+ 10 - 0
Cargo.toml

@@ -0,0 +1,10 @@
+[package]
+name = "ralloc"
+version = "0.1.0"
+authors = ["ticki <ticki@users.noreply.github.com>"]
+
+[dependencies.extra]
+git = "https://github.com/redox-os/libextra.git"
+
+[target.'cfg(unix)'.dependencies]
+syscall = "0.2.1"

+ 129 - 0
design.txt

@@ -0,0 +1,129 @@
+
+
+Allocate.
+=========
+
+We start with our initial segment.
+
+   Address space
+  I---------------------------------I
+B
+l
+k
+s
+
+We then split it at the aligner, which is used for making sure that the pointer is aligned properly.
+
+   Address space
+  I------I
+B   ^    I--------------------------I
+l  al
+k
+s
+
+We then use the remaining block, but leave the excessive space.
+
+
+   Address space
+  I------I
+B                           I--------I
+l        \_________________/
+k        our allocated block.
+s
+
+The pointer to the marked area is then returned.
+
+Deallocate
+==========
+
+
+   Address space
+  I------I
+B                                  I--------I
+l        \_________________/
+k     the used block we want to deallocate.
+s
+
+We start by inserting the block, while keeping the list sorted. See `insertion` for details.
+
+
+   Address space
+  I------I
+B        I-----------------I
+l                                  I--------I
+k
+s
+
+Now the merging phase starts. We first observe that the first and the second block shares the end and the start respectively, in other words, we can merge these by adding the size together:
+
+   Address space
+  I------------------------I
+B                                  I--------I
+l
+k
+s
+
+Insertion
+=========
+
+We want to insert the block denoted by the tildes into our list. Perform a binary search to find where insertion is appropriate.
+
+   Address space
+  I------I
+B < here                      I--------I
+l                                              I------------I
+k
+s                                                             I---I
+             I~~~~~~~~~~I
+
+If the entry is not empty, we check if the block can be merged to the left (i.e., the previous block). If not, check if it is possible to the right. If both of these fails, we keep pushing the blocks to the right to the next entry until a empty entry is reached:
+
+   Address space
+  I------I
+B < here                      I--------I <~ this one cannot move down, due to being blocked.
+l
+k                                              I------------I <~ thus we have moved this one down.
+s                                                             I---I
+             I~~~~~~~~~~I
+
+Repeating yields:
+
+   Address space
+  I------I
+B < here
+l                             I--------I <~ this one cannot move down, due to being blocked.
+k                                              I------------I <~ thus we have moved this one down.
+s                                                             I---I
+             I~~~~~~~~~~I
+
+Now an empty space is left out, meaning that we can insert the block:
+
+   Address space
+  I------I
+B            I----------I
+l                             I--------I
+k                                              I------------I
+s                                                             I---I
+
+The insertion is now completed.
+
+Reallocation.
+=============
+
+We will first try to perform an in-place reallocation, and if that fails, we will use memmove.
+
+   Address space
+  I------I
+B \~~~~~~~~~~~~~~~~~~~~~/
+l     needed
+k
+s
+
+We simply find the block next to our initial block. If this block is free and have sufficient size, we will simply merge it into our initial block. If these conditions are not met, we have to deallocate our list, and then allocate a new one, after which we use memmove to copy the data over to the newly allocated list.
+
+Guarantees made.
+================
+
+1. The list is always sorted.
+2. No two free blocks overlap.
+3. No two free blocks are adjacent.

+ 88 - 0
src/block.rs

@@ -0,0 +1,88 @@
+use std::{ops, cmp};
+use std::ptr::Unique;
+
+pub struct Block {
+    pub size: usize,
+    pub ptr: Unique<u8>,
+}
+
+impl Block {
+    pub unsafe fn end(&self) -> Unique<u8> {
+        Unique::new((self.size + *self.ptr as usize) as *mut _)
+    }
+
+    pub fn left_to(&self, to: &Block) -> bool {
+        self.size + *self.ptr as usize == *to.ptr as usize
+    }
+}
+
+impl PartialOrd for Block {
+    fn partial_cmp(&self, other: &Block) -> Option<cmp::Ordering> {
+        self.ptr.partial_cmp(&other.ptr)
+    }
+}
+
+impl Ord for Block {
+    fn cmp(&self, other: &Block) -> cmp::Ordering {
+        self.ptr.cmp(&other.ptr)
+    }
+}
+
+impl cmp::PartialEq for Block {
+    fn eq(&self, _: &Block) -> bool {
+        false
+    }
+}
+
+impl cmp::Eq for Block {}
+
+#[derive(PartialEq, Eq)]
+pub struct BlockEntry {
+    block: Block,
+    pub free: bool,
+}
+
+impl ops::Deref for BlockEntry {
+    type Target = Block;
+
+    fn deref(&self) -> &Block {
+        &self.block
+    }
+}
+
+impl ops::DerefMut for BlockEntry {
+    fn deref_mut(&mut self) -> &mut Block {
+        &mut self.block
+    }
+}
+
+impl PartialOrd for BlockEntry {
+    fn partial_cmp(&self, other: &BlockEntry) -> Option<cmp::Ordering> {
+        self.block.partial_cmp(other)
+    }
+}
+
+impl Ord for BlockEntry {
+    fn cmp(&self, other: &BlockEntry) -> cmp::Ordering {
+        self.block.cmp(other)
+    }
+}
+
+impl<'a> ops::AddAssign<&'a mut BlockEntry> for BlockEntry {
+    fn add_assign(&mut self, rhs: &mut BlockEntry) {
+        self.size += rhs.size;
+        // Free the other block.
+        rhs.free = false;
+
+        debug_assert!(self.left_to(&rhs));
+    }
+}
+
+impl From<Block> for BlockEntry {
+    fn from(block: Block) -> BlockEntry {
+        BlockEntry {
+            block: block,
+            free: true,
+        }
+    }
+}

+ 231 - 0
src/block_list.rs

@@ -0,0 +1,231 @@
+use block::{BlockEntry, Block};
+use sys;
+
+use std::mem::{align_of, size_of};
+use std::{ops, ptr, slice, cmp};
+use std::ptr::Unique;
+
+pub struct BlockList {
+    cap: usize,
+    len: usize,
+    ptr: Unique<BlockEntry>,
+}
+
+fn aligner(ptr: *mut u8, align: usize) -> usize {
+    align - ptr as usize % align
+}
+
+fn canonicalize_brk(size: usize) -> usize {
+    const BRK_MULTIPLIER: usize = 1;
+    const BRK_MIN: usize = 200;
+    const BRK_MIN_EXTRA: usize = 500;
+
+    cmp::max(BRK_MIN, size + cmp::min(BRK_MULTIPLIER * size, BRK_MIN_EXTRA))
+}
+
+impl BlockList {
+    pub fn alloc(&mut self, size: usize, align: usize) -> Unique<u8> {
+        let mut ins = None;
+
+        for (n, i) in self.iter_mut().enumerate() {
+            let aligner = aligner(*i.ptr, align);
+
+            if i.size - aligner >= size {
+                // Set the excessive space as free.
+                ins = Some((n, Block {
+                    size: i.size - aligner - size,
+                    ptr: unsafe { Unique::new((*i.ptr as usize + aligner + size) as *mut _) },
+                }));
+
+                // Leave the stub behind.
+                if aligner == 0 {
+                    i.free = false;
+                } else {
+                    i.size = aligner;
+                }
+            }
+        }
+
+        if let Some((n, b)) = ins {
+            let res = unsafe {
+                Unique::new((*b.ptr as usize - size) as *mut _)
+            };
+
+            if b.size != 0 {
+                self.insert(n, b.into());
+            }
+
+            res
+        } else {
+            // No fitting block found. Allocate a new block.
+            self.alloc_new(size, align)
+        }
+    }
+
+    fn push(&mut self, block: BlockEntry) {
+        let len = self.len;
+        self.reserve(len + 1);
+
+        unsafe {
+            ptr::write((*self.ptr as usize + self.len * size_of::<Block>()) as *mut _, block);
+        }
+    }
+
+    fn search(&mut self, block: &Block) -> Result<usize, usize> {
+        self.binary_search_by(|x| (**x).cmp(block))
+    }
+
+    fn alloc_new(&mut self, size: usize, align: usize) -> Unique<u8> {
+        // Calculate the canonical size (extra space is allocated to limit the number of system calls).
+        let can_size = canonicalize_brk(size);
+        // Get the previous segment end.
+        let seg_end = sys::segment_end().unwrap_or_else(|x| x.handle());
+        // Use SYSBRK to allocate extra data segment.
+        let ptr = sys::inc_brk(can_size + aligner(seg_end, align)).unwrap_or_else(|x| x.handle());
+
+        let res = unsafe {
+            Unique::new((*ptr as usize + align) as *mut _)
+        };
+        let extra = unsafe {
+            Unique::new((*res as usize + size) as *mut _)
+        };
+
+        // Add it to the list. This will not change the order, since the pointer is higher than all
+        // the previous blocks.
+        self.push(Block {
+            size: align,
+            ptr: ptr,
+        }.into());
+
+        // Add the extra space allocated.
+        self.push(Block {
+            size: can_size - size,
+            ptr: extra,
+        }.into());
+
+        res
+    }
+
+    fn realloc_inplace(&mut self, ind: usize, old_size: usize, size: usize) -> Result<(), ()> {
+        if ind == self.len - 1 { return Err(()) }
+
+        let additional = old_size - size;
+
+        if old_size + self[ind + 1].size >= size {
+            // Leave the excessive space.
+            self[ind + 1].ptr = unsafe {
+                Unique::new((*self[ind + 1].ptr as usize + additional) as *mut _)
+            };
+            self[ind + 1].size -= additional;
+
+            // Set the excessive block as free if it is empty.
+            if self[ind + 1].size == 0 {
+                self[ind + 1].free = false;
+            }
+
+            Ok(())
+        } else {
+            Err(())
+        }
+    }
+
+    pub fn realloc(&mut self, block: Block, new_size: usize, align: usize) -> Unique<u8> {
+        let ind = self.find(&block);
+
+        if self.realloc_inplace(ind, block.size, new_size).is_ok() {
+            block.ptr
+        } else {
+            // Reallocation cannot be done inplace.
+
+            // Allocate a new block with the same size.
+            let ptr = self.alloc(new_size, align);
+
+            // Copy the old data to the new location.
+            unsafe { ptr::copy(*block.ptr, *ptr, block.size); }
+
+            // Free the old block.
+            self.free(block);
+
+            ptr
+        }
+    }
+
+    fn reserve(&mut self, needed: usize) {
+        if needed > self.cap {
+            // Reallocate the block list.
+            self.ptr = unsafe {
+                let block = Block {
+                    ptr: Unique::new(*self.ptr as *mut _),
+                    size: self.cap,
+                };
+
+                Unique::new(*self.realloc(block, needed * 2, align_of::<Block>()) as *mut _)
+            };
+            // Update the capacity.
+            self.cap = needed * 2;
+        }
+    }
+
+    fn find(&mut self, block: &Block) -> usize {
+        match self.search(block) {
+            Ok(x) => x,
+            Err(x) => x,
+        }
+    }
+
+    pub fn free(&mut self, block: Block) {
+        let ind = self.find(&block);
+
+        // Try to merge left.
+        if ind != 0 && self[ind - 1].left_to(&block) {
+            self[ind - 1].size += block.size;
+        // Try to merge right.
+        } else if ind < self.len - 1 && self[ind].left_to(&block) {
+            self[ind].size += block.size;
+        } else {
+            self.insert(ind, block.into());
+        }
+    }
+
+    fn insert(&mut self, ind: usize, block: BlockEntry) {
+        let len = self.len;
+
+        // Find the next gap, where a used block were.
+        let n = self.iter()
+            .skip(ind)
+            .enumerate()
+            .filter(|&(_, x)| x.free)
+            .next().map(|x| x.0)
+            .unwrap_or_else(|| {
+                // No gap was found, so we need to reserve space for new elements.
+                self.reserve(len + 1);
+                ind
+            });
+
+        // Memmove the blocks to close in that gap.
+        unsafe {
+            ptr::copy(self[ind..].as_ptr(), self[ind + 1..].as_mut_ptr(), self.len - n);
+        }
+
+        // Place the block left to the moved line.
+        self[ind] = block;
+        self.len += 1;
+    }
+}
+
+impl ops::Deref for BlockList {
+    type Target = [BlockEntry];
+
+    fn deref(&self) -> &[BlockEntry] {
+        unsafe {
+            slice::from_raw_parts(*self.ptr as *const _, self.len)
+        }
+    }
+}
+impl ops::DerefMut for BlockList {
+    fn deref_mut(&mut self) -> &mut [BlockEntry] {
+        unsafe {
+            slice::from_raw_parts_mut(*self.ptr, self.len)
+        }
+    }
+}

+ 20 - 0
src/lib.rs

@@ -0,0 +1,20 @@
+#![cfg_attr(not(test), feature(oom))]
+#![cfg_attr(test, feature(const_fn))]
+
+#![feature(alloc)]
+#![feature(stmt_expr_attributes)]
+#![feature(unique)]
+
+#[cfg(target_os = "redox")]
+extern crate system;
+#[cfg(not(target_os = "redox"))]
+#[macro_use]
+extern crate syscall;
+
+#[macro_use]
+extern crate extra;
+extern crate alloc;
+
+mod sys;
+mod block;
+mod block_list;

+ 183 - 0
src/sys.rs

@@ -0,0 +1,183 @@
+use std::ptr::Unique;
+
+/// Out of memory.
+pub fn oom() -> ! {
+    #[cfg(test)]
+    panic!("Out of memory.");
+
+    #[cfg(not(test))]
+    ::alloc::oom();
+}
+
+/// A system call error.
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+pub enum Error {
+    /// Sir, we're running outta memory!
+    OutOfMemory,
+    /// Arithmetic overflow.
+    ArithOverflow,
+    /// An unknown error occurred.
+    Unknown,
+}
+
+impl Error {
+    /// Handle this error with the appropriate method.
+    pub fn handle(self) -> ! {
+        match self {
+            Error::OutOfMemory | Error::ArithOverflow => oom(),
+            Error::Unknown => panic!("Unknown OS error.")
+        }
+    }
+}
+
+/// Retrieve the end of the current data segment.
+///
+/// This will not change the state of the process in any way, and is thus safe.
+pub fn segment_end() -> Result<*mut u8, Error> {
+    unsafe {
+        sys_brk(0)
+    }.map(|x| x as *mut _)
+}
+
+/// Increment data segment of this process by some (signed) _n_, return a pointer to the new data
+/// segment start.
+///
+/// This uses the system call BRK as backend.
+pub fn inc_brk(n: usize) -> Result<Unique<u8>, Error> {
+    let orig_seg_end = try!(segment_end()) as usize;
+    if n == 0 {
+        unsafe {
+            return Ok(Unique::new(orig_seg_end as *mut u8))
+        }
+    }
+
+    let expected_end = maybe!(orig_seg_end.checked_add(n) => return Err(Error::ArithOverflow));
+    let new_seg_end = try!(unsafe { sys_brk(expected_end) });
+
+    if new_seg_end != expected_end {
+        // Reset the break.
+        try!(unsafe { sys_brk(orig_seg_end) });
+
+        Err(Error::OutOfMemory)
+    } else {
+        Ok(unsafe { Unique::new(orig_seg_end as *mut u8) })
+    }
+}
+
+#[cfg(target_os = "redox")]
+unsafe fn sys_brk(n: usize) -> Result<usize, Error> {
+    use system::syscall;
+
+    if let Ok(ret) = syscall::sys_brk(n) {
+        Ok(ret)
+    } else {
+        Err(Error::Unknown)
+    }
+}
+
+#[cfg(not(target_os = "redox"))]
+unsafe fn sys_brk(n: usize) -> Result<usize, Error> {
+    let ret = syscall!(BRK, n);
+
+    if ret == !0 {
+        Err(Error::Unknown)
+    } else {
+        Ok(ret)
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+    use std::ptr;
+
+    #[test]
+    fn test_oom() {
+        assert_eq!(inc_brk(9999999999999), Err(Error::OutOfMemory));
+    }
+
+    #[test]
+    fn test_write() {
+        let alloc_before = Box::new("hello from the outside.");
+        let ptr = unsafe { (segment_end().unwrap() as *const u8).offset(-1) };
+        let byte_end = unsafe { ptr::read(ptr) };
+
+        let abc = "abc";
+        let mem = inc_brk(8).unwrap() as *mut u64;
+        unsafe {
+            *mem = 90823;
+            *mem = 2897309273;
+            *mem = 293872;
+            *mem = 0xDEADBEAFDEADBEAF;
+            *mem = 99999;
+
+            assert_eq!(*mem, 99999);
+        }
+
+        // Do some heap allocations.
+        println!("test");
+        let bx = Box::new("yo mamma is so nice.");
+        println!("{}", bx);
+
+        assert_eq!(*bx, "yo mamma is so nice.");
+        assert_eq!(*alloc_before, "hello from the outside.");
+        // Check that the stack frame is unaltered.
+        assert_eq!(abc, "abc");
+        assert_eq!(byte_end, unsafe { ptr::read(ptr) });
+    }
+
+    #[test]
+    fn test_read() {
+        let mem = inc_brk(8).unwrap() as *mut u64;
+        unsafe {
+            assert_eq!(*mem, 0);
+        }
+    }
+
+    #[test]
+    fn test_overflow() {
+        assert_eq!(inc_brk(!0), Err(Error::ArithOverflow));
+        assert_eq!(inc_brk(!0 - 2000), Err(Error::ArithOverflow));
+    }
+
+    #[test]
+    fn test_empty() {
+        assert_eq!(inc_brk(0), segment_end())
+    }
+
+    #[test]
+    fn test_seq() {
+        let a = inc_brk(4).unwrap() as usize;
+        let b = inc_brk(5).unwrap() as usize;
+        let c = inc_brk(6).unwrap() as usize;
+        let d = inc_brk(7).unwrap() as usize;
+
+        assert_eq!(a + 4, b);
+        assert_eq!(b + 5, c);
+        assert_eq!(c + 6, d);
+    }
+
+    #[test]
+    fn test_thread() {
+        use std::thread;
+
+        let mut threads = Vec::new();
+
+        for _ in 0..1000 {
+            threads.push(thread::spawn(|| {
+                inc_brk(9999).unwrap();
+            }));
+        }
+
+        for i in threads {
+            i.join().unwrap();
+        }
+    }
+
+    #[test]
+    fn test_segment_end() {
+        assert_eq!(segment_end().unwrap(), segment_end().unwrap());
+        assert_eq!(segment_end().unwrap(), segment_end().unwrap());
+        assert_eq!(segment_end().unwrap(), segment_end().unwrap());
+    }
+}