rcore-os / rCore-Tutorial-Book-v3

A book about how to write OS kernels in Rust easily.
https://rcore-os.github.io/rCore-Tutorial-Book-v3/
GNU General Public License v3.0
1.17k stars 221 forks source link

rCore-Tutorial-Book-v3/chapter4/1rust-dynamic-allocation #51

Open utterances-bot opened 3 years ago

utterances-bot commented 3 years ago

Rust 中的动态内存分配 — rCore-Tutorial-Book-v3 0.1 文档

https://rcore-os.github.io/rCore-Tutorial-Book-v3/chapter4/1rust-dynamic-allocation.html

Tokubara commented 3 years ago

[alloc_error_handler], 这感觉是个回调函数性质的函数, 那对它的函数签名是有什么规定么? 比如说, 我怎么知道我能接受些什么参数?

wyfcyx commented 3 years ago

@Tokubara 目前应该只能标记一个参数为 core::alloc::Layout 的函数吧,参考 Issue 51540

mailofzxf commented 3 years ago

“比如分配了之后没有回收,则会导致 内存溢出;” 这种bug应该称为“内存泄露”吧?

Millione commented 2 years ago

可变借用和不可变借用不能共存

不能同时共存

vcheckzen commented 2 years ago

可变借用和不可变借用不能共存

不能同时共存

共就是同时的意思啊。它们可以存在于同一函数中,但生命周期不重叠

// 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);
wei-huan commented 2 years ago

调用 heap_test() 前要记得先调用 init_heap()

dzwduan commented 2 years ago

buddy_system_allcator = 0.8.0时,需要修改: static HEAP_ALLOCATOR: LockedHeap::<32> = LockedHeap::<32>::empty();

Hoblovski commented 1 year ago

Python/Java 通过 引用计数 (Reference Counting) 对所有的对象进行运行时的动态管理,一套 垃圾回收 (GC, Garbage Collection) 机制会被自动定期触发,每次都会检查所有的对象,如果其引用计数为零则可以将该对象占用的内存从堆上回收以待后续其他的对象使用。这样做完全杜绝了内存安全问题,但是性能开销则很大,而且 GC 触发的时机和每次 GC 的耗时都是无法预测的,还使得软件的执行性能不够确定。

这句话有 citation 吗?Python/Java 的内存管理应该不是基于引用计数的把?

至少主要的 JVM 实现应该都用的是 mark and sweep 的 GC 吧?

wyfcyx commented 1 year ago

@Hoblovski 多谢指正。这段目前是我的印象流,之后会做些调研然后修改。

CelestialMelody commented 1 year ago

请教一下,heap_test 可以改为 rust 单元测试吗?(不太会改来用,感觉需要做一些其他工作,我会出现 error[E0463]: can't find crate for test的问题)

mxq-151 commented 1 year ago

有个疑问,alloc进行内存申请不是系统调用,那么HEAP_ALLOCATOR是如何共享到各应用程序的,或者换个说法,HEAP_ALLOCATOR是如何全局管理应用内存分配的

wyfcyx commented 1 year ago

@CelestialMelody 我们的内核是在no_std环境下运行,而常用到的rust单元测试需要标准库的支持。如果想在no_std环境下使用rust单元测试的话,BlogOS的这篇文章或许有些帮助。

wyfcyx commented 1 year ago

@mxq-151 HEAP_ALLOCATOR仅负责内核内部的动态内存分配,应用完全不会和它打交道。对于用户态来说,每个应用的地址空间中都预留了一部分空间用于动态内存分配(位于每个应用的全局数据段.data段中),具体实现在user/src/lib.rs中可以找到,和内核中的实现很像。当然目前这种实现,应用会浪费很多堆内存,更好的方法当应用堆空间不足时,通过系统调用向内核申请内存并扩充堆空间使得应用能够继续进行动态内存分配,这通常由编程语言标准库(如libc)负责。

mxq-151 commented 1 year ago

关于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
    }
}
wyfcyx commented 1 year ago

@mxq-151 用户态的代码访问的虚拟地址是在内核设置好应用的页表之后由MMU硬件翻译成物理地址的。具体来说,MMU只能将当前代码所在的地址空间的虚拟地址翻译成物理地址,而对于其他地址空间中的虚拟地址MMU则不能完成翻译或者无法正确翻译。正因如此,在sys_write的情形中,内核代码不能依赖MMU翻译应用传给内核的应用地址空间的虚拟地址,所以我们需要手动查页表将应用地址空间的虚拟地址翻译成物理地址,而后再进行处理。

mxq-151 commented 1 year ago

明白,但是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");
        }
    }
wyfcyx commented 1 year ago

@mxq-151 是否是通过以下代码将root_ppn写入到satp寄存器,MMU硬件便实现了以上的检索逻辑 是这样的。

