写在前面

本随笔是非常菜的菜鸡写的。如有问题请及时提出。

可以联系:1160712160@qq.com

GitHhub:https://github.com/WindDevil (目前啥也没有

引言

兜兜转转又是新的一章的开始,还是首先要看官方手册里的理论介绍和内容.

这里主要还是提纲挈领地摘抄里面的部分内容,在下面用更小的标题表现.

本章目的

本章展现了操作系统为实现“理想”而要扩展的一系列功能:

  • 通过动态内存分配,提高了应用程序对内存的动态使用效率
  • 通过页表的虚实内存映射机制,简化了编译器对应用的地址空间设置
  • 通过页表的虚实内存映射机制,加强了应用之间,应用与内核之间的内存隔离,增强了系统安全
  • 通过页表的虚实内存映射机制,可以实现空分复用 (提出,但没有实现)

需求

在大多数应用(也就是应用开发者)的视角中,它们会独占一整个 CPU 和特定(连续或不连续)的内存空间。当然,通过上一章的学习,我们知道在现代操作系统中,出于公平性的考虑,我们极少会让独占 CPU 这种情况发生。所以应用自认为的独占 CPU 只是内核想让应用看到的一种 幻象 (Illusion) ,而 CPU 计算资源被 时分复用 (TDM, Time-Division Multiplexing) 的实质被内核通过恰当的抽象隐藏了起来,对应用不可见。

与之相对,我们目前还没有对内存管理功能进行进一步拓展,仅仅是把程序放到某处的物理内存中。在内存访问方面,所有的应用都直接通过物理地址访问物理内存,这使得应用开发者需要了解繁琐的物理地址空间布局,访问内存也很不方便。在上一章中,出于任务切换的需要,所有的应用都在初始化阶段被加载到内存中并同时驻留下去直到它们全部运行结束。而且,所有的应用都直接通过物理地址访问物理内存。

这会带来以下问题:

  • 首先,内核提供给应用的内存访问接口不够透明,也不好用。由于应用直接访问物理内存,这需要它在构建的时候就清楚所运行计算机的物理内存空间布局,还需规划自己需要被加载到哪个地址运行。为了避免冲突可能还需要应用的开发者们对此进行协商,这显然是一件在今天看来不够通用且极端麻烦的事情。
  • 其次,内核并没有对应用的访存行为进行任何保护措施,每个应用都有计算机系统中整个物理内存的读写权力。即使应用被限制在 U 特权级下运行,它还是能够造成很多麻烦:比如它可以读写其他应用的数据来窃取信息或者破坏其它应用的正常运行;甚至它还可以修改内核的代码段来替换掉原本的 trap_handler 函数,来挟持内核执行恶意代码。总之,这造成系统既不安全、也不稳定。
  • 再次,目前应用的内存使用空间在其运行前已经限定死了,内核不能灵活地给应用程序提供的运行时动态可用内存空间。比如一个应用结束后,这个应用所占的空间就被释放了,但这块空间无法动态地给其它还在运行的应用使用。

解决方案

为了简化应用开发,防止应用胡作非为,本章将更好地管理物理内存,并提供给应用一个抽象出来的更加透明易用、也更加安全的访存接口,这就是基于分页机制的虚拟内存。站在应用程序运行的角度看,就是存在一个从“0”地址开始的非常大的可读/可写/可执行的地址空间(Address Space),而站在操作系统的角度看,每个应用被局限在分配给它的物理内存空间中运行,无法读写其它应用和操作系统所在的内存空间。

硬件支持

实现地址空间的第一步就是实现分页机制,建立好虚拟内存和物理内存的页映射关系。此过程需要硬件支持,硬件细节与具体CPU相关,涉及地址映射机制等,相对比较复杂。

需要思考的问题

总体而言,我们需要思考如下问题:

  • 硬件中物理内存的范围是什么?
  • 哪些物理内存空间需要建立页映射关系?
  • 如何建立页表使能分页机制?
  • 如何确保 OS 能够在分页机制使能前后的不同时间段中都能正常寻址和执行代码?
  • 页目录表(一级)的起始地址设置在哪里?
  • 二级/三级等页表的起始地址设置在哪里,需要多大空间?
  • 如何设置页目录表项/页表项的内容?
  • 如果要让每个任务有自己的地址空间,那每个任务是否要有自己的页表?
  • 代表应用程序的任务和操作系统需要有各自的页表吗?
  • 在有了页表之后,任务和操作系统之间应该如何传递数据?

