Open mapleFU opened 3 years ago
这里逻辑都取巧了,使用了 HugePage
, 所以在 part-5 之前是没有实现缺页异常的逻辑的。
然后内核侧的 buddy
用了别的库,但是这里是在 .bss
段上分配的,在 .bss
上分配内存来处理做内核用的堆,让内核能用上库
Lab2 的逻辑大概如下:
bss
上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 的地址。
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
这包括内核代码啊,草
Lab3 感觉 Linker Script 和 Entry 顺序有点怪,老实说我没完全看懂, 感觉可以参考这个 但其实我还没完全懂,似乎就是 _entry
的时候既有高地址,又有低地址。然后 boot page table 的时候就分配了两个 1GB 的,其他全部没用。
"修改内核" 这部分,给了低地址和高地址共同映射到 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
里面删除。
Segment 表示一段虚拟内存,有两种分配方式:Linear
(OS 开始那些那样线性映射到物理内存),Framed
需要分配。
Mapping
保存了具体的内存映射关系,页表这东西是一层一层的,所以叫多级页表。Mapping
负责存储页表的 PageTracker
。find_entry
实现了查找的逻辑。
不过这里好像没有实现什么找不到内存然后 hook 上去的逻辑,有点 jb 失败啊
这是 Rust 的链接脚本:
同时,我们引入了
我们在
linker.ld
设置了程序的入口为_start
,然后 OpenSBI 把自己内存读取,同时做下面的操作:这个地方,我们就创建完了内核的映射。
在 Lab3 最开始,我们已经在
entry.asm
里面完成了一次初始的映射,但是实际上,是有一些不足的:重新映射的时候,对应的内存地址有
.align 4k
, 都是写死的。不过物理内存映射比较麻烦。感觉它这里逻辑是全部映射完了。