rCore-Tutorial-Book-v3
rCore-Tutorial-Book-v3 copied to clipboard
rCore-Tutorial-Book-v3/chapter4/1rust-dynamic-allocation
Rust 中的动态内存分配 — rCore-Tutorial-Book-v3 0.1 文档
https://rcore-os.github.io/rCore-Tutorial-Book-v3/chapter4/1rust-dynamic-allocation.html
#[alloc_error_handler], 这感觉是个回调函数性质的函数, 那对它的函数签名是有什么规定么? 比如说, 我怎么知道我能接受些什么参数?
@Tokubara 目前应该只能标记一个参数为 core::alloc::Layout
的函数吧,参考 Issue 51540 。
“比如分配了之后没有回收,则会导致 内存溢出;” 这种bug应该称为“内存泄露”吧?
可变借用和不可变借用不能共存
不能同时共存
可变借用和不可变借用不能共存
不能同时共存
共就是同时的意思啊。它们可以存在于同一函数中,但生命周期不重叠
// https://doc.rust-lang.org/book/ch04-02-references-and-borrowing.html
let mut s = String::from("hello");
let r1 = &s; // no problem
let r2 = &s; // no problem
println!("{} and {}", r1, r2);
// variables r1 and r2 will not be used after this point
let r3 = &mut s; // no problem
println!("{}", r3);
调用 heap_test() 前要记得先调用 init_heap()
buddy_system_allcator = 0.8.0时,需要修改: static HEAP_ALLOCATOR: LockedHeap::<32> = LockedHeap::<32>::empty();
Python/Java 通过 引用计数 (Reference Counting) 对所有的对象进行运行时的动态管理,一套 垃圾回收 (GC, Garbage Collection) 机制会被自动定期触发,每次都会检查所有的对象,如果其引用计数为零则可以将该对象占用的内存从堆上回收以待后续其他的对象使用。这样做完全杜绝了内存安全问题,但是性能开销则很大,而且 GC 触发的时机和每次 GC 的耗时都是无法预测的,还使得软件的执行性能不够确定。
这句话有 citation 吗?Python/Java 的内存管理应该不是基于引用计数的把?
至少主要的 JVM 实现应该都用的是 mark and sweep 的 GC 吧?
@Hoblovski 多谢指正。这段目前是我的印象流,之后会做些调研然后修改。
请教一下,heap_test 可以改为 rust 单元测试吗?(不太会改来用,感觉需要做一些其他工作,我会出现 error[E0463]: can't find crate for test
的问题)
有个疑问,alloc进行内存申请不是系统调用,那么HEAP_ALLOCATOR是如何共享到各应用程序的,或者换个说法,HEAP_ALLOCATOR是如何全局管理应用内存分配的
@CelestialMelody 我们的内核是在no_std
环境下运行,而常用到的rust单元测试需要标准库的支持。如果想在no_std
环境下使用rust单元测试的话,BlogOS的这篇文章或许有些帮助。
@mxq-151 HEAP_ALLOCATOR
仅负责内核内部的动态内存分配,应用完全不会和它打交道。对于用户态来说,每个应用的地址空间中都预留了一部分空间用于动态内存分配(位于每个应用的全局数据段.data
段中),具体实现在user/src/lib.rs
中可以找到,和内核中的实现很像。当然目前这种实现,应用会浪费很多堆内存,更好的方法当应用堆空间不足时,通过系统调用向内核申请内存并扩充堆空间使得应用能够继续进行动态内存分配,这通常由编程语言标准库(如libc)负责。
关于HEAP_ALLOCATOR的动态分配明白了,但是关于虚拟内存有不明白的地方: 在用户态程序中如下代码:
let mut v: Vec<usize> = Vec::new();
for i in 0..500 {
v.push(i);
}
v的地址是不是虚拟地址,如果是虚拟地址,转换成物理地址发生在哪里,具体代码是什么 有以上疑惑主要是因为代码中看到内核态有主动转换虚拟地址为物理地址,但以上代码却没看找到相关转换逻辑
/os/src/syscall/fs.rs
use crate::fs::{open_file, OpenFlags};
use crate::mm::{translated_byte_buffer, translated_str, UserBuffer};
use crate::task::{current_task, current_user_token};
pub fn sys_write(fd: usize, buf: *const u8, len: usize) -> isize {
let token = current_user_token();
let task = current_task().unwrap();
let inner = task.inner_exclusive_access();
if fd >= inner.fd_table.len() {
return -1;
}
if let Some(file) = &inner.fd_table[fd] {
if !file.writable() {
return -1;
}
let file = file.clone();
// release current task TCB manually to avoid multi-borrow
drop(inner);
//转换地址
file.write(UserBuffer::new(translated_byte_buffer(token, buf, len))) as isize
} else {
-1
}
}
@mxq-151 用户态的代码访问的虚拟地址是在内核设置好应用的页表之后由MMU硬件翻译成物理地址的。具体来说,MMU只能将当前代码所在的地址空间的虚拟地址翻译成物理地址,而对于其他地址空间中的虚拟地址MMU则不能完成翻译或者无法正确翻译。正因如此,在sys_write
的情形中,内核代码不能依赖MMU翻译应用传给内核的应用地址空间的虚拟地址,所以我们需要手动查页表将应用地址空间的虚拟地址翻译成物理地址,而后再进行处理。
明白,但是MMU是如何调用是如何调用虚拟地址转物理地址的逻辑的: os/src/mm/page_table.rs
pub fn translate_va(&self, va: VirtAddr) -> Option<PhysAddr> {
self.find_pte(va.clone().floor()).map(|pte| {
let aligned_pa: PhysAddr = pte.ppn().into();
let offset = va.page_offset();
let aligned_pa_usize: usize = aligned_pa.into();
(aligned_pa_usize + offset).into()
})
}
是否是通过以下代码将root_ppn写入到satp寄存器,MMU硬件便实现了以上的检索逻辑
pub fn activate(&self) {
let satp = self.page_table.token();
unsafe {
satp::write(satp);
asm!("sfence.vma");
}
}
@mxq-151 是否是通过以下代码将root_ppn写入到satp寄存器,MMU硬件便实现了以上的检索逻辑
是这样的。
thx
所以heap_test
中的这段代码:
let mut v: Vec<usize> = Vec::new();
for i in 0..500 {
v.push(i);
}
中的v
是虚拟地址还是物理地址啊?
以及还想问一下在给HEAP_ALLOCATOR
初始化的时候用到的HEAP_SPACE
的大小是0x2000000
,可是我们的物理内存实际大小不是也是只有8MB吗?
@lyingqi HEAP_SPACE_SIZE
是由config.rs
中的常数KERNEL_HEAP_SIZE = 0x30_0000
决定的
计算机科学家给与了 Atlas Supervisor 操作系统高度的评价。Brinch Hansen 认为它是操作系统史上最重大的突破。Simon Lavington 认为它是第一个可识别的现代操作系统。
本章的引言里有错别字,是“给予”不是“给与”
@hnyls2002 我看的代码里是pub const KERNEL_HEAP_SIZE: usize = 0x200_0000;
以及还想问一下在给
HEAP_ALLOCATOR
初始化的时候用到的HEAP_SPACE
的大小是0x2000000
,可是我们的物理内存实际大小不是也是只有8MB吗?
QEMU 给到的内存大小也不止8MB,0x80000000..0x88000000 有128MB,是默认值,可以通过参数 -m 调整比如 -m 256M
[rustsbi] Implementation : RustSBI-QEMU Version 0.2.0-alpha.2 [rustsbi] Platform Name : riscv-virtio,qemu [rustsbi] Platform SMP : 1 [rustsbi] Platform Memory : 0x80000000..0x88000000
计算机科学家给与了 Atlas Supervisor 操作系统高度的评价。Brinch Hansen 认为它是操作系统史上最重大的突破。Simon Lavington 认为它是第一个可识别的现代操作系统。
本章的引言里有错别字,是“给予”不是“给与”
今肥田尚多,未有垦辟。其悉以赋贫民,给与粮种,务尽地力,勿令游手
@lyingqi 8MiB是之前支持K210平台的说法,现在默认不支持K210了,只考虑QEMU,所以把这句话改掉了。
请教一下,heap_test 可以改为 rust 单元测试吗?(不太会改来用,感觉需要做一些其他工作,我会出现
error[E0463]: can't find crate for test
的问题)
目前仅支持 x86, 需要用到一个 bootimage crate, 我折腾过一天,如果只是想去掉报错的话可以在每个包含 main 的程序开头和结尾分别就加上如下代码。 但想在 qemu 运行 test binary 目前没找到比较合适的办法。希望有大佬能不吝赐教。
#![reexport_test_harness_main = "test_main"]
#![feature(custom_test_frameworks)]
#![test_runner(test_runner)]
pub fn test_runner(_test: &[&dyn Fn()]) {
loop {}
}
所以
heap_test
中的这段代码:let mut v: Vec<usize> = Vec::new(); for i in 0..500 { v.push(i); }
中的
v
是虚拟地址还是物理地址啊?
物理地址吧,我在 ch3 的基础上直接加上的 heap_allocator, 之后运行测试是直接通过的。
更新: 看后续章节可能开启分页管理后会有不同。
有关内存碎片的旁注里最后一段话有一些地方可以改进:
为何应用开发者在编程中“看不到”内存碎片?这是因为动态内存管理有更底层的系统标准库来完成的,它能看到并进行管理。而应用开发者只需调用系统标准库提供的内存申请/释放函数接口即可
- 这里“有”是个错别字,应为“由”
- “只需”感觉与这段话的主旨不太相符,这段话是要说明内存碎片对应用开发者的透明性,而非应用开发者可以使用的功能,因此“只需”--改为-->“只能”比较好
以 C 语言为例,可以说自 malloc 拿到指向一个变量的指针到 free 将它回收之前的这段时间,这个变量在堆上存在。由于需要跨越函数调用,我们需要作为堆上数据代表的变量在函数间以参数或返回值的形式进行传递,而这些变量一般都很小(如一个指针),其拷贝开销可以忽略。
这段建议为:以 C 语言为例,malloc 分配到一块内存时,会在程序栈上创建指向它的指针变量。在创建成功到 free 将它回收之前的这段时间,这块内存本身以及指针变量都一直存在,内存块在堆上存在,指针变量在程序栈上存在。当需要跨越函数调用时,我们只需把作为堆上数据代表的指针变量在函数间以参数或返回值的形式进行传递,而这些变量一般都很小(如一个指针),其拷贝开销可以忽略。