体验环节

经典切换代码到第四章分支,然后一举运行:

cd ~/App/rCore-Tutorial-v3
git checkout ch4
cd os
make run

这里注意如果不能切换到ch4很有可能是因为没丢弃或者保存分支,尝试使用git checkout .指令.

运行结果:

[rustsbi] RustSBI version 0.3.1, adapting to RISC-V SBI v1.0.0
.______ __ __ _______.___________. _______..______ __
| _ \ | | | | / | | / || _ \ | |
| |_) | | | | | | (----`---| |----`| (----`| |_) || |
| / | | | | \ \ | | \ \ | _ < | |
| |\ \----.| `--' |.----) | | | .----) | | |_) || |
| _| `._____| \______/ |_______/ |__| |_______/ |______/ |__|
[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
[rustsbi] Boot HART : 0
[rustsbi] Device Tree Region : 0x87000000..0x87000f02
[rustsbi] Firmware Address : 0x80000000
[rustsbi] Supervisor Address : 0x80200000
[rustsbi] pmp01: 0x00000000..0x80000000 (-wr)
[rustsbi] pmp02: 0x80000000..0x80200000 (---)
[rustsbi] pmp03: 0x80200000..0x88000000 (xwr)
[rustsbi] pmp04: 0x88000000..0x00000000 (-wr)
[kernel] Hello, world!
.text [0x80200000, 0x8020c000)
.rodata [0x8020c000, 0x80210000)
.data [0x80210000, 0x80266000)
.bss [0x80266000, 0x80577000)
mapping .text section
mapping .rodata section
mapping .data section
mapping .bss section
mapping physical memory
mapping memory-mapped registers
[kernel] back to world!
remap_test passed!
init TASK_MANAGER
num_app = 7
power_3 [10000/300000]
power_3 [20000/300000]
power_3 [30000/300000]
power_3 [40000/300000]
power_3 [50000/300000]
power_3 [60000/300000]
power_3 [70000/300000]
power_3 [80000/300000]
power_3 [90000/300000]
power_3 [100000/300000]
power_3 [110000/300000]
power_3 [120000/300000]
power_3 [130000/300000]
power_3 [140000/300000]
power_3 [150000/300000]
power_3 [160000/300000]
power_3 [170000/300000]
power_3 [180000/300000]
power_3 [190000/300000]
power_3 [200000/300000]
power_3 [210000/300000]
power_3 [220000/300000]
power_3 [230000/300000]
power_3 [240000/300000]
power_3 [250000/300000]
power_3 [260000/300000]
power_3 [270000/300000]
power_3 [280000/300000]
power_3 [290000/300000]
power_3 [300000/300000]
3^300000 = 612461288(MOD 998244353)
Test power_3 OK!
[kernel] Application exited with code 0
power_5 [10000/210000]
power_5 [20000/210000]
power_5 [30000/210000]
power_5 [40000/210000]
power_5 [50000/210000]
power_5 [60000/210000]
power_5 [70000/210000]
power_5 [80000/210000]
power_5 [90000/210000]
power_5 [100000/210000]
power_5 [110000/210000]
power_5 [120000/210000]
power_5 [130000/210000]
power_5 [140000/210000]
power_5 [150000/210000]
power_7 [10000/240000]
power_7 [20000/240000]
power_7 [30000/240000]
power_7 [40000/240000]
power_7 [50000/240000]
power_7 [60000/240000]
power_7 [70000/240000]
power_7 [80000/240000]
power_7 [90000/240000]
power_7 [100000/240000]
power_7 [110000/240000]
power_7 [120000/240000]
power_7 [130000/240000]
power_7 [140000/240000]
power_7 [150000/240000]
power_7 [160000/240000]
power_7 [170000/240000]
power_7 [180000/240000]
power_7 [190000/240000]
power_7 [200000/240000]
power_7 [210000/240000]
power_7 [220000/240000]
power_7 [230000/240000]
power_7 [240000/240000]
7^240000 = 304164893(MOD 998244353)
Test power_7 OK!
[kernel] Application exited with code 0 load_fault APP running... Into Test load_fault, we will insert an invalid load operation...
Kernel should kill this application!
[kernel] PageFault in application, bad addr = 0x0, bad instruction = 0x100c0, kernel killed it. store_fault APP running... Into Test store_fault, we will insert an invalid store operation...
Kernel should kill this application!
[kernel] PageFault in application, bad addr = 0x0, bad instruction = 0x100c0, kernel killed it.
Test sbrk start.
origin break point = 17000
one page allocated, break point = 18000
try write to allocated page
write ok
10 page allocated, break point = 22000
11 page DEALLOCATED, break point = 17000
try DEALLOCATED more one page, should be failed.
Test sbrk almost OK!
now write to deallocated page, should cause page fault.
[kernel] PageFault in application, bad addr = 0x17000, bad instruction = 0x1124a, kernel killed it.
power_5 [160000/210000]
power_5 [170000/210000]
power_5 [180000/210000]
power_5 [190000/210000]
power_5 [200000/210000]
power_5 [210000/210000]
5^210000 = 527227302(MOD 998244353)
Test power_5 OK!
[kernel] Application exited with code 0
Test sleep OK!
[kernel] Application exited with code 0
All applications completed!

本章框图

这里需要和上一章的框图进行对比才能知道本章做了什么改进.

这是本章框图:

这是上章框图:

可以看到的不同分为两个部分:

  1. 硬件需求不同
  2. 系统架构不同

硬件需求

观察框图,可以看到本章的框图中要求:

  1. CPU with MMU 要求 CPU 含有MMU,也即内存管理单元(Memory Management Unit)是计算机硬件的一部分,主要负责处理内存管理和虚拟地址到物理地址的转换。
  2. MEM with PageTable,要求内存支持页表,Page Table 是操作系统用于管理虚拟内存的一种数据结构,它记录了虚拟地址与物理地址之间的映射关系。Page Table 需要硬件支持,特别是 MMU(Memory Management Unit)来实现虚拟地址到物理地址的快速转换。

总而言之就是需要MMU.

系统架构

本章框图中增加了:

  1. APP层和OS层的跳板Trampline
  2. 每个APP的地址空间
  3. 内核的地址空间
  4. 物理页帧分配

等功能.

这里摘自官方手册:

在具体实现上,扩展了 TaskManager 的管理范围,每个 Task 的上下文 Task Context 还包括该任务的地址空间,在切换任务时,也要切换任务的地址空间。新增的内存管理模块主要包括与内核中动态内存分配相关的页帧分配、堆分配,以及表示应用地址空间的 Apps MemSets 类型和内核自身地址空间的 Kernel MemSet类型。 MemSet 类型所包含的页表 PageTable 建立了虚实地址映射关系,而另外一个 MemArea 表示任务的合法空间范围。

建议

代码的阅读顺序参考官方手册.

这里代码繁多,尤其是涉及到内存的变换之后就会面临对前面所有的数据结构的大范围的重写,对trap的处理和对__switch的重构.

建议多看代码,多理清问题,这样才能顺利度过这最难的一章.

[rCore学习笔记 027]地址空间的更多相关文章

  1. 【原】Java学习笔记027 - 泛型

    package cn.temptation.test; import java.util.ArrayList; import java.util.Iterator; public class Samp ...

  2. 《Linux内核设计与实现》课本第五章学习笔记——20135203齐岳

    <Linux内核设计与实现>课本第五章学习笔记 By20135203齐岳 与内核通信 用户空间进程和硬件设备之间通过系统调用来交互,其主要作用有三个. 为用户空间提供了硬件的抽象接口. 保 ...

  3. 孙鑫VC学习笔记:多线程编程

    孙鑫VC学习笔记:多线程编程 SkySeraph Dec 11st 2010  HQU Email:zgzhaobo@gmail.com    QQ:452728574 Latest Modified ...

  4. MIT 6.828 JOS学习笔记2. Lab 1 Part 1.2: PC bootstrap

    Lab 1 Part 1: PC bootstrap 我们继续~ PC机的物理地址空间 这一节我们将深入的探究到底PC是如何启动的.首先我们看一下通常一个PC的物理地址空间是如何布局的:        ...

  5. 《Linux内核设计与实现》课本第一章&第二章学习笔记

    <Linux内核设计与实现>课本学习笔记 By20135203齐岳 一.Linux内核简介 Unix内核的特点 Unix很简洁,所提供的系统调用都有很明确的设计目的. Unix中一切皆文件 ...

  6. KTHREAD 线程调度 SDT TEB SEH shellcode中DLL模块机制动态获取 《寒江独钓》内核学习笔记(5)

    目录 . 相关阅读材料 . <加密与解密3> . [经典文章翻译]A_Crash_Course_on_the_Depths_of_Win32_Structured_Exception_Ha ...

  7. ETHREAD APC 《寒江独钓》内核学习笔记(4)

    继续学习windows 中和线程有关系的数据结构: ETHREAD.KTHREAD.TEB 1. 相关阅读材料 <windows 内核原理与实现> --- 潘爱民 2. 数据结构分析 我们 ...

  8. EPROCESS 进程/线程优先级 句柄表 GDT LDT 页表 《寒江独钓》内核学习笔记(2)

    在学习笔记(1)中,我们学习了IRP的数据结构的相关知识,接下来我们继续来学习内核中很重要的另一批数据结构: EPROCESS/KPROCESS/PEB.把它们放到一起是因为这三个数据结构及其外延和w ...

  9. IRP IO_STACK_LOCATION 《寒江独钓》内核学习笔记(1)

    在学习内核过滤驱动的过程中,遇到了大量的涉及IRP操作的代码,这里有必要对IRP的数据结构和与之相关的API函数做一下笔记. 1. 相关阅读资料 <深入解析 windows 操作系统(第4版,中 ...

  10. java多线程学习笔记——详细

    一.线程类  1.新建状态(New):新创建了一个线程对象.        2.就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法.该状态的线程位于可运行线程池中, ...

随机推荐

  1. Vue3 之 reactive、ref、toRef、toRefs 使用与区别,源码分析详细注释

    目录 reactive.ref.toRef.toRefs 使用与区别 reactive ref 作用及用法 toRef 作用及用法 toRefs 作用及用法 ref,toRef,toRefs 源码实现 ...

  2. Vue源码剖析

    目录 Vue 响应式数据 Vue 中如何进行依赖收集 Vue 中模板编译原理 Vue 生命周期钩子 Vue 组件 data 为什么必须是个函数? nextTick 原理 set 方法实现原理 虚拟 d ...

  3. 使用vxe-table组件,控制台报错:缺少必要的“{0}”参数,这可能会导致出现错误

    这是由于使用vxe表格,给列属性设置 type="html" 只需要开启存,需要启用column-config.useKey与row-config.useKey就可以了

  4. c++ 17 demo

    1 // Cpp.cpp : 此文件包含 "main" 函数.程序执行将在此处开始并结束. 2 // 3 4 #include <iostream> 5 #includ ...

  5. linux部署Python UI自动化项目过程

    1.安装chrome浏览器 下载 访问谷歌中文网站:Google Chrome 网络浏览器. 将页面滑到最下面,点击其他平台, 在弹出的页面选择linux 选择对应的系统版本进行下载. 下载后的deb ...

  6. 【.bat】IISExpress配置通过IP访问程序

    本页只记录便携运行方式脚本 详细IISExpress配置方法请看: VS的IISExpress配置通过IP访问程序 网络信息:192.168.1.45:8378 Run.bat :: run as a ...

  7. innodb存储引擎了解

    mysql常用的存储引擎分为innodb和myisam 其中innodb具有支持事务,执行行级锁,支持MVCC,外键,自动增长列,崩溃恢复等特性.并且mysql在5.5.5之后是数据的默认存储引擎 文 ...

  8. 【SQL】 去掉最后一段,只保留前段

    需求描述: 例如给出这样一个地址或者其他字符: 10.11.12.13 192.168.177.209101.102.103.104.105 ... 要求只保留前面的部分,去掉最后一部分 10.11. ...

  9. 如何将AI模型与CAE(计算机辅助工程)结合 —— AI大模型能否用于CAE有限元分析和数值模拟仿真的工业软件领域?

    引自: https://www.zhihu.com/question/611863569/answer/3271029434?utm_id=0 有限元分析中的三个要素,几何模型,本构模型和边界条件. ...

  10. NVIDIA公司在实体机器人上的第一步尝试 —— Nova Cater AMR —— 九号机器人与英伟达联合开发的自动驾驶研发平台“Nova Cater AMR(简称:NC)”

    相关: https://www.leiphone.com/category/robot/Hgy9i8azqGncESIB.html Nova Cater AMR是一款仓储运货机器人,可以应用在仓储物流 ...