1个稳定版本
使用旧的Rust 2015
1.0.0 | 2016年12月21日 |
---|
#204 在 内存管理
120KB
1.5K SLoC
ralloc
一个快速且内存高效的用户空间分配器。
这个分配器被用作Redox的默认分配器。
关于其状态的说明。
它完全工作,尽管它比jemalloc慢一些,因为它尚未优化。
我认为代码质量的状态非常好。
支持的平台
- BSD
- Linux
- Mac OS X
- Redox
- Windows
使用ralloc
确保你有Rust nightly。
将 ralloc
添加到 Cargo.toml
[dependencies.ralloc]
git = "https://github.com/redox-os/ralloc.git"
然后在主文件中导入它
extern crate ralloc;
ralloc
现在可以使用了!
请注意,ralloc
不能与另一个分配器共存,除非它们是故意兼容的。
功能
线程局部分配
Ralloc使用全局局部模型,允许在不使用锁、同步或原子写入的情况下分配或释放。这提供了合理的性能,同时保持了灵活性和多线程的能力。
一流的调试器支持(默认:valgrind)
ralloc
在启用debugger
功能时,向ralloc_shim
中指定的两个调试器符号提供数据。默认的shim
实现连接到valgrind
,因此可以与ralloc
一起使用以检测内存泄漏和未初始化的使用。
一切都可以自定义
您可以在ralloc
中配置、调整和自定义几乎所有内容。通过更改shim
模块,这可以轻松实现。
例如,您可以更改重新分配策略、memtrim限制、日志目标等。
日志记录
如果您启用log
功能,您将获得分配器的详细日志,例如:
| : BRK'ing a block of size, 80, and alignment 8. (at bookkeeper.rs:458)
| : Pushing 0x5578dacb2000[0x0] and 0x5578dacb2050[0xffb8]. (at bookkeeper.rs:490)
|x : Freeing 0x1[0x0]. (at bookkeeper.rs:409)
x| : BRK'ing a block of size, 4, and alignment 1. (at bookkeeper.rs:458)
x| : Pushing 0x5578dacc2008[0x0] and 0x5578dacc200c[0xfffd]. (at bookkeeper.rs:490)
x|x : Reallocating 0x5578dacc2008[0x4] to size 8 with align 1. (at bookkeeper.rs:272)
x|x : Inplace reallocating 0x5578dacc2008[0x4] to size 8. (at bookkeeper.rs:354)
_|x : Freeing 0x5578dacb2058[0xffb0]. (at bookkeeper.rs:409)
_|x : Inserting block 0x5578dacb2058[0xffb0]. (at bookkeeper.rs:635)
在左侧,您可以看到块池的状态。x
表示非空块,_
表示空块,|
表示光标。
a[b]
是地址为a
大小为b
的块的语法。
您可以在shim
中设置日志级别(例如,避免过多的信息)。
自定义内存不足处理器
您可以通过以下方式设置自定义的OOM处理器:
extern crate ralloc;
fn my_handler() -> ! {
println!("Oh no! You ran out of memory.");
}
fn main() {
ralloc::set_oom_handler(my_handler);
// Do some stuff...
}
线程特定的OOM处理器。
您可以为当前线程重写全局OOM处理器。启用 thread_oom
功能,然后进行以下操作:
extern crate ralloc;
fn my_handler() -> ! {
println!("Oh no! You ran out of memory.");
}
fn main() {
ralloc::set_thread_oom_handler(my_handler);
// Do some stuff...
}
部分释放
许多分配器限制释放为已分配的块,也就是说,您不能对其进行算术运算或分割。 ralloc
没有这样的限制
extern crate ralloc;
use std::mem;
fn main() {
// We allocate 200 bytes.
let vec = vec![0u8; 200];
// Cast it to a pointer.
let ptr = vec.as_mut_ptr();
// To avoid UB, we leak the vector.
mem::forget(vec);
// Now, we create two vectors, each being 100 bytes long, effectively
// splitting the original vector in half.
let a = Vec::from_raw_parts(ptr, 100, 100);
let b = Vec::from_raw_parts(ptr.offset(100), 100, 100);
// Now, the destructor of a and b is called... Without a segfault!
}
顶级安全
如果您愿意为了额外的安全性牺牲一点性能,可以通过编译时使用 security
标志来编译 ralloc
。这将使释放操作清零,以及其他一些操作。
换句话说,攻击者无法注入恶意代码或数据,这些代码或数据在忘记初始化您分配的数据时可能会被利用。
代码验证
分配器对安全性极为关键。如果相同的地址被分配给两个不同的调用者,您将面临各种漏洞。因此,代码的审查和验证非常重要。
ralloc
使用多阶段验证模型
- 类型检查器。验证的大部分工作完全在静态完成,并通过类型检查器强制执行。我们大量使用 Rust 的安全性特性,特别是亲和类型。
- 单元测试。
ralloc
具有完整的单元测试覆盖率,即使是私有接口。 - 集成测试套件。
ralloc
使用一种生成测试,其中测试通过一组固定函数“展开”。这允许相对较少的测试(例如,几百行)成倍增加,效果更佳。 - 运行时检查。
ralloc
尽量避免运行时测试,但在某些情况下这是不可能的。当确定的性能损失较小而安全性收益显著时,我们使用运行时检查(如缓冲区溢出检查)。 - 调试断言。
ralloc
包含许多调试断言,在调试模式下启用。这允许对双释放、内存损坏、泄漏以及对齐检查等进行非常仔细的测试。 - 人工审查。一人或多人审查补丁以确保高安全性。
通过类型系统实现安全性
ralloc
大量使用 Rust 的类型系统来确保安全性。内部,ralloc
有一个名为 Block
的原始类型。这很简单,表示内存的连续段,但有趣的是它如何在编译时通过亲和类型系统进行检查以确保唯一性。
这只是许多例子中的一个。
平台无关
ralloc
是平台无关的。它依赖于 ralloc_shim
,这是一个用于平台相关函数的最小接口。提供了一个默认的 ralloc_shim
实现(支持 Mac OS、Linux 和 BSD)。
强制就地重新分配
就地重新分配可能比 memcpy 重新分配快得多。libc 的一个限制是您不能仅就地重新分配(一种可能失败的方法,它保证了缓冲区中没有 memcpy)。
有一种可失败的就地重新分配方式提供了一些有趣的可能性。
extern crate ralloc;
fn main() {
let buf = ralloc::alloc(40, 1);
// BRK'ing 20 bytes...
let ptr = unsafe { ralloc::inplace_realloc(buf, 40, 45).unwrap() };
// The buffer is now 45 bytes long!
}
安全的 SBRK
ralloc
提供了一个 sbrk
,可以安全地使用而不会破坏分配器
extern crate ralloc;
fn main() {
// BRK'ing 20 bytes...
let ptr = unsafe { ralloc::sbrk(20) };
}
无用的对齐
对齐不必是 2 的幂。
计划中的功能
可失败分配
您通常希望根据情况处理 OOM。当处理非常大的分配时,这一点尤其正确。
ralloc
允许这样做
extern crate ralloc;
fn main() {
let buf = ralloc::try_alloc(8, 4);
// `buf` is a Result: It is Err(()) if the allocation failed.
}
依赖关系
~5MB
~138K SLoC