RISC-V Non-MMU Linux学习笔记
资料来源:
https://tinylab.org/riscv-non-mmu-linux-part1/#:~:text=MMU%20%E6%98%AF%E7%8E%B0%E4%BB%A3%E5%A4%84%E7%90%86%E5%99%A8
https://www.kernel.org/doc/Documentation/nommu-mmap.txt
MMU 是现代处理器中一个非常重要的硬件特性,主要用于虚拟地址与物理地址之间的转换。RISC-V 下可以通过设置 SATP 寄存器为 0 来彻底关闭 MMU。关掉 MMU 以后,所有的程序将共享同一片物理地址,而不再是“独占”一大片连续的虚拟地址空间,因此内核和应用的行为方式都将受到很大的影响。另外,在 RISC-V Linux 下,关闭 MMU 以后,内核和应用都将工作在纯 Machine Mode 模式,原有的 Machine Mode 的 Firmware 也不再需要,所以,这里也涉及到整个软件架构的变化,从原有的 M/S/U 三层权限变成更为扁平的 M/U 两层权限。
这篇博客中分析了 MMU 关闭后将影响哪些功能。PAGE_OFFSET = 0x80000000,不再需要 SBI Firmware,binfmt 的格式仅支持 binfmt flat,而不再支持 ELF等。
由于 ELF 格式依赖 MMU,所以禁用 MMU 以后的内核需要专门启用另外一种名为 FLAT 的程序格式,否则将无法正确地执行应用程序。这种格式来自 uClibc — 一种专门为嵌入式 Linux 系统设计的 C 库。在6.10版本的linux kernel上支持了FDPIC ELF格式,是ELF的一个变种,允许在没有MMU的系统上运行位置无关代码(PIC)。它在内部实现上与标准的ELF格式有所不同,特别是在程序链接表(PLT)的实现上,但对程序员来说,从外部来看,它仍然是一个ELF文件。FDPIC ELF格式支持动态加载和共享库的创建与维护,这在FLAT格式中是不支持的。
uClibc 支持 FLAT 的方式比较特殊,它并没有试图开发一系列完整的工具来支持 FLAT 格式,而是开发了一个转换工具 elf2flt。这个工具一般不独立使用,而是安装进其他工具链的目录结构中,以某种巧妙的方式去调整链接器的工作过程,进而最终产生 FLAT 格式的可执行文件。目前最好的使用 elf2flt 的方式是,直接用 Buildroot 构建一个完整的已经自动编译并安装好了 elf2flt 的 uClibc 工具链。要获得该工具链,仅需要在编译 Buildroot 时,同样禁用 MMU 即可。
一个nommu系统没有虚拟地址,任何“映射”只是跟踪分配了什么物理内存。对nommu的分配必须找到物理内存的连续区域,将其设置为零,并返回指向它开头的指针。这意味着如果malloc()找不到足够大的连续内存块,它将返回NULL。所有程序文本都必须是可重定位的:所以我们不能使用标准的ELF二进制文件。nommu要求在编译时指定显式堆栈大小,因为nommu系统无法自动增加堆栈(没有“保护页面”错误)。这些堆栈应该尽可能小,因为整个大小是在程序启动时分配的(需要连续的不可共享分配),如果系统无法获取该内存,则exec失败。默认大小(8k)基本上是内核堆栈。兆字节堆栈的标准Linux假设不适合用于nommu系统。
内核对无mmu条件下的内存映射的支持有限,例如在uClinux环境。从用户空间的角度来看,内存映射与mmap()系统调用、shmat()调用和execve()系统调用一起使用。从内核的角度来看,execve()映射实际上是由binfmt驱动程序执行的,它回调到mmap()例程来完成实际的工作。内存映射行为还影响fork(), vfork(), clone()和ptrace()的工作方式。在uClinux下,没有fork(), clone()必须提供CLONE_VM标志(子进程与父进程运行于相同的内存空间)。
匿名映射,MAP_PRIVATE:
在MMU的情况下:使用虚拟页,虚拟页可以映射到任意的物理页;在fork()中copy-on-write;
在无MMU的情况下:虚拟区域只能是连续页。
匿名映射,MAP_SHARED:
在MMU的情况下:除了没有CLONE_VM,其余与MAP_PRIVATE类似;
在无MMU的情况下:与MAP_PRIVATE一致。
文件,MAP_PRIVATE, PROT_READ / pro_exec, !PROT_WRITE:
在MMU的情况下:使用虚拟页读入文件,对文件的改变反映到内存中,fork时复制;
在无MMU的情况下:重用文件映射;写文件不影响映射;对内存中映射的写入其他进程可见,但不应该发生。
文件, MAP_PRIVATE, PROT_READ / PROT_EXEC, PROT_WRITE
在MMU的情况下:在写实际发生前被复制;写操作不映射到内存中,而是由swap支持;
在无MMU的情况下:与!PROT_WRITE相似,除了获得副本而不是共享。