mxq-151 commented 1 year ago

thx

ghost commented 1 year ago

所以heap_test中的这段代码:

  let mut v: Vec<usize> = Vec::new();
  for i in 0..500 {
      v.push(i);
  }

中的v是虚拟地址还是物理地址啊?

ghost commented 1 year ago

以及还想问一下在给HEAP_ALLOCATOR初始化的时候用到的HEAP_SPACE的大小是0x2000000,可是我们的物理内存实际大小不是也是只有8MB吗?

hnyls2002 commented 1 year ago

@lyingqi HEAP_SPACE_SIZE 是由config.rs中的常数KERNEL_HEAP_SIZE = 0x30_0000决定的

linyn-zero commented 1 year ago

计算机科学家给与了 Atlas Supervisor 操作系统高度的评价。Brinch Hansen 认为它是操作系统史上最重大的突破。Simon Lavington 认为它是第一个可识别的现代操作系统。

本章的引言里有错别字,是“给予”不是“给与”

xuanz20 commented 1 year ago

@hnyls2002 我看的代码里是pub const KERNEL_HEAP_SIZE: usize = 0x200_0000;

kayoch1n commented 1 year ago

以及还想问一下在给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

AnlangA commented 1 year ago

计算机科学家给与了 Atlas Supervisor 操作系统高度的评价。Brinch Hansen 认为它是操作系统史上最重大的突破。Simon Lavington 认为它是第一个可识别的现代操作系统。

本章的引言里有错别字,是“给予”不是“给与”

今肥田尚多,未有垦辟。其悉以赋贫民,给与粮种,务尽地力,勿令游手

wyfcyx commented 1 year ago

@lyingqi 8MiB是之前支持K210平台的说法,现在默认不支持K210了,只考虑QEMU,所以把这句话改掉了。

HangX-Ma commented 1 year ago

请教一下,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 {}
}
HangX-Ma commented 1 year ago

所以heap_test中的这段代码:

  let mut v: Vec<usize> = Vec::new();
  for i in 0..500 {
      v.push(i);
  }

中的v是虚拟地址还是物理地址啊?

物理地址吧,我在 ch3 的基础上直接加上的 heap_allocator, 之后运行测试是直接通过的。

更新: 看后续章节可能开启分页管理后会有不同。

chestNutLsj commented 1 year ago

有关内存碎片的旁注里最后一段话有一些地方可以改进:

为何应用开发者在编程中“看不到”内存碎片?这是因为动态内存管理更底层的系统标准库来完成的,它能看到并进行管理。而应用开发者只需调用系统标准库提供的内存申请/释放函数接口即可

ziyouwa commented 12 months ago

以 C 语言为例,可以说自 malloc 拿到指向一个变量的指针到 free 将它回收之前的这段时间,这个变量在堆上存在。由于需要跨越函数调用,我们需要作为堆上数据代表的变量在函数间以参数或返回值的形式进行传递,而这些变量一般都很小(如一个指针),其拷贝开销可以忽略。

这段建议为:以 C 语言为例,malloc 在堆上分配到一块内存时,会在程序栈上创建指向它的指针变量。在创建成功到 free 将它回收之前的这段时间,这块内存本身以及指针变量都一直存在,内存块在堆上存在,指针变量在程序栈上存在。当需要跨越函数调用时,我们只需把指针变量在函数间以参数或返回值的形式进行传递,而这些变量一般都很小(如一个指针),其拷贝开销可以忽略。

0xDamn commented 10 months ago

@mxq-151 HEAP_ALLOCATOR仅负责内核内部的动态内存分配,应用完全不会和它打交道。对于用户态来说,每个应用的地址空间中都预留了一部分空间用于动态内存分配(位于每个应用的全局数据段.data段中),具体实现在user/src/lib.rs中可以找到,和内核中的实现很像。当然目前这种实现,应用会浪费很多堆内存,更好的方法当应用堆空间不足时,通过系统调用向内核申请内存并扩充堆空间使得应用能够继续进行动态内存分配,这通常由编程语言标准库(如libc)负责。

当应用的堆内存不足时可以通过系统调用申请更多空间。

Q: 内核的堆内存是固定大小的吗?还是可以向给硬件提供SEE的sbi申请更多的空间?

msdlisper commented 8 months ago

@0xDamn

Q: 内核的堆内存是固定大小的吗?还是可以向给硬件提供SEE的sbi申请更多的空间?

看目前的实现内核内存是写死到.bss位置(地址从0开始), 并且是一个固定的大小

static mut HEAP_SPACE: [u8; KERNEL_HEAP_SIZE] = [0; KERNEL_HEAP_SIZE];

个人觉得只是暂时方案, 毕竟内核的内存也是需要管理的