updates to memory management

pull/1/head
Zhiyuan Shao 4 years ago
parent 59d384bf5b
commit b2f0e183da

@ -6,7 +6,7 @@
**4.1.1 练习一OS内存的初始化过程** **4.1.1 练习一OS内存的初始化过程**
****"pk/mmap.c"内有 pk_vm_init()函数阅读该函数了解OS内存初始化的过程。 在"pk/mmap.c"内有 pk_vm_init()函数阅读该函数了解OS内存初始化的过程。
``` ```
364 uintptr_t pk_vm_init() 364 uintptr_t pk_vm_init()
@ -47,9 +47,7 @@
391 } 391 }
``` ```
以上代码中我们给出了大体的注释请根据以上代码尝试画出RISCV的物理内存结构图。 以上代码中我们给出了大体的注释请根据以上代码读者可以尝试画一下PK的逻辑地址空间结构图以及逻辑地址空间到物理地址空间的映射关系。
**4.1.2 练习二first_fit内存页分配算法需要编程** **4.1.2 练习二first_fit内存页分配算法需要编程**
@ -108,22 +106,22 @@ Score: 20/20
### 4.2 基础知识 ### 4.2 基础知识
**4.2.1 物理内存空间与空间编址** **4.2.1 物理内存空间与编址**
计算机的存储结构可以抽象的看做由N个连续的字节组成的数组。想一想在数组中我们如何找到一个元素对了是下标那么我们如何在内存中如何找打一个元素呢自然也是下标。这个下标的起始位置和位数由机器本身决定我们称之为“物理地址”。 计算机的存储结构可以抽象的看做由N个连续的字节组成的数组。想一想在数组中我们如何找到一个元素对了是下标那么我们如何在内存中如何找打一个元素呢自然也是下标。这个下标的起始位置和位数由机器本身决定我们称之为“物理地址”。
在riscv中内存地址是从0x80000000 开始的。在pke的连接文件pke.lds中我们可以看到这样两行 至于物理内存的大小由于我们的RISC-V目标机也就是我们的pke以及app运行的环境是由spike模拟器构造的构造过程中可以通过命令行的-m选项来指定物理内存的大小。而且spike会将目标机的物理内存地址从0x8000-0000开始编制。例如如果物理内存空间的大小为2GBspike的默认值则目标机的物理地址范围为[0x8000-0000, 0x10000-0000]其中0x10000-0000已经超过32位能够表达的范围了但是我们目标机是64位机再例如如果目标机物理内存空间大小为1GB启动spike时带入-m1024m参数则目标机的物理地址范围为[0x8000-0000, 0xC000-0000]。在以下的讨论中我们用符号PHYMEM_TOP代表物理内存空间的高地址部分在以上的两个例子中PHYMEM_TOP分别为0x10000-0000和0xC000-0000。在定义了PHYMEM_TOP符号后物理内存的范围就可以表示为[0x8000-0000, PHYMEM_TOP]。
我们的PK内核的逻辑编址可以通过查看pke.lds得知pke.lds有以下规则
``` ```
14 /* Begining of code and text segment */ 14 /* Begining of code and text segment */
15 . = 0x80000000; 15 . = 0x80000000;
``` ```
至于内存的大小,还记得实验二中的-m选项吗spike模拟器可以通过-m选项配置物理内存的大小。 可见PK内核的逻辑地址的起始也是0x8000-0000这也就意味着PK内核实际上采用的是直接地址映射的办法保证在未打开分页情况下逻辑地址到物理地址的映射的。代理内核的本质也是一段程序他本身是需要内存空间的而这一段空间在PK的设计中是静态分配给内核使用的不能被再分配给任何应用。那么静态分配给代理内核的内存空间具体是哪一段内存区域呢
现在思考一下问题首先为什么需要物理内存的管理这个问题可以用另一个问题回答当程序如malloc申请一段内存空间的时候你如何准确的给出一片符合大小要求的且安全可用的内存空间。
其次,我们现在管理的可用内存是那一部分? 代理内核的本质也是一段程序他本身是需要内存空间的而这一段空间自然不能再被分配。除去内核本身占的空间内核可支配的物理空间从0x80016000开始大小在PKE设定为8M2048个页面故而供内核支配的的内存的范围为first_free_page~first_free_paddr。如下图所示。 通过阅读PK的代码我们可知PK内核占据了以下这一段
``` ```
KERNTOP------->+---------------------------------+ 0x80816000 KERNTOP------->+---------------------------------+ 0x80816000
@ -131,31 +129,26 @@ Score: 20/20
| Kern Physical Memory | | Kern Physical Memory |
| | 8M 2048pages | | 8M 2048pages
(first_free_page)| | (first_free_page)| |
KERNBSE -----> +---------------------------------+ 0x80016000 DRAM_BASE----> +---------------------------------+ 0x80016000
| Kern Text/Data/BBS | | Kern Text/Data/BBS |
KERN------>+---------------------------------+ 0x80000000 KERN------>+---------------------------------+ 0x80000000
``` ```
也就是说,[0x8000-0000, 0x8081-6000]这段物理内存空间是被PK内核所“保留”的余下的物理内存空间为[0x8081-6000PHYMEM_TOP]也就是下图中的Empty Memory*)部分,这部分内存将会是我们的操作系统需要真正用于动态分配(给应用程序)的空间,**而本实验就是要管理这部分物理内存空间**。
再往上便是我们可以支配的空间了KERNTOP~Top Memory
``` ```
kernel/user PHYMEM_TOP ----> +-------------------------------------------------+
4G ------------> +-------------------------------------------------+
| | | |
| Empty Memory (*) | | Empty Memory (*) |
| | | |
Top Memmory ---> +-------------------------------------------------+ 随物理内存大小移动 KERNTOP ---> +-------------------------------------------------+ 0x80816000
| | (first_free_paddr)| |
| User Remapped Memory | | PK kernel resevered |
| | | |
| | | |
first_free_paddr->+-------------------------------------------------+ 0x80816000 KERN ----> +-------------------------------------------------+ 0x80000000
``` ```
最后我们来看物理内存分配的单位操作系统中物理页是物理内存分配的基本单位。一个物理页的大小是4KB我们使用结构体Page来表示其结构如图 最后我们来看物理内存分配的单位操作系统中物理页是物理内存分配的基本单位。一个物理页的大小是4KB我们使用结构体Page来表示其结构如图
``` ```
@ -194,14 +187,11 @@ Page结构体对应着物理页我们来看Page结构体同物理地址之间
<img src="pictures/fig4_2.png" alt="fig4_2" style="zoom:80%;" /> <img src="pictures/fig4_2.png" alt="fig4_2" style="zoom:80%;" />
**3.2.2** **中断的处理过程** **3.2.2** **中断的处理过程**
考虑一下,当程序执行到中断之前程序是有自己的运行状态的例如寄存器里保持的上下文数据。当中断发生硬件在自动设置完中断原因和中断地址后就会调转到中断处理程序而中断处理程序同样会使用寄存器于是当程序从中断处理程序返回时需要保存需要被调用者保存的寄存器我们称之为callee-saved寄存器。 当程序执行到中断之前程序是有自己的运行状态的例如寄存器里保持的上下文数据。当中断发生硬件在自动设置完中断原因和中断地址后就会调转到中断处理程序而中断处理程序同样会使用寄存器于是当程序从中断处理程序返回时需要保存需要被调用者保存的寄存器我们称之为callee-saved寄存器。
在PK的machine/minit.c中间中便通过delegate_traps()将部分中断及同步异常委托给S模式。同学们可以查看具体是哪些中断及同步异常 在PK的machine/minit.c中间中便通过delegate_traps()将部分中断及同步异常委托给S模式。同学们可以查看具体是哪些中断及同步异常
``` ```
43 // send S-mode interrupts and most exceptions straight to S-mode 43 // send S-mode interrupts and most exceptions straight to S-mode
@ -226,9 +216,9 @@ Page结构体对应着物理页我们来看Page结构体同物理地址之间
62 } 62 }
``` ```
这里介绍一下RISCV的中断委托机制在默认的情况下所有的异常都会被交由机器模式处理。但正如我们知道的那样大部分的系统调用都是在S模式下处理的因此RISCV提供了这一委托机制可以选择性的将中断交由S模式处理从而完全绕过M模式。 这里介绍一下RISCV的中断委托机制在默认的情况下所有的异常都会被交由机器模式处理。但正如我们知道的那样大部分的系统调用都是在S模式下处理的因此RISCV提供了这一委托机制可以选择性的将中断交由S模式处理从而完全绕过M模式。
接下我们继续看S模式下的中断处理。在pk目录下的pk.c文件中的boot_loader函数中将&trap_entry写入了stvec寄存器中stvec保存着发生异常时处理器需要跳转到的地址也就是说当中断发生我们将跳转至trap_entry现在我们继续跟踪trap_entry。trap_entry在pk目录下的entry.S中其代码如下 接下我们继续看S模式下的中断处理。在pk目录下的pk.c文件中的boot_loader函数中将&trap_entry写入了stvec寄存器中stvec保存着发生异常时处理器需要跳转到的地址也就是说当中断发生我们将跳转至trap_entry现在我们继续跟踪trap_entry。trap_entry在pk目录下的entry.S中其代码如下
``` ```
60 trap_entry: 60 trap_entry:
@ -241,11 +231,11 @@ Page结构体对应着物理页我们来看Page结构体同物理地址之间
67 jal handle_trap 67 jal handle_trap
``` ```
在61行交换了sp与sscratch的值这里是为了根据sscratch的值判断该中断是来源于U模式还是S模式。 在61行交换了sp与sscratch的值这里是为了根据sscratch的值判断该中断是来源于U模式还是S模式。
如果sp也就是传入的sscratch值不为零则跳转至64行若sscratch的值为零则恢复原sp中的值。这是因为当中断来源于S模式是sscratch的值为0sp中存储的就是内核的堆栈地址。而当中断来源于U模式时sp中存储的是用户的堆栈地址sscratch中存储的则是内核的堆栈地址需要交换二者是sp指向内核的堆栈地址。 如果sp也就是传入的sscratch值不为零则跳转至64行若sscratch的值为零则恢复原sp中的值。这是因为当中断来源于S模式是sscratch的值为0sp中存储的就是内核的堆栈地址。而当中断来源于U模式时sp中存储的是用户的堆栈地址sscratch中存储的则是内核的堆栈地址需要交换二者是sp指向内核的堆栈地址。
接着在64,65行保存上下文最后跳转至67行处理trap。handle_trap在pk目录下的handlers.c文件中代码如下 接着在64,65行保存上下文最后跳转至67行处理trap。handle_trap在pk目录下的handlers.c文件中代码如下
``` ```
112 void handle_trap(trapframe_t* tf) 112 void handle_trap(trapframe_t* tf)
@ -271,7 +261,7 @@ Page结构体对应着物理页我们来看Page结构体同物理地址之间
132 }; 132 };
``` ```
handle_trap函数中实现了S模式下各类中断的处理。可以看到代码的126行就对应着系统调用的处理handle_syscall的实现如下 handle_trap函数中实现了S模式下各类中断的处理。可以看到代码的126行就对应着系统调用的处理handle_syscall的实现如下
``` ```
100 static void handle_syscall(trapframe_t* tf) 100 static void handle_syscall(trapframe_t* tf)
@ -282,7 +272,7 @@ Page结构体对应着物理页我们来看Page结构体同物理地址之间
105 } 105 }
``` ```
还记得我们在例3.1中是将中断号写入x17寄存器嘛其对应的就是这里do_syscall的最后一个参数我们跟踪进入do_syscall函数其代码如下 还记得我们在例3.1中是将中断号写入x17寄存器嘛其对应的就是这里do_syscall的最后一个参数我们跟踪进入do_syscall函数其代码如下
``` ```
313 long do_syscall(long a0, long a1, long a2, long a3, long a4, long a5, unsigned long n) 313 long do_syscall(long a0, long a1, long a2, long a3, long a4, long a5, unsigned long n)
@ -330,4 +320,4 @@ Page结构体对应着物理页我们来看Page结构体同物理地址之间
355 } 355 }
``` ```
do_syscall中通过传入的系统调用号n查询syscall_table得到对应的函数并最终执行系统调用。 do_syscall中通过传入的系统调用号n查询syscall_table得到对应的函数并最终执行系统调用。

