mapleFU / mwishCore

The kernel of mwish following the uCore
7 stars 0 forks source link

关于 rCore 内存的一点记录 #5

Open mapleFU opened 3 years ago

mapleFU commented 3 years ago

这是 Rust 的链接脚本:

# 编译的目标平台
[build]
target = "riscv64imac-unknown-none-elf"

# 使用我们的 linker script 来进行链接
[target.riscv64imac-unknown-none-elf]
rustflags = [
    "-C", "link-arg=-Tsrc/linker.ld",
]

同时,我们引入了

// 汇编编写的程序入口,具体见该文件
global_asm!(include_str!("entry.asm"));

我们在 linker.ld 设置了程序的入口为 _start,然后 OpenSBI 把自己内存读取,同时做下面的操作:

OpenSBI 所做的一件事情就是把 CPU 从 M Mode 切换到 S Mode,接着跳转到一个固定地址 0x80200000,开始执行内核代码。

这个地方,我们就创建完了内核的映射。

在 Lab3 最开始,我们已经在 entry.asm 里面完成了一次初始的映射,但是实际上,是有一些不足的:

我们看到各个段之间的访问权限是不同的。在现在的映射下,我们甚至可以修改内核 .text 段的代码。因为我们通过一个标志位 W 为 1 的页表项完成映射。

因此,我们考虑对这些段分别进行重映射,使得他们的访问权限被正确设置。

重新映射的时候,对应的内存地址有 .align 4k, 都是写死的。不过物理内存映射比较麻烦。感觉它这里逻辑是全部映射完了。

mapleFU commented 3 years ago

这里逻辑都取巧了,使用了 HugePage, 所以在 part-5 之前是没有实现缺页异常的逻辑的。

然后内核侧的 buddy 用了别的库,但是这里是在 .bss 段上分配的,在 .bss 上分配内存来处理做内核用的堆,让内核能用上库

mapleFU commented 3 years ago

Lab2 的逻辑大概如下:

  1. 开了一个 static 的大数组,这个段是未初始化的,所以会被丢到 bss
  2. unsafe 写入 bss 是可以的,这里也不需要操纵段其实就能完成,因为编译之后产生的 elf 的话,因为我们在 linker script 上改了 base address, 所以这段内存位置肯定是合理的。
  3. LockedHeap 实现了 Trait GlobalAlloc, 因此能够分配释放内存,#[alloc_error_handler] 处理申请内存失败之后的行为

我们是靠 qemu + OpenSBI 来启动程序的,物理内存可以看看知乎这篇 帖子 , 可以看到,物理内存本身也不是连续的。 Qemu 有一个 MemEntry, 如下

static const struct MemmapEntry {
    hwaddr base;
    hwaddr size;
} virt_memmap[] = {
    [VIRT_DEBUG] =       {        0x0,         0x100 },
    [VIRT_MROM] =        {     0x1000,        0xf000 },
    [VIRT_TEST] =        {   0x100000,        0x1000 },
    [VIRT_RTC] =         {   0x101000,        0x1000 },
    [VIRT_CLINT] =       {  0x2000000,       0x10000 },
    [VIRT_PCIE_PIO] =    {  0x3000000,       0x10000 },
    [VIRT_PLIC] =        {  0xc000000, VIRT_PLIC_SIZE(VIRT_CPUS_MAX * 2) },
    [VIRT_UART0] =       { 0x10000000,         0x100 },
    [VIRT_VIRTIO] =      { 0x10001000,        0x1000 },
    [VIRT_FLASH] =       { 0x20000000,     0x4000000 },
    [VIRT_PCIE_ECAM] =   { 0x30000000,    0x10000000 },
    [VIRT_PCIE_MMIO] =   { 0x40000000,    0x40000000 },
    [VIRT_DRAM] =        { 0x80000000,           0x0 },
};

然后 linker script 里面记录了 kernel end. 这里声明了一个 KERNEL_END_ADDRESS 表示 kernel 结束的地址,拿到了 linker script 的地址。

mapleFU commented 3 years ago

Lab2 最后代码写的很取巧,DRAM 代码都开始用 Page 分配了 Q: 原来是什么样子的,不用 Page 分配的情况下? A: linker script 指定段,函数被定义好,然后 _start 是用户定义的,我们跑到 rust_main 上,我们在代码里有 boot stack

# 目前 _start 的功能:将预留的栈空间写入 $sp,然后跳转至 rust_main
_start:
    la sp, boot_stack_top
    call rust_main

    # 回忆:bss 段是 ELF 文件中只记录长度,而全部初始化为 0 的一段内存空间
    # 这里声明字段 .bss.stack 作为操作系统启动时的栈
    .section .bss.stack
    .global boot_stack
boot_stack:
    # 16K 启动栈大小
    .space 4096 * 16
    .global boot_stack_top
boot_stack_top:

我们之前的代码跑在 boot stack 上。原来的栈相当于 DRAM 空间的 .bss

Lab2 最后一节,我们把 [0x80000000, 0x8800_0000] 的 DRAM 区域利用了起来,不过 Lab2 物理内存管理感觉有点不对劲,感觉 0x80000000 这包括内核代码啊,草

mapleFU commented 3 years ago

rcore_memory_layout

mapleFU commented 3 years ago

Lab3 感觉 Linker Script 和 Entry 顺序有点怪,老实说我没完全看懂, 感觉可以参考这个 但其实我还没完全懂,似乎就是 _entry 的时候既有高地址,又有低地址。然后 boot page table 的时候就分配了两个 1GB 的,其他全部没用。

mapleFU commented 3 years ago

"修改内核" 这部分,给了低地址和高地址共同映射到 0x8000_0000 开始 1GB 内存的权限。我估计访问一些高地址会因为物理内存不够炸掉吧

然后代码实现了 PageTableEntry, 它的内容是一个 usize, 然后封装了一下对应的操作。这个我提了一个 issue: https://github.com/rcore-os/rCore-Tutorial/issues/130

/// 存有 512 个页表项的页表
///
/// 注意我们不会使用常规的 Rust 语法来创建 `PageTable`。相反,我们会分配一个物理页,
/// 其对应了一段物理内存,然后直接把其当做页表进行读写。我们会在操作系统中用一个「指针」
/// [`PageTableTracker`] 来记录这个页表。
#[repr(C)]
pub struct PageTable {
    pub entries: [PageTableEntry; PAGE_SIZE / 8],
}

页表的实现使用了比较基础的上面的结构,然后这个只是页表的 Root

pub struct PageTableTracker(pub FrameTracker);

FrameTracker 管理物理 Page 的生命周期, 从一个 buddy 的 FRAME_ALLOCATOR 产生,管理所有物理页面。Drop 的时候会自己从 FrameTracker 里面删除。

mapleFU commented 3 years ago

Segment 表示一段虚拟内存,有两种分配方式:Linear (OS 开始那些那样线性映射到物理内存),Framed 需要分配。

Mapping 保存了具体的内存映射关系,页表这东西是一层一层的,所以叫多级页表。Mapping 负责存储页表的 PageTrackerfind_entry 实现了查找的逻辑。

不过这里好像没有实现什么找不到内存然后 hook 上去的逻辑,有点 jb 失败啊