MIT 6.828 | JOS | 关于虚拟空间和物理空间的总结
MIT 6.828 | JOS | 关于虚拟空间和物理空间的总结
🚩 Question:
做lab过程中越来越迷糊,明明虚拟空间和物理空间都是4G,为什么还要有映射?又为啥映射要像JOS这样映射?
解决途径:
停下来,根据当前lab的进展,再回头看上学期操作系统的ppt & 上网冲浪查资料!意识到自己对于分段机制
理解不够。
最强总结:https://www.cnblogs.com/tolimit/p/4775945.html?utm_source=tuicool&utm_medium=referral
解决过程:
分段机制主要功能只有两点:
- 将物理内存划分为多个段,让操作系统可以使用大于其地址线对应的物理内存(比如正常情况下32位地址线可以访问4G大小的内存,但是有分段后则可访问大于4G的内存)。
- 权限控制,将每个段设置权限位,让不同的程序访问不同的段。
🎈 linux内核只用到了权限控制的功能,但是我一直将其理解为第一个功能!!!
🚩🚩🚩🚩注意:JOS中的分段功能并没有实现
正常的操作系统通常采用两个部件来完成对内核地址的保护,一个是通过段机制来实现的,但是JOS中的分段功能并没有实现。二就是通过分页机制来实现,通过把页表项中的 Supervisor/User位置0,那么用户态的代码就不能访问内存中的这个页。
物理地址空间
+------------------+ <- 0xFFFFFFFF (4GB)
| 32-bit |
| memory mapped |
| devices |
| |
/\/\/\/\/\/\/\/\/\/\
/\/\/\/\/\/\/\/\/\/\
| |
| Unused |
| |
+------------------+ <- depends on amount of RAM
| |
| |
| Extended Memory |
| |
| |
+------------------+ <- 0x00100000 (1MB)
| BIOS ROM |
+------------------+ <- 0x000F0000 (960KB)
| 16-bit devices, |
| expansion ROMs |
+------------------+ <- 0x000C0000 (768KB)
| VGA Display |
+------------------+ <- 0x000A0000 (640KB)
| |
| Low Memory |
| |
+------------------+ <- 0x00000000
GDT/LDT && 分段机制
采用的方式就是引入了数据结构GDT,即全局描述符表,不知道有多少人听过?说简单点,GDT就是一个数组,每一个元素就是一个描述符,多个组合一起就构成了全局描述符表。而每一个描述符共64位,包含了以下的这些信息:段基址、段长度、属性。原来的段寄存器,比如CS,DS等存的值则不是段偏移了,而是GDT的索引,通过该索引就可以找到对应的描述符。现在是不是明白了呢?接下来我们再详细的解释一下一个描述符中各个位的意义吧。
LDT 可以看作是多级索引的第二级GDT。
Linux 4种段寄存器取值
也就是linux只有4种段的选择。
保护模式地址映射
从实模式切换到保护模式
1、准备GDT
2、用lgdt指令加载gdtr
3、打开A20地址线
4、将cr0寄存器的最后一位(PE位)置1:
PE位为0时,CPU运行在实模式下;
PE位为1时,CPU运行在保护模式下.
5、跳转,进入保护模式
bootloader为止的模型
🚩 感谢图源
物理内存的分布
-
Lab1进入内存后,开启分页模式,将虚拟地址[0, 4MB)映射到物理地址[0, 4MB),[0xF0000000, 0xF0000000+4MB)映射到[0, 4MB)(/kern/entry.S)
-
lab1 结束后物理分布如下:
- lab2 中mem_init()结束之后,物理地址分布如下:
终极问题:物理空间和虚拟空间都是4G,为什么分布不一样?
这个问题困扰了我很久,并且在迷迷糊糊做lab的时候没有搞明白。
在总结梳理了前面的知识之后,终于可以解决这个问题了!
参考:
MIT 6.828 Lab Guide
CPU访问内存之后,就可以向其他kernel的其它模块、内核线程、用户空间进程、等等)提供服务,主要包括:
- 以虚拟地址(VA)的形式,为应用程序提供远大于物理内存的虚拟地址空间(Virtual Address Space)
- 每个进程都有独立的虚拟地址空间,不会相互影响,进而可提供非常好的内存保护(memory protection)
- 提供内存映射(Memory Mapping)机制,以便把物理内存、I/O空间、Kernel Image、文件等对象映射到相应进程的地址空间中,方便进程的访问
- 提供公平、高效的物理内存分配(Physical Memory Allocation)算法
- 提供进程间内存共享的方法(以虚拟内存的形式),也称作Shared Virtual Memory
在提供这些服务之前需要对内存进行合理的划分和管理。
物理地址空间布局
Linux系统在初始化时,会根据实际的物理内存的大小,为每个物理页面创建一个page对象,所有的page对象构成一个mem_map数组。进一步,针对不同的用途,Linux内核将所有的物理页面划分到3类内存管理区中,如图,分别为ZONE_DMA,ZONE_NORMAL,ZONE_HIGHMEM。
- ZONE_DMA 的范围是 0~16M,该区域的物理页面专门供 I/O 设备的 DMA 使用。之所以需要单独管理 DMA 的物理页面,是因为 DMA 使用物理地址访问内存,不经过 MMU,并且需要连续的缓冲区,所以为了能够提供物理上连续的缓冲区,必须从物理地址空间专门划分一段区域用于 DMA。
- ZONE_NORMAL 的范围是 16M~896M,该区域的物理页面是内核能够直接使用的。
- ZONE_HIGHMEM 的范围是 896M~结束,该区域即为高端内存,内核不能直接使用。
Linux内核空间虚拟地址分布
在 Kernel Image 下面有 16M 的内核空间用于 DMA 操作。位于内核空间高端的 128M 地址主要由3部分组成,分别为 vmalloc area、持久化内核映射区、临时内核映射区。
由于 ZONE_NORMAL 和内核线性空间存在直接映射关系,所以内核会将频繁使用的数据如 Kernel 代码、GDT、IDT、PGD、mem_map 数组等放在 ZONE_NORMAL 里。而将用户数据、页表(PT)等不常用数据放在 ZONE_HIGHMEM 里,只在要访问这些数据时才建立映射关系(kmap())。比如,当内核要访问 I/O 设备存储空间时,就使用 ioremap() 将位于物理地址高端的 mmio 区内存映射到内核空间的 vmalloc area 中,在使用完之后便断开映射关系。
Linux用户空间虚拟地址分布
用户进程的代码区一般从虚拟地址空间的 0x08048000 开始,这是为了便于检查空指针。代码区之上便是数据区,未初始化数据区,堆区,栈区,以及参数、全局环境变量。
Linux物理地址和虚拟地址的关系
Linux 将 4G 的线性地址空间分为2部分,0~3G 为 user space,3G~4G 为 kernel space。
🚩🚩🚩 【超级重要】
由于开启了分页机制,内核想要访问物理地址空间的话,必须先建立映射关系,然后通过虚拟地址来访问。为了能够访问所有的物理地址空间,就要将全部物理地址空间映射到 1G 的内核线性空间中,这显然不可能。于是,内核将 0~896M 的物理地址空间一对一映射到自己的线性地址空间中,这样它便可以随时访问 ZONE_DMA 和 ZONE_NORMAL 里的物理页面;此时内核剩下的 128M 线性地址空间不足以完全映射所有的 ZONE_HIGHMEM,Linux 采取了动态映射的方法,即按需的将 ZONE_HIGHMEM 里的物理页面映射到 kernel space 的最后 128M 线性地址空间里,使用完之后释放映射关系,以供其它物理页面映射。虽然这样存在效率的问题,但是内核毕竟可以正常的访问所有的物理地址空间了。
总结如下:
总结JOS 的虚拟空间和物理空间对应关系
做完实验后,我将与虚拟空间和物理空间有关的内容进行了梳理,画出了下图:
1、[UPAGES, UVPT]: 在虚拟地址中的这段内存就是对应的在实际物理地址里 pages 数组的存储位置。可以看到这段地址在ULIM之下,也就是说操作系统开放pages数组便于让用户程序可以访问。为什么呢?比如说假如有的程序想要知道一个物理页面被引用了多少次,那么根据相应的pages[i].pp_ref就可以知道了。我们看到这个区域在布局中分配了 PTSIZE 的大小,那么这个大小够么?因为我们知道在物理页面中pages所占用的大小为npage × sizeof (struct PageInfo) =npage ×8B(使用sizeof()查看,一个指针占4字节,uint16_t是2个字节,可是struct PageInfo占8字节,有些迷惑,可借鉴uint16_t类型在 Bochs 模拟出来的 x86 平台上是4字节理解)。我们看看PTSIZE的空间可以装struct Page的个数为1024 ×4KB / 8B = 4MB / 8B = 1/2M。一个Page结构对应一个实际大小为4KB的物理页面,所以这个PTSIZE大小的虚拟地址空间能够管理 1/2 M × 4KB =2GB的物理内存,这个对于我们的QEMU模拟器来讲应该还是足够了。
2、[UVPT, ULIM]: 这部分是一个系统页目录——kern_pgdir。开放给用户只读,可以使用户得到当前内存中某个虚拟地址对应的物物理页面地址是多少。关于这部分,我们需要看一下:
kern/pmap.c
kern_pgdir这部分实际紧接在kernel 的 end 后面,也即在实际的物理地址里,上图最下面一句话使以物理地址 PADDR(kern_pgdir) 开始的一页成为了虚拟地址的 PDX = PDX(UVPT) 的页表。这部分实际占物理空间 PGSIZE, 但是在虚拟地址上却分配了 PTSIZE 大小。
注意 UVPD 的虚拟地址为 PDX(UVPT)<<22 | PDX(UVPT)<<12 | 0000000000 。
详细的 UVPT 介绍见:Lecture 5
3、[KERNBASE, 4G] : 此部分映射实际物理内存中从0开始的中断向量表IDT、BIOS程序、IO端口以及操作系统内核等,该部分虚拟地址 - KERNBASE 就是物理地址。
从这个布局结构我们就可以看出来,实际上虚拟内存中分配给用户的只有[0, ULIM)大概3.7GB而非4GB的空间(ULIM = 0xef800000),其余的空间需要分配给操作系统。