@ -78,11 +78,9 @@ running app4_2 : OK
**5.2.1 虚拟地址空间** **5.2.1 虚拟地址空间**
物理地址唯一,且有限的,但现在的操作系统上往往有不止一个的程序在运行。如果只有物理地址,那对于程序员来说无疑是相当繁琐的。程序不知道那些内存可用,那些内存已经被其他程序占有,这就意味着操作系统必须管理所有的物理地址,并且所有所有代码都是共用的。 物理地址唯一,且有限的,但现在的操作系统上往往有不止一个的程序在运行。如果只有物理地址,那对于程序员来说无疑是相当繁琐的。程序不知道那些内存可用,那些内存已经被其他程序占有,这就意味着操作系统必须管理所有的物理地址,并且所有所有代码都是共用的。
为了解决上述问题,操作系统引入了虚拟地址的概念。每个进程拥有着独立的虚拟地址空间,这个空间是连续的,一个虚拟页可以映射到不同或者相同的物理页。这就是我们所说的虚拟地址。在程序中,我们所使用的变量的地址均为虚拟地址。
为了解决上述问题,操作系统引入了虚拟地址的概念。每个进程拥有着独立的虚拟地址空间,这个空间是连续的,一个虚拟页可以映射到不同或者相同的物理页。这就是我们所说的虚拟地址。在程序中,我们所使用的变量的地址均为虚拟地址。
**5.2.2 虚拟地址同物理地址之间的转换** **5.2.2 虚拟地址同物理地址之间的转换**
@ -94,9 +92,9 @@ running app4_2 : OK
图5.1 RISCV64 物理地址 图5.1 RISCV64 物理地址
可以看到物理地址由PPN物理页号与Offset偏移量组成。这里的PPN就对应着上述的物理页。 可以看到物理地址由PPN物理页号与Offset偏移量组成。这里的PPN就对应着上述的物理页。
现在我们来看RISCV虚拟地址的定义 现在我们来看RISCV虚拟地址的定义
<img src="pictures/fig5_2.png" alt="fig5_2" style="zoom:80%;" /> <img src="pictures/fig5_2.png" alt="fig5_2" style="zoom:80%;" />
@ -230,9 +228,7 @@ running app4_2 : OK
245 } 245 }
``` ```
对于一个没有对应物理地址的虚拟地址我们需要进行如下的处理。首先找到该物理地址所对应的pte这里你可能会使用到__walk函数__walk中调用了上文中我们讨论过的__walk_internal函数对于一个给定的虚拟地址返回其对于的pte其定义如下
对于一个没有对应物理地址的虚拟地址我们需要进行如下的处理。首先找到该物理地址所对应的pte这里你可能会使用到__walk函数__walk中调用了上文中我们讨论过的__walk_internal函数对于一个给定的虚拟地址返回其对于的pte其定义如下
``` ```
138 pte_t* __walk(uintptr_t addr) 138 pte_t* __walk(uintptr_t addr)

Loading…
Cancel
Save